Merge from Chromium at DEPS revision r167172

This commit was generated by merge_to_master.py.

Change-Id: Ib8d56fd5ae39a2d7e8c91dcd76cc6d13f25f2aab
diff --git a/chrome/common/DEPS b/chrome/common/DEPS
new file mode 100644
index 0000000..cefae53
--- /dev/null
+++ b/chrome/common/DEPS
@@ -0,0 +1,34 @@
+include_rules = [
+  "+chrome/plugin",  # For checking whether we're a plugin process.
+  "+grit",  # For generated headers
+  "+libxml",
+  "+ppapi/c",  # For various types.
+  "+ppapi/proxy",
+  "+ppapi/shared_impl",
+  "+remoting/client/plugin",
+  "+sandbox/win/src",
+  "+skia",
+  "+webkit/glue",
+  "+webkit/plugins",
+  "+webkit/user_agent",
+
+  # TODO(jam): remove me once chrome_notifcation_types.h moves to browser
+  "+content/public/browser/notification_types.h",
+
+  # Other libraries.
+  "+chrome/third_party/xdg_user_dirs",
+  "+third_party/bzip2",
+  "+third_party/mt19937ar",
+  "+third_party/npapi",
+  "+third_party/re2",
+  "+third_party/sqlite",
+  "+third_party/zlib",
+
+  # This is required by all_messages.h to allow logging of all messages. It
+  # can't be moved to chrome/common/ because it has so many dependencies in
+  # chrome/browser/.
+  "+chrome/browser/importer/profile_import_process_messages.h",
+
+  # FIXME - refactor code and remove these dependencies
+  "+chrome/installer",
+]
diff --git a/chrome/common/OWNERS b/chrome/common/OWNERS
new file mode 100644
index 0000000..b8246d0
--- /dev/null
+++ b/chrome/common/OWNERS
@@ -0,0 +1,15 @@
+per-file chrome_switches.cc=*
+per-file chrome_switches.h=*
+per-file pref_names.cc=*
+per-file pref_names.h=*
+per-file url_constants.cc=*
+per-file url_constants.h=*
+
+# Changes to IPC messages require a security review to avoid introducing
+# new sandbox escapes.
+per-file *_messages.h=set noparent
+per-file *_messages.h=cdn@chromium.org
+per-file *_messages.h=jln@chromium.org
+per-file *_messages.h=jschuh@chromium.org
+per-file *_messages.h=palmer@chromium.org
+per-file *_messages.h=tsepez@chromium.org
diff --git a/chrome/common/all_messages.h b/chrome/common/all_messages.h
new file mode 100644
index 0000000..114bf9d
--- /dev/null
+++ b/chrome/common/all_messages.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 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.
+
+// Multiply-included file, hence no include guard.
+// Inclusion of all message files present in chrome. Keep this file
+// up-to-date when adding a new value to the IPCMessageStart enum in
+// ipc/ipc_message_start.h to ensure the corresponding message file is
+// included here. Message classes used exclusively outside of chrome
+// should not be listed here and instead get an exemption in
+// chrome/tools/ipclist/ipclist.cc.
+#if !defined(OS_ANDROID)
+#include "chrome/browser/importer/profile_import_process_messages.h"
+#endif
+// We can't make common_message_generator.h include automation_messages, since
+// otherwise the Chrome Frame binaries will link in a lot of unrelated chrome
+// code. Chrome Frame should not be depending on the chrome target...
+// See http:://crbug.com/101208 and http://crbug.com/101215
+#include "chrome/common/automation_messages.h"
+#include "chrome/common/common_message_generator.h"
+#include "chrome/common/nacl_messages.h"
diff --git a/chrome/common/attrition_experiments.h b/chrome/common/attrition_experiments.h
new file mode 100644
index 0000000..1404b49
--- /dev/null
+++ b/chrome/common/attrition_experiments.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_ATTRITION_EXPERIMENTS_H_
+#define CHROME_COMMON_ATTRITION_EXPERIMENTS_H_
+
+#include "grit/chromium_strings.h"
+
+namespace attrition_experiments {
+
+// A list of all the IDs we use for the attrition experiments.
+enum Experiment {
+  kEnUs1 = IDS_TRY_TOAST_HEADING,
+  kEnUs2 = IDS_TRY_TOAST_HEADING2,
+  kEnUs3 = IDS_TRY_TOAST_HEADING3,
+  kEnUs4 = IDS_TRY_TOAST_HEADING4,
+  kSkype1 = IDS_TRY_TOAST_HEADING_SKYPE,
+};
+
+// A comma-separated list of brand codes that are associated with Skype.
+const wchar_t kSkypeBrandCode[] = L"SKPC,SKPG,SKPH,SKPI,SKPL,SKPM,SKPN";
+
+}  // namespace
+
+#endif  // CHROME_COMMON_ATTRITION_EXPERIMENTS_H_
diff --git a/chrome/common/auto_start_linux.cc b/chrome/common/auto_start_linux.cc
new file mode 100644
index 0000000..6041f0e
--- /dev/null
+++ b/chrome/common/auto_start_linux.cc
@@ -0,0 +1,87 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/auto_start_linux.h"
+
+#include "base/environment.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/nix/xdg_util.h"
+#include "base/string_tokenizer.h"
+
+namespace {
+
+const FilePath::CharType kAutostart[] = "autostart";
+
+FilePath GetAutostartDirectory(base::Environment* environment) {
+  FilePath result = base::nix::GetXDGDirectory(environment,
+                                               base::nix::kXdgConfigHomeEnvVar,
+                                               base::nix::kDotConfigDir);
+  result = result.Append(kAutostart);
+  return result;
+}
+
+}  // namespace
+
+bool AutoStart::AddApplication(const std::string& autostart_filename,
+                               const std::string& application_name,
+                               const std::string& command_line,
+                               bool is_terminal_app) {
+  scoped_ptr<base::Environment> environment(base::Environment::Create());
+  FilePath autostart_directory = GetAutostartDirectory(environment.get());
+  if (!file_util::DirectoryExists(autostart_directory) &&
+      !file_util::CreateDirectory(autostart_directory)) {
+    return false;
+  }
+
+  FilePath autostart_file = autostart_directory.Append(autostart_filename);
+  std::string terminal = is_terminal_app ? "true" : "false";
+  std::string autostart_file_contents =
+      "[Desktop Entry]\n"
+      "Type=Application\n"
+      "Terminal=" + terminal + "\n"
+      "Exec=" + command_line + "\n"
+      "Name=" + application_name + "\n";
+  std::string::size_type content_length = autostart_file_contents.length();
+  if (file_util::WriteFile(autostart_file, autostart_file_contents.c_str(),
+                           content_length) !=
+      static_cast<int>(content_length)) {
+    file_util::Delete(autostart_file, false);
+    return false;
+  }
+  return true;
+}
+
+bool AutoStart::Remove(const std::string& autostart_filename) {
+  scoped_ptr<base::Environment> environment(base::Environment::Create());
+  FilePath autostart_directory = GetAutostartDirectory(environment.get());
+  FilePath autostart_file = autostart_directory.Append(autostart_filename);
+  return file_util::Delete(autostart_file, false);
+}
+
+bool AutoStart::GetAutostartFileContents(
+    const std::string& autostart_filename, std::string* contents) {
+  scoped_ptr<base::Environment> environment(base::Environment::Create());
+  FilePath autostart_directory = GetAutostartDirectory(environment.get());
+  FilePath autostart_file = autostart_directory.Append(autostart_filename);
+  return file_util::ReadFileToString(autostart_file, contents);
+}
+
+bool AutoStart::GetAutostartFileValue(const std::string& autostart_filename,
+                                      const std::string& value_name,
+                                      std::string* value) {
+  std::string contents;
+  if (!GetAutostartFileContents(autostart_filename, &contents))
+    return false;
+  StringTokenizer tokenizer(contents, "\n");
+  std::string token = value_name + "=";
+  while (tokenizer.GetNext()) {
+    if (tokenizer.token().substr(0, token.length()) == token) {
+      *value = tokenizer.token().substr(token.length());
+      return true;
+    }
+  }
+  return false;
+}
diff --git a/chrome/common/auto_start_linux.h b/chrome/common/auto_start_linux.h
new file mode 100644
index 0000000..935d40a
--- /dev/null
+++ b/chrome/common/auto_start_linux.h
@@ -0,0 +1,33 @@
+// 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.
+
+#ifndef CHROME_COMMON_AUTO_START_LINUX_H_
+#define CHROME_COMMON_AUTO_START_LINUX_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+
+class AutoStart {
+ public:
+  // Registers an application to autostart on user login. |is_terminal_app|
+  // specifies whether the app will run in a terminal window.
+  static bool AddApplication(const std::string& autostart_filename,
+                             const std::string& application_name,
+                             const std::string& command_line,
+                             bool is_terminal_app);
+  // Removes an autostart file.
+  static bool Remove(const std::string& autostart_filename);
+  // Gets the entire contents of an autostart file.
+  static bool GetAutostartFileContents(const std::string& autostart_filename,
+                                       std::string* contents);
+  // Gets a specific value from an autostart file.
+  static bool GetAutostartFileValue(const std::string& autostart_filename,
+                                    const std::string& value_name,
+                                    std::string* value);
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(AutoStart);
+};
+
+#endif  // CHROME_COMMON_AUTO_START_LINUX_H_
diff --git a/chrome/common/autofill_messages.h b/chrome/common/autofill_messages.h
new file mode 100644
index 0000000..b60c868
--- /dev/null
+++ b/chrome/common/autofill_messages.h
@@ -0,0 +1,241 @@
+// Copyright (c) 2012 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.
+
+// Multiply-included message file, hence no include guard.
+
+#include <string>
+
+#include "base/time.h"
+#include "chrome/common/common_param_traits_macros.h"
+#include "chrome/common/form_data.h"
+#include "chrome/common/form_data_predictions.h"
+#include "chrome/common/form_field_data.h"
+#include "chrome/common/form_field_data_predictions.h"
+#include "chrome/common/password_form_fill_data.h"
+#include "content/public/common/password_form.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_message_utils.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebFormElement.h"
+#include "ui/gfx/rect.h"
+
+#define IPC_MESSAGE_START AutofillMsgStart
+
+IPC_STRUCT_TRAITS_BEGIN(FormFieldData)
+  IPC_STRUCT_TRAITS_MEMBER(label)
+  IPC_STRUCT_TRAITS_MEMBER(name)
+  IPC_STRUCT_TRAITS_MEMBER(value)
+  IPC_STRUCT_TRAITS_MEMBER(form_control_type)
+  IPC_STRUCT_TRAITS_MEMBER(autocomplete_attribute)
+  IPC_STRUCT_TRAITS_MEMBER(max_length)
+  IPC_STRUCT_TRAITS_MEMBER(is_autofilled)
+  IPC_STRUCT_TRAITS_MEMBER(is_focusable)
+  IPC_STRUCT_TRAITS_MEMBER(should_autocomplete)
+  IPC_STRUCT_TRAITS_MEMBER(option_values)
+  IPC_STRUCT_TRAITS_MEMBER(option_contents)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(FormFieldDataPredictions)
+  IPC_STRUCT_TRAITS_MEMBER(field)
+  IPC_STRUCT_TRAITS_MEMBER(signature)
+  IPC_STRUCT_TRAITS_MEMBER(heuristic_type)
+  IPC_STRUCT_TRAITS_MEMBER(server_type)
+  IPC_STRUCT_TRAITS_MEMBER(overall_type)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(FormData)
+  IPC_STRUCT_TRAITS_MEMBER(name)
+  IPC_STRUCT_TRAITS_MEMBER(method)
+  IPC_STRUCT_TRAITS_MEMBER(origin)
+  IPC_STRUCT_TRAITS_MEMBER(action)
+  IPC_STRUCT_TRAITS_MEMBER(user_submitted)
+  IPC_STRUCT_TRAITS_MEMBER(fields)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(FormDataPredictions)
+  IPC_STRUCT_TRAITS_MEMBER(data)
+  IPC_STRUCT_TRAITS_MEMBER(signature)
+  IPC_STRUCT_TRAITS_MEMBER(experiment_id)
+  IPC_STRUCT_TRAITS_MEMBER(fields)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(PasswordFormFillData)
+  IPC_STRUCT_TRAITS_MEMBER(basic_data)
+  IPC_STRUCT_TRAITS_MEMBER(additional_logins)
+  IPC_STRUCT_TRAITS_MEMBER(wait_for_username)
+IPC_STRUCT_TRAITS_END()
+
+IPC_ENUM_TRAITS(WebKit::WebFormElement::AutocompleteResult)
+
+// Autofill messages sent from the browser to the renderer.
+
+// Reply to the AutofillHostMsg_QueryFormFieldAutofill message with the
+// Autofill suggestions.
+IPC_MESSAGE_ROUTED5(AutofillMsg_SuggestionsReturned,
+                    int /* id of the request message */,
+                    std::vector<string16> /* names */,
+                    std::vector<string16> /* labels */,
+                    std::vector<string16> /* icons */,
+                    std::vector<int> /* unique_ids */)
+
+// Reply to the AutofillHostMsg_FillAutofillFormData message with the
+// Autofill form data.
+IPC_MESSAGE_ROUTED2(AutofillMsg_FormDataFilled,
+                    int /* id of the request message */,
+                    FormData /* form data */)
+
+// Fill a password form and prepare field autocomplete for multiple
+// matching logins. Lets the renderer know if it should disable the popup
+// because the browser process will own the popup UI.
+IPC_MESSAGE_ROUTED2(AutofillMsg_FillPasswordForm,
+                    PasswordFormFillData, /* the fill form data*/
+                    bool /* disable popup */ )
+
+// Send the heuristic and server field type predictions to the renderer.
+IPC_MESSAGE_ROUTED1(
+    AutofillMsg_FieldTypePredictionsAvailable,
+    std::vector<FormDataPredictions> /* forms */)
+
+// Tells the renderer that the next form will be filled for real.
+IPC_MESSAGE_ROUTED0(AutofillMsg_SetAutofillActionFill)
+
+// Clears the currently displayed Autofill results.
+IPC_MESSAGE_ROUTED0(AutofillMsg_ClearForm)
+
+// Tells the renderer that the next form will be filled as a preview.
+IPC_MESSAGE_ROUTED0(AutofillMsg_SetAutofillActionPreview)
+
+// Tells the renderer that the Autofill previewed form should be cleared.
+IPC_MESSAGE_ROUTED0(AutofillMsg_ClearPreviewedForm)
+
+// Sets the currently selected node's value.
+IPC_MESSAGE_ROUTED1(AutofillMsg_SetNodeText,
+                    string16 /* new node text */)
+
+// Sets the currently selected node's value to be the given data list value.
+IPC_MESSAGE_ROUTED1(AutofillMsg_AcceptDataListSuggestion,
+                    string16 /* accepted data list value */)
+
+// Tells the renderer to populate the correct password fields with this
+// generated password.
+IPC_MESSAGE_ROUTED1(AutofillMsg_GeneratedPasswordAccepted,
+                    string16 /* generated_password */)
+
+// Tells the renderer whether password generation is enabled.
+IPC_MESSAGE_ROUTED1(AutofillMsg_PasswordGenerationEnabled,
+                    bool /* is_enabled */)
+
+// Tells the renderer that the password field has accept the suggestion.
+IPC_MESSAGE_ROUTED1(AutofillMsg_AcceptPasswordAutofillSuggestion,
+                    string16 /* username value*/)
+
+// Tells the renderer that this password form is not blacklisted.  A form can
+// be blacklisted if a user chooses "never save passwords for this site".
+IPC_MESSAGE_ROUTED1(AutofillMsg_FormNotBlacklisted,
+                    content::PasswordForm /* form checked */)
+
+// Sent when interactive autocomplete finishes.
+IPC_MESSAGE_ROUTED1(AutofillMsg_RequestAutocompleteFinished,
+                    WebKit::WebFormElement::AutocompleteResult /* result */)
+
+// Autofill messages sent from the renderer to the browser.
+
+// Notification that forms have been seen that are candidates for
+// filling/submitting by the AutofillManager.
+IPC_MESSAGE_ROUTED2(AutofillHostMsg_FormsSeen,
+                    std::vector<FormData> /* forms */,
+                    base::TimeTicks /* timestamp */)
+
+// Notification that password forms have been seen that are candidates for
+// filling/submitting by the password manager.
+IPC_MESSAGE_ROUTED1(AutofillHostMsg_PasswordFormsParsed,
+                    std::vector<content::PasswordForm> /* forms */)
+
+// Notification that initial layout has occurred and the following password
+// forms are visible on the page (e.g. not set to display:none.)
+IPC_MESSAGE_ROUTED1(AutofillHostMsg_PasswordFormsRendered,
+                    std::vector<content::PasswordForm> /* forms */)
+
+// Notification that a form has been submitted.  The user hit the button.
+IPC_MESSAGE_ROUTED2(AutofillHostMsg_FormSubmitted,
+                    FormData /* form */,
+                    base::TimeTicks /* timestamp */)
+
+// Notification that a form field's value has changed.
+IPC_MESSAGE_ROUTED3(AutofillHostMsg_TextFieldDidChange,
+                    FormData /* the form */,
+                    FormFieldData /* the form field */,
+                    base::TimeTicks /* timestamp */)
+
+// Queries the browser for Autofill suggestions for a form input field.
+IPC_MESSAGE_ROUTED5(AutofillHostMsg_QueryFormFieldAutofill,
+                    int /* id of this message */,
+                    FormData /* the form */,
+                    FormFieldData /* the form field */,
+                    gfx::Rect /* input field bounds, window-relative */,
+                    bool /* display warning if autofill disabled */)
+
+// Sent when the popup with Autofill suggestions for a form is shown.
+IPC_MESSAGE_ROUTED1(AutofillHostMsg_DidShowAutofillSuggestions,
+                    bool /* is this a new popup? */)
+
+// Instructs the browser to fill in the values for a form using Autofill
+// profile data.
+IPC_MESSAGE_ROUTED4(AutofillHostMsg_FillAutofillFormData,
+                    int /* id of this message */,
+                    FormData /* the form  */,
+                    FormFieldData /* the form field  */,
+                    int /* profile unique ID */)
+
+// Sent when a form is previewed with Autofill suggestions.
+IPC_MESSAGE_ROUTED0(AutofillHostMsg_DidPreviewAutofillFormData)
+
+// Sent when a form is filled with Autofill suggestions.
+IPC_MESSAGE_ROUTED1(AutofillHostMsg_DidFillAutofillFormData,
+                    base::TimeTicks /* timestamp */)
+
+// Sent when a form receives a request to do interactive autocomplete.
+IPC_MESSAGE_ROUTED1(AutofillHostMsg_RequestAutocomplete,
+                    FormData /* form_data */)
+
+// Instructs the browser to remove the specified Autocomplete entry from the
+// database.
+IPC_MESSAGE_ROUTED2(AutofillHostMsg_RemoveAutocompleteEntry,
+                    string16 /* field name */,
+                    string16 /* value */)
+
+// Instructs the browser to show the Autofill dialog.
+IPC_MESSAGE_ROUTED0(AutofillHostMsg_ShowAutofillDialog)
+
+// Send when a text field is done editing.
+IPC_MESSAGE_ROUTED0(AutofillHostMsg_DidEndTextFieldEditing)
+
+// Instructs the browser to hide the Autofill popup.
+IPC_MESSAGE_ROUTED0(AutofillHostMsg_HideAutofillPopup)
+
+// Instructs the browser to show the password generation bubble at the
+// specified location. This location should be specified in the renderers
+// coordinate system. Form is the form associated with the password field.
+IPC_MESSAGE_ROUTED3(AutofillHostMsg_ShowPasswordGenerationPopup,
+                    gfx::Rect /* source location */,
+                    int /* max length of the password */,
+                    content::PasswordForm)
+
+// Instruct the browser that a password mapping has been found for a field.
+IPC_MESSAGE_ROUTED2(AutofillHostMsg_AddPasswordFormMapping,
+                    FormFieldData, /* the user name field */
+                    PasswordFormFillData /* password pairings */)
+
+// Instruct the browser to show a popup with the following suggestions from the
+// password manager.
+IPC_MESSAGE_ROUTED3(AutofillHostMsg_ShowPasswordSuggestions,
+                    FormFieldData /* the form field */,
+                    gfx::Rect /* input field bounds, window-relative */,
+                    std::vector<string16> /* suggestions */)
+
+// Inform browser of data list values for the curent field.
+IPC_MESSAGE_ROUTED4(AutofillHostMsg_SetDataList,
+                    std::vector<string16> /* values */,
+                    std::vector<string16> /* labels */,
+                    std::vector<string16> /* icons */,
+                    std::vector<int> /* unique ids */)
diff --git a/chrome/common/automation_constants.cc b/chrome/common/automation_constants.cc
new file mode 100644
index 0000000..1b8d32d
--- /dev/null
+++ b/chrome/common/automation_constants.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/automation_constants.h"
+
+namespace automation {
+
+// JSON value labels for proxy settings that are passed in via
+// AutomationMsg_SetProxyConfig.
+const char kJSONProxyAutoconfig[] = "proxy.autoconfig";
+const char kJSONProxyNoProxy[] = "proxy.no_proxy";
+const char kJSONProxyPacUrl[] = "proxy.pac_url";
+const char kJSONProxyPacMandatory[] = "proxy.pac_mandatory";
+const char kJSONProxyBypassList[] = "proxy.bypass_list";
+const char kJSONProxyServer[] = "proxy.server";
+
+// Named testing interface is used when you want to connect an
+// AutomationProxy to an already-running browser instance.
+const char kNamedInterfacePrefix[] = "NamedTestingInterface:";
+
+const int kChromeDriverAutomationVersion = 1;
+
+namespace {
+
+// Returns the string equivalent of the given |ErrorCode|.
+const char* DefaultMessageForErrorCode(ErrorCode code) {
+  switch (code) {
+    case kUnknownError:
+      return "Unknown error";
+    case kNoJavaScriptModalDialogOpen:
+      return "No JavaScript modal dialog is open";
+    case kBlockedByModalDialog:
+      return "Command blocked by an open modal dialog";
+    case kInvalidId:
+      return "ID is invalid or does not refer to an existing object";
+    default:
+      return "<unknown>";
+  }
+}
+
+}  // namespace
+
+Error::Error() : code_(kUnknownError) { }
+
+Error::Error(ErrorCode code)
+    : code_(code),
+      message_(DefaultMessageForErrorCode(code)) { }
+
+Error::Error(const std::string& error_msg)
+    : code_(kUnknownError), message_(error_msg) { }
+
+Error::Error(ErrorCode code, const std::string& error_msg)
+    : code_(code), message_(error_msg) { }
+
+Error::~Error() { }
+
+ErrorCode Error::code() const {
+  return code_;
+}
+
+const std::string& Error::message() const {
+  return message_;
+}
+
+}  // namespace automation
diff --git a/chrome/common/automation_constants.h b/chrome/common/automation_constants.h
new file mode 100644
index 0000000..4742b39
--- /dev/null
+++ b/chrome/common/automation_constants.h
@@ -0,0 +1,162 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_AUTOMATION_CONSTANTS_H__
+#define CHROME_COMMON_AUTOMATION_CONSTANTS_H__
+
+#include <string>
+
+namespace automation {
+
+// JSON value labels for proxy settings that are passed in via
+// AutomationMsg_SetProxyConfig. These are here since they are used by both
+// AutomationProvider and AutomationProxy.
+extern const char kJSONProxyAutoconfig[];
+extern const char kJSONProxyNoProxy[];
+extern const char kJSONProxyPacUrl[];
+extern const char kJSONProxyPacMandatory[];
+extern const char kJSONProxyBypassList[];
+extern const char kJSONProxyServer[];
+
+// When passing the kTestingChannelID switch to the browser, prepend
+// this prefix to the channel id to enable the named testing interface.
+// Named testing interface is used when you want to connect an
+// AutomationProxy to an already-running browser instance.
+extern const char kNamedInterfacePrefix[];
+
+// Amount of time to wait before querying the browser.
+static const int kSleepTime = 250;
+
+// Recognized by the AutomationProvider's SendWebKeyboardEventToSelectedTab
+// command. Specifies the type of the keyboard event.
+enum KeyEventTypes {
+  kRawKeyDownType = 0,
+  kKeyDownType,
+  kCharType,
+  kKeyUpType,
+};
+
+// Recognized by the AutomationProvider's SendWebKeyboardEventToSelectedTab
+// command. Specifies masks to be used in constructing keyboard event modifiers.
+enum KeyModifierMasks {
+  kShiftKeyMask   = 1 << 0,
+  kControlKeyMask = 1 << 1,
+  kAltKeyMask     = 1 << 2,
+  kMetaKeyMask    = 1 << 3,
+  kNumLockKeyMask = 1 << 4,
+};
+
+// Recognized by the AutomationProvider's ProcessWebMouseEvent command.
+enum MouseEventType {
+  kMouseDown = 0,
+  kMouseUp,
+  kMouseMove,
+  kMouseEnter,
+  kMouseLeave,
+  kContextMenu,
+};
+
+enum MouseButton {
+  kLeftButton = 0,
+  kMiddleButton,
+  kRightButton,
+  kNoButton,
+};
+
+// The current version of ChromeDriver automation supported by Chrome.
+// This needs to be incremented for each change to ChromeDriver automation that
+// is not backwards compatible. Some examples of this would be:
+// - SendJSONRequest or Hello IPC messages change
+// - The interface for an individual ChromeDriver automation call changes in an
+//   incompatible way
+// TODO(kkania): Investigate a better backwards compatible automation solution.
+extern const int kChromeDriverAutomationVersion;
+
+// Automation error codes. These provide the client a simple way
+// to detect certain types of errors it may be interested in handling.
+// The error code values must stay consistent across compatible versions.
+enum ErrorCode {
+  // An unknown error occurred.
+  kUnknownError = 0,
+  // Trying to operate on a JavaScript modal dialog when none is open.
+  kNoJavaScriptModalDialogOpen = 1,
+  // An open modal dialog blocked the operation. The operation may have
+  // partially completed.
+  kBlockedByModalDialog = 2,
+  // An ID was supplied that is invalid or does not refer to an existing object.
+  kInvalidId = 3,
+};
+
+// Represents an automation error. Each error has a code and an error message.
+class Error {
+ public:
+  // Creates an invalid error.
+  Error();
+
+  // Creates an error for the given code. A default message for the given code
+  // will be used as the error message.
+  explicit Error(ErrorCode code);
+
+  // Creates an error for the given message. The |kUnknownError| type will
+  // be used.
+  explicit Error(const std::string& error_msg);
+
+  // Creates an error for the given code and message.
+  Error(ErrorCode code, const std::string& error_msg);
+
+  virtual ~Error();
+
+  ErrorCode code() const;
+  const std::string& message() const;
+
+ private:
+  ErrorCode code_;
+  std::string message_;
+};
+
+}  // namespace automation
+
+// Used by AutomationProxy, declared here so that other headers don't need
+// to include automation_proxy.h.
+enum AutomationLaunchResult {
+  AUTOMATION_LAUNCH_RESULT_INVALID = -1,
+  AUTOMATION_SUCCESS,
+  AUTOMATION_TIMEOUT,
+  AUTOMATION_VERSION_MISMATCH,
+  AUTOMATION_CREATE_TAB_FAILED,
+  AUTOMATION_SERVER_CRASHED,
+  AUTOMATION_CHANNEL_ERROR,
+};
+
+enum AutomationMsg_NavigationResponseValues {
+  AUTOMATION_MSG_NAVIGATION_ERROR = 0,
+  AUTOMATION_MSG_NAVIGATION_SUCCESS,
+  AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED,
+  AUTOMATION_MSG_NAVIGATION_BLOCKED_BY_MODAL_DIALOG,
+};
+
+// Used in the AutomationMsg_GetExtensionProperty to identify which extension
+// property should be retrieved, instead of having separate messages for each
+// property.
+enum AutomationMsg_DEPRECATED_ExtensionProperty {
+  AUTOMATION_MSG_EXTENSION_ID = 0,
+  AUTOMATION_MSG_EXTENSION_NAME,
+  AUTOMATION_MSG_EXTENSION_VERSION,
+  AUTOMATION_MSG_EXTENSION_BROWSER_ACTION_INDEX,
+};
+
+// Specifies the font size on a page which is requested by an automation
+// client.
+enum AutomationPageFontSize {
+  SMALLEST_FONT = 8,
+  SMALL_FONT = 12,
+  MEDIUM_FONT = 16,
+  LARGE_FONT = 24,
+  LARGEST_FONT = 36
+};
+
+enum FindInPageDirection { BACK = 0, FWD = 1 };
+enum FindInPageCase { IGNORE_CASE = 0, CASE_SENSITIVE = 1 };
+
+#endif  // CHROME_COMMON_AUTOMATION_CONSTANTS_H__
diff --git a/chrome/common/automation_events.cc b/chrome/common/automation_events.cc
new file mode 100644
index 0000000..c252133
--- /dev/null
+++ b/chrome/common/automation_events.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/automation_events.h"
+
+ScriptEvaluationRequest::ScriptEvaluationRequest() {
+}
+
+ScriptEvaluationRequest::ScriptEvaluationRequest(
+    const std::string& script,
+    const std::string& frame_xpath)
+    : script(script),
+      frame_xpath(frame_xpath) {
+}
+
+ScriptEvaluationRequest::~ScriptEvaluationRequest() {
+}
+
+AutomationMouseEvent::AutomationMouseEvent() {
+}
+
+AutomationMouseEvent::~AutomationMouseEvent() {
+}
diff --git a/chrome/common/automation_events.h b/chrome/common/automation_events.h
new file mode 100644
index 0000000..44c7232
--- /dev/null
+++ b/chrome/common/automation_events.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_AUTOMATION_EVENTS_H_
+#define CHROME_COMMON_AUTOMATION_EVENTS_H_
+
+#include <string>
+#include <vector>
+
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h"
+
+// A request to evaluate a script in a given frame.
+struct ScriptEvaluationRequest {
+  ScriptEvaluationRequest();
+  ScriptEvaluationRequest(const std::string& script,
+                          const std::string& frame_xpath);
+  ~ScriptEvaluationRequest();
+
+  std::string script;
+  std::string frame_xpath;
+};
+
+// An extension to a normal mouse event that includes data for determining the
+// location of the mouse event.
+struct AutomationMouseEvent {
+  AutomationMouseEvent();
+  ~AutomationMouseEvent();
+
+  WebKit::WebMouseEvent mouse_event;
+
+  // A list of scripts that when evaluated returns a coordinate for the event.
+  // The scripts are chained together in that the output for one script
+  // provides the input for the next. Each script must result in exactly one
+  // JavaScript object. This object will be passed to the next script
+  // as arguments[0]. The only exceptions to this are the first script, which
+  // is passed null. The last script should result in a single JavaScript
+  // object with integer 'x' and 'y' properties.
+  // If empty, the coordinates in the normal mouse event are used.
+  std::vector<ScriptEvaluationRequest> location_script_chain;
+};
+
+#endif  // CHROME_COMMON_AUTOMATION_EVENTS_H_
diff --git a/chrome/common/automation_id.cc b/chrome/common/automation_id.cc
new file mode 100644
index 0000000..bd788ed
--- /dev/null
+++ b/chrome/common/automation_id.cc
@@ -0,0 +1,75 @@
+// 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.
+
+#include "chrome/common/automation_id.h"
+
+#include "base/stringprintf.h"
+#include "base/values.h"
+
+using base::DictionaryValue;
+using base::Value;
+
+// static
+bool AutomationId::FromValue(
+    Value* value, AutomationId* id, std::string* error) {
+  DictionaryValue* dict;
+  if (!value->GetAsDictionary(&dict)) {
+    *error = "automation ID must be a dictionary";
+    return false;
+  }
+  int type;
+  if (!dict->GetInteger("type", &type)) {
+    *error = "automation ID 'type' missing or invalid";
+    return false;
+  }
+  std::string type_id;
+  if (!dict->GetString("id", &type_id)) {
+    *error = "automation ID 'type_id' missing or invalid";
+    return false;
+  }
+  *id = AutomationId(static_cast<Type>(type), type_id);
+  return true;
+}
+
+// static
+bool AutomationId::FromValueInDictionary(
+    DictionaryValue* dict,
+    const std::string& key,
+    AutomationId* id,
+    std::string* error) {
+  Value* id_value;
+  if (!dict->Get(key, &id_value)) {
+    *error = base::StringPrintf("automation ID '%s' missing", key.c_str());
+    return false;
+  }
+  return FromValue(id_value, id, error);
+}
+
+AutomationId::AutomationId() : type_(kTypeInvalid) { }
+
+AutomationId::AutomationId(Type type, const std::string& id)
+    : type_(type), id_(id) { }
+
+bool AutomationId::operator==(const AutomationId& id) const {
+  return type_ == id.type_ && id_ == id.id_;
+}
+
+DictionaryValue* AutomationId::ToValue() const {
+  DictionaryValue* dict = new DictionaryValue();
+  dict->SetInteger("type", type_);
+  dict->SetString("id", id_);
+  return dict;
+}
+
+bool AutomationId::is_valid() const {
+  return type_ != kTypeInvalid;
+}
+
+AutomationId::Type AutomationId::type() const {
+  return type_;
+}
+
+const std::string& AutomationId::id() const {
+  return id_;
+}
diff --git a/chrome/common/automation_id.h b/chrome/common/automation_id.h
new file mode 100644
index 0000000..b05f060
--- /dev/null
+++ b/chrome/common/automation_id.h
@@ -0,0 +1,62 @@
+// 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.
+
+#ifndef CHROME_COMMON_AUTOMATION_ID_H_
+#define CHROME_COMMON_AUTOMATION_ID_H_
+
+#include <string>
+
+namespace base {
+class DictionaryValue;
+class Value;
+}
+
+// A unique ID that JSON automation clients can use to refer to browser
+// entities. The ID has a type so that:
+// 1) supplying an ID of the wrong type can be detected.
+// 2) the client does not have to explicitly supply the type in case multiple
+//    ID types can be accepted (e.g., can use a tab ID or extension popup ID for
+//    executing javascript).
+class AutomationId {
+ public:
+  // The value of each entry should be preserved.
+  enum Type {
+    kTypeInvalid = 0,
+    kTypeTab,
+    kTypeExtensionPopup,
+    kTypeExtensionBgPage,
+    kTypeExtensionInfobar,
+    kTypeExtension,
+    kTypeAppShell,
+  };
+
+  static bool FromValue(
+      base::Value* value, AutomationId* id, std::string* error);
+  static bool FromValueInDictionary(
+      base::DictionaryValue* dict, const std::string& key, AutomationId* id,
+      std::string* error);
+
+  // Constructs an invalid ID.
+  AutomationId();
+
+  // Constructs an ID from the given type and type-specific ID.
+  AutomationId(Type type, const std::string& id);
+
+  bool operator==(const AutomationId& id) const;
+
+  // Returns a new dictionary equivalent to this ID.
+  base::DictionaryValue* ToValue() const;
+
+  // Returns whether the automation ID is valid.
+  bool is_valid() const;
+
+  Type type() const;
+  const std::string& id() const;
+
+ private:
+  Type type_;
+  std::string id_;
+};
+
+#endif  // CHROME_COMMON_AUTOMATION_ID_H_
diff --git a/chrome/common/automation_messages.cc b/chrome/common/automation_messages.cc
new file mode 100644
index 0000000..8aadece
--- /dev/null
+++ b/chrome/common/automation_messages.cc
@@ -0,0 +1,317 @@
+// Copyright (c) 2012 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.
+
+#include "ui/base/models/menu_model.h"
+
+// Get basic type definitions.
+#define IPC_MESSAGE_IMPL
+#include "chrome/common/automation_messages.h"
+
+// Generate constructors.
+#include "ipc/struct_constructor_macros.h"
+#include "chrome/common/automation_messages.h"
+
+// Generate destructors.
+#include "ipc/struct_destructor_macros.h"
+#include "chrome/common/automation_messages.h"
+
+// Generate param traits write methods.
+#include "ipc/param_traits_write_macros.h"
+namespace IPC {
+#include "chrome/common/automation_messages.h"
+}  // namespace IPC
+
+// Generate param traits read methods.
+#include "ipc/param_traits_read_macros.h"
+namespace IPC {
+#include "chrome/common/automation_messages.h"
+}  // namespace IPC
+
+// Generate param traits log methods.
+#include "ipc/param_traits_log_macros.h"
+namespace IPC {
+#include "chrome/common/automation_messages.h"
+}  // namespace IPC
+
+ContextMenuModel::ContextMenuModel() {
+}
+
+ContextMenuModel::~ContextMenuModel() {
+  for (size_t i = 0; i < items.size(); ++i)
+    delete items[i].submenu;
+}
+
+ContextMenuModel::Item::Item()
+    : type(static_cast<int>(ui::MenuModel::TYPE_COMMAND)),
+      item_id(0),
+      checked(false),
+      enabled(true),
+      submenu(NULL) {
+}
+
+namespace IPC {
+
+void ParamTraits<AutomationMouseEvent>::Write(Message* m,
+                                              const param_type& p) {
+  WriteParam(m, std::string(reinterpret_cast<const char*>(&p.mouse_event),
+                            sizeof(p.mouse_event)));
+  WriteParam(m, p.location_script_chain);
+}
+
+bool ParamTraits<AutomationMouseEvent>::Read(const Message* m,
+                                             PickleIterator* iter,
+                                             param_type* p) {
+  std::string mouse_event;
+  if (!ReadParam(m, iter, &mouse_event))
+    return false;
+  memcpy(&p->mouse_event, mouse_event.c_str(), mouse_event.length());
+  if (!ReadParam(m, iter, &p->location_script_chain))
+    return false;
+  return true;
+}
+
+void ParamTraits<AutomationMouseEvent>::Log(const param_type& p,
+                                            std::string* l) {
+  l->append("(");
+  LogParam(std::string(reinterpret_cast<const char*>(&p.mouse_event),
+                       sizeof(p.mouse_event)),
+           l);
+  l->append(", ");
+  LogParam(p.location_script_chain, l);
+  l->append(")");
+}
+
+void ParamTraits<ContextMenuModel>::Write(Message* m,
+                                          const param_type& p) {
+  WriteParam(m, p.items.size());
+  for (size_t i = 0; i < p.items.size(); ++i) {
+    WriteParam(m, static_cast<int>(p.items[i].type));
+    WriteParam(m, p.items[i].item_id);
+    WriteParam(m, p.items[i].label);
+    WriteParam(m, p.items[i].checked);
+    WriteParam(m, p.items[i].enabled);
+
+    if (p.items[i].type == static_cast<int>(ui::MenuModel::TYPE_SUBMENU)) {
+      Write(m, *p.items[i].submenu);
+    }
+  }
+}
+
+bool ParamTraits<ContextMenuModel>::Read(const Message* m,
+                                         PickleIterator* iter,
+                                         param_type* p) {
+  size_t item_count = 0;
+  if (!ReadParam(m, iter, &item_count))
+    return false;
+
+  p->items.reserve(item_count);
+  for (size_t i = 0; i < item_count; ++i) {
+    ContextMenuModel::Item item;
+    if (!ReadParam(m, iter, &item.type))
+      return false;
+    if (!ReadParam(m, iter, &item.item_id))
+      return false;
+    if (!ReadParam(m, iter, &item.label))
+      return false;
+    if (!ReadParam(m, iter, &item.checked))
+      return false;
+    if (!ReadParam(m, iter, &item.enabled))
+      return false;
+
+    if (item.type == static_cast<int>(ui::MenuModel::TYPE_SUBMENU)) {
+      item.submenu = new ContextMenuModel;
+      if (!Read(m, iter, item.submenu)) {
+        delete item.submenu;
+        item.submenu = NULL;
+        return false;
+      }
+    }
+
+    p->items.push_back(item);
+  }
+
+  return true;
+}
+
+void ParamTraits<ContextMenuModel>::Log(const param_type& p,
+                                        std::string* l) {
+  l->append("(");
+  for (size_t i = 0; i < p.items.size(); ++i) {
+    const ContextMenuModel::Item& item = p.items[i];
+    if (i)
+      l->append(", ");
+    l->append("(");
+    LogParam(item.type, l);
+    l->append(", ");
+    LogParam(item.item_id, l);
+    l->append(", ");
+    LogParam(item.label, l);
+    l->append(", ");
+    LogParam(item.checked, l);
+    l->append(", ");
+    LogParam(item.enabled, l);
+    if (item.type == ui::MenuModel::TYPE_SUBMENU) {
+      l->append(", ");
+      Log(*item.submenu, l);
+    }
+    l->append(")");
+  }
+  l->append(")");
+}
+
+// Only the net::UploadData ParamTraits<> definition needs this definition, so
+// keep this in the implementation file so we can forward declare UploadData in
+// the header.
+template <>
+struct ParamTraits<net::UploadElement> {
+  typedef net::UploadElement param_type;
+  static void Write(Message* m, const param_type& p) {
+    WriteParam(m, static_cast<int>(p.type()));
+    switch (p.type()) {
+      case net::UploadElement::TYPE_BYTES: {
+        m->WriteData(p.bytes(), static_cast<int>(p.bytes_length()));
+        break;
+      }
+      default: {
+        DCHECK(p.type() == net::UploadElement::TYPE_FILE);
+        WriteParam(m, p.file_path());
+        WriteParam(m, p.file_range_offset());
+        WriteParam(m, p.file_range_length());
+        WriteParam(m, p.expected_file_modification_time());
+        break;
+      }
+    }
+  }
+  static bool Read(const Message* m, PickleIterator* iter, param_type* r) {
+    int type;
+    if (!ReadParam(m, iter, &type))
+      return false;
+    switch (type) {
+      case net::UploadElement::TYPE_BYTES: {
+        const char* data;
+        int len;
+        if (!m->ReadData(iter, &data, &len))
+          return false;
+        r->SetToBytes(data, len);
+        break;
+      }
+      default: {
+        DCHECK(type == net::UploadElement::TYPE_FILE);
+        FilePath file_path;
+        uint64 offset, length;
+        base::Time expected_modification_time;
+        if (!ReadParam(m, iter, &file_path))
+          return false;
+        if (!ReadParam(m, iter, &offset))
+          return false;
+        if (!ReadParam(m, iter, &length))
+          return false;
+        if (!ReadParam(m, iter, &expected_modification_time))
+          return false;
+        r->SetToFilePathRange(file_path, offset, length,
+                              expected_modification_time);
+        break;
+      }
+    }
+    return true;
+  }
+  static void Log(const param_type& p, std::string* l) {
+    l->append("<net::UploadElement>");
+  }
+};
+
+void ParamTraits<scoped_refptr<net::UploadData> >::Write(Message* m,
+                                                         const param_type& p) {
+  WriteParam(m, p.get() != NULL);
+  if (p) {
+    WriteParam(m, *p->elements());
+    WriteParam(m, p->identifier());
+    WriteParam(m, p->is_chunked());
+    WriteParam(m, p->last_chunk_appended());
+  }
+}
+
+bool ParamTraits<scoped_refptr<net::UploadData> >::Read(const Message* m,
+                                                        PickleIterator* iter,
+                                                        param_type* r) {
+  bool has_object;
+  if (!ReadParam(m, iter, &has_object))
+    return false;
+  if (!has_object)
+    return true;
+  std::vector<net::UploadElement> elements;
+  if (!ReadParam(m, iter, &elements))
+    return false;
+  int64 identifier;
+  if (!ReadParam(m, iter, &identifier))
+    return false;
+  bool is_chunked = false;
+  if (!ReadParam(m, iter, &is_chunked))
+    return false;
+  bool last_chunk_appended = false;
+  if (!ReadParam(m, iter, &last_chunk_appended))
+    return false;
+  *r = new net::UploadData;
+  (*r)->swap_elements(&elements);
+  (*r)->set_identifier(identifier);
+  (*r)->set_is_chunked(is_chunked);
+  (*r)->set_last_chunk_appended(last_chunk_appended);
+  return true;
+}
+
+void ParamTraits<scoped_refptr<net::UploadData> >::Log(const param_type& p,
+                                                       std::string* l) {
+  l->append("<net::UploadData>");
+}
+
+void ParamTraits<net::URLRequestStatus>::Write(Message* m,
+                                               const param_type& p) {
+  WriteParam(m, static_cast<int>(p.status()));
+  WriteParam(m, p.error());
+}
+
+bool ParamTraits<net::URLRequestStatus>::Read(const Message* m,
+                                              PickleIterator* iter,
+                                              param_type* r) {
+  int status, error;
+  if (!ReadParam(m, iter, &status) || !ReadParam(m, iter, &error))
+    return false;
+  r->set_status(static_cast<net::URLRequestStatus::Status>(status));
+  r->set_error(error);
+  return true;
+}
+
+void ParamTraits<net::URLRequestStatus>::Log(const param_type& p,
+                                             std::string* l) {
+  std::string status;
+  switch (p.status()) {
+    case net::URLRequestStatus::SUCCESS:
+      status = "SUCCESS";
+      break;
+    case net::URLRequestStatus::IO_PENDING:
+      status = "IO_PENDING ";
+      break;
+    case net::URLRequestStatus::CANCELED:
+      status = "CANCELED";
+      break;
+    case net::URLRequestStatus::FAILED:
+      status = "FAILED";
+      break;
+    default:
+      status = "UNKNOWN";
+      break;
+  }
+  if (p.status() == net::URLRequestStatus::FAILED)
+    l->append("(");
+
+  LogParam(status, l);
+
+  if (p.status() == net::URLRequestStatus::FAILED) {
+    l->append(", ");
+    LogParam(p.error(), l);
+    l->append(")");
+  }
+}
+
+}  // namespace IPC
diff --git a/chrome/common/automation_messages.h b/chrome/common/automation_messages.h
new file mode 100644
index 0000000..0a011ce
--- /dev/null
+++ b/chrome/common/automation_messages.h
@@ -0,0 +1,234 @@
+// Copyright (c) 2012 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.
+
+// Multiply-included message file, no traditional include guard.
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "chrome/common/automation_constants.h"
+#include "chrome/common/automation_events.h"
+#include "chrome/common/common_param_traits.h"
+#include "chrome/common/content_settings.h"
+#include "content/public/common/common_param_traits.h"
+#include "content/public/common/page_type.h"
+#include "content/public/common/security_style.h"
+#include "googleurl/src/gurl.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_message_utils.h"
+#include "net/base/cert_status_flags.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/upload_data.h"
+#include "net/url_request/url_request_status.h"
+#include "ui/gfx/rect.h"
+#include "webkit/glue/window_open_disposition.h"
+
+IPC_ENUM_TRAITS(AutomationMsg_NavigationResponseValues)
+IPC_ENUM_TRAITS(content::PageType)
+
+IPC_STRUCT_BEGIN(AutomationMsg_Find_Params)
+  // The word(s) to find on the page.
+  IPC_STRUCT_MEMBER(string16, search_string)
+
+  // Whether to search forward or backward within the page.
+  IPC_STRUCT_MEMBER(bool, forward)
+
+  // Whether search should be Case sensitive.
+  IPC_STRUCT_MEMBER(bool, match_case)
+
+  // Whether this operation is first request (Find) or a follow-up (FindNext).
+  IPC_STRUCT_MEMBER(bool, find_next)
+IPC_STRUCT_END()
+
+IPC_STRUCT_BEGIN(AutomationURLResponse)
+  IPC_STRUCT_MEMBER(std::string, mime_type)
+  IPC_STRUCT_MEMBER(std::string, headers)
+  IPC_STRUCT_MEMBER(int64, content_length)
+  IPC_STRUCT_MEMBER(base::Time, last_modified)
+  IPC_STRUCT_MEMBER(std::string, redirect_url)
+  IPC_STRUCT_MEMBER(int, redirect_status)
+  IPC_STRUCT_MEMBER(net::HostPortPair, socket_address)
+  IPC_STRUCT_MEMBER(uint64, upload_size)
+IPC_STRUCT_END()
+
+IPC_STRUCT_BEGIN(ExternalTabSettings)
+  IPC_STRUCT_MEMBER(gfx::NativeWindow, parent)
+  IPC_STRUCT_MEMBER(gfx::Rect, dimensions)
+  IPC_STRUCT_MEMBER(unsigned int, style)
+  IPC_STRUCT_MEMBER(bool, is_incognito)
+  IPC_STRUCT_MEMBER(bool, load_requests_via_automation)
+  IPC_STRUCT_MEMBER(bool, handle_top_level_requests)
+  IPC_STRUCT_MEMBER(GURL, initial_url)
+  IPC_STRUCT_MEMBER(GURL, referrer)
+  IPC_STRUCT_MEMBER(bool, infobars_enabled)
+  IPC_STRUCT_MEMBER(bool, route_all_top_level_navigations)
+IPC_STRUCT_END()
+
+IPC_STRUCT_BEGIN(NavigationInfo)
+  IPC_STRUCT_MEMBER(int, navigation_type)
+  IPC_STRUCT_MEMBER(int, relative_offset)
+  IPC_STRUCT_MEMBER(int, navigation_index)
+  IPC_STRUCT_MEMBER(std::wstring, title)
+  IPC_STRUCT_MEMBER(GURL, url)
+  IPC_STRUCT_MEMBER(GURL, referrer)
+  IPC_STRUCT_MEMBER(content::SecurityStyle, security_style)
+  IPC_STRUCT_MEMBER(bool, displayed_insecure_content)
+  IPC_STRUCT_MEMBER(bool, ran_insecure_content)
+IPC_STRUCT_END()
+
+// A stripped down version of ContextMenuParams.
+IPC_STRUCT_BEGIN(MiniContextMenuParams)
+  // The x coordinate for displaying the menu.
+  IPC_STRUCT_MEMBER(int, screen_x)
+
+  // The y coordinate for displaying the menu.
+  IPC_STRUCT_MEMBER(int, screen_y)
+
+  // This is the URL of the link that encloses the node the context menu was
+  // invoked on.
+  IPC_STRUCT_MEMBER(GURL, link_url)
+
+  // The link URL to be used ONLY for "copy link address". We don't validate
+  // this field in the frontend process.
+  IPC_STRUCT_MEMBER(GURL, unfiltered_link_url)
+
+  // This is the source URL for the element that the context menu was
+  // invoked on.  Example of elements with source URLs are img, audio, and
+  // video.
+  IPC_STRUCT_MEMBER(GURL, src_url)
+
+  // This is the URL of the top level page that the context menu was invoked
+  // on.
+  IPC_STRUCT_MEMBER(GURL, page_url)
+
+  // This is the absolute keyword search URL including the %s search tag when
+  // the "Add as search engine..." option is clicked (left empty if not used).
+  IPC_STRUCT_MEMBER(GURL, keyword_url)
+
+  // This is the URL of the subframe that the context menu was invoked on.
+  IPC_STRUCT_MEMBER(GURL, frame_url)
+IPC_STRUCT_END()
+
+IPC_STRUCT_BEGIN(AttachExternalTabParams)
+  IPC_STRUCT_MEMBER(uint64, cookie)
+  IPC_STRUCT_MEMBER(GURL, url)
+  IPC_STRUCT_MEMBER(gfx::Rect, dimensions)
+  IPC_STRUCT_MEMBER(int, disposition)
+  IPC_STRUCT_MEMBER(bool, user_gesture)
+  IPC_STRUCT_MEMBER(std::string, profile_name)
+IPC_STRUCT_END()
+
+#if defined(OS_WIN) && !defined(USE_AURA)
+
+IPC_STRUCT_BEGIN(Reposition_Params)
+  IPC_STRUCT_MEMBER(HWND, window)
+  IPC_STRUCT_MEMBER(HWND, window_insert_after)
+  IPC_STRUCT_MEMBER(int, left)
+  IPC_STRUCT_MEMBER(int, top)
+  IPC_STRUCT_MEMBER(int, width)
+  IPC_STRUCT_MEMBER(int, height)
+  IPC_STRUCT_MEMBER(int, flags)
+  IPC_STRUCT_MEMBER(bool, set_parent)
+  IPC_STRUCT_MEMBER(HWND, parent_window)
+IPC_STRUCT_END()
+
+#endif  // defined(OS_WIN)
+
+IPC_STRUCT_BEGIN(AutomationURLRequest)
+  IPC_STRUCT_MEMBER(std::string, url)
+  IPC_STRUCT_MEMBER(std::string, method)
+  IPC_STRUCT_MEMBER(std::string, referrer)
+  IPC_STRUCT_MEMBER(std::string, extra_request_headers)
+  IPC_STRUCT_MEMBER(scoped_refptr<net::UploadData>, upload_data)
+  IPC_STRUCT_MEMBER(int, resource_type)  // see webkit/glue/resource_type.h
+  IPC_STRUCT_MEMBER(int, load_flags) // see net/base/load_flags.h
+IPC_STRUCT_END()
+
+IPC_STRUCT_TRAITS_BEGIN(ScriptEvaluationRequest)
+  IPC_STRUCT_TRAITS_MEMBER(script)
+  IPC_STRUCT_TRAITS_MEMBER(frame_xpath)
+IPC_STRUCT_TRAITS_END()
+
+// Singly-included section for struct and custom IPC traits.
+#ifndef CHROME_COMMON_AUTOMATION_MESSAGES_H_
+#define CHROME_COMMON_AUTOMATION_MESSAGES_H_
+
+// This struct passes information about the context menu in Chrome stored
+// as a ui::MenuModel to Chrome Frame.  It is basically a container of
+// items that go in the menu.  An item may be a submenu, so the data
+// structure may be a tree.
+struct ContextMenuModel {
+  ContextMenuModel();
+  ~ContextMenuModel();
+
+  // This struct describes one item in the menu.
+  struct Item {
+    Item();
+
+    // This is the type of the menu item, using values from the enum
+    // ui::MenuModel::ItemType (serialized as an int).
+    int type;
+
+    // This is the command id of the menu item, which will be passed by
+    // Chrome Frame to Chrome if the item is selected.
+    int item_id;
+
+    // This the the menu label, if needed.
+    std::wstring label;
+
+    // These are states of the menu item.
+    bool checked;
+    bool enabled;
+
+    // This recursively describes the submenu if type is
+    // ui::MenuModel::TYPE_SUBMENU.
+    ContextMenuModel* submenu;
+  };
+
+  // This is the list of menu items.
+  std::vector<Item> items;
+};
+
+namespace IPC {
+
+template <>
+struct ParamTraits<AutomationMouseEvent> {
+  typedef AutomationMouseEvent param_type;
+  static void Write(Message* m, const param_type& p);
+  static bool Read(const Message* m, PickleIterator* iter, param_type* p);
+  static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<ContextMenuModel> {
+  typedef ContextMenuModel param_type;
+  static void Write(Message* m, const param_type& p);
+  static bool Read(const Message* m, PickleIterator* iter, param_type* p);
+  static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<scoped_refptr<net::UploadData> > {
+  typedef scoped_refptr<net::UploadData> param_type;
+  static void Write(Message* m, const param_type& p);
+  static bool Read(const Message* m, PickleIterator* iter, param_type* r);
+  static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<net::URLRequestStatus> {
+  typedef net::URLRequestStatus param_type;
+  static void Write(Message* m, const param_type& p);
+  static bool Read(const Message* m, PickleIterator* iter, param_type* r);
+  static void Log(const param_type& p, std::string* l);
+};
+
+}  // namespace IPC
+
+#endif  // CHROME_COMMON_AUTOMATION_MESSAGES_H_
+
+// Keep this internal message file unchanged to preserve line numbering
+// (and hence the dubious __LINE__-based message numberings) across versions.
+#include "chrome/common/automation_messages_internal.h"
diff --git a/chrome/common/automation_messages_internal.h b/chrome/common/automation_messages_internal.h
new file mode 100644
index 0000000..268b3c1
--- /dev/null
+++ b/chrome/common/automation_messages_internal.h
@@ -0,0 +1,1010 @@
+// Copyright (c) 2012 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.
+
+// Defines the IPC messages used by the automation interface.
+
+// NOTE: All IPC messages have either a routing_id of 0 (for asynchronous
+//       messages), or one that's been assigned by the proxy (for calls
+//       which expect a response).  The routing_id shouldn't be used for
+//       any other purpose in these message types.
+
+// NOTE: All the new IPC messages should go at the end.
+//       The test <--> browser IPC message IDs need to match the reference
+//       builds.  Since we now define the IDs based on __LINE__, to allow these
+//       IPC messages to be used to control an old version of Chrome we need
+//       the message IDs to remain the same.  This means that you should not
+//       change the line number of these types of messages. You can, however,
+//       change the browser <--> renderer messages.
+
+#define IPC_MESSAGE_START AutomationMsgStart
+
+// This message is fired when the AutomationProvider is up and running
+// in the app (the app is not fully up at this point). The parameter to this
+// message is the version string of the automation provider. This parameter
+// is defined to be the version string as returned by
+// chrome::VersionInfo::Version().
+// The client can choose to use this version string to decide whether or not
+// it can talk to the provider.
+IPC_MESSAGE_CONTROL1(AutomationMsg_Hello,
+                     std::string)
+
+// This message is fired when the initial tab(s) are finished loading.
+IPC_MESSAGE_CONTROL0(AutomationMsg_InitialLoadsComplete)
+
+// This message notifies the AutomationProvider to append a new tab the
+// window with the given handle. The return value contains the index of
+// the new tab, or -1 if the request failed.
+// The second parameter is the url to be loaded in the new tab.
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_AppendTab,
+                            int,
+                            GURL,
+                            int)
+
+// This message requests the (zero-based) index for the currently
+// active tab in the window with the given handle. The return value contains
+// the index of the active tab, or -1 if the request failed.
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_ActiveTabIndex,
+                            int,
+                            int)
+
+// This message notifies the AutomationProvider to active the tab.
+// The first parameter is the handle to window resource.
+// The second parameter is the (zero-based) index to be activated
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_ActivateTab,
+                            int,
+                            int,
+                            int)
+
+// This message requests the cookie value for given url in the
+// profile of the tab identified by the second parameter.  The first
+// parameter is the URL string. The response contains the length of the
+// cookie value string. On failure, this length = -1.
+IPC_SYNC_MESSAGE_CONTROL2_2(AutomationMsg_GetCookies,
+                            GURL,
+                            int,
+                            int,
+                            std::string)
+
+// This message notifies the AutomationProvider to set and broadcast a cookie
+// with given name and value for the given url in the profile of the tab
+// identified by the third parameter. The first parameter is the URL
+// string, and the second parameter is the cookie name and value to be set.
+// The return value is a non-negative value on success.
+IPC_SYNC_MESSAGE_CONTROL3_1(AutomationMsg_DEPRECATED_SetCookie,
+                            GURL,
+                            std::string,
+                            int,
+                            int)
+
+// This message is used to implement the asynchronous version of
+// NavigateToURL.
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_NavigationAsync,
+                            int /* tab handle */,
+                            GURL,
+                            bool /* result */)
+
+// This message requests the number of browser windows that the app currently
+// has open.  The return value is the number of windows.
+IPC_SYNC_MESSAGE_CONTROL0_1(AutomationMsg_BrowserWindowCount,
+                            int)
+
+// This message requests the handle (int64 app-unique identifier) of the
+// window with the given (zero-based) index.  On error, the returned handle
+// value is 0.
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_BrowserWindow,
+                            int,
+                            int)
+
+// This message requests the number of tabs in the window with the given
+// handle.  The return value contains the number of tabs, or -1 if the
+// request failed.
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_TabCount,
+                            int,
+                            int)
+
+// This message requests the handle of the tab with the given (zero-based)
+// index in the given app window. First parameter specifies the given window
+// handle, second specifies the given tab_index. On error, the returned handle
+// value is 0.
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_Tab,
+                            int,
+                            int,
+                            int)
+
+// This message requests the the title of the tab with the given handle.
+// The return value contains the size of the title string. On error, this
+// value should be -1 and empty string. Note that the title can be empty in
+// which case the size would be 0.
+IPC_SYNC_MESSAGE_CONTROL1_2(AutomationMsg_TabTitle,
+                            int,
+                            int,
+                            std::wstring)
+
+// This message requests the url of the tab with the given handle.
+// The return value contains a success flag and the URL string. The URL will
+// be empty on failure, and it still may be empty on success.
+IPC_SYNC_MESSAGE_CONTROL1_2(AutomationMsg_TabURL,
+                            int /* tab handle */,
+                            bool /* success flag */,
+                            GURL)
+
+// This message notifies the AutomationProxy that a handle that it has
+// previously been given is now invalid.  (For instance, if the handle
+// represented a window which has now been closed.)  The parameter
+// value is the handle.
+IPC_MESSAGE_CONTROL1(AutomationMsg_InvalidateHandle,
+                     int)
+
+// This message notifies the AutomationProvider that a handle is no
+// longer being used, so it can stop paying attention to the
+// associated resource.  The parameter value is the handle.
+IPC_MESSAGE_CONTROL1(AutomationMsg_HandleUnused,
+                     int)
+
+// This message requests that the AutomationProvider executes a JavaScript,
+// which is sent embedded in a 'javascript:' URL.
+// The javascript is executed in context of child frame whose xpath
+// is passed as parameter (context_frame). The execution results in
+// a serialized JSON string response.
+IPC_SYNC_MESSAGE_CONTROL3_1(AutomationMsg_DomOperation,
+                            int /* tab handle */,
+                            std::wstring /* context_frame */,
+                            std::wstring /* the javascript to be executed */,
+                            std::string /* the serialized json string containg
+                                           the result of a javascript
+                                           execution */)
+
+// Is the Download Shelf visible for the specified browser?
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_DEPRECATED_ShelfVisibility,
+                            int /* browser_handle */,
+                            bool /* is_visible */)
+
+// This message requests the bounds of the specified View element in
+// window coordinates.
+// Request:
+//   int - the handle of the window in which the view appears
+//   int - the ID of the view, as specified in chrome/browser/ui/view_ids.h
+//   bool - whether the bounds should be returned in the screen coordinates
+//          (if true) or in the browser coordinates (if false).
+// Response:
+//   bool - true if the view was found
+//   gfx::Rect - the bounds of the view, in window coordinates
+IPC_SYNC_MESSAGE_CONTROL3_2(AutomationMsg_WindowViewBounds,
+                            int,
+                            int,
+                            bool,
+                            bool,
+                            gfx::Rect)
+
+// This message sets the bounds of the window.
+// Request:
+//   int - the handle of the window to resize
+//   gfx::Rect - the bounds of the window
+// Response:
+//   bool - true if the resize was successful
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_SetWindowBounds,
+                            int,
+                            gfx::Rect,
+                            bool)
+
+// TODO(port): Port these messages.
+//
+// This message requests that a drag be performed in window coordinate space
+// Request:
+//   int - the handle of the window that's the context for this drag
+//   std::vector<gfx::Point> - the path of the drag in window coordinate
+//                             space; it should have at least 2 points
+//                             (start and end)
+//   int - the flags which identify the mouse button(s) for the drag, as
+//         defined in chrome/views/event.h
+// Response:
+//   bool - true if the drag could be performed
+IPC_SYNC_MESSAGE_CONTROL4_1(AutomationMsg_WindowDrag,
+                           int,
+                           std::vector<gfx::Point>,
+                           int,
+                           bool,
+                           bool)
+
+// Similar to AutomationMsg_InitialLoadsComplete, this indicates that the
+// new tab ui has completed the initial load of its data.
+// Time is how many milliseconds the load took.
+IPC_MESSAGE_CONTROL1(AutomationMsg_InitialNewTabUILoadComplete,
+                    int /* time */)
+
+// This tells the browser to enable or disable the filtered network layer.
+IPC_MESSAGE_CONTROL1(AutomationMsg_DEPRECATED_SetFilteredInet,
+                     bool /* enabled */)
+
+// Gets the directory that downloads will occur in for the active profile.
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_DEPRECATED_DownloadDirectory,
+                            int /* tab_handle */,
+                            FilePath /* directory */)
+
+// Opens a new browser window.
+// TODO(sky): remove this and replace with OpenNewBrowserWindowOfType.
+// Doing this requires updating the reference build.
+IPC_SYNC_MESSAGE_CONTROL1_0(AutomationMsg_OpenNewBrowserWindow,
+                            bool /* show */ )
+
+// This message requests the handle (int64 app-unique identifier) of the
+// current active top window.  On error, the returned handle value is 0.
+IPC_SYNC_MESSAGE_CONTROL0_1(AutomationMsg_DEPRECATED_ActiveWindow,
+                            int)
+
+// This message requests the window associated with the specified browser
+// handle.
+// The return value contains a success flag and the handle of the window.
+IPC_SYNC_MESSAGE_CONTROL1_2(AutomationMsg_WindowForBrowser,
+                            int /* browser handle */,
+                            bool /* success flag */,
+                            int /* window handle */)
+
+// This message requests that a key press be performed.
+// Request:
+//   int - the handle of the window that's the context for this click
+//   int - the ui::KeyboardCode of the key that was pressed.
+//   int - the flags which identify the modifiers (shift, ctrl, alt)
+//         associated for, as defined in chrome/views/event.h
+IPC_MESSAGE_CONTROL3(AutomationMsg_WindowKeyPress,
+                     int,
+                     int,
+                     int)
+
+// This message notifies the AutomationProvider to create a tab which is
+// hosted by an external process.
+// Request:
+//   ExternalTabSettings - settings for external tab
+IPC_SYNC_MESSAGE_CONTROL1_4(AutomationMsg_CreateExternalTab,
+                            ExternalTabSettings  /* settings*/,
+                            gfx::NativeWindow  /* Tab container window */,
+                            gfx::NativeWindow  /* Tab window */,
+                            int  /* Handle to the new tab */,
+                            int  /* Session Id of the new tab */)
+
+// This message notifies the AutomationProvider to navigate to a specified
+// url in the external tab with given handle. The first parameter is the
+// handle to the tab resource. The second parameter is the target url.
+// The third parameter is the referrer.
+// The return value contains a status code which is nonnegative on success.
+// see AutomationMsg_NavigationResponseValues for the navigation response.
+IPC_SYNC_MESSAGE_CONTROL3_1(AutomationMsg_NavigateInExternalTab,
+                            int,
+                            GURL,
+                            GURL,
+                            AutomationMsg_NavigationResponseValues)
+
+// This message is an outgoing message from Chrome to an external host.
+// It is a notification that the NavigationState was changed
+// Request:
+//   -int: The flags specifying what changed
+//         (see content::InvalidateTypes)
+// Response:
+//   None expected
+IPC_MESSAGE_ROUTED2(AutomationMsg_NavigationStateChanged,
+                    int,  // content::InvalidateTypes
+                    NavigationInfo)  // title, url etc.
+
+// This message is an outgoing message from Chrome to an external host.
+// It is a notification that the target URL has changed (the target URL
+// is the URL of the link that the user is hovering on)
+// Request:
+//   -std::wstring: The new target URL
+// Response:
+//   None expected
+IPC_MESSAGE_ROUTED1(AutomationMsg_UpdateTargetUrl,
+                    std::wstring)
+
+
+// This message requests that a tab be closed.
+// Request:
+//   - int: handle of the tab to close
+//   - bool: if true the proxy blocks until the tab has completely closed,
+//           otherwise the proxy only blocks until it initiates the close.
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_CloseTab,
+                            int,
+                            bool,
+                            bool)
+
+// This message requests that the browser be closed.
+// Request:
+//   - int: handle of the browser which contains the tab
+// Response:
+//  - bool: whether the operation was successfull.
+//  - bool: whether the browser process will be terminated as a result (if
+//          this was the last closed browser window).
+IPC_SYNC_MESSAGE_CONTROL1_2(AutomationMsg_CloseBrowser,
+                            int,
+                            bool,
+                            bool)
+
+#if defined(OS_WIN)
+// TODO(port): Port these messages.
+//
+// This message is an outgoing message from Chrome to an external host.
+// It is a request to process a keyboard accelerator.
+// Request:
+//   -MSG: The keyboard message
+// Response:
+//   None expected
+// TODO(sanjeevr): Ideally we need to add a response from the external
+// host saying whether it processed the accelerator
+IPC_MESSAGE_ROUTED1(AutomationMsg_HandleAccelerator,
+                    MSG)
+
+// This message is sent by the container of an externally hosted tab to
+// reflect any accelerator keys that it did not process. This gives the
+// tab a chance to handle the keys
+// Request:
+//   - int: handle of the tab
+//   -MSG: The keyboard message that the container did not handle
+// Response:
+//   None expected
+IPC_MESSAGE_CONTROL2(AutomationMsg_ProcessUnhandledAccelerator,
+                     int,
+                     MSG)
+#endif  // defined(OS_WIN)
+
+// Sent by the external tab to the host to notify that the user has tabbed
+// out of the tab.
+// Request:
+//   - bool: |reverse| set to true when shift-tabbing out of the tab, false
+//    otherwise.
+// Response:
+//   None expected
+IPC_MESSAGE_ROUTED1(AutomationMsg_TabbedOut,
+                    bool)
+
+// Sent by the external tab host to ask focus to be set to either the first
+// or last element on the page.
+// Request:
+//   - int: handle of the tab
+//   - bool: |reverse|
+//      true: Focus will be set to the last focusable element
+//      false: Focus will be set to the first focusable element
+//   - bool: |restore_focus_to_view|
+//      true: The renderer view associated with the current tab will be
+//            infomed that it is receiving focus.
+// Response:
+//   None expected
+IPC_MESSAGE_CONTROL3(AutomationMsg_SetInitialFocus,
+                     int,
+                     bool,
+                     bool)
+
+// This message is an outgoing message from Chrome to an external host.
+// It is a request to open a url
+// Request:
+//   -GURL: The URL to open
+//   -GURL: The referrer
+//   -int: The WindowOpenDisposition that specifies where the URL should
+//         be opened (new tab, new window etc).
+// Response:
+//   None expected
+IPC_MESSAGE_ROUTED3(AutomationMsg_OpenURL,
+                    GURL,
+                    GURL,
+                    int)
+
+// This message requests the provider to wait until the specified tab has
+// finished restoring after session restore.
+// Request:
+//   - int: handle of the tab
+// Response:
+//  - bool: whether the operation was successful.
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_DEPRECATED_WaitForTabToBeRestored,
+                            int, bool)
+
+// This message is an outgoing message from Chrome to an external host.
+// It is a notification that a navigation happened
+// Request:
+//
+// Response:
+//   None expected
+IPC_MESSAGE_ROUTED1(AutomationMsg_DidNavigate,
+                    NavigationInfo)
+
+// This message requests the different security states of the page displayed
+// in the specified tab.
+// Request:
+//   - int: handle of the tab
+// Response:
+//  - bool: whether the operation was successful.
+//  - SecurityStyle: the security style of the tab.
+//  - net::CertStatus: the status of the server's ssl cert (0 means no errors or
+//                     no ssl was used).
+//  - int: the insecure content state, 0 means no insecure contents.
+
+IPC_SYNC_MESSAGE_CONTROL1_4(AutomationMsg_DEPRECATED_GetSecurityState,
+                            int,
+                            bool,
+                            content::SecurityStyle,
+                            net::CertStatus,
+                            int)
+
+// This message requests the page type of the page displayed in the specified
+// tab (normal, error or interstitial).
+// Request:
+//   - int: handle of the tab
+// Response:
+//  - bool: whether the operation was successful.
+//  - PageType: the type of the page currently displayed.
+IPC_SYNC_MESSAGE_CONTROL1_2(AutomationMsg_DEPRECATED_GetPageType,
+                            int,
+                            bool,
+                            content::PageType)
+
+// This message simulates the user action on the SSL blocking page showing in
+// the specified tab.  This message is only effective if an interstitial page
+// is showing in the tab.
+// Request:
+//   - int: handle of the tab
+//   - bool: whether to proceed or abort the navigation
+// Response:
+//  - AutomationMsg_NavigationResponseValues: result of the operation.
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_DEPRECATED_ActionOnSSLBlockingPage,
+                            int,
+                            bool,
+                            AutomationMsg_NavigationResponseValues)
+
+// Message to request that a browser window is brought to the front and
+// activated.
+// Request:
+//   - int: handle of the browser window.
+// Response:
+//   - bool: True if the browser is brought to the front.
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_BringBrowserToFront,
+                            int,
+                            bool)
+
+// Message to request whether a certain item is enabled of disabled in the
+// menu in the browser window
+//
+// Request:
+//   - int: handle of the browser window.
+//   - int: IDC message identifier to query if enabled
+// Response:
+//   - bool: True if the command is enabled on the menu
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_DEPRECATED_IsMenuCommandEnabled,
+                            int,
+                            int,
+                            bool)
+
+// This message notifies the AutomationProvider to reload the current page in
+// the tab with given handle. The first parameter is the handle to the tab
+// resource.  The return value contains a status code which is nonnegative on
+// success.
+// see AutomationMsg_NavigationResponseValues for the navigation response.
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_Reload,
+                            int,
+                            AutomationMsg_NavigationResponseValues)
+
+// This message requests the execution of a browser command in the browser
+// for which the handle is specified.
+// The return value contains a boolean, whether the command was dispatched.
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_WindowExecuteCommandAsync,
+                            int /* automation handle */,
+                            int /* browser command */,
+                            bool /* success flag */)
+
+// This message requests the execution of a browser command in the browser
+// for which the handle is specified.
+// The return value contains a boolean, whether the command was dispatched
+// and successful executed.
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_WindowExecuteCommand,
+                            int /* automation handle */,
+                            int /* browser command */,
+                            bool /* success flag */)
+
+
+// This message opens the Find window within a tab corresponding to the
+// supplied tab handle.
+IPC_MESSAGE_CONTROL1(AutomationMsg_DEPRECATED_OpenFindInPage,
+                     int /* tab_handle */)
+
+// Posts a message from external host to chrome renderer.
+IPC_MESSAGE_CONTROL4(AutomationMsg_HandleMessageFromExternalHost,
+                     int /* automation handle */,
+                     std::string /* message */,
+                     std::string /* origin */,
+                     std::string /* target */)
+
+// A message for an external host.
+IPC_MESSAGE_ROUTED3(AutomationMsg_ForwardMessageToExternalHost,
+                    std::string /* message */,
+                    std::string /* origin */,
+                    std::string /* target */)
+
+// This message starts a find within a tab corresponding to the supplied
+// tab handle. The parameter |request| specifies what to search for.
+// If an error occurs, |matches_found| will be -1.
+//
+IPC_SYNC_MESSAGE_CONTROL2_2(AutomationMsg_Find,
+                            int /* tab_handle */,
+                            AutomationMsg_Find_Params /* params */,
+                            int /* active_ordinal */,
+                            int /* matches_found */)
+
+// Is the Find window fully visible (and not animating) for the specified
+// tab?
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_FindWindowVisibility,
+                            int /* tab_handle */,
+                            bool /* is_visible */)
+
+// Gets the bookmark bar visibility, animating and detached states.
+// TODO(phajdan.jr): Adjust the last param when the reference build is updated.
+IPC_SYNC_MESSAGE_CONTROL1_3(AutomationMsg_BookmarkBarVisibility,
+                            int /* browser_handle */,
+                            bool, /* is_visible */
+                            bool, /* still_animating */ bool /* is_detached */)
+
+// Uses the specified encoding to override the encoding of the page in the
+// specified web content tab.
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_OverrideEncoding,
+                            int /* tab handle */,
+                            std::string /* overrided encoding name */,
+                            bool /* success */)
+
+// This message is an outgoing message from Chrome to an external host.
+// It is a notification that a navigation failed
+// Request:
+//   -int : The status code.
+//   -GURL:  The URL we failed to navigate to.
+// Response:
+//   None expected
+IPC_MESSAGE_ROUTED2(AutomationMsg_NavigationFailed,
+                    int,
+                    GURL)
+
+#if defined(OS_WIN) && !defined(USE_AURA)
+// This message is an outgoing message from an automation client to Chrome.
+// It is used to reposition a chrome tab window.
+IPC_MESSAGE_CONTROL2(AutomationMsg_TabReposition,
+                     int /* tab handle */,
+                     Reposition_Params /* SetWindowPos params */)
+#endif  // defined(OS_WIN)
+
+// Tab load complete
+IPC_MESSAGE_ROUTED1(AutomationMsg_TabLoaded,
+                    GURL)
+
+// This message requests the tabstrip index of the tab with the given handle.
+// The return value contains the index, which will be -1 on failure.
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_TabIndex,
+                            int,
+                            int)
+
+// This message requests the handle (int64 app-unique identifier) of
+// a valid tabbed browser window, i.e. normal type and non-incognito mode.
+// On error, the returned handle value is 0.
+IPC_SYNC_MESSAGE_CONTROL0_1(AutomationMsg_DEPRECATED_FindTabbedBrowserWindow,
+                            int)
+
+// This message requests the number of normal browser windows, i.e. normal
+// type and non-incognito mode that the app currently has open.  The return
+// value is the number of windows.
+IPC_SYNC_MESSAGE_CONTROL0_1(AutomationMsg_NormalBrowserWindowCount,
+                            int)
+
+// This message tells the browser to start using the new proxy configuration
+// represented by the given JSON string. The parameters used in the JSON
+// string are defined in automation_constants.h.
+IPC_MESSAGE_CONTROL1(AutomationMsg_SetProxyConfig,
+                     std::string /* proxy_config_json_string */)
+
+// Sets Download Shelf visibility for the specified browser.
+IPC_SYNC_MESSAGE_CONTROL2_0(AutomationMsg_DEPRECATED_SetShelfVisibility,
+                            int /* browser_handle */,
+                            bool /* is_visible */)
+
+#if defined(OS_WIN)
+IPC_MESSAGE_ROUTED3(AutomationMsg_ForwardContextMenuToExternalHost,
+                    ContextMenuModel /* description of menu */,
+                    int    /* align flags */,
+                    MiniContextMenuParams /* params */)
+
+IPC_MESSAGE_CONTROL2(AutomationMsg_ForwardContextMenuCommandToChrome,
+                     int /* tab_handle */,
+                     int /* selected_command */)
+#endif  // OS_WIN
+
+// A URL request to be fetched via automation
+IPC_MESSAGE_ROUTED2(AutomationMsg_RequestStart,
+                    int /* request_id */,
+                    AutomationURLRequest /* request */)
+
+// Read data from a URL request to be fetched via automation
+IPC_MESSAGE_ROUTED2(AutomationMsg_RequestRead,
+                    int /* request_id */,
+                    int /* bytes_to_read */)
+
+// Response to a AutomationMsg_RequestStart message
+IPC_MESSAGE_ROUTED2(AutomationMsg_RequestStarted,
+                    int /* request_id */,
+                    AutomationURLResponse /* response */)
+
+// Data read via automation
+IPC_MESSAGE_ROUTED2(AutomationMsg_RequestData,
+                    int /* request_id */,
+                    std::string /* data */)
+
+IPC_MESSAGE_ROUTED2(AutomationMsg_RequestEnd,
+                    int /* request_id */,
+                    net::URLRequestStatus /* status */)
+
+IPC_MESSAGE_CONTROL1(AutomationMsg_PrintAsync,
+                     int /* tab_handle */)
+
+IPC_MESSAGE_ROUTED2(AutomationMsg_SetCookieAsync,
+                    GURL /* url */,
+                    std::string /* cookie */)
+
+IPC_MESSAGE_CONTROL1(AutomationMsg_SelectAll,
+                    int /* tab handle */)
+
+IPC_MESSAGE_CONTROL1(AutomationMsg_Cut,
+                     int /* tab handle */)
+
+IPC_MESSAGE_CONTROL1(AutomationMsg_Copy,
+                     int /* tab handle */)
+
+IPC_MESSAGE_CONTROL1(AutomationMsg_Paste,
+                     int /* tab handle */)
+
+IPC_MESSAGE_CONTROL1(AutomationMsg_ReloadAsync,
+                     int /* tab handle */)
+
+IPC_MESSAGE_CONTROL1(AutomationMsg_StopAsync,
+                     int /* tab handle */)
+
+// This message notifies the AutomationProvider to navigate to a specified
+// url in the tab with given handle. The first parameter is the handle to
+// the tab resource. The second parameter is the target url.  The third
+// parameter is the number of navigations that are required for a successful
+// return value. See AutomationMsg_NavigationResponseValues for the return
+// value.
+IPC_SYNC_MESSAGE_CONTROL3_1(
+    AutomationMsg_NavigateToURLBlockUntilNavigationsComplete,
+    int,
+    GURL,
+    int,
+    AutomationMsg_NavigationResponseValues)
+
+// This message notifies the AutomationProvider to navigate to a specified
+// navigation entry index in the external tab with given handle. The first
+// parameter is the handle to the tab resource. The second parameter is the
+// index of navigation entry.
+// The return value contains a status code which is nonnegative on success.
+// see AutomationMsg_NavigationResponseValues for the navigation response.
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_NavigateExternalTabAtIndex,
+                            int,
+                            int,
+                            AutomationMsg_NavigationResponseValues)
+
+// This message requests the provider to wait until the window count
+// reached the specified value.
+// Request:
+//  - int: target browser window count
+// Response:
+//  - bool: whether the operation was successful.
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_WaitForBrowserWindowCountToBecome,
+                           int,
+                           bool)
+
+// This message notifies the AutomationProvider to navigate back in session
+// history in the tab with given handle. The first parameter is the handle
+// to the tab resource. The second parameter is the number of navigations the
+// provider will wait for.
+// See AutomationMsg_NavigationResponseValues for the navigation response
+// values.
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_GoBackBlockUntilNavigationsComplete,
+                            int,
+                            int,
+                            AutomationMsg_NavigationResponseValues)
+
+// This message notifies the AutomationProvider to navigate forward in session
+// history in the tab with given handle. The first parameter is the handle
+// to the tab resource. The second parameter is the number of navigations
+// the provider will wait for.
+// See AutomationMsg_NavigationResponseValues for the navigation response
+// values.
+IPC_SYNC_MESSAGE_CONTROL2_1(
+    AutomationMsg_GoForwardBlockUntilNavigationsComplete,
+    int,
+    int,
+    AutomationMsg_NavigationResponseValues)
+
+IPC_MESSAGE_ROUTED1(AutomationMsg_AttachExternalTab,
+                    AttachExternalTabParams)
+
+// Sent when the automation client connects to an existing tab.
+IPC_SYNC_MESSAGE_CONTROL3_4(AutomationMsg_ConnectExternalTab,
+                            uint64 /* cookie */,
+                            bool   /* allow/block tab*/,
+                            gfx::NativeWindow  /* parent window */,
+                            gfx::NativeWindow  /* Tab container window */,
+                            gfx::NativeWindow  /* Tab window */,
+                            int  /* Handle to the new tab */,
+                            int  /* Session Id of the new tab */)
+
+// Simulate an end of session. Normally this happens when the user
+// shuts down the machine or logs off.
+// Request:
+//   int - the handle of the browser
+// Response:
+//   bool - true if succesful
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_TerminateSession,
+                            int,
+                            bool)
+
+IPC_MESSAGE_CONTROL2(AutomationMsg_SetPageFontSize,
+                     int /* tab_handle */,
+                     int /* The font size */)
+
+// Returns a metric event duration that was last recorded.  Returns -1 if the
+// event hasn't occurred yet.
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_GetMetricEventDuration,
+                            std::string /* event_name */,
+                            int /* duration ms */)
+
+// Sent by automation provider - go to history entry via automation.
+IPC_MESSAGE_ROUTED1(AutomationMsg_RequestGoToHistoryEntryOffset,
+                    int)   // numbers of entries (negative or positive)
+
+// This message requests the type of the window with the given handle. The
+// return value contains the type (Browser::Type), or -1 if the request
+// failed.
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_Type,
+                            int,
+                            int)
+
+// Opens a new browser window of a specific type.
+IPC_SYNC_MESSAGE_CONTROL2_0(AutomationMsg_OpenNewBrowserWindowOfType,
+                            int   /* Type (Browser::Type) */,
+                            bool  /* show */ )
+
+// This message requests that the mouse be moved to this location, in
+// window coordinate space.
+// Request:
+//   int - the handle of the window that's the context for this click
+//   gfx::Point - the location to move to
+IPC_MESSAGE_CONTROL2(AutomationMsg_WindowMouseMove,
+                     int,
+                     gfx::Point)
+
+// Called when requests should be downloaded using a host browser's
+// download mechanism when chrome is being embedded.
+IPC_MESSAGE_ROUTED1(AutomationMsg_DownloadRequestInHost,
+                    int /* request_id */)
+
+IPC_MESSAGE_CONTROL1(AutomationMsg_SaveAsAsync,
+                     int /* tab handle */)
+
+#if defined(OS_WIN)
+// An incoming message from an automation host to Chrome.  Signals that
+// the browser containing |tab_handle| has moved.
+IPC_MESSAGE_CONTROL1(AutomationMsg_BrowserMove,
+                     int /* tab handle */)
+#endif
+
+// Used to get cookies for the given URL.
+IPC_MESSAGE_ROUTED2(AutomationMsg_GetCookiesFromHost,
+                    GURL /* url */,
+                    int /* opaque_cookie_id */)
+
+IPC_MESSAGE_CONTROL5(AutomationMsg_GetCookiesHostResponse,
+                     int /* tab_handle */,
+                     bool /* success */,
+                     GURL /* url */,
+                     std::string /* cookies */,
+                     int /* opaque_cookie_id */)
+
+// Return the bookmarks encoded as a JSON string.
+IPC_SYNC_MESSAGE_CONTROL1_2(AutomationMsg_DEPRECATED_GetBookmarksAsJSON,
+                            int /* browser_handle */,
+                            std::string /* bookmarks as a JSON string */,
+                            bool /* success */)
+
+// Wait for the bookmark model to load.
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_WaitForBookmarkModelToLoad,
+                            int /* browser_handle */,
+                            bool /* success */)
+
+// Bookmark addition, modification, and removal.
+// Bookmarks are indexed by their id.
+IPC_SYNC_MESSAGE_CONTROL4_1(AutomationMsg_DEPRECATED_AddBookmarkGroup,
+                            int /* browser_handle */,
+                            int64 /* parent_id */,
+                            int /* index */,
+                            std::wstring /* title */,
+                            bool /* success */)
+IPC_SYNC_MESSAGE_CONTROL5_1(AutomationMsg_DEPRECATED_AddBookmarkURL,
+                            int /* browser_handle */,
+                            int64 /* parent_id */,
+                            int /* index */,
+                            std::wstring /* title */,
+                            GURL /* url */,
+                            bool /* success */)
+IPC_SYNC_MESSAGE_CONTROL4_1(AutomationMsg_DEPRECATED_ReparentBookmark,
+                            int /* browser_handle */,
+                            int64 /* id */,
+                            int64 /* new_parent_id */,
+                            int /* index */,
+                            bool /* success */)
+IPC_SYNC_MESSAGE_CONTROL3_1(AutomationMsg_DEPRECATED_SetBookmarkTitle,
+                            int /* browser_handle */,
+                            int64 /* id */,
+                            std::wstring /* title */,
+                            bool /* success */)
+IPC_SYNC_MESSAGE_CONTROL3_1(AutomationMsg_DEPRECATED_SetBookmarkURL,
+                            int /* browser_handle */,
+                            int64 /* id */,
+                            GURL /* url */,
+                            bool /* success */)
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_DEPRECATED_RemoveBookmark,
+                            int /* browser_handle */,
+                            int64 /* id */,
+                            bool /* success */)
+
+// This message informs the browser process to remove the history entries
+// for the specified types across all time ranges. See
+// browsing_data_remover.h for a list of REMOVE_* types supported in the
+// remove_mask parameter.
+IPC_MESSAGE_CONTROL1(AutomationMsg_RemoveBrowsingData,
+                     int)
+
+// Generic pyauto pattern to help avoid future addition of
+// automation messages.
+IPC_SYNC_MESSAGE_CONTROL2_2(AutomationMsg_SendJSONRequestWithBrowserHandle,
+                            int /* browser_handle */,
+                            std::string /* JSON request */,
+                            std::string /* JSON response */,
+                            bool /* success */)
+
+// Resets to the default theme.
+IPC_SYNC_MESSAGE_CONTROL0_0(AutomationMsg_DEPRECIATED_ResetToDefaultTheme)
+
+// This message requests the external tab identified by the tab handle
+// passed in be closed.
+// Request:
+// Response:
+//   None expected
+IPC_MESSAGE_ROUTED0(AutomationMsg_CloseExternalTab)
+
+// This message requests that the external tab identified by the tab handle
+// runs unload handlers if any on the current page.
+// Request:
+//   -int: Tab handle
+//   -bool: result: true->unload, false->don't unload
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_RunUnloadHandlers,
+                            int,
+                            bool)
+
+// This message sets the current zoom level on the tab
+// Request:
+//   -int: Tab handle
+//   -int: Zoom level. Values ZOOM_OUT = -1, RESET = 0, ZOOM_IN  = 1
+// Response:
+//   None expected
+IPC_MESSAGE_CONTROL2(AutomationMsg_SetZoomLevel,
+                     int,
+                     int)
+
+// Waits for tab count to reach target value.
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_WaitForTabCountToBecome,
+                            int /* browser handle */,
+                            int /* target tab count */,
+                            bool /* success */)
+
+// Waits for the infobar count to reach given number.
+IPC_SYNC_MESSAGE_CONTROL2_1(AutomationMsg_WaitForInfoBarCount,
+                            int /* tab handle */,
+                            size_t /* target count */,
+                            bool /* success */)
+
+// Notify the JavaScript engine in the render to change its parameters
+// while performing stress testing.
+IPC_MESSAGE_CONTROL3(AutomationMsg_JavaScriptStressTestControl,
+                     int /* tab handle */,
+                     int /* command */,
+                     int /* type or run */)
+
+// This message posts a task to the PROCESS_LAUNCHER thread. Once processed
+// the response is sent back. This is useful when you want to make sure all
+// changes to the number of processes have completed.
+IPC_SYNC_MESSAGE_CONTROL0_0(AutomationMsg_WaitForProcessLauncherThreadToGoIdle)
+
+// This message is an outgoing message from Chrome to an external host.
+// It is a notification that a popup window position or dimentions have
+// changed
+// Request:
+//   gfx::Rect - the bounds of the window
+// Response:
+//   None expected
+IPC_MESSAGE_ROUTED1(AutomationMsg_MoveWindow,
+                    gfx::Rect /* window position and dimentions */)
+
+// Call BeginTracing on the browser TraceController. This will tell all
+// processes to start collecting trace events via base/debug/trace_event.h.
+IPC_SYNC_MESSAGE_CONTROL1_1(AutomationMsg_BeginTracing,
+                            std::string /* categories */,
+                            bool /* success */)
+
+// End tracing (called after BeginTracing). This blocks until tracing has
+// stopped on all processes and all the events are ready to be retrieved.
+IPC_SYNC_MESSAGE_CONTROL0_2(AutomationMsg_EndTracing,
+                            size_t /* num_trace_chunks */,
+                            bool /* success */)
+
+// Retrieve trace event data (called after EndTracing). Must call exactly
+// |num_trace_chunks| times.
+// TODO(jbates): See bug 100255, IPC send fails if message is too big. This
+// code can be removed if that limitation is fixed.
+IPC_SYNC_MESSAGE_CONTROL0_2(AutomationMsg_GetTracingOutput,
+                            std::string /* trace_chunk */,
+                            bool /* success */)
+
+// Used on Mac OS X to read the number of active Mach ports used in the browser
+// process.
+IPC_SYNC_MESSAGE_CONTROL0_1(AutomationMsg_GetMachPortCount,
+                            int /* number of Mach ports */)
+
+// Generic pyauto pattern to help avoid future addition of
+// automation messages.
+IPC_SYNC_MESSAGE_CONTROL2_2(AutomationMsg_SendJSONRequest,
+                            int /* window_index */,
+                            std::string /* JSON request */,
+                            std::string /* JSON response */,
+                            bool /* success */)
+
+// Browser -> renderer messages.
+
+// Requests a snapshot.
+IPC_MESSAGE_ROUTED0(AutomationMsg_SnapshotEntirePage)
+
+#if !defined(NO_TCMALLOC) && (defined(OS_LINUX) || defined(OS_CHROMEOS))
+// Requests to dump a heap profile.
+IPC_MESSAGE_ROUTED1(AutomationMsg_HeapProfilerDump,
+                    std::string /* reason */)
+#endif  // !defined(NO_TCMALLOC) && (defined(OS_LINUX) || defined(OS_CHROMEOS))
+
+// Requests processing of the given mouse event.
+IPC_MESSAGE_ROUTED1(AutomationMsg_ProcessMouseEvent,
+                    AutomationMouseEvent)
+
+// Renderer -> browser messages.
+
+// Sent as a response to |AutomationMsg_Snapshot|.
+IPC_MESSAGE_ROUTED3(AutomationMsg_SnapshotEntirePageACK,
+                    bool /* success */,
+                    std::vector<unsigned char> /* png bytes */,
+                    std::string /* error message */)
+
+// Sent when the renderer has scheduled a client redirect to occur.
+IPC_MESSAGE_ROUTED2(AutomationMsg_WillPerformClientRedirect,
+                    int64 /* frame_id */,
+                    double /* # of seconds till redirect will be performed */)
+
+// Sent when the renderer has completed or canceled a client redirect for a
+// particular frame. This message may be sent multiple times for the same
+// redirect.
+IPC_MESSAGE_ROUTED1(AutomationMsg_DidCompleteOrCancelClientRedirect,
+                    int64 /* frame_id */)
+
+// Sent right before processing a mouse event at the given point.
+// This is needed in addition to AutomationMsg_ProcessMouseEventACK so that
+// the client knows where the event occurred even if the event causes a modal
+// dialog to appear which blocks further messages.
+IPC_MESSAGE_ROUTED1(AutomationMsg_WillProcessMouseEventAt,
+                    gfx::Point)
+
+// Sent when the automation mouse event has been processed.
+IPC_MESSAGE_ROUTED2(AutomationMsg_ProcessMouseEventACK,
+                    bool /* success */,
+                    std::string /* error message */)
+
+// YOUR NEW MESSAGE MIGHT NOT BELONG HERE.
+// This is the section for renderer -> browser automation messages. If it is
+// an automation <-> browser message, put it above this section. The "no line
+// number change" applies only to the automation <-> browser messages.
diff --git a/chrome/common/badge_util.cc b/chrome/common/badge_util.cc
new file mode 100644
index 0000000..e33f8d4
--- /dev/null
+++ b/chrome/common/badge_util.cc
@@ -0,0 +1,103 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/badge_util.h"
+
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkTypeface.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/size.h"
+
+namespace badge_util {
+
+SkPaint* GetBadgeTextPaintSingleton() {
+#if defined(OS_MACOSX)
+  const char kPreferredTypeface[] = "Helvetica Bold";
+#else
+  const char kPreferredTypeface[] = "Arial";
+#endif
+
+  static SkPaint* text_paint = NULL;
+  if (!text_paint) {
+    text_paint = new SkPaint;
+    text_paint->setAntiAlias(true);
+    text_paint->setTextAlign(SkPaint::kLeft_Align);
+
+    SkTypeface* typeface = SkTypeface::CreateFromName(
+        kPreferredTypeface, SkTypeface::kBold);
+    // Skia doesn't do any font fallback---if the user is missing the font then
+    // typeface will be NULL. If we don't do manual fallback then we'll crash.
+    if (typeface) {
+      text_paint->setFakeBoldText(true);
+    } else {
+      // Fall back to the system font. We don't bold it because we aren't sure
+      // how it will look.
+      // For the most part this code path will only be hit on Linux systems
+      // that don't have Arial.
+      ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+      const gfx::Font& base_font = rb.GetFont(ResourceBundle::BaseFont);
+      typeface = SkTypeface::CreateFromName(
+          base_font.GetFontName().c_str(), SkTypeface::kNormal);
+      DCHECK(typeface);
+    }
+
+    text_paint->setTypeface(typeface);
+    // |text_paint| adds its own ref. Release the ref from CreateFontName.
+    typeface->unref();
+  }
+  return text_paint;
+}
+
+SkBitmap DrawBadgeIconOverlay(const SkBitmap& icon,
+                              float font_size,
+                              const string16& text,
+                              const string16& fallback) {
+  const int kMinPadding = 1;
+
+  // Calculate the proper style/text overlay to render on the badge.
+  SkPaint* paint = badge_util::GetBadgeTextPaintSingleton();
+  paint->setTextSize(SkFloatToScalar(font_size));
+  paint->setColor(SK_ColorWHITE);
+
+  std::string badge_text = UTF16ToUTF8(text);
+
+  // See if the text will fit - otherwise use a default.
+  SkScalar text_width = paint->measureText(badge_text.c_str(),
+                                           badge_text.size());
+
+  if (SkScalarRound(text_width) > (icon.width() - kMinPadding * 2)) {
+    // String is too large - use the alternate text.
+    badge_text = UTF16ToUTF8(fallback);
+    text_width = paint->measureText(badge_text.c_str(), badge_text.size());
+  }
+
+  // When centering the text, we need to make sure there are an equal number
+  // of pixels on each side as otherwise the text looks off-center. So if the
+  // padding would be uneven, clip one pixel off the right side.
+  int badge_width = icon.width();
+  if ((SkScalarRound(text_width) % 1) != (badge_width % 1))
+    badge_width--;
+
+  // Render the badge bitmap and overlay into a canvas.
+  scoped_ptr<gfx::Canvas> canvas(new gfx::Canvas(
+      gfx::Size(badge_width, icon.height()), ui::SCALE_FACTOR_100P, false));
+  canvas->DrawImageInt(gfx::ImageSkia(icon), 0, 0);
+
+  // Draw the text overlay centered horizontally and vertically. Skia expects
+  // us to specify the lower left coordinate of the text box, which is why we
+  // add 'font_size - 1' to the height.
+  SkScalar x = (badge_width - text_width)/2;
+  SkScalar y = (icon.height() - font_size)/2 + font_size - 1;
+  canvas->sk_canvas()->drawText(
+      badge_text.c_str(), badge_text.size(), x, y, *paint);
+
+  // Return the generated image.
+  return canvas->ExtractImageRep().sk_bitmap();
+}
+
+}  // namespace badge_util
diff --git a/chrome/common/badge_util.h b/chrome/common/badge_util.h
new file mode 100644
index 0000000..85e127e
--- /dev/null
+++ b/chrome/common/badge_util.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2010 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.
+
+#ifndef CHROME_COMMON_BADGE_UTIL_H_
+#define CHROME_COMMON_BADGE_UTIL_H_
+
+#include "base/string16.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+class SkPaint;
+
+// badge_util provides a set of helper routines for rendering dynamically
+// generated text overlays ("badges") on toolbar icons.
+namespace badge_util {
+
+// Helper routine that returns a singleton SkPaint object configured for
+// rendering badge overlay text (correct font, typeface, etc).
+SkPaint* GetBadgeTextPaintSingleton();
+
+// Given an |icon|, renders the |text| centered on the |icon|. If |text| is
+// too large to fit within the bounds of the image, the |fallback| string is
+// rendered instead (or nothing, if |fallback| is empty).
+SkBitmap DrawBadgeIconOverlay(const SkBitmap& icon,
+                              float font_size_in_pixels,
+                              const string16& text,
+                              const string16& fallback);
+
+}  // namespace badge_util;
+
+#endif  // CHROME_COMMON_BADGE_UTIL_H_
diff --git a/chrome/common/benchmarking_messages.h b/chrome/common/benchmarking_messages.h
new file mode 100644
index 0000000..02c8f3d
--- /dev/null
+++ b/chrome/common/benchmarking_messages.h
@@ -0,0 +1,47 @@
+// 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.
+
+// Multiply-included file, no traditional include guard.
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "build/build_config.h"
+
+#include "ipc/ipc_message_macros.h"
+#include "ui/gfx/native_widget_types.h"
+
+#define IPC_MESSAGE_START ChromeBenchmarkingMsgStart
+
+// Message sent from the renderer to the browser to request that the browser
+// close all sockets.  Used for debugging/testing.
+IPC_MESSAGE_CONTROL0(ChromeViewHostMsg_CloseCurrentConnections)
+
+// Message sent from the renderer to the browser to request that the browser
+// enable or disable the cache.  Used for debugging/testing.
+IPC_MESSAGE_CONTROL1(ChromeViewHostMsg_SetCacheMode,
+                     bool /* enabled */)
+
+// Message sent from the renderer to the browser to request that the browser
+// clear the cache.  Used for debugging/testing.
+// |result| is the returned status from the operation.
+IPC_SYNC_MESSAGE_CONTROL0_1(ChromeViewHostMsg_ClearCache,
+                            int /* result */)
+
+// Message sent from the renderer to the browser to request that the browser
+// clear the host cache.  Used for debugging/testing.
+// |result| is the returned status from the operation.
+IPC_SYNC_MESSAGE_CONTROL0_1(ChromeViewHostMsg_ClearHostResolverCache,
+                            int /* result */)
+
+// Message sent from the renderer to the browser to request that the browser
+// enable or disable spdy.  Used for debugging/testing/benchmarking.
+IPC_MESSAGE_CONTROL1(ChromeViewHostMsg_EnableSpdy,
+                     bool /* enable */)
+
+// Message sent from the renderer to the browser to request that the browser
+// clear the predictor cache.  Used for debugging/testing.
+// |result| is the returned status from the operation.
+IPC_SYNC_MESSAGE_CONTROL0_1(ChromeViewHostMsg_ClearPredictorCache,
+                            int /* result */)
diff --git a/chrome/common/bzip2_error_handler.cc b/chrome/common/bzip2_error_handler.cc
new file mode 100644
index 0000000..e66bcf3
--- /dev/null
+++ b/chrome/common/bzip2_error_handler.cc
@@ -0,0 +1,14 @@
+// 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.
+
+#include <ostream>
+
+#include "base/logging.h"
+
+// We define BZ_NO_STDIO in third_party/bzip2 to remove its internal STDERR
+// error reporting.  This requires us to export our own error handler.
+extern "C"
+void bz_internal_error(int errcode) {
+  LOG(FATAL) << "bzip2 internal error: " << errcode;
+}
diff --git a/chrome/common/bzip2_unittest.cc b/chrome/common/bzip2_unittest.cc
new file mode 100644
index 0000000..0f14205
--- /dev/null
+++ b/chrome/common/bzip2_unittest.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2006-2008 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.
+
+#if defined(USE_SYSTEM_LIBBZ2)
+#include <bzlib.h>
+#else
+#include "third_party/bzip2/bzlib.h"
+#endif
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+  class Bzip2Test : public testing::Test {
+  };
+};
+
+// This test does a simple round trip to test that the bzip2 library is
+// present and working.
+TEST(Bzip2Test, Roundtrip) {
+  char input[] = "Test Data, More Test Data, Even More Data of Test";
+  char output[256];
+
+  memset(output, 0, arraysize(output));
+
+  bz_stream stream;
+  stream.bzalloc = NULL;
+  stream.bzfree = NULL;
+  stream.opaque = NULL;
+  int result = BZ2_bzCompressInit(&stream,
+                                  9,   // 900k block size
+                                  0,   // quiet
+                                  0);  // default work factor
+  ASSERT_EQ(BZ_OK, result);
+
+  stream.next_in = input;
+  stream.avail_in = arraysize(input) - 1;
+  stream.next_out = output;
+  stream.avail_out = arraysize(output);
+  do {
+    result = BZ2_bzCompress(&stream, BZ_FINISH);
+  } while (result == BZ_FINISH_OK);
+  ASSERT_EQ(BZ_STREAM_END, result);
+  result = BZ2_bzCompressEnd(&stream);
+  ASSERT_EQ(BZ_OK, result);
+  int written = stream.total_out_lo32;
+
+  // Make sure we wrote something; otherwise not sure what to expect
+  ASSERT_GT(written, 0);
+
+  // Now decompress and check that we got the same thing.
+  result = BZ2_bzDecompressInit(&stream, 0, 0);
+  ASSERT_EQ(BZ_OK, result);
+  char output2[256];
+  memset(output2, 0, arraysize(output2));
+
+  stream.next_in = output;
+  stream.avail_in = written;
+  stream.next_out = output2;
+  stream.avail_out = arraysize(output2);
+
+  do {
+    result = BZ2_bzDecompress(&stream);
+  } while (result == BZ_OK);
+  ASSERT_EQ(result, BZ_STREAM_END);
+  result = BZ2_bzDecompressEnd(&stream);
+  ASSERT_EQ(result, BZ_OK);
+
+  EXPECT_EQ(arraysize(input) - 1, stream.total_out_lo32);
+  EXPECT_STREQ(input, output2);
+}
diff --git a/chrome/common/cancelable_task_tracker.cc b/chrome/common/cancelable_task_tracker.cc
new file mode 100644
index 0000000..3570e05
--- /dev/null
+++ b/chrome/common/cancelable_task_tracker.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/cancelable_task_tracker.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop_proxy.h"
+#include "base/synchronization/cancellation_flag.h"
+#include "base/task_runner.h"
+
+using base::Bind;
+using base::CancellationFlag;
+using base::Closure;
+using base::hash_map;
+using base::TaskRunner;
+
+namespace {
+
+void RunIfNotCanceled(const CancellationFlag* flag, const Closure& task) {
+  if (!flag->IsSet())
+    task.Run();
+}
+
+void RunIfNotCanceledThenUntrack(const CancellationFlag* flag,
+                                 const Closure& task,
+                                 const Closure& untrack) {
+  RunIfNotCanceled(flag, task);
+  untrack.Run();
+}
+
+}  // namespace
+
+// static
+const CancelableTaskTracker::TaskId CancelableTaskTracker::kBadTaskId = 0;
+
+CancelableTaskTracker::CancelableTaskTracker()
+    : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
+      next_id_(1) {}
+
+CancelableTaskTracker::~CancelableTaskTracker() {
+  TryCancelAll();
+}
+
+CancelableTaskTracker::TaskId CancelableTaskTracker::PostTask(
+    TaskRunner* task_runner,
+    const tracked_objects::Location& from_here,
+    const Closure& task) {
+  return PostTaskAndReply(task_runner, from_here, task, Bind(&base::DoNothing));
+}
+
+CancelableTaskTracker::TaskId CancelableTaskTracker::PostTaskAndReply(
+    TaskRunner* task_runner,
+    const tracked_objects::Location& from_here,
+    const Closure& task,
+    const Closure& reply) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  // We need a MessageLoop to run reply.
+  DCHECK(base::MessageLoopProxy::current());
+
+  // Owned by reply callback below.
+  CancellationFlag* flag = new CancellationFlag();
+
+  TaskId id = next_id_;
+  next_id_++;  // int64 is big enough that we ignore the potential overflow.
+
+  const Closure& untrack_closure = Bind(
+      &CancelableTaskTracker::Untrack, weak_factory_.GetWeakPtr(), id);
+  bool success = task_runner->PostTaskAndReply(
+      from_here,
+      Bind(&RunIfNotCanceled, flag, task),
+      Bind(&RunIfNotCanceledThenUntrack,
+           base::Owned(flag), reply, untrack_closure));
+
+  if (!success)
+    return kBadTaskId;
+
+  Track(id, flag);
+  return id;
+}
+
+void CancelableTaskTracker::TryCancel(TaskId id) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  hash_map<TaskId, CancellationFlag*>::const_iterator it = task_flags_.find(id);
+  if (it == task_flags_.end()) {
+    // 3 possibilities:
+    // 1. Task (and reply) has finished running.
+    // 2. Task was canceled before and already untracked.
+    // 3. The TaskId is bad or never used.
+    // Since we only try best to cancel task and reply, it's OK to ignore these.
+    return;
+  }
+  it->second->Set();
+}
+
+void CancelableTaskTracker::TryCancelAll() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  for (hash_map<TaskId, CancellationFlag*>::const_iterator it =
+           task_flags_.begin();
+       it != task_flags_.end();
+       ++it) {
+    it->second->Set();
+  }
+}
+
+void CancelableTaskTracker::Track(TaskId id, CancellationFlag* flag) {
+  bool success = task_flags_.insert(std::make_pair(id, flag)).second;
+  DCHECK(success);
+}
+
+void CancelableTaskTracker::Untrack(TaskId id) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  size_t num = task_flags_.erase(id);
+  DCHECK_EQ(1u, num);
+}
diff --git a/chrome/common/cancelable_task_tracker.h b/chrome/common/cancelable_task_tracker.h
new file mode 100644
index 0000000..0a7289b
--- /dev/null
+++ b/chrome/common/cancelable_task_tracker.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 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.
+
+// CancelableTaskTracker posts tasks (in the form of a Closure) to a TaskRunner,
+// and is able to cancel the task later if it's not needed anymore. On
+// destruction, CancelableTaskTracker will cancel all tracked tasks.
+//
+// Each cancelable task can be associated with a reply (also a Closure). After
+// the task is run on the TaskRunner, |reply| will be posted back to originating
+// TaskRunner.
+//
+// NOTE:
+//
+// CancelableCallback (base/cancelable_callback.h) and WeakPtr binding are
+// preferred solutions for canceling a task. However, they don't support
+// cancelation from another thread. This is sometimes a performance critical
+// requirement. E.g. We need to cancel database lookup task on DB thread when
+// user changes inputed text. If it is performance critical to do a best effort
+// cancelation of a task, then CancelableTaskTracker is appropriate, otherwise
+// use one of the other mechanisms.
+//
+// THREAD-SAFETY:
+//
+// 1. CancelableTaskTracker objects are not thread safe. They must be created,
+// used, and destroyed on the originating thread that posts the task. It's safe
+// to destroy a CancelableTaskTracker while there are outstanding tasks. This is
+// commonly used to cancel all outstanding tasks.
+//
+// 2. Both task and reply are deleted on the originating thread.
+
+#ifndef CHROME_COMMON_CANCELABLE_TASK_TRACKER_H_
+#define CHROME_COMMON_CANCELABLE_TASK_TRACKER_H_
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/hash_tables.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+
+namespace base {
+class CancellationFlag;
+class TaskRunner;
+}  // namespace base
+
+namespace tracked_objects {
+class Location;
+}  // namespace tracked_objects
+
+class CancelableTaskTracker {
+ public:
+  // All values except kBadTaskId are valid.
+  typedef int64 TaskId;
+  static const TaskId kBadTaskId;
+
+  CancelableTaskTracker();
+
+  // Cancels all tracked tasks.
+  ~CancelableTaskTracker();
+
+  TaskId PostTask(base::TaskRunner* task_runner,
+                  const tracked_objects::Location& from_here,
+                  const base::Closure& task);
+
+  TaskId PostTaskAndReply(base::TaskRunner* task_runner,
+                          const tracked_objects::Location& from_here,
+                          const base::Closure& task,
+                          const base::Closure& reply);
+
+  // After calling this function, |task| and |reply| will not run. If the
+  // cancelation happens when |task| is running or has finished running, |reply|
+  // will not run. If |reply| is running or has finished running, cancellation
+  // is a noop.
+  //
+  // Note. It's OK to cancel a |task| for more than once. The later calls are
+  // noops.
+  void TryCancel(TaskId id);
+
+  // It's OK to call this function for more than once. The later calls are
+  // noops.
+  void TryCancelAll();
+
+ private:
+  void Track(TaskId id, base::CancellationFlag* flag);
+  void Untrack(TaskId id);
+
+  base::hash_map<TaskId, base::CancellationFlag*> task_flags_;
+  base::WeakPtrFactory<CancelableTaskTracker> weak_factory_;
+
+  TaskId next_id_;
+  base::ThreadChecker thread_checker_;
+
+  DISALLOW_COPY_AND_ASSIGN(CancelableTaskTracker);
+};
+
+#endif  // CHROME_COMMON_CANCELABLE_TASK_TRACKER_H_
diff --git a/chrome/common/cancelable_task_tracker_unittest.cc b/chrome/common/cancelable_task_tracker_unittest.cc
new file mode 100644
index 0000000..60315bc
--- /dev/null
+++ b/chrome/common/cancelable_task_tracker_unittest.cc
@@ -0,0 +1,328 @@
+// 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.
+
+#include "chrome/common/cancelable_task_tracker.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::Bind;
+using base::Closure;
+using base::Owned;
+using base::TaskRunner;
+using base::Thread;
+using base::Unretained;
+using base::WaitableEvent;
+
+namespace {
+
+class WaitableEventScoper {
+ public:
+  explicit WaitableEventScoper(WaitableEvent* event) : event_(event) {}
+  ~WaitableEventScoper() {
+    if (event_)
+      event_->Signal();
+  }
+ private:
+  WaitableEvent* event_;
+  DISALLOW_COPY_AND_ASSIGN(WaitableEventScoper);
+};
+
+class CancelableTaskTrackerTest : public testing::Test {
+ protected:
+  CancelableTaskTrackerTest()
+      : task_id_(CancelableTaskTracker::kBadTaskId),
+        test_data_(0),
+        task_thread_start_event_(true, false) {}
+
+  virtual void SetUp() {
+    task_thread_.reset(new Thread("task thread"));
+    client_thread_.reset(new Thread("client thread"));
+    task_thread_->Start();
+    client_thread_->Start();
+
+    task_thread_runner_ = task_thread_->message_loop_proxy();
+    client_thread_runner_ = client_thread_->message_loop_proxy();
+
+    // Create tracker on client thread.
+    WaitableEvent tracker_created(true, false);
+    client_thread_runner_->PostTask(
+        FROM_HERE,
+        Bind(&CancelableTaskTrackerTest::CreateTrackerOnClientThread,
+             Unretained(this), &tracker_created));
+    tracker_created.Wait();
+
+    // Block server thread so we can prepare the test.
+    task_thread_runner_->PostTask(
+        FROM_HERE,
+        Bind(&WaitableEvent::Wait, Unretained(&task_thread_start_event_)));
+  }
+
+  virtual void TearDown() {
+    UnblockTaskThread();
+
+    // Create tracker on client thread.
+    WaitableEvent tracker_destroyed(true, false);
+    client_thread_runner_->PostTask(
+        FROM_HERE,
+        Bind(&CancelableTaskTrackerTest::DestroyTrackerOnClientThread,
+             Unretained(this), &tracker_destroyed));
+    tracker_destroyed.Wait();
+
+    client_thread_->Stop();
+    task_thread_->Stop();
+  }
+
+  void RunOnClientAndWait(
+      void (*func)(CancelableTaskTrackerTest*, WaitableEvent*)) {
+    WaitableEvent event(true, false);
+    client_thread_runner_->PostTask(FROM_HERE,
+                                    Bind(func, Unretained(this), &event));
+    event.Wait();
+  }
+
+ public:
+  // Client thread posts tasks and runs replies.
+  scoped_refptr<TaskRunner> client_thread_runner_;
+
+  // Task thread runs tasks.
+  scoped_refptr<TaskRunner> task_thread_runner_;
+
+  // |tracker_| can only live on client thread.
+  scoped_ptr<CancelableTaskTracker> tracker_;
+
+  CancelableTaskTracker::TaskId task_id_;
+
+  void UnblockTaskThread() {
+    task_thread_start_event_.Signal();
+  }
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Testing data and related functions
+  int test_data_;  // Defaults to 0.
+
+  Closure IncreaseTestDataAndSignalClosure(WaitableEvent* event) {
+    return Bind(&CancelableTaskTrackerTest::IncreaseDataAndSignal,
+                &test_data_, event);
+  }
+
+  Closure DecreaseTestDataClosure(WaitableEvent* event) {
+    return Bind(&CancelableTaskTrackerTest::DecreaseData,
+                Owned(new WaitableEventScoper(event)), &test_data_);
+  }
+
+ private:
+  void CreateTrackerOnClientThread(WaitableEvent* event) {
+    tracker_.reset(new CancelableTaskTracker());
+    event->Signal();
+  }
+
+  void DestroyTrackerOnClientThread(WaitableEvent* event) {
+    tracker_.reset();
+    event->Signal();
+  }
+
+  static void IncreaseDataAndSignal(int* data, WaitableEvent* event) {
+    (*data)++;
+    if (event)
+      event->Signal();
+  }
+
+  static void DecreaseData(WaitableEventScoper* event_scoper, int* data) {
+    (*data) -= 2;
+  }
+
+  scoped_ptr<Thread> client_thread_;
+  scoped_ptr<Thread> task_thread_;
+
+  WaitableEvent task_thread_start_event_;
+};
+
+#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST
+typedef CancelableTaskTrackerTest CancelableTaskTrackerDeathTest;
+
+TEST_F(CancelableTaskTrackerDeathTest, PostFromDifferentThread) {
+  // The default style "fast" does not support multi-threaded tests.
+  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
+
+  EXPECT_DEATH(
+      tracker_->PostTask(task_thread_runner_,
+                         FROM_HERE,
+                         DecreaseTestDataClosure(NULL)),
+      "");
+}
+
+void CancelOnDifferentThread_Test(CancelableTaskTrackerTest* test,
+                                  WaitableEvent* event) {
+  test->task_id_ = test->tracker_->PostTask(
+      test->task_thread_runner_,
+      FROM_HERE,
+      test->DecreaseTestDataClosure(event));
+  EXPECT_NE(CancelableTaskTracker::kBadTaskId, test->task_id_);
+
+  // Canceling a non-existed task is noop.
+  test->tracker_->TryCancel(test->task_id_ + 1);
+
+  test->UnblockTaskThread();
+}
+
+TEST_F(CancelableTaskTrackerDeathTest, CancelOnDifferentThread) {
+  // The default style "fast" does not support multi-threaded tests.
+  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
+
+  // Post a task and we'll try canceling it on a different thread.
+  RunOnClientAndWait(&CancelOnDifferentThread_Test);
+
+  // Canceling on the wrong thread.
+  EXPECT_DEATH(tracker_->TryCancel(task_id_), "");
+
+  // Even canceling a non-existant task will crash.
+  EXPECT_DEATH(tracker_->TryCancel(task_id_ + 1), "");
+}
+
+void TrackerCancelAllOnDifferentThread_Test(
+    CancelableTaskTrackerTest* test, WaitableEvent* event) {
+  test->task_id_ = test->tracker_->PostTask(
+      test->task_thread_runner_,
+      FROM_HERE,
+      test->DecreaseTestDataClosure(event));
+  EXPECT_NE(CancelableTaskTracker::kBadTaskId, test->task_id_);
+  test->UnblockTaskThread();
+}
+
+TEST_F(CancelableTaskTrackerDeathTest, TrackerCancelAllOnDifferentThread) {
+  // The default style "fast" does not support multi-threaded tests.
+  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
+
+  // |tracker_| can only live on client thread.
+  EXPECT_DEATH(tracker_.reset(), "");
+
+  RunOnClientAndWait(&TrackerCancelAllOnDifferentThread_Test);
+
+  EXPECT_DEATH(tracker_->TryCancelAll(), "");
+  EXPECT_DEATH(tracker_.reset(), "");
+}
+
+#endif  // (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) &&
+        //     GTEST_HAS_DEATH_TEST
+
+void Canceled_Test(CancelableTaskTrackerTest* test, WaitableEvent* event) {
+  test->task_id_ = test->tracker_->PostTask(
+      test->task_thread_runner_,
+      FROM_HERE,
+      test->DecreaseTestDataClosure(event));
+  EXPECT_NE(CancelableTaskTracker::kBadTaskId, test->task_id_);
+
+  test->tracker_->TryCancel(test->task_id_);
+  test->UnblockTaskThread();
+}
+
+TEST_F(CancelableTaskTrackerTest, Canceled) {
+  RunOnClientAndWait(&Canceled_Test);
+  EXPECT_EQ(0, test_data_);
+}
+
+void SignalAndWaitThenIncrease(WaitableEvent* start_event,
+                               WaitableEvent* continue_event,
+                               int* data) {
+  start_event->Signal();
+  continue_event->Wait();
+  (*data)++;
+}
+
+void CancelWhileTaskRunning_Test(CancelableTaskTrackerTest* test,
+                                 WaitableEvent* event) {
+  WaitableEvent task_start_event(true, false);
+  WaitableEvent* task_continue_event = new WaitableEvent(true, false);
+
+  test->task_id_ = test->tracker_->PostTaskAndReply(
+      test->task_thread_runner_,
+      FROM_HERE,
+      Bind(&SignalAndWaitThenIncrease,
+           &task_start_event, Owned(task_continue_event), &test->test_data_),
+      test->DecreaseTestDataClosure(event));
+  EXPECT_NE(CancelableTaskTracker::kBadTaskId, test->task_id_);
+
+  test->UnblockTaskThread();
+  task_start_event.Wait();
+
+  // Now task is running. Let's try to cancel.
+  test->tracker_->TryCancel(test->task_id_);
+
+  // Let task continue.
+  task_continue_event->Signal();
+}
+
+TEST_F(CancelableTaskTrackerTest, CancelWhileTaskRunning) {
+  RunOnClientAndWait(&CancelWhileTaskRunning_Test);
+
+  // Task will continue running but reply will be canceled.
+  EXPECT_EQ(1, test_data_);
+}
+
+void NotCanceled_Test(CancelableTaskTrackerTest* test, WaitableEvent* event) {
+  test->task_id_ = test->tracker_->PostTaskAndReply(
+      test->task_thread_runner_,
+      FROM_HERE,
+      test->IncreaseTestDataAndSignalClosure(NULL),
+      test->DecreaseTestDataClosure(event));
+  EXPECT_NE(CancelableTaskTracker::kBadTaskId, test->task_id_);
+
+  test->UnblockTaskThread();
+}
+
+TEST_F(CancelableTaskTrackerTest, NotCanceled) {
+  RunOnClientAndWait(&NotCanceled_Test);
+  EXPECT_EQ(-1, test_data_);
+}
+
+void TrackerDestructed_Test(CancelableTaskTrackerTest* test,
+                            WaitableEvent* event) {
+  test->task_id_ = test->tracker_->PostTaskAndReply(
+      test->task_thread_runner_,
+      FROM_HERE,
+      test->IncreaseTestDataAndSignalClosure(NULL),
+      test->DecreaseTestDataClosure(event));
+  EXPECT_NE(CancelableTaskTracker::kBadTaskId, test->task_id_);
+
+  test->tracker_.reset();
+  test->UnblockTaskThread();
+}
+
+TEST_F(CancelableTaskTrackerTest, TrackerDestructed) {
+  RunOnClientAndWait(&TrackerDestructed_Test);
+  EXPECT_EQ(0, test_data_);
+}
+
+void TrackerDestructedAfterTask_Test(CancelableTaskTrackerTest* test,
+                                     WaitableEvent* event) {
+  WaitableEvent task_done_event(true, false);
+  test->task_id_ = test->tracker_->PostTaskAndReply(
+      test->task_thread_runner_,
+      FROM_HERE,
+      test->IncreaseTestDataAndSignalClosure(&task_done_event),
+      test->DecreaseTestDataClosure(event));
+  ASSERT_NE(CancelableTaskTracker::kBadTaskId, test->task_id_);
+
+  test->UnblockTaskThread();
+
+  task_done_event.Wait();
+
+  // At this point, task is already finished on task thread but reply has not
+  // started yet (because this function is still running on client thread).
+  // Now delete the tracker to cancel reply.
+  test->tracker_.reset();
+}
+
+TEST_F(CancelableTaskTrackerTest, TrackerDestructedAfterTask) {
+  RunOnClientAndWait(&TrackerDestructedAfterTask_Test);
+  EXPECT_EQ(1, test_data_);
+}
+
+}  // namespace
diff --git a/chrome/common/child_process_logging.h b/chrome/common/child_process_logging.h
new file mode 100644
index 0000000..fd1e0b9
--- /dev/null
+++ b/chrome/common/child_process_logging.h
@@ -0,0 +1,171 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_CHILD_PROCESS_LOGGING_H_
+#define CHROME_COMMON_CHILD_PROCESS_LOGGING_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/mac/crash_logging.h"
+#include "base/string16.h"
+#include "googleurl/src/gurl.h"
+
+class CommandLine;
+
+namespace content {
+struct GPUInfo;
+}
+
+// The maximum number of active extensions we will report.
+// Also used in chrome/app, but we define it here to avoid a common->app
+// dependency.
+static const size_t kMaxReportedActiveExtensions = 10;
+
+// The maximum number of variation chunks we will report.
+// Also used in chrome/app, but we define it here to avoid a common->app
+// dependency.
+static const size_t kMaxReportedVariationChunks = 15;
+
+// The maximum size of a variation chunk. This size was picked to be
+// consistent between platforms and the value was chosen from the Windows
+// limit of google_breakpad::CustomInfoEntry::kValueMaxLength.
+static const size_t kMaxVariationChunkSize = 64;
+
+// The maximum number of prn-info-* records.
+static const size_t kMaxReportedPrinterRecords = 4;
+
+// The maximum number of command line switches to include in the crash
+// report's metadata. Note that the mini-dump itself will also contain the
+// (original) command line arguments within the PEB.
+// Also used in chrome/app, but we define it here to avoid a common->app
+// dependency.
+static const size_t kMaxSwitches = 15;
+
+namespace child_process_logging {
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+// These are declared here so the crash reporter can access them directly in
+// compromised context without going through the standard library.
+extern char g_active_url[];
+extern char g_channel[];
+extern char g_client_id[];
+extern char g_extension_ids[];
+extern char g_gpu_vendor_id[];
+extern char g_gpu_device_id[];
+extern char g_gpu_driver_ver[];
+extern char g_gpu_ps_ver[];
+extern char g_gpu_vs_ver[];
+extern char g_num_extensions[];
+extern char g_num_switches[];
+extern char g_num_variations[];
+extern char g_num_views[];
+extern char g_printer_info[];
+extern char g_switches[];
+extern char g_variation_chunks[];
+
+// Assume IDs are 32 bytes long.
+static const size_t kExtensionLen = 32;
+
+// Assume command line switches are less than 64 chars.
+static const size_t kSwitchLen = 64;
+
+// Assume printer info strings are less than 64 chars.
+static const size_t kPrinterInfoStrLen = 64;
+#endif
+
+// Sets the URL that is logged if the child process crashes. Use GURL() to clear
+// the URL.
+void SetActiveURL(const GURL& url);
+
+// Sets the Client ID that is used as GUID if a Chrome process crashes.
+void SetClientId(const std::string& client_id);
+
+// Gets the Client ID to be used as GUID for crash reporting. Returns the client
+// id in |client_id| if it's known, an empty string otherwise.
+std::string GetClientId();
+
+// Sets the list of "active" extensions in this process. We overload "active" to
+// mean different things depending on the process type:
+// - browser: all enabled extensions
+// - renderer: the unique set of extension ids from all content scripts
+// - extension: the id of each extension running in this process (there can be
+//   multiple because of process collapsing).
+void SetActiveExtensions(const std::set<std::string>& extension_ids);
+
+// Sets a number of views/tabs opened in this process.
+void SetNumberOfViews(int number_of_views);
+
+// Sets the data on the gpu to send along with crash reports.
+void SetGpuInfo(const content::GPUInfo& gpu_info);
+
+// Sets the data on the printer to send along with crash reports. Data may be
+// separated by ';' up to kMaxReportedPrinterRecords strings. Each substring
+// would be cut to 63 chars.
+void SetPrinterInfo(const char* printer_info);
+
+// Sets the command line arguments to send along with crash reports to the
+// values in |command_line|.
+void SetCommandLine(const CommandLine* command_line);
+
+// Initialize the list of experiment info to send along with crash reports.
+void SetExperimentList(const std::vector<string16>& state);
+
+#if defined(OS_LINUX) || defined(OS_OPENBSD) || defined(OS_MACOSX)
+// Sets the product channel data to send along with crash reports to |channel|.
+void SetChannel(const std::string& channel);
+#endif
+
+// Simple wrapper class that sets the active URL in it's constructor and clears
+// the active URL in the destructor.
+class ScopedActiveURLSetter {
+ public:
+  explicit ScopedActiveURLSetter(const GURL& url)  {
+    SetActiveURL(url);
+  }
+
+  ~ScopedActiveURLSetter()  {
+    SetActiveURL(GURL());
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ScopedActiveURLSetter);
+};
+
+// Set/clear information about currently accessed printer.
+class ScopedPrinterInfoSetter {
+ public:
+  explicit ScopedPrinterInfoSetter(const std::string& printer_info) {
+    SetPrinterInfo(printer_info.c_str());
+  }
+
+  ~ScopedPrinterInfoSetter() {
+    SetPrinterInfo("");
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ScopedPrinterInfoSetter);
+};
+
+}  // namespace child_process_logging
+
+#if defined(OS_MACOSX)
+
+namespace child_process_logging {
+
+void SetActiveURLImpl(const GURL& url,
+                      base::mac::SetCrashKeyValueFuncPtr set_key_func,
+                      base::mac::ClearCrashKeyValueFuncPtr clear_key_func);
+
+extern const int kMaxNumCrashURLChunks;
+extern const int kMaxNumURLChunkValueLength;
+extern const char *kUrlChunkFormatStr;
+
+}  // namespace child_process_logging
+
+#endif  // defined(OS_MACOSX)
+
+#endif  // CHROME_COMMON_CHILD_PROCESS_LOGGING_H_
diff --git a/chrome/common/child_process_logging_mac.mm b/chrome/common/child_process_logging_mac.mm
new file mode 100644
index 0000000..bf447ba
--- /dev/null
+++ b/chrome/common/child_process_logging_mac.mm
@@ -0,0 +1,258 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/child_process_logging.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/command_line.h"
+#include "base/string_number_conversions.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/sys_string_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/metrics/variations/variations_util.h"
+#include "chrome/installer/util/google_update_settings.h"
+#include "content/public/common/gpu_info.h"
+#include "googleurl/src/gurl.h"
+
+namespace child_process_logging {
+
+using base::mac::SetCrashKeyValueFuncPtr;
+using base::mac::ClearCrashKeyValueFuncPtr;
+using base::mac::SetCrashKeyValue;
+using base::mac::ClearCrashKey;
+
+const int kMaxNumCrashURLChunks = 8;
+const int kMaxNumURLChunkValueLength = 255;
+const char *kUrlChunkFormatStr = "url-chunk-%d";
+const char *kGuidParamName = "guid";
+const char *kGPUVendorIdParamName = "gpu-venid";
+const char *kGPUDeviceIdParamName = "gpu-devid";
+const char *kGPUDriverVersionParamName = "gpu-driver";
+const char *kGPUPixelShaderVersionParamName = "gpu-psver";
+const char *kGPUVertexShaderVersionParamName = "gpu-vsver";
+const char *kGPUGLVersionParamName = "gpu-glver";
+const char *kNumberOfViews = "num-views";
+NSString* const kNumExtensionsName = @"num-extensions";
+NSString* const kExtensionNameFormat = @"extension-%zu";
+NSString* const kPrinterInfoNameFormat = @"prn-info-%zu";
+
+// Account for the terminating null character.
+static const size_t kClientIdSize = 32 + 1;
+static char g_client_id[kClientIdSize];
+
+void SetActiveURLImpl(const GURL& url,
+                      SetCrashKeyValueFuncPtr set_key_func,
+                      ClearCrashKeyValueFuncPtr clear_key_func) {
+
+  NSString *kUrlChunkFormatStr_utf8 = [NSString
+      stringWithUTF8String:kUrlChunkFormatStr];
+
+  // First remove any old url chunks we might have lying around.
+  for (int i = 0; i < kMaxNumCrashURLChunks; i++) {
+    // On Windows the url-chunk items are 1-based, so match that.
+    NSString *key = [NSString stringWithFormat:kUrlChunkFormatStr_utf8, i+1];
+    clear_key_func(key);
+  }
+
+  const std::string& raw_url_utf8 = url.possibly_invalid_spec();
+  NSString *raw_url = [NSString stringWithUTF8String:raw_url_utf8.c_str()];
+  size_t raw_url_length = [raw_url length];
+
+  // Bail on zero-length URLs.
+  if (raw_url_length == 0) {
+    return;
+  }
+
+  // Parcel the URL up into up to 8, 255 byte segments.
+  size_t start_ofs = 0;
+  for (int i = 0;
+       i < kMaxNumCrashURLChunks && start_ofs < raw_url_length;
+       ++i) {
+
+    // On Windows the url-chunk items are 1-based, so match that.
+    NSString *key = [NSString stringWithFormat:kUrlChunkFormatStr_utf8, i+1];
+    NSRange range;
+    range.location = start_ofs;
+    range.length = std::min((size_t)kMaxNumURLChunkValueLength,
+                            raw_url_length - start_ofs);
+    NSString *value = [raw_url substringWithRange:range];
+    set_key_func(key, value);
+
+    // Next chunk.
+    start_ofs += kMaxNumURLChunkValueLength;
+  }
+}
+
+void SetClientIdImpl(const std::string& client_id,
+                     SetCrashKeyValueFuncPtr set_key_func) {
+  NSString *key = [NSString stringWithUTF8String:kGuidParamName];
+  NSString *value = [NSString stringWithUTF8String:client_id.c_str()];
+  set_key_func(key, value);
+}
+
+void SetActiveURL(const GURL& url) {
+  SetActiveURLImpl(url, SetCrashKeyValue, ClearCrashKey);
+}
+
+void SetClientId(const std::string& client_id) {
+  std::string str(client_id);
+  ReplaceSubstringsAfterOffset(&str, 0, "-", "");
+
+  base::strlcpy(g_client_id, str.c_str(), kClientIdSize);
+    SetClientIdImpl(str, SetCrashKeyValue);
+
+  std::wstring wstr = ASCIIToWide(str);
+  GoogleUpdateSettings::SetMetricsId(wstr);
+}
+
+std::string GetClientId() {
+  return std::string(g_client_id);
+}
+
+void SetActiveExtensions(const std::set<std::string>& extension_ids) {
+  // Log the count separately to track heavy users.
+  const int count = static_cast<int>(extension_ids.size());
+  SetCrashKeyValue(kNumExtensionsName,
+                   [NSString stringWithFormat:@"%i", count]);
+
+  // Record up to |kMaxReportedActiveExtensions| extensions, clearing
+  // keys if there aren't that many.
+  std::set<std::string>::const_iterator iter = extension_ids.begin();
+  for (size_t i = 0; i < kMaxReportedActiveExtensions; ++i) {
+    NSString* key = [NSString stringWithFormat:kExtensionNameFormat, i];
+    if (iter != extension_ids.end()) {
+      SetCrashKeyValue(key, [NSString stringWithUTF8String:iter->c_str()]);
+      ++iter;
+    } else {
+      ClearCrashKey(key);
+    }
+  }
+}
+
+void SetGpuKeyValue(const char* param_name, const std::string& value_str,
+                    SetCrashKeyValueFuncPtr set_key_func) {
+  NSString *key = [NSString stringWithUTF8String:param_name];
+  NSString *value = [NSString stringWithUTF8String:value_str.c_str()];
+  set_key_func(key, value);
+}
+
+void SetGpuInfoImpl(const content::GPUInfo& gpu_info,
+                    SetCrashKeyValueFuncPtr set_key_func) {
+  SetGpuKeyValue(kGPUVendorIdParamName,
+                 base::StringPrintf("0x%04x", gpu_info.gpu.vendor_id),
+                 set_key_func);
+  SetGpuKeyValue(kGPUDeviceIdParamName,
+                 base::StringPrintf("0x%04x", gpu_info.gpu.device_id),
+                 set_key_func);
+  SetGpuKeyValue(kGPUDriverVersionParamName,
+                 gpu_info.driver_version,
+                 set_key_func);
+  SetGpuKeyValue(kGPUPixelShaderVersionParamName,
+                 gpu_info.pixel_shader_version,
+                 set_key_func);
+  SetGpuKeyValue(kGPUVertexShaderVersionParamName,
+                 gpu_info.vertex_shader_version,
+                 set_key_func);
+  SetGpuKeyValue(kGPUGLVersionParamName,
+                 gpu_info.gl_version,
+                 set_key_func);
+}
+
+void SetGpuInfo(const content::GPUInfo& gpu_info) {
+  SetGpuInfoImpl(gpu_info, SetCrashKeyValue);
+}
+
+void SetPrinterInfo(const char* printer_info) {
+  std::vector<std::string> info;
+  base::SplitString(printer_info, L';', &info);
+  info.resize(kMaxReportedPrinterRecords);
+  for (size_t i = 0; i < info.size(); ++i) {
+    NSString* key = [NSString stringWithFormat:kPrinterInfoNameFormat, i];
+    ClearCrashKey(key);
+    if (!info[i].empty()) {
+      NSString *value = [NSString stringWithUTF8String:info[i].c_str()];
+      SetCrashKeyValue(key, value);
+    }
+  }
+}
+
+void SetNumberOfViewsImpl(int number_of_views,
+                          SetCrashKeyValueFuncPtr set_key_func) {
+  NSString *key = [NSString stringWithUTF8String:kNumberOfViews];
+  NSString *value = [NSString stringWithFormat:@"%d", number_of_views];
+  set_key_func(key, value);
+}
+
+void SetNumberOfViews(int number_of_views) {
+  SetNumberOfViewsImpl(number_of_views, SetCrashKeyValue);
+}
+
+void SetCommandLine(const CommandLine* command_line) {
+  DCHECK(command_line);
+  if (!command_line)
+    return;
+
+  // These should match the corresponding strings in breakpad_win.cc.
+  NSString* const kNumSwitchesKey = @"num-switches";
+  NSString* const kSwitchKeyFormat = @"switch-%zu";  // 1-based.
+
+  // Note the total number of switches, not including the exec path.
+  const CommandLine::StringVector& argv = command_line->argv();
+  SetCrashKeyValue(kNumSwitchesKey,
+                   [NSString stringWithFormat:@"%zu", argv.size() - 1]);
+
+  size_t key_i = 0;
+  for (size_t i = 1; i < argv.size() && key_i < kMaxSwitches; ++i) {
+    // TODO(shess): Skip boring switches.
+    NSString* key = [NSString stringWithFormat:kSwitchKeyFormat, (key_i + 1)];
+    NSString* value = base::SysUTF8ToNSString(argv[i]);
+    SetCrashKeyValue(key, value);
+    key_i++;
+  }
+
+  // Clear out any stale keys.
+  for (; key_i < kMaxSwitches; ++key_i) {
+    NSString* key = [NSString stringWithFormat:kSwitchKeyFormat, (key_i + 1)];
+    ClearCrashKey(key);
+  }
+}
+
+void SetExperimentList(const std::vector<string16>& experiments) {
+  // These should match the corresponding strings in breakpad_win.cc.
+  NSString* const kNumExperimentsKey = @"num-experiments";
+  NSString* const kExperimentChunkFormat = @"experiment-chunk-%zu";  // 1-based.
+
+  std::vector<string16> chunks;
+  chrome_variations::GenerateVariationChunks(experiments, &chunks);
+
+  // Store up to |kMaxReportedVariationChunks| chunks.
+  for (size_t i = 0; i < kMaxReportedVariationChunks; ++i) {
+    NSString* key = [NSString stringWithFormat:kExperimentChunkFormat, i + 1];
+    if (i < chunks.size()) {
+      NSString* value = base::SysUTF16ToNSString(chunks[i]);
+      SetCrashKeyValue(key, value);
+    } else {
+      ClearCrashKey(key);
+    }
+  }
+
+  // Make note of the total number of experiments, which may be greater than
+  // what was able to fit in |kMaxReportedVariationChunks|. This is useful when
+  // correlating stability with the number of experiments running
+  // simultaneously.
+  SetCrashKeyValue(kNumExperimentsKey,
+                   [NSString stringWithFormat:@"%zu", experiments.size()]);
+}
+
+void SetChannel(const std::string& channel) {
+  // This should match the corresponding string in breakpad_win.cc.
+  NSString* const kChannelKey = @"channel";
+
+  SetCrashKeyValue(kChannelKey, base::SysUTF8ToNSString(channel));
+}
+
+}  // namespace child_process_logging
diff --git a/chrome/common/child_process_logging_mac_unittest.mm b/chrome/common/child_process_logging_mac_unittest.mm
new file mode 100644
index 0000000..feedcbe
--- /dev/null
+++ b/chrome/common/child_process_logging_mac_unittest.mm
@@ -0,0 +1,143 @@
+// 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.
+
+#include "chrome/common/child_process_logging.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/logging.h"
+#include "base/mac/crash_logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+typedef PlatformTest ChildProcessLoggingTest;
+
+namespace {
+
+// Class to mock breakpad's setkeyvalue/clearkeyvalue functions needed for
+// SetActiveRendererURLImpl.
+// The Keys are stored in a static dictionary and methods are provided to
+// verify correctness.
+class MockBreakpadKeyValueStore {
+ public:
+  MockBreakpadKeyValueStore() {
+    // Only one of these objects can be active at once.
+    DCHECK(dict == NULL);
+    dict = [[NSMutableDictionary alloc] init];
+  }
+
+  ~MockBreakpadKeyValueStore() {
+    // Only one of these objects can be active at once.
+    DCHECK(dict != NULL);
+    [dict release];
+    dict = NULL;
+  }
+
+  static void SetKeyValue(NSString* key, NSString* value) {
+    DCHECK(dict != NULL);
+    [dict setObject:value forKey:key];
+  }
+
+  static void ClearKeyValue(NSString *key) {
+    DCHECK(dict != NULL);
+    [dict removeObjectForKey:key];
+  }
+
+  int CountDictionaryEntries() {
+    return [dict count];
+  }
+
+  bool VerifyDictionaryContents(const std::string &url) {
+    using child_process_logging::kMaxNumCrashURLChunks;
+    using child_process_logging::kMaxNumURLChunkValueLength;
+    using child_process_logging::kUrlChunkFormatStr;
+
+    int num_url_chunks = CountDictionaryEntries();
+    EXPECT_TRUE(num_url_chunks <= kMaxNumCrashURLChunks);
+
+    NSString *kUrlChunkFormatStr_utf8 = [NSString
+        stringWithUTF8String:kUrlChunkFormatStr];
+
+    NSString *accumulated_url = @"";
+    for (int i = 0; i < num_url_chunks; ++i) {
+      // URL chunk names are 1-based.
+      NSString *key = [NSString stringWithFormat:kUrlChunkFormatStr_utf8, i+1];
+      EXPECT_TRUE(key != NULL);
+      NSString *value = [dict objectForKey:key];
+      EXPECT_TRUE([value length] > 0);
+      EXPECT_TRUE([value length] <= (unsigned)kMaxNumURLChunkValueLength);
+      accumulated_url = [accumulated_url stringByAppendingString:value];
+    }
+
+    NSString *expected_url = [NSString stringWithUTF8String:url.c_str()];
+    return([accumulated_url isEqualToString:expected_url]);
+  }
+
+ private:
+  static NSMutableDictionary* dict;
+  DISALLOW_COPY_AND_ASSIGN(MockBreakpadKeyValueStore);
+};
+
+// static
+NSMutableDictionary* MockBreakpadKeyValueStore::dict;
+
+}  // namespace
+
+// Call through to SetActiveURLImpl using the functions from
+// MockBreakpadKeyValueStore.
+void SetActiveURLWithMock(const GURL& url) {
+  using child_process_logging::SetActiveURLImpl;
+
+  base::mac::SetCrashKeyValueFuncPtr setFunc =
+      MockBreakpadKeyValueStore::SetKeyValue;
+  base::mac::ClearCrashKeyValueFuncPtr clearFunc =
+      MockBreakpadKeyValueStore::ClearKeyValue;
+
+  SetActiveURLImpl(url, setFunc, clearFunc);
+}
+
+TEST_F(ChildProcessLoggingTest, TestUrlSplitting) {
+  using child_process_logging::kMaxNumCrashURLChunks;
+  using child_process_logging::kMaxNumURLChunkValueLength;
+
+  const std::string short_url("http://abc/");
+  std::string long_url("http://");
+  std::string overflow_url("http://");
+
+  long_url += std::string(kMaxNumURLChunkValueLength * 2, 'a');
+  long_url += "/";
+
+  int max_num_chars_stored_in_dump = kMaxNumURLChunkValueLength *
+      kMaxNumCrashURLChunks;
+  overflow_url += std::string(max_num_chars_stored_in_dump + 1, 'a');
+  overflow_url += "/";
+
+  // Check that Clearing NULL URL works.
+  MockBreakpadKeyValueStore mock;
+  SetActiveURLWithMock(GURL());
+  EXPECT_EQ(mock.CountDictionaryEntries(), 0);
+
+  // Check that we can set a URL.
+  SetActiveURLWithMock(GURL(short_url.c_str()));
+  EXPECT_TRUE(mock.VerifyDictionaryContents(short_url));
+  EXPECT_EQ(mock.CountDictionaryEntries(), 1);
+  SetActiveURLWithMock(GURL());
+  EXPECT_EQ(mock.CountDictionaryEntries(), 0);
+
+  // Check that we can replace a long url with a short url.
+  SetActiveURLWithMock(GURL(long_url.c_str()));
+  EXPECT_TRUE(mock.VerifyDictionaryContents(long_url));
+  SetActiveURLWithMock(GURL(short_url.c_str()));
+  EXPECT_TRUE(mock.VerifyDictionaryContents(short_url));
+  SetActiveURLWithMock(GURL());
+  EXPECT_EQ(mock.CountDictionaryEntries(), 0);
+
+
+  // Check that overflow works correctly.
+  SetActiveURLWithMock(GURL(overflow_url.c_str()));
+  EXPECT_TRUE(mock.VerifyDictionaryContents(
+      overflow_url.substr(0, max_num_chars_stored_in_dump)));
+  SetActiveURLWithMock(GURL());
+  EXPECT_EQ(mock.CountDictionaryEntries(), 0);
+}
diff --git a/chrome/common/child_process_logging_posix.cc b/chrome/common/child_process_logging_posix.cc
new file mode 100644
index 0000000..d9e8485
--- /dev/null
+++ b/chrome/common/child_process_logging_posix.cc
@@ -0,0 +1,172 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/child_process_logging.h"
+
+#include "base/command_line.h"
+#include "base/format_macros.h"
+#include "base/string_number_conversions.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/metrics/variations/variations_util.h"
+#include "chrome/installer/util/google_update_settings.h"
+#include "content/public/common/gpu_info.h"
+#include "googleurl/src/gurl.h"
+
+namespace child_process_logging {
+
+// Account for the terminating null character.
+static const size_t kMaxActiveURLSize = 1024 + 1;
+static const size_t kClientIdSize = 32 + 1;
+static const size_t kChannelSize = 32;
+
+// We use static strings to hold the most recent active url and the client
+// identifier. If we crash, the crash handler code will send the contents of
+// these strings to the browser.
+char g_active_url[kMaxActiveURLSize];
+char g_client_id[kClientIdSize];
+
+char g_channel[kChannelSize] = "";
+
+static const size_t kGpuStringSize = 32;
+char g_gpu_vendor_id[kGpuStringSize] = "";
+char g_gpu_device_id[kGpuStringSize] = "";
+char g_gpu_driver_ver[kGpuStringSize] = "";
+char g_gpu_ps_ver[kGpuStringSize] = "";
+char g_gpu_vs_ver[kGpuStringSize] = "";
+
+char g_printer_info[kPrinterInfoStrLen * kMaxReportedPrinterRecords + 1] = "";
+
+static const size_t kNumSize = 32;
+char g_num_extensions[kNumSize] = "";
+char g_num_switches[kNumSize] = "";
+char g_num_variations[kNumSize] = "";
+char g_num_views[kNumSize] = "";
+
+static const size_t kMaxExtensionSize =
+    kExtensionLen * kMaxReportedActiveExtensions + 1;
+char g_extension_ids[kMaxExtensionSize] = "";
+
+// Assume command line switches are less than 64 chars.
+static const size_t kMaxSwitchesSize = kSwitchLen * kMaxSwitches + 1;
+char g_switches[kMaxSwitchesSize] = "";
+
+static const size_t kMaxVariationChunksSize =
+    kMaxVariationChunkSize * kMaxReportedVariationChunks + 1;
+char g_variation_chunks[kMaxVariationChunksSize] = "";
+
+void SetActiveURL(const GURL& url) {
+  base::strlcpy(g_active_url, url.possibly_invalid_spec().c_str(),
+                arraysize(g_active_url));
+}
+
+void SetClientId(const std::string& client_id) {
+  std::string str(client_id);
+  ReplaceSubstringsAfterOffset(&str, 0, "-", "");
+
+  if (str.empty())
+    return;
+
+  base::strlcpy(g_client_id, str.c_str(), kClientIdSize);
+  std::wstring wstr = ASCIIToWide(str);
+  GoogleUpdateSettings::SetMetricsId(wstr);
+}
+
+std::string GetClientId() {
+  return std::string(g_client_id);
+}
+
+void SetActiveExtensions(const std::set<std::string>& extension_ids) {
+  snprintf(g_num_extensions, arraysize(g_num_extensions), "%" PRIuS,
+           extension_ids.size());
+
+  std::string extension_str;
+  std::set<std::string>::const_iterator iter = extension_ids.begin();
+  for (size_t i = 0;
+       i < kMaxReportedActiveExtensions && iter != extension_ids.end();
+       ++i, ++iter) {
+    extension_str += *iter;
+  }
+  base::strlcpy(g_extension_ids, extension_str.c_str(),
+                arraysize(g_extension_ids));
+}
+
+void SetGpuInfo(const content::GPUInfo& gpu_info) {
+  snprintf(g_gpu_vendor_id, arraysize(g_gpu_vendor_id), "0x%04x",
+           gpu_info.gpu.vendor_id);
+  snprintf(g_gpu_device_id, arraysize(g_gpu_device_id), "0x%04x",
+           gpu_info.gpu.device_id);
+  base::strlcpy(g_gpu_driver_ver, gpu_info.driver_version.c_str(),
+                arraysize(g_gpu_driver_ver));
+  base::strlcpy(g_gpu_ps_ver, gpu_info.pixel_shader_version.c_str(),
+                arraysize(g_gpu_ps_ver));
+  base::strlcpy(g_gpu_vs_ver,  gpu_info.vertex_shader_version.c_str(),
+                arraysize(g_gpu_vs_ver));
+}
+
+void SetPrinterInfo(const char* printer_info) {
+  std::string printer_info_str;
+  std::vector<std::string> info;
+  base::SplitString(printer_info, L';', &info);
+  DCHECK_LE(info.size(), kMaxReportedPrinterRecords);
+  for (size_t i = 0; i < info.size(); ++i) {
+    printer_info_str += info[i];
+    // Truncate long switches, align short ones with spaces to be trimmed later.
+    printer_info_str.resize((i + 1) * kPrinterInfoStrLen, ' ');
+  }
+  base::strlcpy(g_printer_info, printer_info_str.c_str(),
+                arraysize(g_printer_info));
+}
+
+void SetNumberOfViews(int number_of_views) {
+  snprintf(g_num_views, arraysize(g_num_views), "%d", number_of_views);
+}
+
+void SetCommandLine(const CommandLine* command_line) {
+  const CommandLine::StringVector& argv = command_line->argv();
+
+  snprintf(g_num_switches, arraysize(g_num_switches), "%" PRIuS,
+           argv.size() - 1);
+
+  std::string command_line_str;
+  for (size_t argv_i = 1;
+       argv_i < argv.size() && argv_i <= kMaxSwitches;
+       ++argv_i) {
+    command_line_str += argv[argv_i];
+    // Truncate long switches, align short ones with spaces to be trimmed later.
+    command_line_str.resize(argv_i * kSwitchLen, ' ');
+  }
+  base::strlcpy(g_switches, command_line_str.c_str(), arraysize(g_switches));
+}
+
+void SetExperimentList(const std::vector<string16>& experiments) {
+  std::vector<string16> chunks;
+  chrome_variations::GenerateVariationChunks(experiments, &chunks);
+
+  // Store up to |kMaxReportedVariationChunks| chunks.
+  std::string chunks_str;
+  const size_t number_of_chunks_to_report =
+      std::min(chunks.size(), kMaxReportedVariationChunks);
+  for (size_t i = 0; i < number_of_chunks_to_report; ++i) {
+    chunks_str += UTF16ToUTF8(chunks[i]);
+    // Align short chunks with spaces to be trimmed later.
+    chunks_str.resize(i * kMaxVariationChunkSize, ' ');
+  }
+  base::strlcpy(g_variation_chunks, chunks_str.c_str(),
+                arraysize(g_variation_chunks));
+
+  // Make note of the total number of experiments, which may be greater than
+  // what was able to fit in |kMaxReportedVariationChunks|. This is useful when
+  // correlating stability with the number of experiments running
+  // simultaneously.
+  snprintf(g_num_variations, arraysize(g_num_variations), "%" PRIuS,
+           experiments.size());
+}
+
+void SetChannel(const std::string& channel) {
+  base::strlcpy(g_channel, channel.c_str(), arraysize(g_channel));
+}
+
+}  // namespace child_process_logging
diff --git a/chrome/common/child_process_logging_win.cc b/chrome/common/child_process_logging_win.cc
new file mode 100644
index 0000000..45a29dd
--- /dev/null
+++ b/chrome/common/child_process_logging_win.cc
@@ -0,0 +1,259 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/child_process_logging.h"
+
+#include <windows.h>
+
+#include "base/command_line.h"
+#include "base/string_util.h"
+#include "base/string_number_conversions.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/metrics/variations/variations_util.h"
+#include "chrome/installer/util/google_update_settings.h"
+#include "content/public/common/gpu_info.h"
+#include "googleurl/src/gurl.h"
+
+namespace child_process_logging {
+
+namespace {
+
+// exported in breakpad_win.cc: void __declspec(dllexport) __cdecl SetActiveURL.
+typedef void (__cdecl *MainSetActiveURL)(const wchar_t*);
+
+// exported in breakpad_win.cc: void __declspec(dllexport) __cdecl SetClientId.
+typedef void (__cdecl *MainSetClientId)(const wchar_t*);
+
+// exported in breakpad_win.cc:
+//   void __declspec(dllexport) __cdecl SetNumberOfExtensions.
+typedef void (__cdecl *MainSetNumberOfExtensions)(int);
+
+// exported in breakpad_win.cc:
+// void __declspec(dllexport) __cdecl SetExtensionID.
+typedef void (__cdecl *MainSetExtensionID)(size_t, const wchar_t*);
+
+// exported in breakpad_win.cc: void __declspec(dllexport) __cdecl SetGpuInfo.
+typedef void (__cdecl *MainSetGpuInfo)(const wchar_t*, const wchar_t*,
+                                       const wchar_t*, const wchar_t*,
+                                       const wchar_t*);
+
+// exported in breakpad_win.cc:
+//     void __declspec(dllexport) __cdecl SetPrinterInfo.
+typedef void (__cdecl *MainSetPrinterInfo)(const wchar_t*);
+
+// exported in breakpad_win.cc:
+//   void __declspec(dllexport) __cdecl SetNumberOfViews.
+typedef void (__cdecl *MainSetNumberOfViews)(int);
+
+// exported in breakpad_win.cc:
+//   void __declspec(dllexport) __cdecl SetCommandLine2
+typedef void (__cdecl *MainSetCommandLine)(const wchar_t**, size_t);
+
+// exported in breakpad_field_trial_win.cc:
+//   void __declspec(dllexport) __cdecl SetExperimentList3
+typedef void (__cdecl *MainSetExperimentList)(const wchar_t**, size_t, size_t);
+
+// Copied from breakpad_win.cc.
+void StringVectorToCStringVector(const std::vector<std::wstring>& wstrings,
+                                 std::vector<const wchar_t*>* cstrings) {
+  cstrings->clear();
+  cstrings->reserve(wstrings.size());
+  for (size_t i = 0; i < wstrings.size(); ++i)
+    cstrings->push_back(wstrings[i].c_str());
+}
+
+}  // namespace
+
+void SetActiveURL(const GURL& url) {
+  static MainSetActiveURL set_active_url = NULL;
+  // note: benign race condition on set_active_url.
+  if (!set_active_url) {
+    HMODULE exe_module = GetModuleHandle(chrome::kBrowserProcessExecutableName);
+    if (!exe_module)
+      return;
+    set_active_url = reinterpret_cast<MainSetActiveURL>(
+        GetProcAddress(exe_module, "SetActiveURL"));
+    if (!set_active_url)
+      return;
+  }
+
+  (set_active_url)(UTF8ToWide(url.possibly_invalid_spec()).c_str());
+}
+
+void SetClientId(const std::string& client_id) {
+  std::string str(client_id);
+  // Remove all instance of '-' char from the GUID. So BCD-WXY becomes BCDWXY.
+  ReplaceSubstringsAfterOffset(&str, 0, "-", "");
+
+  if (str.empty())
+    return;
+
+  std::wstring wstr = ASCIIToWide(str);
+  std::wstring old_wstr;
+  if (!GoogleUpdateSettings::GetMetricsId(&old_wstr) ||
+      wstr != old_wstr)
+    GoogleUpdateSettings::SetMetricsId(wstr);
+
+  static MainSetClientId set_client_id = NULL;
+  // note: benign race condition on set_client_id.
+  if (!set_client_id) {
+    HMODULE exe_module = GetModuleHandle(chrome::kBrowserProcessExecutableName);
+    if (!exe_module)
+      return;
+    set_client_id = reinterpret_cast<MainSetClientId>(
+        GetProcAddress(exe_module, "SetClientId"));
+    if (!set_client_id)
+      return;
+  }
+  (set_client_id)(wstr.c_str());
+}
+
+std::string GetClientId() {
+  std::wstring wstr_client_id;
+  if (GoogleUpdateSettings::GetMetricsId(&wstr_client_id))
+    return WideToASCII(wstr_client_id);
+  else
+    return std::string();
+}
+
+void SetActiveExtensions(const std::set<std::string>& extension_ids) {
+  static MainSetNumberOfExtensions set_number_of_extensions = NULL;
+  // note: benign race condition on set_number_of_extensions.
+  if (!set_number_of_extensions) {
+    HMODULE exe_module = GetModuleHandle(chrome::kBrowserProcessExecutableName);
+    if (!exe_module)
+      return;
+    set_number_of_extensions = reinterpret_cast<MainSetNumberOfExtensions>(
+        GetProcAddress(exe_module, "SetNumberOfExtensions"));
+    if (!set_number_of_extensions)
+      return;
+  }
+
+  static MainSetExtensionID set_extension_id = NULL;
+  // note: benign race condition on set_extension_id.
+  if (!set_extension_id) {
+    HMODULE exe_module = GetModuleHandle(chrome::kBrowserProcessExecutableName);
+    if (!exe_module)
+      return;
+    set_extension_id = reinterpret_cast<MainSetExtensionID>(
+        GetProcAddress(exe_module, "SetExtensionID"));
+    if (!set_extension_id)
+      return;
+  }
+
+  (set_number_of_extensions)(static_cast<int>(extension_ids.size()));
+
+  std::set<std::string>::const_iterator iter = extension_ids.begin();
+  for (size_t i = 0; i < kMaxReportedActiveExtensions; ++i) {
+    if (iter != extension_ids.end()) {
+      (set_extension_id)(i, ASCIIToWide(iter->c_str()).c_str());
+      ++iter;
+    } else {
+      (set_extension_id)(i, L"");
+    }
+  }
+}
+
+void SetGpuInfo(const content::GPUInfo& gpu_info) {
+  static MainSetGpuInfo set_gpu_info = NULL;
+  // note: benign race condition on set_gpu_info.
+  if (!set_gpu_info) {
+    HMODULE exe_module = GetModuleHandle(chrome::kBrowserProcessExecutableName);
+    if (!exe_module)
+      return;
+    set_gpu_info = reinterpret_cast<MainSetGpuInfo>(
+        GetProcAddress(exe_module, "SetGpuInfo"));
+    if (!set_gpu_info)
+      return;
+  }
+  (set_gpu_info)(
+      base::StringPrintf(L"0x%04x", gpu_info.gpu.vendor_id).c_str(),
+      base::StringPrintf(L"0x%04x", gpu_info.gpu.device_id).c_str(),
+      UTF8ToUTF16(gpu_info.driver_version).c_str(),
+      UTF8ToUTF16(gpu_info.pixel_shader_version).c_str(),
+      UTF8ToUTF16(gpu_info.vertex_shader_version).c_str());
+}
+
+void SetPrinterInfo(const char* printer_info) {
+  static MainSetPrinterInfo set_printer_info = NULL;
+  // note: benign race condition on set_printer_info.
+  if (!set_printer_info) {
+    HMODULE exe_module = GetModuleHandle(chrome::kBrowserProcessExecutableName);
+    if (!exe_module)
+      return;
+    set_printer_info = reinterpret_cast<MainSetPrinterInfo>(
+        GetProcAddress(exe_module, "SetPrinterInfo"));
+    if (!set_printer_info)
+      return;
+  }
+  (set_printer_info)(UTF8ToWide(printer_info).c_str());
+}
+
+void SetCommandLine(const CommandLine* command_line) {
+  static MainSetCommandLine set_command_line = NULL;
+  // note: benign race condition on set_command_line.
+  if (!set_command_line) {
+    HMODULE exe_module = GetModuleHandle(chrome::kBrowserProcessExecutableName);
+    if (!exe_module)
+      return;
+    set_command_line = reinterpret_cast<MainSetCommandLine>(
+        GetProcAddress(exe_module, "SetCommandLine2"));
+    if (!set_command_line)
+      return;
+  }
+
+  if (command_line->argv().empty())
+    return;
+
+  std::vector<const wchar_t*> cstrings;
+  StringVectorToCStringVector(command_line->argv(), &cstrings);
+  (set_command_line)(&cstrings[0], cstrings.size());
+}
+
+void SetExperimentList(const std::vector<string16>& experiments) {
+  static MainSetExperimentList set_experiment_list = NULL;
+  // note: benign race condition on set_experiment_list.
+  if (!set_experiment_list) {
+    HMODULE exe_module = GetModuleHandle(chrome::kBrowserProcessExecutableName);
+    if (!exe_module)
+      return;
+    set_experiment_list = reinterpret_cast<MainSetExperimentList>(
+        GetProcAddress(exe_module, "SetExperimentList3"));
+    if (!set_experiment_list)
+      return;
+  }
+
+  std::vector<string16> chunks;
+  chrome_variations::GenerateVariationChunks(experiments, &chunks);
+
+  // If the list is empty, notify the child process of the number of experiments
+  // and exit early.
+  if (chunks.empty()) {
+    (set_experiment_list)(NULL, 0, 0);
+    return;
+  }
+
+  std::vector<const wchar_t*> cstrings;
+  StringVectorToCStringVector(chunks, &cstrings);
+  (set_experiment_list)(&cstrings[0], cstrings.size(), experiments.size());
+}
+
+void SetNumberOfViews(int number_of_views) {
+  static MainSetNumberOfViews set_number_of_views = NULL;
+  // note: benign race condition on set_number_of_views.
+  if (!set_number_of_views) {
+    HMODULE exe_module = GetModuleHandle(chrome::kBrowserProcessExecutableName);
+    if (!exe_module)
+      return;
+    set_number_of_views = reinterpret_cast<MainSetNumberOfViews>(
+        GetProcAddress(exe_module, "SetNumberOfViews"));
+    if (!set_number_of_views)
+      return;
+  }
+  (set_number_of_views)(number_of_views);
+}
+
+}  // namespace child_process_logging
diff --git a/chrome/common/chrome_constants.cc b/chrome/common/chrome_constants.cc
new file mode 100644
index 0000000..2697b35
--- /dev/null
+++ b/chrome/common/chrome_constants.cc
@@ -0,0 +1,250 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/chrome_constants.h"
+
+#include "base/file_path.h"
+
+#define FPL FILE_PATH_LITERAL
+
+#if defined(OS_MACOSX)
+#define CHROMIUM_PRODUCT_STRING "Chromium"
+#if defined(GOOGLE_CHROME_BUILD)
+#define PRODUCT_STRING "Google Chrome"
+#elif defined(CHROMIUM_BUILD)
+#define PRODUCT_STRING "Chromium"
+#else
+#error Unknown branding
+#endif
+#endif  // defined(OS_MACOSX)
+
+#if defined(OS_WIN)
+#if defined(GOOGLE_CHROME_BUILD)
+#define PRODUCT_STRING_PATH L"Google\\Chrome"
+#elif defined(CHROMIUM_BUILD)
+#define PRODUCT_STRING_PATH L"Chromium"
+#else
+#error Unknown branding
+#endif
+#endif  // defined(OS_WIN)
+
+namespace chrome {
+
+const char kChromeVersionEnvVar[] = "CHROME_VERSION";
+
+// The following should not be used for UI strings; they are meant
+// for system strings only. UI changes should be made in the GRD.
+//
+// There are four constants used to locate the executable name and path:
+//
+//     kBrowserProcessExecutableName
+//     kHelperProcessExecutableName
+//     kBrowserProcessExecutablePath
+//     kHelperProcessExecutablePath
+//
+// In one condition, our tests will be built using the Chrome branding
+// though we want to actually execute a Chromium branded application.
+// This happens for the reference build on Mac.  To support that case,
+// we also include a Chromium version of each of the four constants and
+// in the UITest class we support switching to that version when told to
+// do so.
+
+#if defined(OS_WIN)
+const FilePath::CharType kBrowserProcessExecutableNameChromium[] =
+    FPL("chrome.exe");
+const FilePath::CharType kBrowserProcessExecutableName[] = FPL("chrome.exe");
+const FilePath::CharType kHelperProcessExecutableNameChromium[] =
+    FPL("chrome.exe");
+const FilePath::CharType kHelperProcessExecutableName[] = FPL("chrome.exe");
+#elif defined(OS_MACOSX)
+const FilePath::CharType kBrowserProcessExecutableNameChromium[] =
+    FPL(CHROMIUM_PRODUCT_STRING);
+const FilePath::CharType kBrowserProcessExecutableName[] = FPL(PRODUCT_STRING);
+const FilePath::CharType kHelperProcessExecutableNameChromium[] =
+    FPL(CHROMIUM_PRODUCT_STRING " Helper");
+const FilePath::CharType kHelperProcessExecutableName[] =
+    FPL(PRODUCT_STRING " Helper");
+#elif defined(OS_ANDROID)
+// NOTE: Keep it synced with the process names defined in AndroidManifest.xml.
+const FilePath::CharType kBrowserProcessExecutableName[] = FPL("chrome");
+const FilePath::CharType kBrowserProcessExecutableNameChromium[] =
+    FPL("");
+const FilePath::CharType kHelperProcessExecutableName[] =
+    FPL("sandboxed_process");
+const FilePath::CharType kHelperProcessExecutableNameChromium[] = FPL("");
+#elif defined(OS_POSIX)
+const FilePath::CharType kBrowserProcessExecutableNameChromium[] =
+    FPL("chrome");
+const FilePath::CharType kBrowserProcessExecutableName[] = FPL("chrome");
+// Helper processes end up with a name of "exe" due to execing via
+// /proc/self/exe.  See bug 22703.
+const FilePath::CharType kHelperProcessExecutableNameChromium[] = FPL("exe");
+const FilePath::CharType kHelperProcessExecutableName[] = FPL("exe");
+#endif  // OS_*
+
+#if defined(OS_WIN)
+const FilePath::CharType kBrowserProcessExecutablePathChromium[] =
+    FPL("chrome.exe");
+const FilePath::CharType kBrowserProcessExecutablePath[] = FPL("chrome.exe");
+const FilePath::CharType kHelperProcessExecutablePathChromium[] =
+    FPL("chrome.exe");
+const FilePath::CharType kHelperProcessExecutablePath[] = FPL("chrome.exe");
+#elif defined(OS_MACOSX)
+const FilePath::CharType kBrowserProcessExecutablePathChromium[] =
+    FPL(CHROMIUM_PRODUCT_STRING ".app/Contents/MacOS/" CHROMIUM_PRODUCT_STRING);
+const FilePath::CharType kBrowserProcessExecutablePath[] =
+    FPL(PRODUCT_STRING ".app/Contents/MacOS/" PRODUCT_STRING);
+const FilePath::CharType kHelperProcessExecutablePathChromium[] =
+    FPL(CHROMIUM_PRODUCT_STRING " Helper.app/Contents/MacOS/"
+        CHROMIUM_PRODUCT_STRING " Helper");
+const FilePath::CharType kHelperProcessExecutablePath[] =
+    FPL(PRODUCT_STRING " Helper.app/Contents/MacOS/" PRODUCT_STRING " Helper");
+#elif defined(OS_ANDROID)
+const FilePath::CharType kBrowserProcessExecutablePath[] = FPL("chrome");
+const FilePath::CharType kHelperProcessExecutablePath[] = FPL("chrome");
+const FilePath::CharType kBrowserProcessExecutablePathChromium[] =
+    FPL("chrome");
+const FilePath::CharType kHelperProcessExecutablePathChromium[] = FPL("chrome");
+#elif defined(OS_POSIX)
+const FilePath::CharType kBrowserProcessExecutablePathChromium[] =
+    FPL("chrome");
+const FilePath::CharType kBrowserProcessExecutablePath[] = FPL("chrome");
+const FilePath::CharType kHelperProcessExecutablePathChromium[] = FPL("chrome");
+const FilePath::CharType kHelperProcessExecutablePath[] = FPL("chrome");
+#endif  // OS_*
+
+#if defined(OS_MACOSX)
+const FilePath::CharType kFrameworkName[] =
+    FPL(PRODUCT_STRING " Framework.framework");
+
+const char* const kHelperFlavorSuffixes[] = {
+  FPL("EH"),  // Executable heap
+  FPL("NP"),  // No PIE
+  NULL
+};
+#endif  // OS_MACOSX
+
+const wchar_t kNaClAppName[] = L"nacl64";
+#if defined(GOOGLE_CHROME_BUILD)
+const wchar_t kBrowserAppName[] = L"Chrome";
+#else
+const wchar_t kBrowserAppName[] = L"Chromium";
+#endif
+
+#if defined(OS_WIN)
+const FilePath::CharType kMetroDriverDll[] = FPL("metro_driver.dll");
+const wchar_t kStatusTrayWindowClass[] = L"Chrome_StatusTrayWindow";
+#endif  // defined(OS_WIN)
+
+const wchar_t kMessageWindowClass[] = L"Chrome_MessageWindow";
+const wchar_t kCrashReportLog[] = L"Reported Crashes.txt";
+const wchar_t kTestingInterfaceDLL[] = L"testing_interface.dll";
+const char    kInitialProfile[] = "Default";
+const char    kMultiProfileDirPrefix[] = "Profile ";
+const wchar_t kBrowserResourcesDll[] = L"chrome.dll";
+const FilePath::CharType kExtensionFileExtension[] = FPL(".crx");
+const FilePath::CharType kExtensionKeyFileExtension[] = FPL(".pem");
+
+// filenames
+#if defined(OS_ANDROID)
+const FilePath::CharType kAndroidCacheFilename[] = FPL("AndroidCache");
+#endif
+const FilePath::CharType kArchivedHistoryFilename[] = FPL("Archived History");
+const FilePath::CharType kBookmarksFileName[] = FPL("Bookmarks");
+const FilePath::CharType kCacheDirname[] = FPL("Cache");
+const FilePath::CharType kCookieFilename[] = FPL("Cookies");
+const FilePath::CharType kCRLSetFilename[] =
+    FPL("Certificate Revocation Lists");
+const FilePath::CharType kCustomDictionaryFileName[] =
+    FPL("Custom Dictionary.txt");
+const FilePath::CharType kExtensionsCookieFilename[] = FPL("Extension Cookies");
+const FilePath::CharType kFaviconsFilename[] = FPL("Favicons");
+const FilePath::CharType kFirstRunSentinel[] = FPL("First Run");
+const FilePath::CharType kHistoryFilename[] = FPL("History");
+const FilePath::CharType kJumpListIconDirname[] = FPL("JumpListIcons");
+const FilePath::CharType kLocalStateFilename[] = FPL("Local State");
+const FilePath::CharType kLoginDataFileName[] = FPL("Login Data");
+const FilePath::CharType kManagedModePolicyFilename[] =
+    FPL("Managed Mode Settings");
+const FilePath::CharType kMediaCacheDirname[] = FPL("Media Cache");
+const FilePath::CharType kNewTabThumbnailsFilename[] = FPL("Top Thumbnails");
+const FilePath::CharType kOBCertFilename[] = FPL("Origin Bound Certs");
+const FilePath::CharType kPreferencesFilename[] = FPL("Preferences");
+const FilePath::CharType kReadmeFilename[] = FPL("README");
+const FilePath::CharType kSafeBrowsingBaseFilename[] = FPL("Safe Browsing");
+const FilePath::CharType kServiceStateFileName[] = FPL("Service State");
+const FilePath::CharType kShortcutsDatabaseName[] = FPL("Shortcuts");
+const FilePath::CharType kSingletonCookieFilename[] = FPL("SingletonCookie");
+const FilePath::CharType kSingletonLockFilename[] = FPL("SingletonLock");
+const FilePath::CharType kSingletonSocketFilename[] = FPL("SingletonSocket");
+const FilePath::CharType kSyncCredentialsFilename[] = FPL("Sync Credentials");
+const FilePath::CharType kThemePackFilename[] = FPL("Cached Theme.pak");
+const FilePath::CharType kThumbnailsFilename[] = FPL("Thumbnails");
+const FilePath::CharType kTopSitesFilename[] = FPL("Top Sites");
+const FilePath::CharType kWebAppDirname[] = FPL("Web Applications");
+const FilePath::CharType kWebDataFilename[] = FPL("Web Data");
+
+// File name of the Pepper Flash plugin on different platforms.
+const FilePath::CharType kPepperFlashPluginFilename[] =
+#if defined(OS_MACOSX)
+    FPL("PepperFlashPlayer.plugin");
+#elif defined(OS_WIN)
+    FPL("pepflashplayer.dll");
+#else  // OS_LINUX, etc.
+    FPL("libpepflashplayer.so");
+#endif
+
+// directory names
+const wchar_t kUserDataDirname[] = L"User Data";
+
+#if defined(OS_CHROMEOS)
+const FilePath::CharType kDriveCacheDirname[] = FPL("GCache");
+#endif  // defined(OS_CHROMEOS)
+
+// We don't enable record mode in the released product because users could
+// potentially be tricked into running a product in record mode without
+// knowing it.  Enable in debug builds.  Playback mode is allowed always,
+// because it is useful for testing and not hazardous by itself.
+#ifndef NDEBUG
+// const bool kRecordModeEnabled = true;
+#else
+// const bool kRecordModeEnabled = false;
+#endif
+
+const bool kRecordModeEnabled = true;
+
+const char* const kUnknownLanguageCode = "und";
+
+const int kJavascriptMessageExpectedDelay = 1000;
+
+#if defined(OS_ANDROID)
+const bool kEnableTouchIcon = true;
+#else
+const bool kEnableTouchIcon = false;
+#endif
+
+const float kMaxShareOfExtensionProcesses = 0.30f;
+
+#if defined(OS_LINUX)
+extern const int kLowestRendererOomScore = 300;
+extern const int kHighestRendererOomScore = 1000;
+#endif
+
+#if defined(OS_WIN)
+// This is used by the PreRead experiment.
+const char kPreReadEnvironmentVariable[] = "CHROME_PRE_READ_EXPERIMENT";
+// This is used by chrome in Windows 8 metro mode.
+const wchar_t kMetroChromeUserDataSubDir[] = L"Metro";
+const wchar_t kMetroNavigationAndSearchMessage[] =
+    L"CHROME_METRO_NAV_SEARCH_REQUEST";
+const wchar_t kMetroGetCurrentTabInfoMessage[] =
+    L"CHROME_METRO_GET_CURRENT_TAB_INFO";
+const wchar_t kMetroRegistryPath[] =
+    L"Software\\" PRODUCT_STRING_PATH L"\\Metro";
+const wchar_t kLaunchModeValue[] = L"launch_mode";
+#endif
+
+}  // namespace chrome
+
+#undef FPL
diff --git a/chrome/common/chrome_constants.h b/chrome/common/chrome_constants.h
new file mode 100644
index 0000000..6ecce26
--- /dev/null
+++ b/chrome/common/chrome_constants.h
@@ -0,0 +1,145 @@
+// Copyright (c) 2012 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.
+
+// A handful of resource-like constants related to the Chrome application.
+
+#ifndef CHROME_COMMON_CHROME_CONSTANTS_H_
+#define CHROME_COMMON_CHROME_CONSTANTS_H_
+
+#include "base/file_path.h"
+
+namespace chrome {
+
+extern const char kChromeVersion[];
+
+extern const char kChromeVersionEnvVar[];
+
+extern const FilePath::CharType kBrowserProcessExecutableName[];
+extern const FilePath::CharType kHelperProcessExecutableName[];
+extern const FilePath::CharType kBrowserProcessExecutablePath[];
+extern const FilePath::CharType kHelperProcessExecutablePath[];
+extern const FilePath::CharType kBrowserProcessExecutableNameChromium[];
+extern const FilePath::CharType kHelperProcessExecutableNameChromium[];
+extern const FilePath::CharType kBrowserProcessExecutablePathChromium[];
+extern const FilePath::CharType kHelperProcessExecutablePathChromium[];
+#if defined(OS_MACOSX)
+extern const FilePath::CharType kFrameworkName[];
+
+// The helper .app bundle name and executable name may have one of these
+// suffixes to identify specific features, or it may have no suffix at all.
+// This is a NULL-terminated array of strings. If kHelperFlavorSuffixes
+// contains "EN", "MF", and NULL, it indicates that if the normal helper is
+// named Chromium Helper.app, helper executables could show up at any of
+// Chromium Helper.app/Contents/MacOS/Chromium Helper,
+// Chromium Helper EN.app/Contents/MacOS/Chromium Helper EN, and
+// Chromium Helper MF.app/Contents/MacOS/Chromium Helper MF.
+extern const FilePath::CharType* const kHelperFlavorSuffixes[];
+#endif  // OS_MACOSX
+extern const wchar_t kBrowserAppName[];
+#if defined(OS_WIN)
+extern const FilePath::CharType kMetroDriverDll[];
+extern const wchar_t kStatusTrayWindowClass[];
+#endif  // defined(OS_WIN)
+extern const wchar_t kMessageWindowClass[];
+extern const wchar_t kCrashReportLog[];
+extern const wchar_t kTestingInterfaceDLL[];
+extern const char    kInitialProfile[];
+extern const char    kMultiProfileDirPrefix[];
+extern const wchar_t kBrowserResourcesDll[];
+extern const wchar_t kNaClAppName[];
+extern const FilePath::CharType kExtensionFileExtension[];
+extern const FilePath::CharType kExtensionKeyFileExtension[];
+
+// filenames
+#if defined(OS_ANDROID)
+extern const FilePath::CharType kAndroidCacheFilename[];
+#endif
+extern const FilePath::CharType kArchivedHistoryFilename[];
+extern const FilePath::CharType kBookmarksFileName[];
+extern const FilePath::CharType kCacheDirname[];
+extern const FilePath::CharType kCookieFilename[];
+extern const FilePath::CharType kCRLSetFilename[];
+extern const FilePath::CharType kCustomDictionaryFileName[];
+extern const FilePath::CharType kExtensionsCookieFilename[];
+extern const FilePath::CharType kFaviconsFilename[];
+extern const FilePath::CharType kFirstRunSentinel[];
+extern const FilePath::CharType kHistoryFilename[];
+extern const FilePath::CharType kJumpListIconDirname[];
+extern const FilePath::CharType kLocalStateFilename[];
+extern const FilePath::CharType kLoginDataFileName[];
+extern const FilePath::CharType kManagedModePolicyFilename[];
+extern const FilePath::CharType kMediaCacheDirname[];
+extern const FilePath::CharType kNewTabThumbnailsFilename[];
+extern const FilePath::CharType kOBCertFilename[];
+extern const FilePath::CharType kPreferencesFilename[];
+extern const FilePath::CharType kReadmeFilename[];
+extern const FilePath::CharType kSafeBrowsingBaseFilename[];
+extern const FilePath::CharType kServiceStateFileName[];
+extern const FilePath::CharType kShortcutsDatabaseName[];
+extern const FilePath::CharType kSingletonCookieFilename[];
+extern const FilePath::CharType kSingletonLockFilename[];
+extern const FilePath::CharType kSingletonSocketFilename[];
+extern const FilePath::CharType kSyncCredentialsFilename[];
+extern const FilePath::CharType kThemePackFilename[];
+extern const FilePath::CharType kThumbnailsFilename[];
+extern const FilePath::CharType kTopSitesFilename[];
+extern const FilePath::CharType kWebAppDirname[];
+extern const FilePath::CharType kWebDataFilename[];
+
+// File name of the Pepper Flash plugin on different platforms.
+extern const FilePath::CharType kPepperFlashPluginFilename[];
+
+// directory names
+extern const wchar_t kUserDataDirname[];
+
+#if defined(OS_CHROMEOS)
+extern const FilePath::CharType kDriveCacheDirname[];
+#endif  // defined(OS_CHROMEOS)
+
+extern const bool kRecordModeEnabled;
+
+// The language code used when the language of a page could not be detected.
+// (Matches what the CLD -Compact Language Detection- library reports.)
+extern const char* const kUnknownLanguageCode;
+
+// If another javascript message box is displayed within
+// kJavascriptMessageExpectedDelay of a previous javascript message box being
+// dismissed, display an option to suppress future message boxes from this
+// contents.
+extern const int kJavascriptMessageExpectedDelay;
+
+// Are touch icons enabled? False by default.
+extern const bool kEnableTouchIcon;
+
+// Fraction of the total number of processes to be used for hosting
+// extensions. If we have more extensions than this percentage, we will start
+// combining extensions in existing processes. This allows web pages to have
+// enough render processes and not be starved when a lot of extensions are
+// installed.
+extern const float kMaxShareOfExtensionProcesses;
+
+#if defined(OS_LINUX)
+// The highest and lowest assigned OOM score adjustment
+// (oom_score_adj) used by the OomPriority Manager.
+extern const int kLowestRendererOomScore;
+extern const int kHighestRendererOomScore;
+#endif
+
+#if defined(OS_WIN)
+// This is used by the PreRead experiment.
+extern const char kPreReadEnvironmentVariable[];
+// Used by Metro Chrome to create the profile under a custom subdirectory.
+extern const wchar_t kMetroChromeUserDataSubDir[];
+// Used by Metro Chrome to initiate navigation and search requests.
+extern const wchar_t kMetroNavigationAndSearchMessage[];
+// Used by Metro Chrome to get information about the current tab.
+extern const wchar_t kMetroGetCurrentTabInfoMessage[];
+// Used by Metro Chrome to store activation state.
+extern const wchar_t kMetroRegistryPath[];
+extern const wchar_t kLaunchModeValue[];
+#endif
+
+}  // namespace chrome
+
+#endif  // CHROME_COMMON_CHROME_CONSTANTS_H_
diff --git a/chrome/common/chrome_content_client.cc b/chrome/common/chrome_content_client.cc
new file mode 100644
index 0000000..d187f06
--- /dev/null
+++ b/chrome/common/chrome_content_client.cc
@@ -0,0 +1,468 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/chrome_content_client.h"
+
+#include "base/command_line.h"
+#include "base/cpu.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/process_util.h"
+#include "base/string_number_conversions.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/common/child_process_logging.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/chrome_version_info.h"
+#include "chrome/common/pepper_flash.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/url_constants.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/pepper_plugin_info.h"
+#include "content/public/common/url_constants.h"
+#include "grit/common_resources.h"
+#include "ppapi/shared_impl/ppapi_permissions.h"
+#include "remoting/client/plugin/pepper_entrypoints.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/layout.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "webkit/plugins/npapi/plugin_list.h"
+#include "webkit/plugins/plugin_constants.h"
+#include "webkit/user_agent/user_agent_util.h"
+
+#include "flapper_version.h"  // In SHARED_INTERMEDIATE_DIR.
+#include "widevine_cdm_version.h"  // In SHARED_INTERMEDIATE_DIR.
+
+#if defined(OS_WIN)
+#include "base/win/registry.h"
+#include "base/win/windows_version.h"
+#include "sandbox/win/src/sandbox.h"
+#elif defined(OS_MACOSX)
+#include "chrome/common/chrome_sandbox_type_mac.h"
+#endif
+
+namespace {
+
+const char kPDFPluginName[] = "Chrome PDF Viewer";
+const char kPDFPluginMimeType[] = "application/pdf";
+const char kPDFPluginExtension[] = "pdf";
+const char kPDFPluginDescription[] = "Portable Document Format";
+const char kPDFPluginPrintPreviewMimeType
+   [] = "application/x-google-chrome-print-preview-pdf";
+const uint32 kPDFPluginPermissions = ppapi::PERMISSION_PRIVATE |
+                                     ppapi::PERMISSION_DEV;
+
+const char kNaClPluginName[] = "Native Client";
+const char kNaClPluginMimeType[] = "application/x-nacl";
+const char kNaClPluginExtension[] = "nexe";
+const char kNaClPluginDescription[] = "Native Client Executable";
+const uint32 kNaClPluginPermissions = ppapi::PERMISSION_PRIVATE |
+                                      ppapi::PERMISSION_DEV;
+
+const char kNaClOldPluginName[] = "Chrome NaCl";
+
+const char kO3DPluginName[] = "Google Talk Plugin Video Accelerator";
+const char kO3DPluginMimeType[] ="application/vnd.o3d.auto";
+const char kO3DPluginExtension[] = "";
+const char kO3DPluginDescription[] = "O3D MIME";
+const uint32 kO3DPluginPermissions = ppapi::PERMISSION_PRIVATE |
+                                     ppapi::PERMISSION_DEV;
+
+const char kGTalkPluginName[] = "Google Talk Plugin";
+const char kGTalkPluginMimeType[] ="application/googletalk";
+const char kGTalkPluginExtension[] = ".googletalk";
+const char kGTalkPluginDescription[] = "Google Talk Plugin";
+const uint32 kGTalkPluginPermissions = ppapi::PERMISSION_PRIVATE |
+                                       ppapi::PERMISSION_DEV;
+
+#if defined(WIDEVINE_CDM_AVAILABLE)
+const char kWidevineCdmPluginExtension[] = "";
+const uint32 kWidevineCdmPluginPermissions = ppapi::PERMISSION_PRIVATE |
+                                             ppapi::PERMISSION_DEV;
+#endif  // WIDEVINE_CDM_AVAILABLE
+
+#if defined(ENABLE_REMOTING)
+#if defined(GOOGLE_CHROME_BUILD)
+const char kRemotingViewerPluginName[] = "Chrome Remote Desktop Viewer";
+#else
+const char kRemotingViewerPluginName[] = "Chromoting Viewer";
+#endif  // defined(GOOGLE_CHROME_BUILD)
+const char kRemotingViewerPluginDescription[] =
+    "This plugin allows you to securely access other computers that have been "
+    "shared with you. To use this plugin you must first install the "
+    "<a href=\"https://chrome.google.com/remotedesktop\">"
+    "Chrome Remote Desktop</a> webapp.";
+const FilePath::CharType kRemotingViewerPluginPath[] =
+    FILE_PATH_LITERAL("internal-remoting-viewer");
+// Use a consistent MIME-type regardless of branding.
+const char kRemotingViewerPluginMimeType[] =
+    "application/vnd.chromium.remoting-viewer";
+const char kRemotingViewerPluginMimeExtension[] = "";
+const char kRemotingViewerPluginMimeDescription[] = "";
+#endif  // defined(ENABLE_REMOTING)
+
+const char kInterposeLibraryPath[] =
+    "@executable_path/../../../libplugin_carbon_interpose.dylib";
+
+// Appends the known built-in plugins to the given vector. Some built-in
+// plugins are "internal" which means they are compiled into the Chrome binary,
+// and some are extra shared libraries distributed with the browser (these are
+// not marked internal, aside from being automatically registered, they're just
+// regular plugins).
+void ComputeBuiltInPlugins(std::vector<content::PepperPluginInfo>* plugins) {
+  // PDF.
+  //
+  // Once we're sandboxed, we can't know if the PDF plugin is available or not;
+  // but (on Linux) this function is always called once before we're sandboxed.
+  // So the first time through test if the file is available and then skip the
+  // check on subsequent calls if yes.
+  static bool skip_pdf_file_check = false;
+  FilePath path;
+  if (PathService::Get(chrome::FILE_PDF_PLUGIN, &path)) {
+    if (skip_pdf_file_check || file_util::PathExists(path)) {
+      content::PepperPluginInfo pdf;
+      pdf.path = path;
+      pdf.name = kPDFPluginName;
+      webkit::WebPluginMimeType pdf_mime_type(kPDFPluginMimeType,
+                                              kPDFPluginExtension,
+                                              kPDFPluginDescription);
+      webkit::WebPluginMimeType print_preview_pdf_mime_type(
+          kPDFPluginPrintPreviewMimeType,
+          kPDFPluginExtension,
+          kPDFPluginDescription);
+      pdf.mime_types.push_back(pdf_mime_type);
+      pdf.mime_types.push_back(print_preview_pdf_mime_type);
+      pdf.permissions = kPDFPluginPermissions;
+      plugins->push_back(pdf);
+
+      skip_pdf_file_check = true;
+    }
+  }
+
+  // Handle the Native Client just like the PDF plugin. This means that it is
+  // enabled by default. This allows apps installed from the Chrome Web Store
+  // to use NaCl even if the command line switch isn't set. For other uses of
+  // NaCl we check for the command line switch.
+  static bool skip_nacl_file_check = false;
+  if (PathService::Get(chrome::FILE_NACL_PLUGIN, &path)) {
+    if (skip_nacl_file_check || file_util::PathExists(path)) {
+      content::PepperPluginInfo nacl;
+      nacl.path = path;
+      nacl.name = kNaClPluginName;
+      webkit::WebPluginMimeType nacl_mime_type(kNaClPluginMimeType,
+                                               kNaClPluginExtension,
+                                               kNaClPluginDescription);
+      nacl.mime_types.push_back(nacl_mime_type);
+      nacl.permissions = kNaClPluginPermissions;
+      plugins->push_back(nacl);
+
+      skip_nacl_file_check = true;
+    }
+  }
+
+  static bool skip_o3d_file_check = false;
+  if (PathService::Get(chrome::FILE_O3D_PLUGIN, &path)) {
+    if (skip_o3d_file_check || file_util::PathExists(path)) {
+      content::PepperPluginInfo o3d;
+      o3d.path = path;
+      o3d.name = kO3DPluginName;
+      o3d.is_out_of_process = true;
+      o3d.is_sandboxed = false;
+      o3d.permissions = kO3DPluginPermissions;
+      webkit::WebPluginMimeType o3d_mime_type(kO3DPluginMimeType,
+                                              kO3DPluginExtension,
+                                              kO3DPluginDescription);
+      o3d.mime_types.push_back(o3d_mime_type);
+      plugins->push_back(o3d);
+
+      skip_o3d_file_check = true;
+    }
+  }
+
+  static bool skip_gtalk_file_check = false;
+  if (PathService::Get(chrome::FILE_GTALK_PLUGIN, &path)) {
+    if (skip_gtalk_file_check || file_util::PathExists(path)) {
+      content::PepperPluginInfo gtalk;
+      gtalk.path = path;
+      gtalk.name = kGTalkPluginName;
+      gtalk.is_out_of_process = true;
+      gtalk.is_sandboxed = false;
+      gtalk.permissions = kGTalkPluginPermissions;
+      webkit::WebPluginMimeType gtalk_mime_type(kGTalkPluginMimeType,
+                                                kGTalkPluginExtension,
+                                                kGTalkPluginDescription);
+      gtalk.mime_types.push_back(gtalk_mime_type);
+      plugins->push_back(gtalk);
+
+      skip_gtalk_file_check = true;
+    }
+  }
+
+#if defined(WIDEVINE_CDM_AVAILABLE)
+  static bool skip_widevine_cdm_file_check = false;
+  if (PathService::Get(chrome::FILE_WIDEVINE_CDM_PLUGIN, &path)) {
+    if (skip_widevine_cdm_file_check || file_util::PathExists(path)) {
+      content::PepperPluginInfo widevine_cdm;
+      widevine_cdm.is_out_of_process = true;
+      widevine_cdm.path = path;
+      widevine_cdm.name = kWidevineCdmPluginName;
+      widevine_cdm.description = kWidevineCdmPluginDescription;
+      widevine_cdm.version = WIDEVINE_CDM_VERSION_STRING;
+      webkit::WebPluginMimeType widevine_cdm_mime_type(
+          kWidevineCdmPluginMimeType,
+          kWidevineCdmPluginExtension,
+          kWidevineCdmPluginMimeTypeDescription);
+      widevine_cdm.mime_types.push_back(widevine_cdm_mime_type);
+      widevine_cdm.permissions = kWidevineCdmPluginPermissions;
+      plugins->push_back(widevine_cdm);
+
+      skip_widevine_cdm_file_check = true;
+    }
+  }
+#endif  // WIDEVINE_CDM_AVAILABLE
+
+  // The Remoting Viewer plugin is built-in.
+#if defined(ENABLE_REMOTING)
+  content::PepperPluginInfo info;
+  info.is_internal = true;
+  info.name = kRemotingViewerPluginName;
+  info.description = kRemotingViewerPluginDescription;
+  info.path = FilePath(kRemotingViewerPluginPath);
+  webkit::WebPluginMimeType remoting_mime_type(
+      kRemotingViewerPluginMimeType,
+      kRemotingViewerPluginMimeExtension,
+      kRemotingViewerPluginMimeDescription);
+  info.mime_types.push_back(remoting_mime_type);
+  info.internal_entry_points.get_interface = remoting::PPP_GetInterface;
+  info.internal_entry_points.initialize_module =
+      remoting::PPP_InitializeModule;
+  info.internal_entry_points.shutdown_module = remoting::PPP_ShutdownModule;
+
+  plugins->push_back(info);
+#endif
+}
+
+content::PepperPluginInfo CreatePepperFlashInfo(const FilePath& path,
+                                                const std::string& version) {
+  content::PepperPluginInfo plugin;
+
+  // Flash being out of process is handled separately than general plugins
+  // for testing purposes.
+  plugin.is_out_of_process = !CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kPpapiFlashInProcess);
+  plugin.name = kFlashPluginName;
+  plugin.path = path;
+  plugin.permissions = kPepperFlashPermissions;
+
+  std::vector<std::string> flash_version_numbers;
+  base::SplitString(version, '.', &flash_version_numbers);
+  if (flash_version_numbers.size() < 1)
+    flash_version_numbers.push_back("11");
+  // |SplitString()| puts in an empty string given an empty string. :(
+  else if (flash_version_numbers[0].empty())
+    flash_version_numbers[0] = "11";
+  if (flash_version_numbers.size() < 2)
+    flash_version_numbers.push_back("2");
+  if (flash_version_numbers.size() < 3)
+    flash_version_numbers.push_back("999");
+  if (flash_version_numbers.size() < 4)
+    flash_version_numbers.push_back("999");
+  // E.g., "Shockwave Flash 10.2 r154":
+  plugin.description = plugin.name + " " + flash_version_numbers[0] + "." +
+      flash_version_numbers[1] + " r" + flash_version_numbers[2];
+  plugin.version = JoinString(flash_version_numbers, '.');
+  webkit::WebPluginMimeType swf_mime_type(kFlashPluginSwfMimeType,
+                                          kFlashPluginSwfExtension,
+                                          kFlashPluginSwfDescription);
+  plugin.mime_types.push_back(swf_mime_type);
+  webkit::WebPluginMimeType spl_mime_type(kFlashPluginSplMimeType,
+                                          kFlashPluginSplExtension,
+                                          kFlashPluginSplDescription);
+  plugin.mime_types.push_back(spl_mime_type);
+
+  return plugin;
+}
+
+void AddPepperFlashFromCommandLine(
+    std::vector<content::PepperPluginInfo>* plugins) {
+  const CommandLine::StringType flash_path =
+      CommandLine::ForCurrentProcess()->GetSwitchValueNative(
+          switches::kPpapiFlashPath);
+  if (flash_path.empty())
+    return;
+
+  // Also get the version from the command-line. Should be something like 11.2
+  // or 11.2.123.45.
+  std::string flash_version =
+      CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          switches::kPpapiFlashVersion);
+
+  plugins->push_back(
+      CreatePepperFlashInfo(FilePath(flash_path), flash_version));
+}
+
+bool GetBundledPepperFlash(content::PepperPluginInfo* plugin,
+                           bool* override_npapi_flash) {
+#if defined(FLAPPER_AVAILABLE)
+  // Ignore bundled Pepper Flash if there is Pepper Flash specified from the
+  // command-line.
+  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kPpapiFlashPath))
+    return false;
+
+  bool force_disable = CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kDisableBundledPpapiFlash);
+  if (force_disable)
+    return false;
+
+// For Linux ia32, Flapper requires SSE2.
+#if defined(OS_LINUX) && defined(ARCH_CPU_X86)
+  if (!base::CPU().has_sse2())
+    return false;
+#endif  // ARCH_CPU_X86
+
+  FilePath flash_path;
+  if (!PathService::Get(chrome::FILE_PEPPER_FLASH_PLUGIN, &flash_path))
+    return false;
+
+  bool force_enable = CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kEnableBundledPpapiFlash);
+
+  *plugin = CreatePepperFlashInfo(flash_path, FLAPPER_VERSION_STRING);
+  *override_npapi_flash = force_enable || IsPepperFlashEnabledByDefault();
+  return true;
+#else
+  return false;
+#endif  // FLAPPER_AVAILABLE
+}
+
+}  // namespace
+
+namespace chrome {
+
+const char* const ChromeContentClient::kPDFPluginName = ::kPDFPluginName;
+const char* const ChromeContentClient::kNaClPluginName = ::kNaClPluginName;
+const char* const ChromeContentClient::kNaClOldPluginName =
+    ::kNaClOldPluginName;
+
+void ChromeContentClient::SetActiveURL(const GURL& url) {
+  child_process_logging::SetActiveURL(url);
+}
+
+void ChromeContentClient::SetGpuInfo(const content::GPUInfo& gpu_info) {
+  child_process_logging::SetGpuInfo(gpu_info);
+}
+
+void ChromeContentClient::AddPepperPlugins(
+    std::vector<content::PepperPluginInfo>* plugins) {
+  ComputeBuiltInPlugins(plugins);
+  AddPepperFlashFromCommandLine(plugins);
+
+  // Don't try to register Pepper Flash if there exists a Pepper Flash field
+  // trial. It will be registered separately.
+  if (!ConductingPepperFlashFieldTrial() && IsPepperFlashEnabledByDefault()) {
+    content::PepperPluginInfo plugin;
+    bool add_at_beginning = false;
+    if (GetBundledPepperFlash(&plugin, &add_at_beginning))
+      plugins->push_back(plugin);
+  }
+}
+
+void ChromeContentClient::AddNPAPIPlugins(
+    webkit::npapi::PluginList* plugin_list) {
+}
+
+void ChromeContentClient::AddAdditionalSchemes(
+    std::vector<std::string>* standard_schemes,
+    std::vector<std::string>* savable_schemes) {
+  standard_schemes->push_back(kExtensionScheme);
+  savable_schemes->push_back(kExtensionScheme);
+  standard_schemes->push_back(kExtensionResourceScheme);
+  savable_schemes->push_back(kExtensionResourceScheme);
+#if defined(OS_CHROMEOS)
+  standard_schemes->push_back(kCrosScheme);
+#endif
+}
+
+bool ChromeContentClient::HasWebUIScheme(const GURL& url) const {
+  return url.SchemeIs(chrome::kChromeDevToolsScheme) ||
+         url.SchemeIs(chrome::kChromeInternalScheme) ||
+         url.SchemeIs(chrome::kChromeUIScheme);
+}
+
+bool ChromeContentClient::CanHandleWhileSwappedOut(
+    const IPC::Message& msg) {
+  // Any Chrome-specific messages (apart from those listed in
+  // CanSendWhileSwappedOut) that must be handled by the browser when sent from
+  // swapped out renderers.
+  switch (msg.type()) {
+    case ChromeViewHostMsg_Snapshot::ID:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+std::string ChromeContentClient::GetProduct() const {
+  chrome::VersionInfo version_info;
+  std::string product("Chrome/");
+  product += version_info.is_valid() ? version_info.Version() : "0.0.0.0";
+  return product;
+}
+
+std::string ChromeContentClient::GetUserAgent() const {
+  std::string product = GetProduct();
+#if defined(OS_ANDROID)
+  CommandLine* command_line = CommandLine::ForCurrentProcess();
+  if (command_line->HasSwitch(switches::kUseMobileUserAgent))
+    product += " Mobile";
+#endif
+  return webkit_glue::BuildUserAgentFromProduct(product);
+}
+
+string16 ChromeContentClient::GetLocalizedString(int message_id) const {
+  return l10n_util::GetStringUTF16(message_id);
+}
+
+base::StringPiece ChromeContentClient::GetDataResource(
+    int resource_id,
+    ui::ScaleFactor scale_factor) const {
+  return ResourceBundle::GetSharedInstance().GetRawDataResourceForScale(
+      resource_id, scale_factor);
+}
+
+gfx::Image& ChromeContentClient::GetNativeImageNamed(int resource_id) const {
+  return ResourceBundle::GetSharedInstance().GetNativeImageNamed(resource_id);
+}
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+bool ChromeContentClient::GetSandboxProfileForSandboxType(
+    int sandbox_type,
+    int* sandbox_profile_resource_id) const {
+  DCHECK(sandbox_profile_resource_id);
+  if (sandbox_type == CHROME_SANDBOX_TYPE_NACL_LOADER) {
+    *sandbox_profile_resource_id = IDR_NACL_SANDBOX_PROFILE;
+    return true;
+  }
+  return false;
+}
+
+std::string ChromeContentClient::GetCarbonInterposePath() const {
+  return std::string(kInterposeLibraryPath);
+}
+#endif
+
+bool ChromeContentClient::GetBundledFieldTrialPepperFlash(
+    content::PepperPluginInfo* plugin,
+    bool* override_npapi_flash) {
+  if (!ConductingPepperFlashFieldTrial())
+    return false;
+  return GetBundledPepperFlash(plugin, override_npapi_flash);
+}
+
+}  // namespace chrome
diff --git a/chrome/common/chrome_content_client.h b/chrome/common/chrome_content_client.h
new file mode 100644
index 0000000..a3b43f3
--- /dev/null
+++ b/chrome/common/chrome_content_client.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_CHROME_CONTENT_CLIENT_H_
+#define CHROME_COMMON_CHROME_CONTENT_CLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "content/public/common/content_client.h"
+
+namespace chrome {
+
+class ChromeContentClient : public content::ContentClient {
+ public:
+  static const char* const kPDFPluginName;
+  static const char* const kNaClPluginName;
+  static const char* const kNaClOldPluginName;
+
+  virtual void SetActiveURL(const GURL& url) OVERRIDE;
+  virtual void SetGpuInfo(const content::GPUInfo& gpu_info) OVERRIDE;
+  virtual void AddPepperPlugins(
+      std::vector<content::PepperPluginInfo>* plugins) OVERRIDE;
+  virtual void AddNPAPIPlugins(
+      webkit::npapi::PluginList* plugin_list) OVERRIDE;
+  virtual void AddAdditionalSchemes(
+      std::vector<std::string>* standard_schemes,
+      std::vector<std::string>* saveable_shemes) OVERRIDE;
+  virtual bool HasWebUIScheme(const GURL& url) const OVERRIDE;
+  virtual bool CanHandleWhileSwappedOut(const IPC::Message& msg) OVERRIDE;
+  virtual std::string GetProduct() const OVERRIDE;
+  virtual std::string GetUserAgent() const OVERRIDE;
+  virtual string16 GetLocalizedString(int message_id) const OVERRIDE;
+  virtual base::StringPiece GetDataResource(
+      int resource_id,
+      ui::ScaleFactor scale_factor) const OVERRIDE;
+  virtual gfx::Image& GetNativeImageNamed(int resource_id) const OVERRIDE;
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+  virtual bool GetSandboxProfileForSandboxType(
+      int sandbox_type,
+      int* sandbox_profile_resource_id) const OVERRIDE;
+  virtual std::string GetCarbonInterposePath() const OVERRIDE;
+#endif
+
+  // Gets information about the bundled Pepper Flash for field trial.
+  // |override_npapi_flash| indicates whether it should take precedence over
+  // the internal NPAPI Flash.
+  // Returns false if bundled Pepper Flash is not available. In that case,
+  // |plugin| and |override_npapi_flash| are not touched.
+  //
+  // TODO(yzshen): We need this method because currently we are having a field
+  // trial with bundled Pepper Flash, and need extra information about how to
+  // order bundled Pepper Flash and internal NPAPI Flash. Once the field trial
+  // is over, we should merge this into AddPepperPlugins().
+  bool GetBundledFieldTrialPepperFlash(content::PepperPluginInfo* plugin,
+                                       bool* override_npapi_flash);
+};
+
+}  // namespace chrome
+
+#endif  // CHROME_COMMON_CHROME_CONTENT_CLIENT_H_
diff --git a/chrome/common/chrome_content_client_unittest.cc b/chrome/common/chrome_content_client_unittest.cc
new file mode 100644
index 0000000..11c52ce
--- /dev/null
+++ b/chrome/common/chrome_content_client_unittest.cc
@@ -0,0 +1,84 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/chrome_content_client.h"
+
+#include "base/command_line.h"
+#include "base/string_split.h"
+#include "content/public/common/content_switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void CheckUserAgentStringOrdering(bool mobile_device) {
+  std::vector<std::string> pieces;
+
+  // Check if the pieces of the user agent string come in the correct order.
+  chrome::ChromeContentClient content_client;
+  std::string buffer = content_client.GetUserAgent();
+
+  base::SplitStringUsingSubstr(buffer, "Mozilla/5.0 (", &pieces);
+  ASSERT_EQ(2u, pieces.size());
+  buffer = pieces[1];
+  EXPECT_EQ("", pieces[0]);
+
+  base::SplitStringUsingSubstr(buffer, ") AppleWebKit/", &pieces);
+  ASSERT_EQ(2u, pieces.size());
+  buffer = pieces[1];
+  std::string os_str = pieces[0];
+
+  base::SplitStringUsingSubstr(buffer, " (KHTML, like Gecko) ", &pieces);
+  ASSERT_EQ(2u, pieces.size());
+  buffer = pieces[1];
+  std::string webkit_version_str = pieces[0];
+
+  base::SplitStringUsingSubstr(buffer, " Safari/", &pieces);
+  ASSERT_EQ(2u, pieces.size());
+  std::string product_str = pieces[0];
+  std::string safari_version_str = pieces[1];
+
+  // Not sure what can be done to better check the OS string, since it's highly
+  // platform-dependent.
+  EXPECT_TRUE(os_str.size() > 0);
+
+  // Check that the version numbers match.
+  EXPECT_TRUE(webkit_version_str.size() > 0);
+  EXPECT_TRUE(safari_version_str.size() > 0);
+  EXPECT_EQ(webkit_version_str, safari_version_str);
+
+  EXPECT_EQ(0u, product_str.find("Chrome/"));
+  if (mobile_device) {
+    // "Mobile" gets tacked on to the end for mobile devices, like phones.
+    const std::string kMobileStr = " Mobile";
+    EXPECT_EQ(kMobileStr,
+              product_str.substr(product_str.size() - kMobileStr.size()));
+  }
+}
+
+}  // namespace
+
+
+namespace chrome_common {
+
+TEST(ChromeContentClientTest, Basic) {
+#if !defined(OS_ANDROID)
+  CheckUserAgentStringOrdering(false);
+#else
+  const char* kArguments[] = {"chrome"};
+  CommandLine::Reset();
+  CommandLine::Init(1, kArguments);
+  CommandLine* command_line = CommandLine::ForCurrentProcess();
+
+  // Do it for regular devices.
+  ASSERT_FALSE(command_line->HasSwitch(switches::kUseMobileUserAgent));
+  CheckUserAgentStringOrdering(false);
+
+  // Do it for mobile devices.
+  command_line->AppendSwitch(switches::kUseMobileUserAgent);
+  ASSERT_TRUE(command_line->HasSwitch(switches::kUseMobileUserAgent));
+  CheckUserAgentStringOrdering(true);
+#endif
+}
+
+}  // namespace chrome_common
diff --git a/chrome/common/chrome_notification_types.h b/chrome/common/chrome_notification_types.h
new file mode 100644
index 0000000..55988a9
--- /dev/null
+++ b/chrome/common/chrome_notification_types.h
@@ -0,0 +1,1273 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_CHROME_NOTIFICATION_TYPES_H_
+#define CHROME_COMMON_CHROME_NOTIFICATION_TYPES_H_
+
+#include "build/build_config.h"
+#include "content/public/browser/notification_types.h"
+
+namespace chrome {
+
+enum NotificationType {
+  NOTIFICATION_CHROME_START = content::NOTIFICATION_CONTENT_END,
+
+  // Browser-window ----------------------------------------------------------
+
+  // This message is sent after a window has been opened.  The source is a
+  // Source<Browser> containing the affected Browser.  No details are
+  // expected.
+  NOTIFICATION_BROWSER_OPENED = NOTIFICATION_CHROME_START,
+
+  // This message is sent soon after BROWSER_OPENED, and indicates that
+  // the Browser's |window_| is now non-NULL. The source is a Source<Browser>
+  // containing the affected Browser.  No details are expected.
+  NOTIFICATION_BROWSER_WINDOW_READY,
+
+  // This message is sent when a browser is closing. The source is a
+  // Source<Browser> containing the affected Browser. No details are expected.
+  // This is sent prior to BROWSER_CLOSED, and may be sent more than once for a
+  // particular browser.
+  NOTIFICATION_BROWSER_CLOSING,
+
+  // This message is sent after a window has been closed.  The source is a
+  // Source<Browser> containing the affected Browser.  No details are exptected.
+  NOTIFICATION_BROWSER_CLOSED,
+
+  // This message is sent when closing a browser has been cancelled, either by
+  // the user cancelling a beforeunload dialog, or IsClosingPermitted()
+  // disallowing closing. This notification implies that no BROWSER_CLOSING or
+  // BROWSER_CLOSED notification will be sent.
+  // The source is a Source<Browser> containing the affected browser. No details
+  // are expected.
+  NOTIFICATION_BROWSER_CLOSE_CANCELLED,
+
+  // Indicates that a top window has been closed.  The source is the HWND
+  // that was closed, no details are expected.
+  NOTIFICATION_WINDOW_CLOSED,
+
+#if defined(OS_LINUX)
+  // On Linux maximize can be an asynchronous operation. This notification
+  // indicates that the window has been maximized. The source is
+  // a Source<BrowserWindow> containing the BrowserWindow that was maximized.
+  // No details are expected.
+  NOTIFICATION_BROWSER_WINDOW_MAXIMIZED,
+#endif  // defined(OS_LINUX)
+
+  // Sent when the language (English, French...) for a page has been detected.
+  // The details Details<std::string> contain the ISO 639-1 language code and
+  // the source is Source<WebContents>.
+  NOTIFICATION_TAB_LANGUAGE_DETERMINED,
+
+  // Sent when a page has been translated. The source is the tab for that page
+  // (Source<WebContents>) and the details are the language the page was
+  // originally in and the language it was translated to
+  // (std::pair<std::string, std::string>).
+  NOTIFICATION_PAGE_TRANSLATED,
+
+  // Sent after the renderer returns a snapshot of tab contents.
+  // The source (Source<content::WebContents>) is the RenderViewHost for which
+  // the snapshot was generated and the details (Details<const SkBitmap>) is
+  // the actual snapshot.
+  NOTIFICATION_TAB_SNAPSHOT_TAKEN,
+
+  // The user has changed the browser theme. The source is a
+  // Source<ThemeService>. There are no details.
+  NOTIFICATION_BROWSER_THEME_CHANGED,
+
+  // Sent when the renderer returns focus to the browser, as part of focus
+  // traversal. The source is the browser, there are no details.
+  NOTIFICATION_FOCUS_RETURNED_TO_BROWSER,
+
+  // A new tab is created from an existing tab to serve as a target of a
+  // navigation that is about to happen. The source will be a Source<Profile>
+  // corresponding to the profile in which the new tab will live.  Details in
+  // the form of a RetargetingDetails object are provided.
+  NOTIFICATION_RETARGETING,
+
+  // Application-wide ----------------------------------------------------------
+
+  // This message is sent when the application is terminating (the last
+  // browser window has shutdown as part of an explicit user-initiated exit,
+  // or the user closed the last browser window on Windows/Linux and there are
+  // no BackgroundContents keeping the browser running). No source or details
+  // are passed.
+  NOTIFICATION_APP_TERMINATING,
+
+#if defined(OS_MACOSX)
+  // This notification is sent when the app has no key window, such as when
+  // all windows are closed but the app is still active. No source or details
+  // are provided.
+  NOTIFICATION_NO_KEY_WINDOW,
+#endif
+
+  // This is sent when the user has chosen to exit the app, but before any
+  // browsers have closed. This is sent if the user chooses to exit (via exit
+  // menu item or keyboard shortcut) or to restart the process (such as in flags
+  // page), not if Chrome exits by some other means (such as the user closing
+  // the last window). No source or details are passed.
+  //
+  // Note that receiving this notification does not necessarily mean the process
+  // will exit because the shutdown process can be cancelled by an unload
+  // handler.  Use APP_TERMINATING for such needs.
+  NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST,
+
+  // Application-modal dialogs -----------------------------------------------
+
+  // Sent after an application-modal dialog has been shown. The source
+  // is the dialog.
+  NOTIFICATION_APP_MODAL_DIALOG_SHOWN,
+
+  // This message is sent when a new InfoBar has been added to a
+  // InfoBarTabHelper.  The source is a Source<InfoBarTabHelper> with a
+  // pointer to the InfoBarTabHelper the InfoBar was added to.  The details
+  // is a Details<InfoBarDelegate> with a pointer to the delegate that was
+  // added.
+  NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED,
+
+  // This message is sent when an InfoBar is about to be removed from a
+  // InfoBarTabHelper.  The source is a Source<InfoBarTabHelper> with a
+  // pointer to the InfoBarTabHelper the InfoBar was removed from.  The
+  // details is a Details<std::pair<InfoBarDelegate*, bool> > with a pointer
+  // to the removed delegate and whether the removal should be animated.
+  NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
+
+  // This message is sent when an InfoBar is replacing another infobar in a
+  // InfoBarTabHelper.  The source is a Source<InfoBarTabHelper> with a
+  // pointer to the InfoBarTabHelper the InfoBar was removed from.  The
+  // details is a Details<std::pair<InfoBarDelegate*, InfoBarDelegate*> > with
+  // pointers to the old and new delegates, respectively.
+  NOTIFICATION_TAB_CONTENTS_INFOBAR_REPLACED,
+
+  // This is sent when an externally hosted tab is closed.  No details are
+  // expected.
+  NOTIFICATION_EXTERNAL_TAB_CLOSED,
+
+  // Indicates that the new page tab has finished loading. This is used for
+  // performance testing to see how fast we can load it after startup, and is
+  // only called once for the lifetime of the browser. The source is unused.
+  // Details is an integer: the number of milliseconds elapsed between
+  // starting and finishing all painting.
+  NOTIFICATION_INITIAL_NEW_TAB_UI_LOAD,
+
+#if defined(OS_ANDROID)
+  // Indicates that the new tab page is ready.  This is different than
+  // NOTIFICATION_INITIAL_NEW_TAB_UI_LOAD as the NTP might do some more in-page
+  // navigations after it's done loading, potentially causing flakyness in tests
+  // that would navigate as soon as the NTP is done loading.
+  // When this notification happen, it guarantees the page is not going to do
+  // any further navigation.
+  // The source is the WebContents containing the NTP.
+  NOTIFICATION_NEW_TAB_READY,
+#endif
+
+  // Used to fire notifications about how long various events took to
+  // complete.  E.g., this is used to get more fine grained timings from the
+  // new tab page.  The source is a WebContents and the details is a
+  // MetricEventDurationDetails.
+  NOTIFICATION_METRIC_EVENT_DURATION,
+
+  // This notification is sent when extensions::TabHelper::SetExtensionApp is
+  // invoked. The source is the extensions::TabHelper SetExtensionApp was
+  // invoked on.
+  NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
+
+  // Notification posted when the element that is focused and currently accepts
+  // keyboard input inside the webpage has been touched.  The source is the
+  // RenderViewHost and the details are not used.
+  NOTIFICATION_FOCUSED_EDITABLE_NODE_TOUCHED,
+
+  // Tabs --------------------------------------------------------------------
+
+  // Sent when a tab is added to a WebContentsDelegate. The source is the
+  // WebContentsDelegate and the details is the added WebContents.
+  NOTIFICATION_TAB_ADDED,
+
+  // This notification is sent after a tab has been appended to the tab_strip.
+  // The source is a Source<WebContents> of the tab being added. There
+  // are no details.
+  NOTIFICATION_TAB_PARENTED,
+
+  // This message is sent before a tab has been closed.  The source is a
+  // Source<NavigationController> with a pointer to the controller for the
+  // closed tab.  No details are expected.
+  //
+  // See also content::NOTIFICATION_WEB_CONTENTS_DESTROYED, which is sent when
+  // the WebContents containing the NavigationController is destroyed.
+  NOTIFICATION_TAB_CLOSING,
+
+  // Stuff inside the tabs ---------------------------------------------------
+
+  // Sent when the bookmark bubble hides. The source is the profile, the
+  // details unused.
+  NOTIFICATION_BOOKMARK_BUBBLE_HIDDEN,
+
+  // This notification is sent when the result of a find-in-page search is
+  // available with the browser process. The source is a Source<WebContents>.
+  // Details encompass a FindNotificationDetail object that tells whether the
+  // match was found or not found.
+  NOTIFICATION_FIND_RESULT_AVAILABLE,
+
+#if defined(OS_ANDROID)
+  // This notification is sent when the match rects of a find-in-page search
+  // are available. The source is a Source<WebContents>. Details encompass a
+  // FindMatchRectsDetails object that contains the result version and the
+  // rects information.
+  NOTIFICATION_FIND_MATCH_RECTS_AVAILABLE,
+#endif
+
+  // BackgroundContents ------------------------------------------------------
+
+  // A new background contents was opened by script. The source is the parent
+  // profile and the details are BackgroundContentsOpenedDetails.
+  NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
+
+  // The background contents navigated to a new location. The source is the
+  // parent Profile, and the details are the BackgroundContents that was
+  // navigated.
+  NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED,
+
+  // The background contents were closed by someone invoking window.close()
+  // or the parent application was uninstalled.
+  // The source is the parent profile, and the details are the
+  // BackgroundContents.
+  NOTIFICATION_BACKGROUND_CONTENTS_CLOSED,
+
+  // The background contents is being deleted. The source is the
+  // parent Profile, and the details are the BackgroundContents being deleted.
+  NOTIFICATION_BACKGROUND_CONTENTS_DELETED,
+
+  // The background contents has crashed. The source is the parent Profile,
+  // and the details are the BackgroundContents.
+  NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED,
+
+  // The background contents associated with a hosted app has changed (either
+  // a new background contents has been created, or an existing background
+  // contents has closed). The source is the parent Profile, and the details
+  // are the BackgroundContentsService.
+  NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED,
+
+  // Chrome has entered/exited background mode. The source is the
+  // BackgroundModeManager and the details are a boolean value which is set to
+  // true if Chrome is now in background mode.
+  NOTIFICATION_BACKGROUND_MODE_CHANGED,
+
+  // This is sent when a login prompt is shown.  The source is the
+  // Source<NavigationController> for the tab in which the prompt is shown.
+  // Details are a LoginNotificationDetails which provide the LoginHandler
+  // that should be given authentication.
+  NOTIFICATION_AUTH_NEEDED,
+
+  // This is sent when authentication credentials have been supplied (either
+  // by the user or by an automation service), but before we've actually
+  // received another response from the server.  The source is the
+  // Source<NavigationController> for the tab in which the prompt was shown.
+  // Details are an AuthSuppliedLoginNotificationDetails which provide the
+  // LoginHandler that should be given authentication as well as the supplied
+  // username and password.
+  NOTIFICATION_AUTH_SUPPLIED,
+
+  // This is sent when an authentication request has been dismissed without
+  // supplying credentials (either by the user or by an automation service).
+  // The source is the Source<NavigationController> for the tab in which the
+  // prompt was shown. Details are a LoginNotificationDetails which provide
+  // the LoginHandler that should be cancelled.
+  NOTIFICATION_AUTH_CANCELLED,
+
+  // History -----------------------------------------------------------------
+
+  // Sent when a history service has finished loading. The source is the
+  // profile that the history service belongs to, and the details is the
+  // HistoryService.
+  NOTIFICATION_HISTORY_LOADED,
+
+  // Sent when a URL has been added or modified. This is used by the in-memory
+  // URL database and the InMemoryURLIndex (both used by autocomplete) to track
+  // changes to the main history system.
+  //
+  // The source is the profile owning the history service that changed, and
+  // the details is history::URLsModifiedDetails that lists the modified or
+  // added URLs.
+  NOTIFICATION_HISTORY_URLS_MODIFIED,
+
+  // Sent when the user visits a URL.
+  //
+  // The source is the profile owning the history service that changed, and
+  // the details is history::URLVisitedDetails.
+  NOTIFICATION_HISTORY_URL_VISITED,
+
+  // Sent when one or more URLs are deleted.
+  //
+  // The source is the profile owning the history service that changed, and
+  // the details is history::URLsDeletedDetails that lists the deleted URLs.
+  NOTIFICATION_HISTORY_URLS_DELETED,
+
+  // Sent when a keyword search term is updated. The source is the Profile and
+  // the details are history::KeywordSearchTermDetails
+  NOTIFICATION_HISTORY_KEYWORD_SEARCH_TERM_UPDATED,
+
+  // Sent by history when the favicon of a URL changes.  The source is the
+  // profile, and the details is history::FaviconChangeDetails (see
+  // history_notifications.h).
+  NOTIFICATION_FAVICON_CHANGED,
+
+  // Sent by FaviconTabHelper when a tab's favicon has been successfully
+  // updated.
+  NOTIFICATION_FAVICON_UPDATED,
+
+  // Profiles -----------------------------------------------------------------
+
+  // Sent after a Profile has been created. This notification is sent both for
+  // normal and OTR profiles.
+  // The details are none and the source is the new profile.
+  NOTIFICATION_PROFILE_CREATED,
+
+  // Sent after a Profile has been added to ProfileManager.
+  // The details are none and the source is the new profile.
+  NOTIFICATION_PROFILE_ADDED,
+
+  // Sent before a Profile is destroyed. This notification is sent both for
+  // normal and OTR profiles.
+  // The details are none and the source is a Profile*.
+  NOTIFICATION_PROFILE_DESTROYED,
+
+  // Sent after the URLRequestContextGetter for a Profile has been initialized.
+  // The details are none and the source is a Profile*.
+  NOTIFICATION_PROFILE_URL_REQUEST_CONTEXT_GETTER_INITIALIZED,
+
+  // TopSites ----------------------------------------------------------------
+
+  // Sent by TopSites when it finishes loading. The source is the profile the
+  // details the TopSites.
+  NOTIFICATION_TOP_SITES_LOADED,
+
+  // Sent by TopSites when it has finished updating its most visited URLs
+  // cache after querying the history service. The source is the TopSites and
+  // the details a CancelableRequestProvider::Handle from the history service
+  // query.
+  // Used only in testing.
+  NOTIFICATION_TOP_SITES_UPDATED,
+
+  // Sent by TopSites when the either one of the most visited urls changed, or
+  // one of the images changes. The source is the TopSites, the details not
+  // used.
+  NOTIFICATION_TOP_SITES_CHANGED,
+
+  // Bookmarks ---------------------------------------------------------------
+
+  // Sent when the starred state of a URL changes. A URL is starred if there
+  // is at least one bookmark for it. The source is a Profile and the details
+  // is history::URLsStarredDetails that contains the list of URLs and
+  // whether they were starred or unstarred.
+  NOTIFICATION_URLS_STARRED,
+
+  // Sent when the bookmark bar model finishes loading. This source is the
+  // Profile, and the details aren't used.
+  NOTIFICATION_BOOKMARK_MODEL_LOADED,
+
+  // Sent when the bookmark bubble is shown for a particular URL. The source
+  // is the profile, the details the URL.
+  NOTIFICATION_BOOKMARK_BUBBLE_SHOWN,
+
+  // Task Manager ------------------------------------------------------------
+
+  // Sent when WebUI TaskManager opens and is ready for showing tasks.
+  NOTIFICATION_TASK_MANAGER_WINDOW_READY,
+
+  // The TaskManagerChildProcessResourceProvider collects the list of child
+  // processes when StartUpdating is called. This data is collected on the IO
+  // thread and passed back to the UI thread. Once all entries are added to the
+  // task manager, this notification is sent.
+  NOTIFICATION_TASK_MANAGER_CHILD_PROCESSES_DATA_READY,
+
+  // Sent when a renderer process is notified of new v8 heap statistics. The
+  // source is the ID of the renderer process, and the details are a
+  // V8HeapStatsDetails object.
+  NOTIFICATION_RENDERER_V8_HEAP_STATS_COMPUTED,
+
+  // Sent when a renderer process is notified of a new FPS value. The source
+  // is the ID of the renderer process, and the details are an FPSDetails
+  // object.
+  NOTIFICATION_RENDERER_FPS_COMPUTED,
+
+  // Non-history storage services --------------------------------------------
+
+  // Notification that the TemplateURLService has finished loading from the
+  // database. The source is the TemplateURLService, and the details are
+  // NoDetails.
+  NOTIFICATION_TEMPLATE_URL_SERVICE_LOADED,
+
+  // Sent when a TemplateURL is removed from the model. The source is the
+  // Profile, and the details the id of the TemplateURL being removed.
+  NOTIFICATION_TEMPLATE_URL_REMOVED,
+
+  // Sent when the prefs relating to the default search engine have changed due
+  // to policy.  Source and details are unused.
+  NOTIFICATION_DEFAULT_SEARCH_POLICY_CHANGED,
+
+  // The state of a web resource has been changed. A resource may have been
+  // added, removed, or altered. Source is WebResourceService, and the
+  // details are NoDetails.
+  NOTIFICATION_PROMO_RESOURCE_STATE_CHANGED,
+
+  // A safe browsing database update completed.  Source is the
+  // SafeBrowsingService and the details are a bool indicating whether the
+  // update was successful.
+  NOTIFICATION_SAFE_BROWSING_UPDATE_COMPLETE,
+
+  // Autocomplete ------------------------------------------------------------
+
+  // Sent by the autocomplete controller when done.  The source is the
+  // AutocompleteController, the details not used.
+  NOTIFICATION_AUTOCOMPLETE_CONTROLLER_RESULT_READY,
+
+  // This is sent when an item of the Omnibox popup is selected. The source
+  // is the profile.
+  NOTIFICATION_OMNIBOX_OPENED_URL,
+
+  // Sent when the Google URL for a profile has been updated.  Some services
+  // cache this value and need to update themselves when it changes.  See
+  // google_util::GetGoogleURLAndUpdateIfNecessary().  The source is the
+  // Profile, the details a GoogleURLTracker::UpdatedDetails containing the old
+  // and new URLs.
+  //
+  // Note that because incognito mode requests for the GoogleURLTracker are
+  // redirected to the non-incognito profile's copy, this notification will only
+  // ever fire on non-incognito profiles; thus listeners should use
+  // GetOriginalProfile() when constructing a Source to filter against.
+  NOTIFICATION_GOOGLE_URL_UPDATED,
+
+  // Printing ----------------------------------------------------------------
+
+  // Notification from PrintJob that an event occurred. It can be that a page
+  // finished printing or that the print job failed. Details is
+  // PrintJob::EventDetails. Source is a PrintJob.
+  NOTIFICATION_PRINT_JOB_EVENT,
+
+  // Sent when a PrintJob has been released.
+  // Source is the WebContents that holds the print job.
+  NOTIFICATION_PRINT_JOB_RELEASED,
+
+  // Shutdown ----------------------------------------------------------------
+
+  // Sent when WM_ENDSESSION has been received, after the browsers have been
+  // closed but before browser process has been shutdown. The source/details
+  // are all source and no details.
+  NOTIFICATION_SESSION_END,
+
+  // User Scripts ------------------------------------------------------------
+
+  // Sent when there are new user scripts available.  The details are a
+  // pointer to SharedMemory containing the new scripts.
+  NOTIFICATION_USER_SCRIPTS_UPDATED,
+
+  // User Style Sheet --------------------------------------------------------
+
+  // Sent when the user style sheet has changed.
+  NOTIFICATION_USER_STYLE_SHEET_UPDATED,
+
+  // Extensions --------------------------------------------------------------
+
+  // Sent when a CrxInstaller finishes. Source is the CrxInstaller that
+  // finished. The details are the extension which was installed.
+  NOTIFICATION_CRX_INSTALLER_DONE,
+
+  // Sent when the known installed extensions have all been loaded.  In
+  // testing scenarios this can happen multiple times if extensions are
+  // unloaded and reloaded. The source is a Profile.
+  NOTIFICATION_EXTENSIONS_READY,
+
+  // Sent when an extension icon being displayed in the location bar is updated.
+  // The source is the Profile and the details are the WebContents for
+  // the tab.
+  NOTIFICATION_EXTENSION_LOCATION_BAR_UPDATED,
+
+  // Sent when a new extension is loaded. The details are an Extension, and
+  // the source is a Profile.
+  NOTIFICATION_EXTENSION_LOADED,
+
+  // An error occured while attempting to load an extension. The details are a
+  // string with details about why the load failed.
+  NOTIFICATION_EXTENSION_LOAD_ERROR,
+
+  // Sent when an extension is enabled. Under most circumstances, listeners
+  // will want to use NOTIFICATION_EXTENSION_LOADED. This notification is only
+  // fired when the "Enable" button is hit in the extensions tab.  The details
+  // are an Extension, and the source is a Profile.
+  NOTIFICATION_EXTENSION_ENABLED,
+
+  // Sent when attempting to load a new extension, but they are disabled. The
+  // details are an Extension*, and the source is a Profile*.
+  NOTIFICATION_EXTENSION_UPDATE_DISABLED,
+
+  // Sent when an extension's permissions change. The details are an
+  // UpdatedExtensionPermissionsInfo, and the source is a Profile.
+  NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED,
+
+  // Sent when an extension install turns out to not be a theme.
+  NOTIFICATION_NO_THEME_DETECTED,
+
+  // Sent when new extensions are installed. The details are an Extension, and
+  // the source is a Profile.
+  NOTIFICATION_EXTENSION_INSTALLED,
+
+  // An error occured during extension install. The details are a string with
+  // details about why the install failed.
+  NOTIFICATION_EXTENSION_INSTALL_ERROR,
+
+  // Sent when an extension install is not allowed, as indicated by
+  // PendingExtensionInfo::ShouldAllowInstall. The details are an Extension,
+  // and the source is a Profile.
+  NOTIFICATION_EXTENSION_INSTALL_NOT_ALLOWED,
+
+  // Sent when an extension has been uninstalled. The details are an Extension,
+  // and the source is a Profile.
+  NOTIFICATION_EXTENSION_UNINSTALLED,
+
+  // Sent when an extension uninstall is not allowed because the extension is
+  // not user manageable.  The details are an Extension, and the source is a
+  // Profile.
+  NOTIFICATION_EXTENSION_UNINSTALL_NOT_ALLOWED,
+
+  // Sent when an extension is unloaded. This happens when an extension is
+  // uninstalled or disabled. The details are an UnloadedExtensionInfo, and
+  // the source is a Profile.
+  //
+  // Note that when this notification is sent, ExtensionService has already
+  // removed the extension from its internal state.
+  NOTIFICATION_EXTENSION_UNLOADED,
+
+  // Sent after a new ExtensionHost is created. The details are
+  // an ExtensionHost* and the source is a Profile*.
+  NOTIFICATION_EXTENSION_HOST_CREATED,
+
+  // Sent before an ExtensionHost is destroyed. The details are
+  // an ExtensionHost* and the source is a Profile*.
+  NOTIFICATION_EXTENSION_HOST_DESTROYED,
+
+  // Sent by an ExtensionHost when it has finished its initial page load,
+  // including any external resources.
+  // The details are an ExtensionHost* and the source is a Profile*.
+  NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING,
+
+  // Sent by an ExtensionHost when its render view requests closing through
+  // window.close(). The details are an ExtensionHost* and the source is a
+  // Profile*.
+  NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
+
+  // Sent when extension render process ends (whether it crashes or closes).
+  // The details are an ExtensionHost* and the source is a Profile*. Not sent
+  // during browser shutdown.
+  NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
+
+  // Sent when a background page is ready so other components can load.
+  NOTIFICATION_EXTENSION_BACKGROUND_PAGE_READY,
+
+  // Sent when a pop-up extension view is ready, so that notification may
+  // be sent to pending callbacks.  Note that this notification is sent
+  // after all onload callbacks have been invoked in the main frame.
+  // The details is the ExtensionHost* hosted within the popup, and the source
+  // is a Profile*.
+  NOTIFICATION_EXTENSION_POPUP_VIEW_READY,
+
+  // Sent when a browser action's state has changed. The source is the
+  // ExtensionAction* that changed.  The details are the Profile* that the
+  // browser action belongs to.
+  NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
+
+  // Sent when the count of page actions has changed. Note that some of them
+  // may not apply to the current page. The source is a LocationBar*. There
+  // are no details.
+  NOTIFICATION_EXTENSION_PAGE_ACTION_COUNT_CHANGED,
+
+  // Sent when a browser action's visibility has changed. The source is the
+  // ExtensionPrefs* that changed. The details are a Extension*.
+  NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED,
+
+  // Sent when a page action's visibility has changed. The source is the
+  // ExtensionAction* that changed. The details are a WebContents*.
+  NOTIFICATION_EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED,
+
+  // Sent when an extension command has been removed. The source is the profile
+  // and the details is a std::pair of two std::string objects (an extension ID
+  // and the name of the command being removed).
+  NOTIFICATION_EXTENSION_COMMAND_REMOVED,
+
+  // Sent when an extension command has been added. The source is the profile
+  // and the details is a std::pair of two std::string objects (an extension ID
+  // and the name of the command being added).
+  NOTIFICATION_EXTENSION_COMMAND_ADDED,
+
+  // Sent when an extension command shortcut for a browser action is activated
+  // on Mac. The source is the profile and the details is a std::string
+  // containing an extension ID.
+  NOTIFICATION_EXTENSION_COMMAND_BROWSER_ACTION_MAC,
+
+  // Sent when an extension command shortcut for a page action is activated
+  // on Mac. The source is the profile and the details is a std::string
+  // containing an extension ID.
+  NOTIFICATION_EXTENSION_COMMAND_PAGE_ACTION_MAC,
+
+  // Sent when an extension command shortcut for a script badge is activated
+  // on Mac. The source is the profile and the details is a std::string
+  // containing an extension ID.
+  NOTIFICATION_EXTENSION_COMMAND_SCRIPT_BADGE_MAC,
+
+  // A new extension RenderViewHost has been registered. The details are
+  // the RenderViewHost*.
+  NOTIFICATION_EXTENSION_VIEW_REGISTERED,
+
+  // An extension RenderViewHost has been unregistered. The details are
+  // the RenderViewHost*.
+  NOTIFICATION_EXTENSION_VIEW_UNREGISTERED,
+
+  // Sent by an extension to notify the browser about the results of a unit
+  // test.
+  NOTIFICATION_EXTENSION_TEST_PASSED,
+  NOTIFICATION_EXTENSION_TEST_FAILED,
+
+  // Sent by extension test javascript code, typically in a browser test. The
+  // sender is a std::string representing the extension id, and the details
+  // are a std::string with some message. This is particularly useful when you
+  // want to have C++ code wait for javascript code to do something.
+  NOTIFICATION_EXTENSION_TEST_MESSAGE,
+
+  // Sent when an bookmarks extensions API function was successfully invoked.
+  // The source is the id of the extension that invoked the function, and the
+  // details are a pointer to the const BookmarksFunction in question.
+  NOTIFICATION_EXTENSION_BOOKMARKS_API_INVOKED,
+
+  // Sent when a downloads extensions API event is fired. The source is an
+  // ExtensionDownloadsEventRouter::NotificationSource, and the details is a
+  // std::string containing json. Used for testing.
+  NOTIFICATION_EXTENSION_DOWNLOADS_EVENT,
+
+  // Sent when an omnibox extension has sent back omnibox suggestions. The
+  // source is the profile, and the details are an ExtensionOmniboxSuggestions
+  // object.
+  NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY,
+
+  // Sent when the user accepts the input in an extension omnibox keyword
+  // session. The source is the profile.
+  NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED,
+
+  // Sent when an omnibox extension has updated the default suggestion. The
+  // source is the profile.
+  NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED,
+
+  // Sent when a recording session for speech input has started.
+  NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STARTED,
+
+  // Sent when a recording session for speech input has stopped.
+  NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STOPPED,
+
+  // Sent when a recording session for speech input has failed.
+  NOTIFICATION_EXTENSION_SPEECH_INPUT_FAILED,
+
+  // Sent when the extension updater starts checking for updates to installed
+  // extensions. The source is a Profile, and there are no details.
+  NOTIFICATION_EXTENSION_UPDATING_STARTED,
+
+  // Sent when the extension updater is finished checking for updates to
+  // installed extensions. The source is a Profile, and there are no details.
+  // NOTE: It's possible that there are extension updates still being
+  // installed by the extension service at the time this notification fires.
+  NOTIFICATION_EXTENSION_UPDATING_FINISHED,
+
+  // The extension updater found an update and will attempt to download and
+  // install it. The source is a Profile, and the details are an extension id
+  // (const std::string).
+  NOTIFICATION_EXTENSION_UPDATE_FOUND,
+
+  // Sent when one or more extensions changed their warning status (like
+  // slowing down Chrome or conflicting with each other).
+  // The source is a Profile.
+  NOTIFICATION_EXTENSION_WARNING_CHANGED,
+
+  // An installed app changed notification state (added or removed
+  // notifications). The source is a Profile, and the details are a string
+  // with the extension id of the app.
+  NOTIFICATION_APP_NOTIFICATION_STATE_CHANGED,
+
+  // Finished loading app notification manager.
+  // The source is AppNotificationManager, and the details are NoDetails.
+  NOTIFICATION_APP_NOTIFICATION_MANAGER_LOADED,
+
+  // Component Updater -------------------------------------------------------
+
+  // Sent when the component updater starts doing update checks. If no
+  // component has been registered for update this notification is not
+  // generated. The source is the component updater itself and there are
+  // no details.
+  NOTIFICATION_COMPONENT_UPDATER_STARTED,
+
+  // Sent when the component updater is going to take a long nap. The
+  // source is the component updater itself and there are no details.
+  NOTIFICATION_COMPONENT_UPDATER_SLEEPING,
+
+  // Sent when there is a new version of a registered component. After
+  // the notification is send the component will be downloaded. The source
+  // is the id of the component and there are no details.
+  NOTIFICATION_COMPONENT_UPDATE_FOUND,
+
+  // Send when the new component has been downloaded and an installation
+  // or upgrade is about to be attempted. The source is the id of the
+  // component and there are no details.
+  NOTIFICATION_COMPONENT_UPDATE_READY,
+
+  // Desktop Notifications ---------------------------------------------------
+
+  // This notification is sent when a balloon is connected to a renderer
+  // process to render the balloon contents.  The source is a
+  // Source<BalloonHost> with a pointer to the the balloon.  A
+  // NOTIFY_BALLOON_DISCONNECTED is guaranteed before the source pointer
+  // becomes junk. No details expected.
+  NOTIFICATION_NOTIFY_BALLOON_CONNECTED,
+
+  // This message is sent after a balloon is disconnected from the renderer
+  // process. The source is a Source<BalloonHost> with a pointer to the
+  // balloon host (the pointer is usable). No details are expected.
+  NOTIFICATION_NOTIFY_BALLOON_DISCONNECTED,
+
+  // Web Database Service ----------------------------------------------------
+
+  // This notification is sent whenever autofill entries are
+  // changed.  The detail of this notification is a list of changes
+  // represented by a vector of AutofillChange.  Each change
+  // includes a change type (add, update, or remove) as well as the
+  // key of the entry that was affected.
+  NOTIFICATION_AUTOFILL_ENTRIES_CHANGED,
+
+  // Sent when an AutofillProfile has been added/removed/updated in the
+  // WebDatabase.  The detail is an AutofillProfileChange.
+  NOTIFICATION_AUTOFILL_PROFILE_CHANGED,
+
+  // Sent when an Autofill CreditCard has been added/removed/updated in the
+  // WebDatabase.  The detail is an AutofillCreditCardChange.
+  NOTIFICATION_AUTOFILL_CREDIT_CARD_CHANGED,
+
+  // Sent when multiple Autofill entries have been modified by Sync.
+  // The source is the WebDataService in use by Sync.  No details are specified.
+  NOTIFICATION_AUTOFILL_MULTIPLE_CHANGED,
+
+  // This notification is sent whenever the web database service has finished
+  // loading the web database.  No details are expected.
+  NOTIFICATION_WEB_DATABASE_LOADED,
+
+  // Upgrade notifications ---------------------------------------------------
+
+  // Sent when Chrome believes an update has been installed and available for
+  // long enough with the user shutting down to let it take effect. See
+  // upgrade_detector.cc for details on how long it waits. No details are
+  // expected.
+  NOTIFICATION_UPGRADE_RECOMMENDED,
+
+  // Sent when a critical update has been installed. No details are expected.
+  NOTIFICATION_CRITICAL_UPGRADE_INSTALLED,
+
+  // Software incompatibility notifications ----------------------------------
+
+  // Sent when Chrome has finished compiling the list of loaded modules (and
+  // other modules of interest). No details are expected.
+  NOTIFICATION_MODULE_LIST_ENUMERATED,
+
+  // Sent when Chrome is done scanning the module list and when the user has
+  // acknowledged the module incompatibility. No details are expected.
+  NOTIFICATION_MODULE_INCOMPATIBILITY_BADGE_CHANGE,
+
+  // Accessibility Notifications ---------------------------------------------
+
+  // Notification that a window in the browser UI (not the web content)
+  // was opened, for propagating to an accessibility extension.
+  // Details will be an AccessibilityWindowInfo.
+  NOTIFICATION_ACCESSIBILITY_WINDOW_OPENED,
+
+  // Notification that a window in the browser UI was closed.
+  // Details will be an AccessibilityWindowInfo.
+  NOTIFICATION_ACCESSIBILITY_WINDOW_CLOSED,
+
+  // Notification that a control in the browser UI was focused.
+  // Details will be an AccessibilityControlInfo.
+  NOTIFICATION_ACCESSIBILITY_CONTROL_FOCUSED,
+
+  // Notification that a control in the browser UI had its action taken,
+  // like pressing a button or toggling a checkbox.
+  // Details will be an AccessibilityControlInfo.
+  NOTIFICATION_ACCESSIBILITY_CONTROL_ACTION,
+
+  // Notification that text box in the browser UI had text change.
+  // Details will be an AccessibilityControlInfo.
+  NOTIFICATION_ACCESSIBILITY_TEXT_CHANGED,
+
+  // Notification that a pop-down menu was opened, for propagating
+  // to an accessibility extension.
+  // Details will be an AccessibilityMenuInfo.
+  NOTIFICATION_ACCESSIBILITY_MENU_OPENED,
+
+  // Notification that a pop-down menu was closed, for propagating
+  // to an accessibility extension.
+  // Details will be an AccessibilityMenuInfo.
+  NOTIFICATION_ACCESSIBILITY_MENU_CLOSED,
+
+  // Content Settings --------------------------------------------------------
+
+  // Sent when content settings change. The source is a HostContentSettings
+  // object, the details are ContentSettingsNotificationsDetails.
+  NOTIFICATION_CONTENT_SETTINGS_CHANGED,
+
+  // Sent when the collect cookies dialog is shown. The source is a
+  // TabSpecificContentSettings object, there are no details.
+  NOTIFICATION_COLLECTED_COOKIES_SHOWN,
+
+  // Sent when a non-default setting in the the notification content settings
+  // map has changed. The source is the DesktopNotificationService, the
+  // details are None.
+  NOTIFICATION_DESKTOP_NOTIFICATION_SETTINGS_CHANGED,
+
+  // Sent when content settings change for a tab. The source is a
+  // content::WebContents object, the details are None.
+  NOTIFICATION_WEB_CONTENT_SETTINGS_CHANGED,
+
+  // Sync --------------------------------------------------------------------
+
+  // The sync service has finished the datatype configuration process. The
+  // source is the ProfileSyncService object of the Profile. There are no
+  // details.
+  NOTIFICATION_SYNC_CONFIGURE_DONE,
+
+  // The sync service has started the datatype configuration process. The source
+  // is the ProfileSyncService object of the Profile. There are no details.
+  NOTIFICATION_SYNC_CONFIGURE_START,
+
+  // A service is requesting a sync datatype refresh for the current profile.
+  // The details value is a const syncer::ModelTypePayloadMap.
+  // If the payload map is empty, it should be treated as an invalidation for
+  // all enabled types. This is used by session sync.
+  NOTIFICATION_SYNC_REFRESH_LOCAL,
+
+  // External notification requesting a sync datatype refresh for the current
+  // profile. The details value is a const syncer::ModelTypePayloadMap.
+  // If the payload map is empty, it should be treated as an invalidation for
+  // all enabled types. This is used for notifications on Android.
+  NOTIFICATION_SYNC_REFRESH_REMOTE,
+
+  // The session service has been saved.  This notification type is only sent
+  // if there were new SessionService commands to save, and not for no-op save
+  // operations.
+  NOTIFICATION_SESSION_SERVICE_SAVED,
+
+  // A foreign session has been updated.  If a new tab page is open, the
+  // foreign session handler needs to update the new tab page's foreign
+  // session data.
+  NOTIFICATION_FOREIGN_SESSION_UPDATED,
+
+  // Foreign sessions has been disabled. New tabs should not display foreign
+  // session data.
+  NOTIFICATION_FOREIGN_SESSION_DISABLED,
+
+  // All tab metadata has been loaded from disk asynchronously.
+  // Sent on the UI thread.
+  // The source is the Profile. There are no details.
+  NOTIFICATION_SESSION_RESTORE_COMPLETE,
+
+  // Cookies -----------------------------------------------------------------
+
+  // Sent when a cookie changes. The source is a Profile object, the details
+  // are a ChromeCookieDetails object.
+  NOTIFICATION_COOKIE_CHANGED,
+
+  // Token Service -----------------------------------------------------------
+
+  // When the token service has a new token available for a service, one of
+  // these notifications is issued per new token.
+  // The source is a TokenService on the Profile. The details are a
+  // TokenAvailableDetails object.
+  NOTIFICATION_TOKEN_AVAILABLE,
+
+  // When there aren't any additional tokens left to load, this notification
+  // is sent.
+  // The source is a TokenService on the profile. There are no details.
+  NOTIFICATION_TOKEN_LOADING_FINISHED,
+
+  // If a token request failed, one of these is issued per failed request.
+  // The source is a TokenService on the Profile. The details are a
+  // TokenRequestFailedDetails object.
+  NOTIFICATION_TOKEN_REQUEST_FAILED,
+
+  // When the token service receives updated credentials with which to generate
+  // new tokens, one of these notifications is issued.
+  // The source is a TokenService on the Profile. The details are a
+  // CredentialsUpdatedDetails object.
+  NOTIFICATION_TOKEN_SERVICE_CREDENTIALS_UPDATED,
+
+  // When a service has a new token they got from a frontend that the
+  // TokenService should know about, fire this notification. The source is the
+  // Profile. The details are a TokenAvailableDetails object.
+  NOTIFICATION_TOKEN_UPDATED,
+
+  // Fired when the TokenService has had all of its tokens removed (such as due
+  // to the user signing out). The source is the TokenService. There are no
+  // details.
+  NOTIFICATION_TOKENS_CLEARED,
+
+  // Sent when a user signs into Google services such as sync.
+  // The source is the Profile. The details are a
+  // GoogleServiceSigninSuccessDetails object.
+  NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL,
+
+  // Sent when a user fails to sign into Google services such as sync.
+  // The source is the Profile. The details are a GoogleServiceAuthError
+  // object.
+  NOTIFICATION_GOOGLE_SIGNIN_FAILED,
+
+  // Sent when the currently signed-in user for a user has been signed out.
+  // The source is the Profile. The details are a
+  // GoogleServiceSignoutDetails object.
+  NOTIFICATION_GOOGLE_SIGNED_OUT,
+
+  // Autofill Notifications --------------------------------------------------
+
+  // Sent when a popup with Autofill suggestions is shown in the renderer.
+  // The source is the corresponding RenderViewHost. There are not details.
+  NOTIFICATION_AUTOFILL_DID_SHOW_SUGGESTIONS,
+
+  // Sent when a form is previewed or filled with Autofill suggestions.
+  // The source is the corresponding RenderViewHost. There are not details.
+  NOTIFICATION_AUTOFILL_DID_FILL_FORM_DATA,
+
+  // Download Notifications --------------------------------------------------
+
+  // Sent when a download is initiated. It is possible that the download will
+  // not actually begin due to the DownloadRequestLimiter cancelling it
+  // prematurely.
+  // The source is the corresponding RenderViewHost. There are no details.
+  NOTIFICATION_DOWNLOAD_INITIATED,
+
+  // Misc --------------------------------------------------------------------
+
+  // Sent when PerformanceMonitor has finished all the initial steps of data
+  // collection and has begun passively observing. The source is the
+  // PerformanceMonitor*. No details are expected.
+  NOTIFICATION_PERFORMANCE_MONITOR_INITIALIZED,
+
+#if defined(OS_CHROMEOS)
+  // Sent when a chromium os user logs in.
+  NOTIFICATION_LOGIN_USER_CHANGED,
+
+  // Sent immediately after the logged-in user's profile is ready.
+  // The details are a Profile object.
+  NOTIFICATION_LOGIN_USER_PROFILE_PREPARED,
+
+  // Sent when the chromium session is first started. If this is a new user this
+  // will not be sent until a profile picture has been selected, unlike
+  // NOTIFICATION_LOGIN_USER_CHANGED which is sent immediately after the user
+  // has logged in. This will be sent again if the browser crashes and restarts.
+  NOTIFICATION_SESSION_STARTED,
+
+  // Sent when user image is updated.
+  NOTIFICATION_LOGIN_USER_IMAGE_CHANGED,
+
+  // Sent by UserManager when a profile image download has been completed.
+  NOTIFICATION_PROFILE_IMAGE_UPDATED,
+
+  // Sent by UserManager when profile image download has failed or user has the
+  // default profile image or no profile image at all. No details are expected.
+  NOTIFICATION_PROFILE_IMAGE_UPDATE_FAILED,
+
+  // Sent when a chromium os user attempts to log in.  The source is
+  // all and the details are AuthenticationNotificationDetails.
+  NOTIFICATION_LOGIN_AUTHENTICATION,
+
+  // Sent when webui lock screen is ready.
+  NOTIFICATION_LOCK_WEBUI_READY,
+
+  // Sent when webui lock screen wallpaper is loaded and displayed.
+  NOTIFICATION_LOCK_BACKGROUND_DISPLAYED,
+
+  // Sent when GAIA iframe has been loaded.
+  // First paint event after this fires NOTIFICATION_LOGIN_WEBUI_VISIBLE.
+  // Possible scenarios:
+  // 1. Boot into device that has user pods display disabled or no users.
+  //    Note that booting with network not connected would first generate
+  //    NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN.
+  // 2. From the user pods list, open "Add User" for the second time
+  //    (see below).
+  // TODO(nkostylev): Send this notification any time "Add User" is activated
+  //                  even if it has been silently preloaded on boot.
+  // Not sent on "silent preload" i.e. when booting into login screen
+  // with user pods, GAIA frame is silently preloaded in the background.
+  // Activating it ("Add User") for the first time would not generate this
+  // notification.
+  NOTIFICATION_LOGIN_WEBUI_LOADED,
+
+  // Sent when the login screen has loaded in retail mode. The first paint event
+  // after this fires NOTIFICATION_LOGIN_WEBUI_VISIBLE.
+  NOTIFICATION_DEMO_WEBUI_LOADED,
+
+  // Sent when the user images on the WebUI login screen have all been loaded.
+  // "Normal boot" i.e. for the device with at least one user would generate
+  // this one on boot.
+  // First paint event after this fires NOTIFICATION_LOGIN_WEBUI_VISIBLE.
+  NOTIFICATION_LOGIN_USER_IMAGES_LOADED,
+
+  // Sent when a network error message is displayed on the WebUI login screen.
+  // First paint event of this fires NOTIFICATION_LOGIN_WEBUI_VISIBLE.
+  NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN,
+
+  // Sent when the first OOBE screen has been displayed. Note that the screen
+  // may not be fully rendered at this point.
+  // First paint event after this fires NOTIFICATION_LOGIN_WEBUI_VISIBLE.
+  NOTIFICATION_WIZARD_FIRST_SCREEN_SHOWN,
+
+  // Sent when the EULA has been accepted in the first-run wizard. This is never
+  // sent if the EULA was already accepted at startup.
+  NOTIFICATION_WIZARD_EULA_ACCEPTED,
+
+  // Sent when the specific part of login WebUI is considered to be visible.
+  // That moment is tracked as the first paint event after one of the:
+  // 1. NOTIFICATION_LOGIN_USER_IMAGES_LOADED
+  // 2. NOTIFICATION_LOGIN_WEBUI_LOADED
+  // 3. NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN
+  // 4. NOTIFICATION_WIZARD_FIRST_SCREEN_SHOWN
+  // 5. NOTIFICATION_DEMO_WEBUI_LOADED
+  //
+  // Possible series of notifications:
+  // 1. Boot into fresh OOBE
+  //    NOTIFICATION_WIZARD_FIRST_SCREEN_SHOWN
+  //    NOTIFICATION_LOGIN_WEBUI_VISIBLE
+  // 2. Boot into user pods list (normal boot)
+  //    NOTIFICATION_LOGIN_USER_IMAGES_LOADED
+  //    NOTIFICATION_LOGIN_WEBUI_VISIBLE
+  // 3. Boot into GAIA sign in UI (user pods display disabled or no users):
+  //    if no network is connected or flaky network
+  //    (NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN +
+  //     NOTIFICATION_LOGIN_WEBUI_VISIBLE)
+  //    NOTIFICATION_LOGIN_WEBUI_LOADED
+  //    NOTIFICATION_LOGIN_WEBUI_VISIBLE
+  // 4. Boot into retail mode
+  //    NOTIFICATION_DEMO_WEBUI_LOADED
+  //    NOTIFICATION_LOGIN_WEBUI_VISIBLE
+  NOTIFICATION_LOGIN_WEBUI_VISIBLE,
+
+  // Sent when proxy dialog is closed.
+  NOTIFICATION_LOGIN_PROXY_CHANGED,
+
+  // Sent when the user list has changed due to a policy change.
+  NOTIFICATION_POLICY_USER_LIST_CHANGED,
+
+  // Sent when a panel state changed.
+  NOTIFICATION_PANEL_STATE_CHANGED,
+
+  // Sent when the window manager's layout mode has changed.
+  NOTIFICATION_LAYOUT_MODE_CHANGED,
+
+  // Sent when the screen lock state has changed. The source is
+  // ScreenLocker and the details is a bool specifing that the
+  // screen is locked. When details is a false, the source object
+  // is being deleted, so the receiver shouldn't use the screen locker
+  // object.
+  NOTIFICATION_SCREEN_LOCK_STATE_CHANGED,
+
+  // Sent by DeviceSettingsService to indicate that the ownership status
+  // changed. If you can, please use DeviceSettingsService::Observer instead.
+  // Other singleton-based services can't use that because Observer
+  // unregistration is impossible due to unpredictable deletion order.
+  NOTIFICATION_OWNERSHIP_STATUS_CHANGED,
+
+  // This is sent to a ChromeOS settings observer when a system setting is
+  // changed. The source is the CrosSettings and the details a std::string of
+  // the changed setting.
+  NOTIFICATION_SYSTEM_SETTING_CHANGED,
+
+  // Sent by SIM unlock dialog when it has finished with the process of
+  // updating RequirePin setting. RequirePin setting might have been changed
+  // to a new value or update might have been canceled.
+  // In either case notification is sent and details contain a bool
+  // that represents current value.
+  NOTIFICATION_REQUIRE_PIN_SETTING_CHANGE_ENDED,
+
+  // Sent by SIM unlock dialog when it has finished the EnterPin or
+  // EnterPuk dialog, either because the user cancelled, or entered a
+  // PIN or PUK.
+  NOTIFICATION_ENTER_PIN_ENDED,
+
+#endif
+
+#if defined(TOOLKIT_VIEWS)
+  // Sent when a bookmark's context menu is shown. Used to notify
+  // tests that the context menu has been created and shown.
+  NOTIFICATION_BOOKMARK_CONTEXT_MENU_SHOWN,
+
+  // Notification that the nested loop using during tab dragging has returned.
+  // Used for testing.
+  NOTIFICATION_TAB_DRAG_LOOP_DONE,
+#endif
+
+  // Send when a context menu is shown. Used to notify tests that the context
+  // menu has been created and shown.
+  NOTIFICATION_RENDER_VIEW_CONTEXT_MENU_SHOWN,
+
+  // Send when a context menu is closed.
+  NOTIFICATION_RENDER_VIEW_CONTEXT_MENU_CLOSED,
+
+  // Sent each time the InstantController is updated.
+  NOTIFICATION_INSTANT_CONTROLLER_UPDATED,
+
+  // Sent when an Instant preview is committed. The Source is the WebContents
+  // containing the committed preview.
+  NOTIFICATION_INSTANT_COMMITTED,
+
+  // Sent when the Instant loader determines whether the page supports the
+  // Instant API or not.
+  NOTIFICATION_INSTANT_SUPPORT_DETERMINED,
+
+  // Sent when the Browser Instant controller resets, this may result from
+  // a preference change.
+  NOTIFICATION_BROWSER_INSTANT_RESET,
+
+  // Sent when the CaptivePortalService checks if we're behind a captive portal.
+  // The Source is the Profile the CaptivePortalService belongs to, and the
+  // Details are a Details<CaptivePortalService::CheckResults>.
+  NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT,
+
+  // Password Store ----------------------------------------------------------
+  // This notification is sent whenenever login entries stored in the password
+  // store are changed. The detail of this notification is a list of changes
+  // represented by a vector of PasswordStoreChange. Each change includes a
+  // change type (ADD, UPDATE, or REMOVE) as well as the
+  // |content::PasswordForm|s that were affected.
+  NOTIFICATION_LOGINS_CHANGED,
+
+  // Sent when an import process has ended.
+  NOTIFICATION_IMPORT_FINISHED,
+
+  // Sent when the applications in the NTP app launcher have been reordered.
+  // The details, if not NoDetails, is the std::string ID of the extension that
+  // was moved.
+  NOTIFICATION_EXTENSION_LAUNCHER_REORDERED,
+
+  // Sent when an app is installed and an NTP has been shown. Source is the
+  // WebContents that was shown, and Details is the string ID of the extension
+  // which was installed.
+  NOTIFICATION_APP_INSTALLED_TO_NTP,
+
+  // Similar to NOTIFICATION_APP_INSTALLED_TO_NTP but used to nofity ash AppList
+  // about installed app. Source is the profile in which the app is installed
+  // and Details is the string ID of the extension.
+  NOTIFICATION_APP_INSTALLED_TO_APPLIST,
+
+#if defined(USE_ASH)
+  // Sent when wallpaper show animation has finished.
+  NOTIFICATION_WALLPAPER_ANIMATION_FINISHED,
+#endif
+
+#if defined(OS_CHROMEOS)
+  // Sent when WebSocketProxy started accepting connections; details is integer
+  // port on which proxy is listening.
+  NOTIFICATION_WEB_SOCKET_PROXY_STARTED,
+#endif
+
+  // Sent when a new web store promo has been loaded.
+  NOTIFICATION_WEB_STORE_PROMO_LOADED,
+
+  // Protocol Handler Registry -----------------------------------------------
+  // Sent when a ProtocolHandlerRegistry is changed. The source is the profile.
+  NOTIFICATION_PROTOCOL_HANDLER_REGISTRY_CHANGED,
+
+  // Sent when the cached profile info has changed.
+  NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
+
+  // Sent when the cached profile has finished writing a profile picture to
+  // disk.
+  NOTIFICATION_PROFILE_CACHE_PICTURE_SAVED,
+
+  // Sent when the browser enters or exits fullscreen mode.
+  NOTIFICATION_FULLSCREEN_CHANGED,
+
+  // Sent when the FullscreenController changes, confirms, or denies mouse lock.
+  // The source is the browser's FullscreenController, no details.
+  NOTIFICATION_MOUSE_LOCK_CHANGED,
+
+  // Sent by the PluginPrefs when there is a change of plugin enable/disable
+  // status. The source is the profile.
+  NOTIFICATION_PLUGIN_ENABLE_STATUS_CHANGED,
+
+  // Panels Notifications. The Panels are small browser windows near the bottom
+  // of the screen.
+  // Sent when all nonblocking bounds animations are finished across panels.
+  // Used only in unit testing.
+  NOTIFICATION_PANEL_BOUNDS_ANIMATIONS_FINISHED,
+
+  // Sent when panel gains/loses focus.
+  // The source is the Panel, no details.
+  // Used only in unit testing.
+  NOTIFICATION_PANEL_CHANGED_ACTIVE_STATUS,
+
+  // Sent when panel is minimized/restored/shows title only etc.
+  // The source is the Panel, no details.
+  NOTIFICATION_PANEL_CHANGED_EXPANSION_STATE,
+
+  // Sent when panel window size is known. This is for platforms where the
+  // window creation is async and size of the window only becomes known later.
+  // Used only in unit testing.
+  NOTIFICATION_PANEL_WINDOW_SIZE_KNOWN,
+
+  // Sent when panel app icon is loaded.
+  // Used only in unit testing.
+  NOTIFICATION_PANEL_APP_ICON_LOADED,
+
+  // Sent when panel strip get updated.
+  // The source is the PanelStrip, no details.
+  // Used only in coordination with notification balloons.
+  NOTIFICATION_PANEL_STRIP_UPDATED,
+
+  // Sent when panel is closed.
+  // The source is the Panel, no details.
+  NOTIFICATION_PANEL_CLOSED,
+
+  // Sent when a global error has changed and the error UI should update it
+  // self. The source is a Source<Profile> containing the profile for the
+  // error. The detail is a GlobalError object that has changed or NULL if
+  // all error UIs should update.
+  NOTIFICATION_GLOBAL_ERRORS_CHANGED,
+
+  // BrowsingDataRemover ----------------------------------------------------
+  // Sent on the UI thread after BrowsingDataRemover has removed browsing data
+  // but before it has notified its explicit observers. The source is a
+  // Source<Profile> containing the profile in which browsing data was removed,
+  // and the detail is a BrowsingDataRemover::NotificationDetail containing the
+  // removal mask and the start of the removal timeframe with which
+  // BrowsingDataRemove::Remove was called.
+  NOTIFICATION_BROWSING_DATA_REMOVED,
+
+  // The user accepted or dismissed a SSL client authentication request.
+  // The source is a Source<net::HttpNetworkSession>.  Details is a
+  // (std::pair<net::SSLCertRequestInfo*, net::X509Certificate*>).
+  NOTIFICATION_SSL_CLIENT_AUTH_CERT_SELECTED,
+
+  // Blocked content.
+  // Sent when content changes to or from the blocked state in
+  // BlockedContentTabHelper.
+  // The source is the WebContents of the blocked content and details
+  // is a boolean: true if the content is entering the blocked state, false
+  // if it is leaving.
+  NOTIFICATION_CONTENT_BLOCKED_STATE_CHANGED,
+
+  // SearchViewController.
+  // Sent when animations initiated by search view controller complete.
+  // The source is the SearchViewController whose animation is finished.
+  // No details.
+  NOTIFICATION_SEARCH_VIEW_CONTROLLER_ANIMATION_FINISHED,
+
+  // Note:-
+  // Currently only Content and Chrome define and use notifications.
+  // Custom notifications not belonging to Content and Chrome should start
+  // from here.
+  NOTIFICATION_CHROME_END,
+};
+
+}  // namespace chrome
+
+
+#endif  // CHROME_COMMON_CHROME_NOTIFICATION_TYPES_H_
diff --git a/chrome/common/chrome_paths.cc b/chrome/common/chrome_paths.cc
new file mode 100644
index 0000000..79867cd
--- /dev/null
+++ b/chrome/common/chrome_paths.cc
@@ -0,0 +1,496 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/chrome_paths.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/mac/bundle_locations.h"
+#include "base/path_service.h"
+#include "base/string_util.h"
+#include "base/sys_info.h"
+#include "base/version.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths_internal.h"
+#include "ui/base/ui_base_paths.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/path_utils.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#endif
+
+#include "widevine_cdm_version.h"  // In SHARED_INTERMEDIATE_DIR.
+
+namespace {
+
+// File name of the internal Flash plugin on different platforms.
+const FilePath::CharType kInternalFlashPluginFileName[] =
+#if defined(OS_MACOSX)
+    FILE_PATH_LITERAL("Flash Player Plugin for Chrome.plugin");
+#elif defined(OS_WIN)
+    FILE_PATH_LITERAL("gcswf32.dll");
+#else  // OS_LINUX, etc.
+    FILE_PATH_LITERAL("libgcflashplayer.so");
+#endif
+
+// The Pepper Flash plugins are in a directory with this name.
+const FilePath::CharType kPepperFlashBaseDirectory[] =
+    FILE_PATH_LITERAL("PepperFlash");
+
+// File name of the internal PDF plugin on different platforms.
+const FilePath::CharType kInternalPDFPluginFileName[] =
+#if defined(OS_WIN)
+    FILE_PATH_LITERAL("pdf.dll");
+#elif defined(OS_MACOSX)
+    FILE_PATH_LITERAL("PDF.plugin");
+#else  // Linux and Chrome OS
+    FILE_PATH_LITERAL("libpdf.so");
+#endif
+
+// File name of the internal NaCl plugin on different platforms.
+const FilePath::CharType kInternalNaClPluginFileName[] =
+#if defined(OS_WIN)
+    FILE_PATH_LITERAL("ppGoogleNaClPluginChrome.dll");
+#elif defined(OS_MACOSX)
+    // TODO(noelallen) Please verify this extention name is correct.
+    FILE_PATH_LITERAL("ppGoogleNaClPluginChrome.plugin");
+#else  // Linux and Chrome OS
+    FILE_PATH_LITERAL("libppGoogleNaClPluginChrome.so");
+#endif
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+// File name of the nacl_helper and nacl_helper_bootstrap, Linux only.
+const FilePath::CharType kInternalNaClHelperFileName[] =
+    FILE_PATH_LITERAL("nacl_helper");
+const FilePath::CharType kInternalNaClHelperBootstrapFileName[] =
+    FILE_PATH_LITERAL("nacl_helper_bootstrap");
+#endif
+
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+
+const FilePath::CharType kO3DPluginFileName[] =
+    FILE_PATH_LITERAL("pepper/libppo3dautoplugin.so");
+
+const FilePath::CharType kGTalkPluginFileName[] =
+    FILE_PATH_LITERAL("pepper/libppgoogletalk.so");
+
+#endif  // defined(OS_POSIX) && !defined(OS_MACOSX)
+
+#if defined(OS_LINUX)
+// The path to the external extension <id>.json files.
+// /usr/share seems like a good choice, see: http://www.pathname.com/fhs/
+const char kFilepathSinglePrefExtensions[] =
+#if defined(GOOGLE_CHROME_BUILD)
+    FILE_PATH_LITERAL("/usr/share/google-chrome/extensions");
+#else
+    FILE_PATH_LITERAL("/usr/share/chromium/extensions");
+#endif  // defined(GOOGLE_CHROME_BUILD)
+#endif  // defined(OS_LINUX)
+
+#if defined(OS_CHROMEOS)
+const char kDefaultAppOrderFileName[] =
+#if defined(GOOGLE_CHROME_BUILD)
+    FILE_PATH_LITERAL("/usr/share/google-chrome/default_app_order.json");
+#else
+    FILE_PATH_LITERAL("/usr/share/chromium/default_app_order.json");
+#endif  // defined(GOOGLE_CHROME_BUILD)
+#endif  // defined(OS_CHROMEOS)
+
+}  // namespace
+
+namespace chrome {
+
+// Gets the path for internal plugins.
+bool GetInternalPluginsDirectory(FilePath* result) {
+#if defined(OS_MACOSX)
+  // If called from Chrome, get internal plugins from a subdirectory of the
+  // framework.
+  if (base::mac::AmIBundled()) {
+    *result = chrome::GetFrameworkBundlePath();
+    DCHECK(!result->empty());
+    *result = result->Append("Internet Plug-Ins");
+    return true;
+  }
+  // In tests, just look in the module directory (below).
+#endif
+
+  // The rest of the world expects plugins in the module directory.
+  return PathService::Get(base::DIR_MODULE, result);
+}
+
+bool PathProvider(int key, FilePath* result) {
+  // Some keys are just aliases...
+  switch (key) {
+    case chrome::DIR_APP:
+      return PathService::Get(base::DIR_MODULE, result);
+    case chrome::DIR_LOGS:
+#ifdef NDEBUG
+      // Release builds write to the data dir
+      return PathService::Get(chrome::DIR_USER_DATA, result);
+#else
+      // Debug builds write next to the binary (in the build tree)
+#if defined(OS_MACOSX)
+      if (!PathService::Get(base::DIR_EXE, result))
+        return false;
+      if (base::mac::AmIBundled()) {
+        // If we're called from chrome, dump it beside the app (outside the
+        // app bundle), if we're called from a unittest, we'll already
+        // outside the bundle so use the exe dir.
+        // exe_dir gave us .../Chromium.app/Contents/MacOS/Chromium.
+        *result = result->DirName();
+        *result = result->DirName();
+        *result = result->DirName();
+      }
+      return true;
+#else
+      return PathService::Get(base::DIR_EXE, result);
+#endif  // defined(OS_MACOSX)
+#endif  // NDEBUG
+    case chrome::FILE_RESOURCE_MODULE:
+      return PathService::Get(base::FILE_MODULE, result);
+  }
+
+  // Assume that we will not need to create the directory if it does not exist.
+  // This flag can be set to true for the cases where we want to create it.
+  bool create_dir = false;
+
+  FilePath cur;
+  switch (key) {
+    case chrome::DIR_USER_DATA:
+      if (!GetDefaultUserDataDirectory(&cur)) {
+        NOTREACHED();
+        return false;
+      }
+      create_dir = true;
+      break;
+#if defined(OS_WIN)
+    case chrome::DIR_ALT_USER_DATA:
+      if (!GetAlternateUserDataDirectory(&cur)) {
+        NOTREACHED();
+        return false;
+      }
+      create_dir = false;
+      break;
+#endif  // OS_WIN
+    case chrome::DIR_USER_DOCUMENTS:
+      if (!GetUserDocumentsDirectory(&cur))
+        return false;
+      create_dir = true;
+      break;
+    case chrome::DIR_USER_MUSIC:
+      if (!GetUserMusicDirectory(&cur))
+        return false;
+      break;
+    case chrome::DIR_USER_PICTURES:
+      if (!GetUserPicturesDirectory(&cur))
+        return false;
+      break;
+    case chrome::DIR_USER_VIDEOS:
+      if (!GetUserVideosDirectory(&cur))
+        return false;
+      break;
+    case chrome::DIR_DEFAULT_DOWNLOADS_SAFE:
+#if defined(OS_WIN) || defined(OS_LINUX)
+      if (!GetUserDownloadsDirectorySafe(&cur))
+        return false;
+      break;
+#else
+      // Fall through for all other platforms.
+#endif
+    case chrome::DIR_DEFAULT_DOWNLOADS:
+#if defined(OS_ANDROID)
+      if (!base::android::GetDownloadsDirectory(&cur))
+        return false;
+#else
+      if (!GetUserDownloadsDirectory(&cur))
+        return false;
+      // Do not create the download directory here, we have done it twice now
+      // and annoyed a lot of users.
+#endif
+      break;
+    case chrome::DIR_CRASH_DUMPS:
+#if defined(OS_CHROMEOS)
+      // ChromeOS uses a separate directory. See http://crosbug.com/25089
+      cur = FilePath("/var/log/chrome");
+#elif defined(OS_ANDROID)
+      if (!base::android::GetCacheDirectory(&cur))
+        return false;
+#else
+      // The crash reports are always stored relative to the default user data
+      // directory.  This avoids the problem of having to re-initialize the
+      // exception handler after parsing command line options, which may
+      // override the location of the app's profile directory.
+      if (!GetDefaultUserDataDirectory(&cur))
+        return false;
+#endif
+      cur = cur.Append(FILE_PATH_LITERAL("Crash Reports"));
+      create_dir = true;
+      break;
+    case chrome::DIR_RESOURCES:
+#if defined(OS_MACOSX)
+      cur = base::mac::FrameworkBundlePath();
+      cur = cur.Append(FILE_PATH_LITERAL("Resources"));
+#else
+      if (!PathService::Get(chrome::DIR_APP, &cur))
+        return false;
+      cur = cur.Append(FILE_PATH_LITERAL("resources"));
+#endif
+      break;
+    case chrome::DIR_INSPECTOR:
+      if (!PathService::Get(chrome::DIR_RESOURCES, &cur))
+        return false;
+      cur = cur.Append(FILE_PATH_LITERAL("inspector"));
+      break;
+    case chrome::DIR_APP_DICTIONARIES:
+#if defined(OS_POSIX)
+      // We can't write into the EXE dir on Linux, so keep dictionaries
+      // alongside the safe browsing database in the user data dir.
+      // And we don't want to write into the bundle on the Mac, so push
+      // it to the user data dir there also.
+      if (!PathService::Get(chrome::DIR_USER_DATA, &cur))
+        return false;
+#else
+      if (!PathService::Get(base::DIR_EXE, &cur))
+        return false;
+#endif
+      cur = cur.Append(FILE_PATH_LITERAL("Dictionaries"));
+      create_dir = true;
+      break;
+    case chrome::DIR_INTERNAL_PLUGINS:
+      if (!GetInternalPluginsDirectory(&cur))
+        return false;
+      break;
+    case chrome::DIR_PEPPER_FLASH_PLUGIN:
+      if (!GetInternalPluginsDirectory(&cur))
+        return false;
+      cur = cur.Append(kPepperFlashBaseDirectory);
+      break;
+    case chrome::DIR_COMPONENT_UPDATED_PEPPER_FLASH_PLUGIN:
+      if (!PathService::Get(chrome::DIR_USER_DATA, &cur))
+        return false;
+      cur = cur.Append(kPepperFlashBaseDirectory);
+      break;
+    case chrome::FILE_LOCAL_STATE:
+      if (!PathService::Get(chrome::DIR_USER_DATA, &cur))
+        return false;
+      cur = cur.Append(chrome::kLocalStateFilename);
+      break;
+    case chrome::FILE_RECORDED_SCRIPT:
+      if (!PathService::Get(chrome::DIR_USER_DATA, &cur))
+        return false;
+      cur = cur.Append(FILE_PATH_LITERAL("script.log"));
+      break;
+    case chrome::FILE_FLASH_PLUGIN:
+    case chrome::FILE_FLASH_PLUGIN_EXISTING:
+      if (!GetInternalPluginsDirectory(&cur))
+        return false;
+      cur = cur.Append(kInternalFlashPluginFileName);
+      if (key == chrome::FILE_FLASH_PLUGIN_EXISTING &&
+          !file_util::PathExists(cur))
+        return false;
+      break;
+    case chrome::FILE_PEPPER_FLASH_PLUGIN:
+      if (!PathService::Get(chrome::DIR_PEPPER_FLASH_PLUGIN, &cur))
+        return false;
+      cur = cur.Append(chrome::kPepperFlashPluginFilename);
+      break;
+    case chrome::FILE_PDF_PLUGIN:
+      if (!GetInternalPluginsDirectory(&cur))
+        return false;
+      cur = cur.Append(kInternalPDFPluginFileName);
+      break;
+    case chrome::FILE_NACL_PLUGIN:
+      if (!GetInternalPluginsDirectory(&cur))
+        return false;
+      cur = cur.Append(kInternalNaClPluginFileName);
+      break;
+    case chrome::DIR_PNACL_BASE:
+      if (!PathService::Get(chrome::DIR_USER_DATA, &cur))
+        return false;
+      cur = cur.Append(FILE_PATH_LITERAL("Pnacl"));
+      break;
+    case chrome::DIR_PNACL_COMPONENT:
+      return false;
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+    case chrome::FILE_NACL_HELPER:
+      if (!PathService::Get(base::DIR_MODULE, &cur))
+        return false;
+      cur = cur.Append(kInternalNaClHelperFileName);
+      break;
+    case chrome::FILE_NACL_HELPER_BOOTSTRAP:
+      if (!PathService::Get(base::DIR_MODULE, &cur))
+        return false;
+      cur = cur.Append(kInternalNaClHelperBootstrapFileName);
+      break;
+    case chrome::FILE_O3D_PLUGIN:
+      if (!PathService::Get(base::DIR_MODULE, &cur))
+        return false;
+      cur = cur.Append(kO3DPluginFileName);
+      break;
+    case chrome::FILE_GTALK_PLUGIN:
+      if (!PathService::Get(base::DIR_MODULE, &cur))
+        return false;
+      cur = cur.Append(kGTalkPluginFileName);
+      break;
+#endif
+#if defined(WIDEVINE_CDM_AVAILABLE)
+    case chrome::FILE_WIDEVINE_CDM_PLUGIN:
+      if (!PathService::Get(base::DIR_MODULE, &cur))
+        return false;
+      cur = cur.Append(kWidevineCdmPluginFileName);
+      break;
+#endif
+    case chrome::FILE_RESOURCES_PACK:
+#if defined(OS_MACOSX)
+      if (base::mac::AmIBundled()) {
+        cur = base::mac::FrameworkBundlePath();
+        cur = cur.Append(FILE_PATH_LITERAL("Resources"))
+                 .Append(FILE_PATH_LITERAL("resources.pak"));
+        break;
+      }
+#elif defined(OS_ANDROID)
+      if (!PathService::Get(ui::DIR_RESOURCE_PAKS_ANDROID, &cur))
+        return false;
+#else
+      // If we're not bundled on mac or Android, resources.pak should be next
+      // to the binary (e.g., for unit tests).
+      if (!PathService::Get(base::DIR_MODULE, &cur))
+        return false;
+#endif
+      cur = cur.Append(FILE_PATH_LITERAL("resources.pak"));
+      break;
+    case chrome::DIR_RESOURCES_EXTENSION:
+      if (!PathService::Get(base::DIR_MODULE, &cur))
+        return false;
+      cur = cur.Append(FILE_PATH_LITERAL("resources"))
+               .Append(FILE_PATH_LITERAL("extension"));
+      break;
+#if defined(OS_CHROMEOS)
+    case chrome::DIR_CHROMEOS_WALLPAPERS:
+      if (!PathService::Get(chrome::DIR_USER_DATA, &cur))
+        return false;
+      cur = cur.Append(FILE_PATH_LITERAL("wallpapers"));
+      break;
+    case chrome::FILE_DEFAULT_APP_ORDER:
+      cur = FilePath(FILE_PATH_LITERAL(kDefaultAppOrderFileName));
+      break;
+#endif
+    // The following are only valid in the development environment, and
+    // will fail if executed from an installed executable (because the
+    // generated path won't exist).
+    case chrome::DIR_GEN_TEST_DATA:
+      if (!PathService::Get(base::DIR_MODULE, &cur))
+        return false;
+      cur = cur.Append(FILE_PATH_LITERAL("test_data"));
+      if (!file_util::PathExists(cur))  // We don't want to create this.
+        return false;
+      break;
+    case chrome::DIR_TEST_DATA:
+      if (!PathService::Get(base::DIR_SOURCE_ROOT, &cur))
+        return false;
+      cur = cur.Append(FILE_PATH_LITERAL("chrome"));
+      cur = cur.Append(FILE_PATH_LITERAL("test"));
+      cur = cur.Append(FILE_PATH_LITERAL("data"));
+      if (!file_util::PathExists(cur))  // We don't want to create this.
+        return false;
+      break;
+    case chrome::DIR_TEST_TOOLS:
+      if (!PathService::Get(base::DIR_SOURCE_ROOT, &cur))
+        return false;
+      cur = cur.Append(FILE_PATH_LITERAL("chrome"));
+      cur = cur.Append(FILE_PATH_LITERAL("tools"));
+      cur = cur.Append(FILE_PATH_LITERAL("test"));
+      if (!file_util::PathExists(cur))  // We don't want to create this
+        return false;
+      break;
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD)
+    case chrome::DIR_POLICY_FILES: {
+#if defined(GOOGLE_CHROME_BUILD)
+      cur = FilePath(FILE_PATH_LITERAL("/etc/opt/chrome/policies"));
+#else
+      cur = FilePath(FILE_PATH_LITERAL("/etc/chromium/policies"));
+#endif
+      break;
+    }
+#endif
+#if defined(OS_MACOSX)
+    case chrome::DIR_MANAGED_PREFS: {
+      if (!GetLocalLibraryDirectory(&cur))
+        return false;
+      cur = cur.Append(FILE_PATH_LITERAL("Managed Preferences"));
+      char* login = getlogin();
+      if (!login)
+        return false;
+      cur = cur.AppendASCII(login);
+      if (!file_util::PathExists(cur))  // We don't want to create this.
+        return false;
+      break;
+    }
+#endif
+#if defined(OS_CHROMEOS) || defined(OS_MACOSX)
+    case chrome::DIR_USER_EXTERNAL_EXTENSIONS: {
+      if (!PathService::Get(chrome::DIR_USER_DATA, &cur))
+        return false;
+      cur = cur.Append(FILE_PATH_LITERAL("External Extensions"));
+      break;
+    }
+#endif
+#if defined(OS_LINUX)
+    case chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS: {
+      cur = FilePath(FILE_PATH_LITERAL(kFilepathSinglePrefExtensions));
+      break;
+    }
+#endif
+    case chrome::DIR_EXTERNAL_EXTENSIONS:
+#if defined(OS_MACOSX)
+      if (!chrome::GetGlobalApplicationSupportDirectory(&cur))
+        return false;
+
+      cur = cur.Append(FILE_PATH_LITERAL("Google"))
+               .Append(FILE_PATH_LITERAL("Chrome"))
+               .Append(FILE_PATH_LITERAL("External Extensions"));
+      create_dir = false;
+#else
+      if (!PathService::Get(base::DIR_MODULE, &cur))
+        return false;
+
+      cur = cur.Append(FILE_PATH_LITERAL("extensions"));
+      create_dir = true;
+#endif
+      break;
+
+    case chrome::DIR_DEFAULT_APPS:
+#if defined(OS_MACOSX)
+      cur = base::mac::FrameworkBundlePath();
+      cur = cur.Append(FILE_PATH_LITERAL("Default Apps"));
+#else
+      if (!PathService::Get(chrome::DIR_APP, &cur))
+        return false;
+      cur = cur.Append(FILE_PATH_LITERAL("default_apps"));
+#endif
+      break;
+
+    default:
+      return false;
+  }
+
+  if (create_dir && !file_util::PathExists(cur) &&
+      !file_util::CreateDirectory(cur))
+    return false;
+
+  *result = cur;
+  return true;
+}
+
+// This cannot be done as a static initializer sadly since Visual Studio will
+// eliminate this object file if there is no direct entry point into it.
+void RegisterPathProvider() {
+  PathService::RegisterProvider(PathProvider, PATH_START, PATH_END);
+}
+
+}  // namespace chrome
diff --git a/chrome/common/chrome_paths.h b/chrome/common/chrome_paths.h
new file mode 100644
index 0000000..b5e632f
--- /dev/null
+++ b/chrome/common/chrome_paths.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_CHROME_PATHS_H__
+#define CHROME_COMMON_CHROME_PATHS_H__
+
+#include "build/build_config.h"
+
+// This file declares path keys for the chrome module.  These can be used with
+// the PathService to access various special directories and files.
+
+namespace chrome {
+
+enum {
+  PATH_START = 1000,
+
+  DIR_APP = PATH_START,         // Directory where dlls and data reside.
+  DIR_LOGS,                     // Directory where logs should be written.
+  DIR_USER_DATA,                // Directory where user data can be written.
+#if defined(OS_WIN)
+  DIR_ALT_USER_DATA,            // Directory of the desktop or metro user data
+                                // (the one that isn't in use).
+#endif
+  DIR_CRASH_DUMPS,              // Directory where crash dumps are written.
+  DIR_RESOURCES,                // Directory containing separate file resources
+                                // used by Chrome at runtime.
+  DIR_INSPECTOR,                // Directory where web inspector is located.
+  DIR_APP_DICTIONARIES,         // Directory where the global dictionaries are.
+  DIR_USER_DOCUMENTS,           // Directory for a user's "My Documents".
+  DIR_USER_MUSIC,               // Directory for a user's music.
+  DIR_USER_PICTURES,            // Directory for a user's pictures.
+  DIR_USER_VIDEOS,              // Directory for a user's videos.
+  DIR_DEFAULT_DOWNLOADS_SAFE,   // Directory for a user's
+                                // "My Documents/Downloads", (Windows) or
+                                // "Downloads". (Linux)
+  DIR_DEFAULT_DOWNLOADS,        // Directory for a user's downloads.
+  DIR_INTERNAL_PLUGINS,         // Directory where internal plugins reside.
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+  DIR_POLICY_FILES,             // Directory for system-wide read-only
+                                // policy files that allow sys-admins
+                                // to set policies for chrome. This directory
+                                // contains subdirectories.
+#endif
+#if defined(OS_MACOSX)
+  DIR_MANAGED_PREFS,            // Directory that stores the managed prefs plist
+                                // files for the current user.
+#endif
+#if defined(OS_CHROMEOS) || defined(OS_MACOSX)
+  DIR_USER_EXTERNAL_EXTENSIONS,  // Directory for per-user external extensions
+                                 // on Chrome Mac.  On Chrome OS, this path is
+                                 // used for OEM customization.
+                                 // Getting this path does not create it.
+#endif
+
+#if defined(OS_LINUX)
+  DIR_STANDALONE_EXTERNAL_EXTENSIONS,  // Directory for 'per-extension'
+                                       // definition manifest files that
+                                       // describe extensions which are to be
+                                       // installed when chrome is run.
+#endif
+  DIR_EXTERNAL_EXTENSIONS,      // Directory where installer places .crx files.
+
+  DIR_DEFAULT_APPS,             // Directory where installer places .crx files
+                                // to be installed when chrome is first run.
+  DIR_PEPPER_FLASH_PLUGIN,      // Directory to the bundled Pepper Flash plugin,
+                                // containing the plugin and the manifest.
+  DIR_COMPONENT_UPDATED_PEPPER_FLASH_PLUGIN,  // Base directory of the Pepper
+                                              // Flash plugins downloaded by the
+                                              // component updater.
+  FILE_RESOURCE_MODULE,         // Full path and filename of the module that
+                                // contains embedded resources (version,
+                                // strings, images, etc.).
+  FILE_LOCAL_STATE,             // Path and filename to the file in which
+                                // machine/installation-specific state is saved.
+  FILE_RECORDED_SCRIPT,         // Full path to the script.log file that
+                                // contains recorded browser events for
+                                // playback.
+  FILE_FLASH_PLUGIN,            // Full path to the internal Flash plugin file.
+                                // Querying this path will succeed no matter the
+                                // file exists or not.
+  FILE_FLASH_PLUGIN_EXISTING,   // Full path to the internal Flash plugin file.
+                                // Querying this path will fail if the file
+                                // doesn't exist.
+  FILE_PEPPER_FLASH_PLUGIN,     // Full path to the bundled Pepper Flash plugin
+                                // file.
+  FILE_PDF_PLUGIN,              // Full path to the internal PDF plugin file.
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+  FILE_NACL_HELPER,             // Full path to Linux nacl_helper executable.
+  FILE_NACL_HELPER_BOOTSTRAP,   // ... and nacl_helper_bootstrap executable.
+#endif
+  FILE_NACL_PLUGIN,             // Full path to the internal NaCl plugin file.
+  DIR_PNACL_BASE,               // Full path to the base dir for PNaCl.
+  DIR_PNACL_COMPONENT,          // Full path to the latest PNaCl version
+                                // (subdir of DIR_PNACL_BASE).
+  FILE_O3D_PLUGIN,              // Full path to the O3D Pepper plugin file.
+  FILE_GTALK_PLUGIN,            // Full path to the GTalk Pepper plugin file.
+  FILE_WIDEVINE_CDM_PLUGIN,     // Full path to the Widevine CDM Pepper plugin
+                                // file.
+  FILE_RESOURCES_PACK,          // Full path to the .pak file containing
+                                // binary data (e.g., html files and images
+                                // used by interal pages).
+  DIR_RESOURCES_EXTENSION,      // Full path to extension resources.
+#if defined(OS_CHROMEOS)
+  DIR_CHROMEOS_WALLPAPERS,      // Directory where downloaded chromeos
+                                // wallpapers reside.
+  FILE_DEFAULT_APP_ORDER,       // Full path to the json file that defines the
+                                // default app order.
+#endif
+
+  // Valid only in development environment; TODO(darin): move these
+  DIR_GEN_TEST_DATA,            // Directory where generated test data resides.
+  DIR_TEST_DATA,                // Directory where unit test data resides.
+  DIR_TEST_TOOLS,               // Directory where unit test tools reside.
+
+  PATH_END
+};
+
+// Call once to register the provider for the path keys defined above.
+void RegisterPathProvider();
+
+}  // namespace chrome
+
+#endif  // CHROME_COMMON_CHROME_PATHS_H__
diff --git a/chrome/common/chrome_paths_android.cc b/chrome/common/chrome_paths_android.cc
new file mode 100644
index 0000000..6a7d9ea
--- /dev/null
+++ b/chrome/common/chrome_paths_android.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/chrome_paths_internal.h"
+
+#include "base/file_path.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+
+namespace chrome {
+
+void GetUserCacheDirectory(const FilePath& profile_dir, FilePath* result) {
+  if (!PathService::Get(base::DIR_CACHE, result))
+    *result = profile_dir;
+}
+
+bool GetDefaultUserDataDirectory(FilePath* result) {
+  return PathService::Get(base::DIR_ANDROID_APP_DATA, result);
+}
+
+bool GetUserDocumentsDirectory(FilePath* result) {
+  if (!GetDefaultUserDataDirectory(result))
+    return false;
+  *result = result->Append("Documents");
+  return true;
+}
+
+bool GetUserDownloadsDirectory(FilePath* result) {
+  if (!GetDefaultUserDataDirectory(result))
+    return false;
+  *result = result->Append("Downloads");
+  return true;
+}
+
+bool GetUserMusicDirectory(FilePath* result) {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+bool GetUserPicturesDirectory(FilePath* result) {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+bool GetUserVideosDirectory(FilePath* result) {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+bool ProcessNeedsProfileDir(const std::string& process_type) {
+  return true;
+}
+
+}  // namespace chrome
diff --git a/chrome/common/chrome_paths_internal.h b/chrome/common/chrome_paths_internal.h
new file mode 100644
index 0000000..60fab11
--- /dev/null
+++ b/chrome/common/chrome_paths_internal.h
@@ -0,0 +1,106 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_CHROME_PATHS_INTERNAL_H_
+#define CHROME_COMMON_CHROME_PATHS_INTERNAL_H_
+
+#include <string>
+
+#include "build/build_config.h"
+
+#if defined(OS_MACOSX)
+#if defined(__OBJC__)
+@class NSBundle;
+#else
+class NSBundle;
+#endif
+#endif
+
+class FilePath;
+
+namespace chrome {
+
+// Get the path to the user's data directory, regardless of whether
+// DIR_USER_DATA has been overridden by a command-line option.
+bool GetDefaultUserDataDirectory(FilePath* result);
+
+#if defined(OS_WIN)
+// Gets the path to the user data directory for the alternate environment to
+// the one in use (metro or desktop).
+bool GetAlternateUserDataDirectory(FilePath *result);
+#endif
+
+// This returns the base directory in which Chrome Frame stores user profiles.
+// Note that this cannot be wrapped in a preprocessor define since
+// CF and Google Chrome want to share the same binaries.
+bool GetChromeFrameUserDataDirectory(FilePath* result);
+
+// Get the path to the user's cache directory.  This is normally the
+// same as the profile directory, but on Linux it can also be
+// $XDG_CACHE_HOME and on Mac it can be under ~/Library/Caches.
+// Note that the Chrome cache directories are actually subdirectories
+// of this directory, with names like "Cache" and "Media Cache".
+// This will always fill in |result| with a directory, sometimes
+// just |profile_dir|.
+void GetUserCacheDirectory(const FilePath& profile_dir, FilePath* result);
+
+// Get the path to the user's documents directory.
+bool GetUserDocumentsDirectory(FilePath* result);
+
+#if defined(OS_WIN) || defined(OS_LINUX)
+// Gets the path to a safe default download directory for a user.
+bool GetUserDownloadsDirectorySafe(FilePath* result);
+#endif
+
+// Get the path to the user's downloads directory.
+bool GetUserDownloadsDirectory(FilePath* result);
+
+// Gets the path to the user's music directory.
+bool GetUserMusicDirectory(FilePath* result);
+
+// Gets the path to the user's pictures directory.
+bool GetUserPicturesDirectory(FilePath* result);
+
+// Gets the path to the user's videos directory.
+bool GetUserVideosDirectory(FilePath* result);
+
+#if defined(OS_MACOSX)
+// The "versioned directory" is a directory in the browser .app bundle.  It
+// contains the bulk of the application, except for the things that the system
+// requires be located at spepcific locations.  The versioned directory is
+// in the .app at Contents/Versions/w.x.y.z.
+FilePath GetVersionedDirectory();
+
+// This overrides the directory returned by |GetVersionedDirectory()|, to be
+// used when |GetVersionedDirectory()| can't automatically determine the proper
+// location. This is the case when the browser didn't load itself but by, e.g.,
+// the app mode loader. This should be called before |ChromeMain()|. This takes
+// ownership of the object |path| and the caller must not delete it.
+void SetOverrideVersionedDirectory(const FilePath* path);
+
+// Most of the application is further contained within the framework.  The
+// framework bundle is located within the versioned directory at a specific
+// path.  The only components in the versioned directory not included in the
+// framework are things that also depend on the framework, such as the helper
+// app bundle.
+FilePath GetFrameworkBundlePath();
+
+// Get the local library directory.
+bool GetLocalLibraryDirectory(FilePath* result);
+
+// Get the global Application Support directory (under /Library/).
+bool GetGlobalApplicationSupportDirectory(FilePath* result);
+
+// Returns the NSBundle for the outer browser application, even when running
+// inside the helper. In unbundled applications, such as tests, returns nil.
+NSBundle* OuterAppBundle();
+
+#endif  // OS_MACOSX
+
+// Checks if the |process_type| has the rights to access the profile.
+bool ProcessNeedsProfileDir(const std::string& process_type);
+
+}  // namespace chrome
+
+#endif  // CHROME_COMMON_CHROME_PATHS_INTERNAL_H_
diff --git a/chrome/common/chrome_paths_linux.cc b/chrome/common/chrome_paths_linux.cc
new file mode 100644
index 0000000..babb501
--- /dev/null
+++ b/chrome/common/chrome_paths_linux.cc
@@ -0,0 +1,153 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/chrome_paths_internal.h"
+
+#include "base/environment.h"
+#include "base/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/nix/xdg_util.h"
+#include "base/path_service.h"
+
+namespace chrome {
+
+using base::nix::GetXDGDirectory;
+using base::nix::GetXDGUserDirectory;
+using base::nix::kDotConfigDir;
+using base::nix::kXdgConfigHomeEnvVar;
+
+namespace {
+
+const char kDownloadsDir[] = "Downloads";
+const char kMusicDir[] = "Music";
+const char kPicturesDir[] = "Pictures";
+const char kVideosDir[] = "Videos";
+
+// Generic function for GetUser{Music,Pictures,Video}Directory.
+bool GetUserMediaDirectory(const std::string& xdg_name,
+                           const std::string& fallback_name,
+                           FilePath* result) {
+#if defined(OS_CHROMEOS)
+  // No local media directories on CrOS.
+  return false;
+#else
+  *result = GetXDGUserDirectory(xdg_name.c_str(), fallback_name.c_str());
+
+  FilePath home = file_util::GetHomeDir();
+  if (*result != home) {
+    FilePath desktop;
+    if (!PathService::Get(base::DIR_USER_DESKTOP, &desktop))
+      return false;
+    if (*result != desktop) {
+      return true;
+    }
+  }
+
+  *result = home.Append(fallback_name);
+  return true;
+#endif
+}
+
+}  // namespace
+
+// See http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+// for a spec on where config files go.  The net effect for most
+// systems is we use ~/.config/chromium/ for Chromium and
+// ~/.config/google-chrome/ for official builds.
+// (This also helps us sidestep issues with other apps grabbing ~/.chromium .)
+bool GetDefaultUserDataDirectory(FilePath* result) {
+  scoped_ptr<base::Environment> env(base::Environment::Create());
+  FilePath config_dir(GetXDGDirectory(env.get(),
+                                      kXdgConfigHomeEnvVar,
+                                      kDotConfigDir));
+#if defined(GOOGLE_CHROME_BUILD)
+  *result = config_dir.Append("google-chrome");
+#else
+  *result = config_dir.Append("chromium");
+#endif
+  return true;
+}
+
+void GetUserCacheDirectory(const FilePath& profile_dir, FilePath* result) {
+  // See http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+  // for a spec on where cache files go.  Our rule is:
+  // - if the user-data-dir in the standard place,
+  //     use same subdirectory of the cache directory.
+  //     (this maps ~/.config/google-chrome to ~/.cache/google-chrome as well
+  //      as the same thing for ~/.config/chromium)
+  // - otherwise, use the profile dir directly.
+
+  // Default value in cases where any of the following fails.
+  *result = profile_dir;
+
+  scoped_ptr<base::Environment> env(base::Environment::Create());
+
+  FilePath cache_dir;
+  if (!PathService::Get(base::DIR_CACHE, &cache_dir))
+    return;
+  FilePath config_dir(GetXDGDirectory(env.get(),
+                                      kXdgConfigHomeEnvVar,
+                                      kDotConfigDir));
+
+  if (!config_dir.AppendRelativePath(profile_dir, &cache_dir))
+    return;
+
+  *result = cache_dir;
+}
+
+bool GetChromeFrameUserDataDirectory(FilePath* result) {
+  scoped_ptr<base::Environment> env(base::Environment::Create());
+  FilePath config_dir(GetXDGDirectory(env.get(),
+                                      kXdgConfigHomeEnvVar,
+                                      kDotConfigDir));
+#if defined(GOOGLE_CHROME_BUILD)
+  *result = config_dir.Append("google-chrome-frame");
+#else
+  *result = config_dir.Append("chrome-frame");
+#endif
+  return true;
+}
+
+bool GetUserDocumentsDirectory(FilePath* result) {
+  *result = GetXDGUserDirectory("DOCUMENTS", "Documents");
+  return true;
+}
+
+bool GetUserDownloadsDirectorySafe(FilePath* result) {
+  FilePath home = file_util::GetHomeDir();
+  *result = home.Append(kDownloadsDir);
+  return true;
+}
+
+bool GetUserDownloadsDirectory(FilePath* result) {
+  *result = base::nix::GetXDGUserDirectory("DOWNLOAD", kDownloadsDir);
+  return true;
+}
+
+// We respect the user's preferred pictures location, unless it is
+// ~ or their desktop directory, in which case we default to ~/Music.
+bool GetUserMusicDirectory(FilePath* result) {
+  return GetUserMediaDirectory("MUSIC", kMusicDir, result);
+}
+
+// We respect the user's preferred pictures location, unless it is
+// ~ or their desktop directory, in which case we default to ~/Pictures.
+bool GetUserPicturesDirectory(FilePath* result) {
+  return GetUserMediaDirectory("PICTURES", kPicturesDir, result);
+}
+
+// We respect the user's preferred pictures location, unless it is
+// ~ or their desktop directory, in which case we default to ~/Videos.
+bool GetUserVideosDirectory(FilePath* result) {
+  return GetUserMediaDirectory("VIDEOS", kVideosDir, result);
+}
+
+bool ProcessNeedsProfileDir(const std::string& process_type) {
+  // For now we have no reason to forbid this on Linux as we don't
+  // have the roaming profile troubles there. Moreover the Linux breakpad needs
+  // profile dir access in all process if enabled on Linux.
+  return true;
+}
+
+}  // namespace chrome
diff --git a/chrome/common/chrome_paths_mac.mm b/chrome/common/chrome_paths_mac.mm
new file mode 100644
index 0000000..5b63f47
--- /dev/null
+++ b/chrome/common/chrome_paths_mac.mm
@@ -0,0 +1,216 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/chrome_paths_internal.h"
+
+#import <Foundation/Foundation.h>
+#include <string.h>
+
+#include <string>
+
+#include "base/base_paths.h"
+#include "base/logging.h"
+#import "base/mac/foundation_util.h"
+#import "base/mac/mac_util.h"
+#import "base/mac/scoped_nsautorelease_pool.h"
+#include "base/path_service.h"
+#include "chrome/common/chrome_constants.h"
+
+namespace {
+
+const FilePath* g_override_versioned_directory = NULL;
+
+// Return a retained (NOT autoreleased) NSBundle* as the internal
+// implementation of chrome::OuterAppBundle(), which should be the only
+// caller.
+NSBundle* OuterAppBundleInternal() {
+  base::mac::ScopedNSAutoreleasePool pool;
+
+  if (!base::mac::AmIBundled()) {
+    // If unbundled (as in a test), there's no app bundle.
+    return nil;
+  }
+
+  if (!base::mac::IsBackgroundOnlyProcess()) {
+    // Shortcut: in the browser process, just return the main app bundle.
+    return [[NSBundle mainBundle] retain];
+  }
+
+  // From C.app/Contents/Versions/1.2.3.4, go up three steps to get to C.app.
+  FilePath versioned_dir = chrome::GetVersionedDirectory();
+  FilePath outer_app_dir = versioned_dir.DirName().DirName().DirName();
+  const char* outer_app_dir_c = outer_app_dir.value().c_str();
+  NSString* outer_app_dir_ns = [NSString stringWithUTF8String:outer_app_dir_c];
+
+  return [[NSBundle bundleWithPath:outer_app_dir_ns] retain];
+}
+
+const char* ProductDirNameInternal() {
+  base::mac::ScopedNSAutoreleasePool pool;
+
+  // Use OuterAppBundle() to get the main app's bundle. This key needs to live
+  // in the main app's bundle because it will be set differently on the canary
+  // channel, and the autoupdate system dictates that there can be no
+  // differences between channels within the versioned directory. This would
+  // normally use base::mac::FrameworkBundle(), but that references the
+  // framework bundle within the versioned directory. Ordinarily, the profile
+  // should not be accessed from non-browser processes, but those processes do
+  // attempt to get the profile directory, so direct them to look in the outer
+  // browser .app's Info.plist for the CrProductDirName key.
+  NSBundle* bundle = chrome::OuterAppBundle();
+  NSString* product_dir_name_ns =
+      [bundle objectForInfoDictionaryKey:@"CrProductDirName"];
+  const char* product_dir_name = [product_dir_name_ns fileSystemRepresentation];
+
+  if (!product_dir_name) {
+#if defined(GOOGLE_CHROME_BUILD)
+    product_dir_name = "Google/Chrome";
+#else
+    product_dir_name = "Chromium";
+#endif
+  }
+
+  // Leaked, but the only caller initializes a static with this result, so it
+  // only happens once, and that's OK.
+  return strdup(product_dir_name);
+}
+
+// ProductDirName returns the name of the directory inside
+// ~/Library/Application Support that should hold the product application
+// data. This can be overridden by setting the CrProductDirName key in the
+// outer browser .app's Info.plist. The default is "Google/Chrome" for
+// officially-branded builds, and "Chromium" for unbranded builds. For the
+// official canary channel, the Info.plist will have CrProductDirName set
+// to "Google/Chrome Canary".
+std::string ProductDirName() {
+  static const char* product_dir_name = ProductDirNameInternal();
+  return std::string(product_dir_name);
+}
+
+}  // namespace
+
+namespace chrome {
+
+bool GetDefaultUserDataDirectory(FilePath* result) {
+  bool success = false;
+  if (result && PathService::Get(base::DIR_APP_DATA, result)) {
+    *result = result->Append(ProductDirName());
+    success = true;
+  }
+  return success;
+}
+
+bool GetUserDocumentsDirectory(FilePath* result) {
+  return base::mac::GetUserDirectory(NSDocumentDirectory, result);
+}
+
+bool GetGlobalApplicationSupportDirectory(FilePath* result) {
+  return base::mac::GetLocalDirectory(NSApplicationSupportDirectory, result);
+}
+
+void GetUserCacheDirectory(const FilePath& profile_dir, FilePath* result) {
+  // If the profile directory is under ~/Library/Application Support,
+  // use a suitable cache directory under ~/Library/Caches.  For
+  // example, a profile directory of ~/Library/Application
+  // Support/Google/Chrome/MyProfileName would use the cache directory
+  // ~/Library/Caches/Google/Chrome/MyProfileName.
+
+  // Default value in cases where any of the following fails.
+  *result = profile_dir;
+
+  FilePath app_data_dir;
+  if (!PathService::Get(base::DIR_APP_DATA, &app_data_dir))
+    return;
+  FilePath cache_dir;
+  if (!PathService::Get(base::DIR_CACHE, &cache_dir))
+    return;
+  if (!app_data_dir.AppendRelativePath(profile_dir, &cache_dir))
+    return;
+
+  *result = cache_dir;
+}
+
+bool GetUserDownloadsDirectory(FilePath* result) {
+  return base::mac::GetUserDirectory(NSDownloadsDirectory, result);
+}
+
+bool GetUserMusicDirectory(FilePath* result) {
+  return base::mac::GetUserDirectory(NSMusicDirectory, result);
+}
+
+bool GetUserPicturesDirectory(FilePath* result) {
+  return base::mac::GetUserDirectory(NSPicturesDirectory, result);
+}
+
+bool GetUserVideosDirectory(FilePath* result) {
+  return base::mac::GetUserDirectory(NSMoviesDirectory, result);
+}
+
+FilePath GetVersionedDirectory() {
+  if (g_override_versioned_directory)
+    return *g_override_versioned_directory;
+
+  // Start out with the path to the running executable.
+  FilePath path;
+  PathService::Get(base::FILE_EXE, &path);
+
+  // One step up to MacOS, another to Contents.
+  path = path.DirName().DirName();
+  DCHECK_EQ(path.BaseName().value(), "Contents");
+
+  if (base::mac::IsBackgroundOnlyProcess()) {
+    // path identifies the helper .app's Contents directory in the browser
+    // .app's versioned directory.  Go up two steps to get to the browser
+    // .app's versioned directory.
+    path = path.DirName().DirName();
+    DCHECK_EQ(path.BaseName().value(), kChromeVersion);
+  } else {
+    // Go into the versioned directory.
+    path = path.Append("Versions").Append(kChromeVersion);
+  }
+
+  return path;
+}
+
+void SetOverrideVersionedDirectory(const FilePath* path) {
+  if (path != g_override_versioned_directory) {
+    delete g_override_versioned_directory;
+    g_override_versioned_directory = path;
+  }
+}
+
+FilePath GetFrameworkBundlePath() {
+  // It's tempting to use +[NSBundle bundleWithIdentifier:], but it's really
+  // slow (about 30ms on 10.5 and 10.6), despite Apple's documentation stating
+  // that it may be more efficient than +bundleForClass:.  +bundleForClass:
+  // itself takes 1-2ms.  Getting an NSBundle from a path, on the other hand,
+  // essentially takes no time at all, at least when the bundle has already
+  // been loaded as it will have been in this case.  The FilePath operations
+  // needed to compute the framework's path are also effectively free, so that
+  // is the approach that is used here.  NSBundle is also documented as being
+  // not thread-safe, and thread safety may be a concern here.
+
+  // The framework bundle is at a known path and name from the browser .app's
+  // versioned directory.
+  return GetVersionedDirectory().Append(kFrameworkName);
+}
+
+bool GetLocalLibraryDirectory(FilePath* result) {
+  return base::mac::GetLocalDirectory(NSLibraryDirectory, result);
+}
+
+NSBundle* OuterAppBundle() {
+  // Cache this. Foundation leaks it anyway, and this should be the only call
+  // to OuterAppBundleInternal().
+  static NSBundle* bundle = OuterAppBundleInternal();
+  return bundle;
+}
+
+bool ProcessNeedsProfileDir(const std::string& process_type) {
+  // For now we have no reason to forbid this on other MacOS as we don't
+  // have the roaming profile troubles there.
+  return true;
+}
+
+}  // namespace chrome
diff --git a/chrome/common/chrome_paths_unittest.cc b/chrome/common/chrome_paths_unittest.cc
new file mode 100644
index 0000000..98f9882
--- /dev/null
+++ b/chrome/common/chrome_paths_unittest.cc
@@ -0,0 +1,64 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/chrome_paths_internal.h"
+
+#include <stdlib.h>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "chrome/common/chrome_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Test the behavior of chrome::GetUserCacheDirectory.
+// See that function's comments for discussion of the subtleties.
+TEST(ChromePaths, UserCacheDir) {
+  FilePath test_profile_dir, cache_dir;
+#if defined(OS_MACOSX)
+  ASSERT_TRUE(PathService::Get(base::DIR_APP_DATA, &test_profile_dir));
+  test_profile_dir = test_profile_dir.Append("foobar");
+  FilePath expected_cache_dir;
+  ASSERT_TRUE(PathService::Get(base::DIR_CACHE, &expected_cache_dir));
+  expected_cache_dir = expected_cache_dir.Append("foobar");
+#elif(OS_POSIX)
+  FilePath homedir = file_util::GetHomeDir();
+  // Note: we assume XDG_CACHE_HOME/XDG_CONFIG_HOME are at their
+  // default settings.
+  test_profile_dir = homedir.Append(".config/foobar");
+  FilePath expected_cache_dir = homedir.Append(".cache/foobar");
+#endif
+
+  // Verify that a profile in the special platform-specific source
+  // location ends up in the special target location.
+#if !defined(OS_WIN)  // No special behavior on Windows.
+  chrome::GetUserCacheDirectory(test_profile_dir, &cache_dir);
+  EXPECT_EQ(expected_cache_dir.value(), cache_dir.value());
+#endif
+
+  // Verify that a profile in some other random directory doesn't use
+  // the special cache dir.
+  test_profile_dir = FilePath(FILE_PATH_LITERAL("/some/other/path"));
+  chrome::GetUserCacheDirectory(test_profile_dir, &cache_dir);
+  EXPECT_EQ(test_profile_dir.value(), cache_dir.value());
+}
+
+#if defined(OS_WINDOWS)
+TEST(ChromePaths, AlternateUserDataDir) {
+  FilePath current_dir;
+  FilePath alternate_dir;
+
+  ASSERT_TRUE(PathService::Get(chrome::DIR_USER_DATA, current_dir));
+
+  // Check that we can get the alternate dir.
+  EXPECT_TRUE(PathService::Get(chrome::DIR_ALT_USER_DATA, alternate_dir));
+
+  // And that it's not the same as the current dir.
+  EXPECTE_NE(current_dir.value(), alternate_dir.value());
+
+  // And that it's the metro dir.
+  EXPECT_EQ(FilePath::StringType(kMetroChromeUserDataSubDir),
+            alternate_dir.DirName().BaseName().value());
+}
+#endif
diff --git a/chrome/common/chrome_paths_win.cc b/chrome/common/chrome_paths_win.cc
new file mode 100644
index 0000000..bb4036d
--- /dev/null
+++ b/chrome/common/chrome_paths_win.cc
@@ -0,0 +1,149 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/chrome_paths_internal.h"
+
+#include <windows.h>
+#include <knownfolders.h>
+#include <shellapi.h>
+#include <shlobj.h>
+#include <shobjidl.h>
+
+#include "base/command_line.h"
+#include "base/file_path.h"
+#include "base/path_service.h"
+#include "base/win/metro.h"
+#include "base/win/scoped_co_mem.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/installer/util/browser_distribution.h"
+#include "content/public/common/content_switches.h"
+
+namespace chrome {
+
+namespace {
+
+// Gets the default user data directory for either the current environment
+// (desktop or metro) or for the other one (metro or desktop).
+bool GetUserDataDirectoryForEnvironment(bool current, FilePath* result) {
+  if (!PathService::Get(base::DIR_LOCAL_APP_DATA, result))
+    return false;
+  BrowserDistribution* dist = BrowserDistribution::GetDistribution();
+  *result = result->Append(dist->GetInstallSubDir());
+
+  // TODO(rsimha): Continue to return the "Metro" subdirectory to allow testing
+  // of sync credential caching in the presence of strange singleton mode.
+  // Delete this block before shipping. See http://crbug.com/144280.
+  const CommandLine* command_line = CommandLine::ForCurrentProcess();
+  if ((command_line->HasSwitch(switches::kEnableSyncCredentialCaching)) &&
+      (base::win::IsMetroProcess() ? current : !current)) {
+    *result = result->Append(kMetroChromeUserDataSubDir);
+  }
+
+  *result = result->Append(chrome::kUserDataDirname);
+  return true;
+}
+
+// Generic function to call SHGetFolderPath().
+bool GetUserDirectory(int csidl_folder, FilePath* result) {
+  // We need to go compute the value. It would be nice to support paths
+  // with names longer than MAX_PATH, but the system functions don't seem
+  // to be designed for it either, with the exception of GetTempPath
+  // (but other things will surely break if the temp path is too long,
+  // so we don't bother handling it.
+  wchar_t path_buf[MAX_PATH];
+  path_buf[0] = 0;
+  if (FAILED(SHGetFolderPath(NULL, csidl_folder, NULL,
+                             SHGFP_TYPE_CURRENT, path_buf))) {
+    return false;
+  }
+  *result = FilePath(path_buf);
+  return true;
+}
+
+}  // namespace
+
+bool GetDefaultUserDataDirectory(FilePath* result) {
+  return GetUserDataDirectoryForEnvironment(true, result);
+}
+
+bool GetAlternateUserDataDirectory(FilePath *result) {
+  return GetUserDataDirectoryForEnvironment(false, result);
+}
+
+bool GetChromeFrameUserDataDirectory(FilePath* result) {
+  if (!PathService::Get(base::DIR_LOCAL_APP_DATA, result))
+    return false;
+  BrowserDistribution* dist = BrowserDistribution::GetSpecificDistribution(
+      BrowserDistribution::CHROME_FRAME);
+  *result = result->Append(dist->GetInstallSubDir());
+  *result = result->Append(chrome::kUserDataDirname);
+  return true;
+}
+
+void GetUserCacheDirectory(const FilePath& profile_dir, FilePath* result) {
+  // This function does more complicated things on Mac/Linux.
+  *result = profile_dir;
+}
+
+bool GetUserDocumentsDirectory(FilePath* result) {
+  return GetUserDirectory(CSIDL_MYDOCUMENTS, result);
+}
+
+// Return a default path for downloads that is safe.
+// We just use 'Downloads' under DIR_USER_DOCUMENTS. Localizing
+// 'downloads' is not a good idea because Chrome's UI language
+// can be changed.
+bool GetUserDownloadsDirectorySafe(FilePath* result) {
+  if (!GetUserDocumentsDirectory(result))
+    return false;
+
+  *result = result->Append(L"Downloads");
+  return true;
+}
+
+// On Vista and higher, use the downloads known folder. Since it can be
+// relocated to point to a "dangerous" folder, callers should validate that the
+// returned path is not dangerous before using it.
+bool GetUserDownloadsDirectory(FilePath* result) {
+  typedef HRESULT (WINAPI *GetKnownFolderPath)(
+      REFKNOWNFOLDERID, DWORD, HANDLE, PWSTR*);
+  GetKnownFolderPath f = reinterpret_cast<GetKnownFolderPath>(
+      GetProcAddress(GetModuleHandle(L"shell32.dll"), "SHGetKnownFolderPath"));
+  base::win::ScopedCoMem<wchar_t> path_buf;
+  if (f && SUCCEEDED(f(FOLDERID_Downloads, 0, NULL, &path_buf))) {
+    *result = FilePath(std::wstring(path_buf));
+    return true;
+  }
+  return GetUserDownloadsDirectorySafe(result);
+}
+
+bool GetUserMusicDirectory(FilePath* result) {
+  return GetUserDirectory(CSIDL_MYMUSIC, result);
+}
+
+bool GetUserPicturesDirectory(FilePath* result) {
+  return GetUserDirectory(CSIDL_MYPICTURES, result);
+}
+
+bool GetUserVideosDirectory(FilePath* result) {
+  return GetUserDirectory(CSIDL_MYVIDEO, result);
+}
+
+bool ProcessNeedsProfileDir(const std::string& process_type) {
+  // On windows we don't want subprocesses other than the browser process and
+  // service processes to be able to use the profile directory because if it
+  // lies on a network share the sandbox will prevent us from accessing it.
+  // TODO(pastarmovj): For now gpu and plugin broker processes are whitelisted
+  // too because they do use the profile dir in some way but this must be
+  // investigated and fixed if possible.
+  return process_type.empty() ||
+         process_type == switches::kServiceProcess ||
+         process_type == switches::kGpuProcess ||
+         process_type == switches::kNaClBrokerProcess ||
+         process_type == switches::kNaClLoaderProcess ||
+         process_type == switches::kPpapiBrokerProcess;
+}
+
+}  // namespace chrome
diff --git a/chrome/common/chrome_result_codes.h b/chrome/common/chrome_result_codes.h
new file mode 100644
index 0000000..a29de59
--- /dev/null
+++ b/chrome/common/chrome_result_codes.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_CHROME_RESULT_CODES_H_
+#define CHROME_COMMON_CHROME_RESULT_CODES_H_
+
+#include "content/public/common/result_codes.h"
+
+namespace chrome {
+
+enum ResultCode {
+  RESULT_CODE_CHROME_START = content::RESULT_CODE_LAST_CODE,
+
+  // An invalid command line url was given.
+  RESULT_CODE_INVALID_CMDLINE_URL = RESULT_CODE_CHROME_START,
+
+  // The process is of an unknown type.
+  RESULT_CODE_BAD_PROCESS_TYPE,
+
+  // A critical chrome file is missing.
+  RESULT_CODE_MISSING_DATA,
+
+  // Failed to make Chrome default browser (not used?).
+  RESULT_CODE_SHELL_INTEGRATION_FAILED,
+
+  // Machine level install exists
+  RESULT_CODE_MACHINE_LEVEL_INSTALL_EXISTS,
+
+  // Uninstall detected another chrome instance.
+  RESULT_CODE_UNINSTALL_CHROME_ALIVE,
+
+  // The user changed their mind.
+  RESULT_CODE_UNINSTALL_USER_CANCEL,
+
+  // Delete profile as well during uninstall.
+  RESULT_CODE_UNINSTALL_DELETE_PROFILE,
+
+  // Command line parameter is not supported.
+  RESULT_CODE_UNSUPPORTED_PARAM,
+
+  // Browser import hung and was killed.
+  RESULT_CODE_IMPORTER_HUNG,
+
+  // Trying to restart the browser we crashed.
+  RESULT_CODE_RESPAWN_FAILED,
+
+  // The EXP1, EXP2, EXP3, EXP4 are generic codes used to communicate some
+  // simple outcome back to the process that launched us. This is used for
+  // experiments and the actual meaning depends on the experiment.
+  // (only EXP2 is used?)
+  RESULT_CODE_NORMAL_EXIT_EXP1,
+  RESULT_CODE_NORMAL_EXIT_EXP2,
+  RESULT_CODE_NORMAL_EXIT_EXP3,
+  RESULT_CODE_NORMAL_EXIT_EXP4,
+
+  // For experiments this return code means that the user canceled causes the
+  // did_run "dr" signal to be reset soi this chrome run does not count as
+  // active chrome usage.
+  RESULT_CODE_NORMAL_EXIT_CANCEL,
+
+  // The profile was in use on another host.
+  RESULT_CODE_PROFILE_IN_USE,
+
+  // Failed to pack an extension via the cmd line.
+  RESULT_CODE_PACK_EXTENSION_ERROR,
+
+  // Failed to silently uninstall an extension.
+  RESULT_CODE_UNINSTALL_EXTENSION_ERROR,
+
+  // The browser process exited early by passing the command line to another
+  // running browser.
+  RESULT_CODE_NORMAL_EXIT_PROCESS_NOTIFIED,
+
+  // A dummy value we should not use. See crbug.com/152285.
+  RESULT_CODE_NOTUSED_1,
+
+  // Failed to install an item from the webstore when the kInstallFromWebstore
+  // command line flag was present.
+  RESULT_CODE_INSTALL_FROM_WEBSTORE_ERROR_2,
+
+  // A dummy value we should not use. See crbug.com/152285.
+  RESULT_CODE_NOTUSED_2,
+
+  // Returned when the user has not yet accepted the EULA.
+  RESULT_CODE_EULA_REFUSED,
+
+  // Last return code (keep this last).
+  RESULT_CODE_CHROME_LAST_CODE,
+};
+
+}  // namespace chrome
+
+#endif  // CHROME_COMMON_CHROME_RESULT_CODES_H_
diff --git a/chrome/common/chrome_sandbox_type_mac.h b/chrome/common/chrome_sandbox_type_mac.h
new file mode 100644
index 0000000..fb4c51f
--- /dev/null
+++ b/chrome/common/chrome_sandbox_type_mac.h
@@ -0,0 +1,16 @@
+// 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.
+
+#ifndef CHROME_COMMON_CHROME_SANDBOX_TYPE_MAC_H_
+#define CHROME_COMMON_CHROME_SANDBOX_TYPE_MAC_H_
+
+#include "content/public/common/sandbox_type_mac.h"
+
+enum ChromeSandboxType {
+  CHROME_SANDBOX_TYPE_FIRST_TYPE = content::SANDBOX_TYPE_AFTER_LAST_TYPE,
+
+  CHROME_SANDBOX_TYPE_NACL_LOADER = CHROME_SANDBOX_TYPE_FIRST_TYPE,
+};
+
+#endif  // CHROME_COMMON_CHROME_SANDBOX_TYPE_MAC_H_
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
new file mode 100644
index 0000000..f5f8c8a
--- /dev/null
+++ b/chrome/common/chrome_switches.cc
@@ -0,0 +1,1632 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/chrome_switches.h"
+
+#include "base/base_switches.h"
+#include "base/command_line.h"
+
+namespace switches {
+
+// -----------------------------------------------------------------------------
+// Can't find the switch you are looking for? try looking in
+// base/base_switches.cc instead.
+//
+// When commenting your switch, please use the same voice as surrounding
+// comments. Imagine "This switch..." at the beginning of the phrase, and it'll
+// all work out.
+// -----------------------------------------------------------------------------
+
+// Enables or disables the "action box" UI in the toolbar.
+const char kActionBox[]                     = "action-box";
+
+// Allows third-party content included on a page to prompt for a HTTP basic
+// auth username/password pair.
+const char kAllowCrossOriginAuthPrompt[]    = "allow-cross-origin-auth-prompt";
+
+// On ChromeOS, file:// access is disabled except for certain whitelisted
+// directories. This switch re-enables file:// for testing.
+const char kAllowFileAccess[]               = "allow-file-access";
+
+// Allows non-https URL for background_page for hosted apps.
+const char kAllowHTTPBackgroundPage[]       = "allow-http-background-page";
+
+// Allows the browser to load extensions that lack a modern manifest when that
+// would otherwise be forbidden.
+const char kAllowLegacyExtensionManifests[] =
+    "allow-legacy-extension-manifests";
+
+// Specifies comma-separated list of extension ids to grant access to TCP/UDP
+// socket APIs.
+const char kAllowNaClSocketAPI[]            = "allow-nacl-socket-api";
+
+// Don't block outdated plugins.
+const char kAllowOutdatedPlugins[]          = "allow-outdated-plugins";
+
+// By default, an https page cannot run JavaScript, CSS or plug-ins from http
+// URLs. This provides an override to get the old insecure behavior.
+const char kAllowRunningInsecureContent[]   = "allow-running-insecure-content";
+
+// Allows injecting extensions and user scripts on the extensions gallery
+// site. Normally prevented for security reasons, but can be useful for
+// automation testing of the gallery.
+const char kAllowScriptingGallery[]         = "allow-scripting-gallery";
+
+// Prevents Chrome from requiring authorization to run certain widely installed
+// but less commonly used plug-ins.
+const char kAlwaysAuthorizePlugins[]        = "always-authorize-plugins";
+
+// Specifies that the extension-app with the specified id should be launched
+// according to its configuration.
+const char kAppId[]                         = "app-id";
+
+// Specifies that the associated value should be launched in "application"
+// mode.
+const char kApp[]                           = "app";
+
+// Specifies the initial size for application windows launched with --app.
+// --app-window-size=w,h
+const char kAppWindowSize[]                 = "app-window-size";
+
+// A URL for the server which assigns channel ids for server pushed app
+// notifications.
+const char kAppNotifyChannelServerURL[]     = "app-notify-channel-server-url";
+
+// Overrides the apps checkout URL, which is used to determine when to expose
+// some private APIs.
+const char kAppsCheckoutURL[]               = "apps-checkout-url";
+
+// The URL that the webstore APIs download extensions from.
+// Note: the URL must contain one '%s' for the extension ID.
+const char kAppsGalleryDownloadURL[]        = "apps-gallery-download-url";
+
+// A setting to cause extension/app installs from the webstore skip the normal
+// confirmation dialog. A value of 'accept' means to always act as if the dialog
+// was accepted, and 'cancel' means to always act as if the dialog was
+// cancelled.
+const char kAppsGalleryInstallAutoConfirmForTests[] =
+    "apps-gallery-install-auto-confirm-for-tests";
+
+// Allows the webstorePrivate APIs to return browser (aka sync) login tokens to
+// be used for auto-login in the Web Store (normally they do not).
+const char kAppsGalleryReturnTokens[]       = "apps-gallery-return-tokens";
+
+// The URL to use for the gallery link in the app launcher.
+const char kAppsGalleryURL[]                = "apps-gallery-url";
+
+// The update url used by gallery/webstore extensions.
+const char kAppsGalleryUpdateURL[]          = "apps-gallery-update-url";
+
+// Whether to always use the new app install bubble when installing an app.
+const char kAppsNewInstallBubble[]          = "apps-new-install-bubble";
+
+// Disable throbber for extension apps.
+const char kAppsNoThrob[]                   = "apps-no-throb";
+
+// Whitelist of servers that Negotiate will generate delegated Kerberos tickets
+// for.
+const char kAuthNegotiateDelegateWhitelist[] =
+    "auth-negotiate-delegate-whitelist";
+
+// HTTP authentication schemes to enable. This is a comma-separated list of
+// authentication schemes (basic, digest, ntlm, and negotiate). By default all
+// schemes are enabled. The primary use of this command line flag is to help
+// triage authentication-related issues reported by end-users.
+const char kAuthSchemes[]                   = "auth-schemes";
+
+// Whitelist of servers which NTLM and Negotiate can automatically authenticate
+// with using the default credentials of the currently logged in user.
+const char kAuthServerWhitelist[]           = "auth-server-whitelist";
+
+// A flag that is used to tell Chrome that it was launched automatically at
+// computer startup and not by some user action.
+const char kAutoLaunchAtStartup[]           = "auto-launch-at-startup";
+
+// Flag used to tell Chrome the base url of the Autofill service.
+const char kAutofillServiceUrl[]            = "autofill-service-url";
+
+// The value of this switch tells the app to listen for and broadcast
+// automation-related messages on IPC channel with the given ID.
+const char kAutomationClientChannelID[]     = "automation-channel";
+
+// Causes the automation provider to reinitialize its IPC channel instead of
+// shutting down when a client disconnects.
+const char kAutomationReinitializeOnChannelError[] =
+    "automation-reinitialize-on-channel-error";
+
+// How often (in seconds) to check for updates. Should only be used for testing
+// purposes.
+const char kCheckForUpdateIntervalSec[]     = "check-for-update-interval";
+
+// Checks the cloud print connector policy, informing the service process if
+// the policy is set to disallow the connector, then quits.
+const char kCheckCloudPrintConnectorPolicy[] =
+    "check-cloud-print-connector-policy";
+
+// Tells Chrome to delay shutdown (for a specified number of seconds) when a
+// Chrome Frame automation channel is closed.
+const char kChromeFrameShutdownDelay[]      = "chrome-frame-shutdown-delay";
+
+// Tells chrome to load the specified version of chrome.dll on Windows. If this
+// version cannot be loaded, Chrome will exit.
+const char kChromeVersion[]                 = "chrome-version";
+
+// Comma-separated list of SSL cipher suites to disable.
+const char kCipherSuiteBlacklist[]          = "cipher-suite-blacklist";
+
+// Clears the token service before using it. This allows simulating the
+// expiration of credentials during testing.
+const char kClearTokenService[]             = "clear-token-service";
+
+// Used with kCloudPrintFile. Tells Chrome to delete the file when finished
+// displaying the print dialog.
+const char kCloudPrintDeleteFile[]          = "cloud-print-delete-file";
+
+// Tells chrome to display the cloud print dialog and upload the specified file
+// for printing.
+const char kCloudPrintFile[]                = "cloud-print-file";
+
+// Specifies the mime type to be used when uploading data from the file
+// referenced by cloud-print-file. Defaults to "application/pdf" if
+// unspecified.
+const char kCloudPrintFileType[]            = "cloud-print-file-type";
+
+// Used with kCloudPrintFile to specify a JSON print ticket for the resulting
+// print job. Defaults to null if unspecified.
+const char kCloudPrintPrintTicket[]         = "cloud-print-print-ticket";
+
+// Used with kCloudPrintFile to specify a title for the resulting print job.
+const char kCloudPrintJobTitle[]            = "cloud-print-job-title";
+
+// The unique id to be used for this cloud print proxy instance.
+const char kCloudPrintProxyId[]             = "cloud-print-proxy-id";
+
+// The URL of the cloud print service to use, overrides any value stored in
+// preferences, and the default. Only used if the cloud print service has been
+// enabled (see enable-cloud-print).
+const char kCloudPrintServiceURL[]          = "cloud-print-service";
+
+// Comma-separated options to troubleshoot the component updater. Only valid
+// for the browser process.
+const char kComponentUpdaterDebug[]         = "component-updater-debug";
+
+// Causes the browser process to inspect loaded and registered DLLs for known
+// conflicts and warn the user.
+const char kConflictingModulesCheck[]       = "conflicting-modules-check";
+
+// Toggles a new version of the content settings dialog in options.
+const char kContentSettings2[]              = "new-content-settings";
+
+// The Country we should use. This is normally obtained from the operating
+// system during first run and cached in the preferences afterwards. This is a
+// string value, the 2 letter code from ISO 3166-1.
+const char kCountry[]                       = "country";
+
+// Causes the browser process to crash if browser threads are not responding
+// for the given number of seconds.
+const char kCrashOnHangSeconds[]            = "crash-on-hang-seconds";
+
+// Comma-separated list of BrowserThreads that cause browser process to crash
+// if the given browser thread is not responsive. UI,IO,DB,FILE,CACHE are the
+// list of BrowserThreads that are supported.
+//
+// For example:
+//    --crash-on-hang-threads=UI,IO --> Crash the browser if UI or IO thread
+//                                      is not responsive.
+const char kCrashOnHangThreads[]            = "crash-on-hang-threads";
+
+// Causes the browser process to crash if the number of browser threads that
+// are responding is equal to the given number.
+//
+// For example:
+//    --crash-on-live=1 --> Crash if only one thread is responsive and all
+//                          other threads are not responsive.
+const char kCrashOnLive[]                   = "crash-on-live";
+
+// Some platforms like ChromeOS default to empty desktop.
+// Browser tests may need to add this switch so that at least one browser
+// instance is created on startup.
+// TODO(nkostylev): Investigate if this switch could be removed.
+// (http://crbug.com/148675)
+const char kCreateBrowserOnStartupForTests[] =
+    "create-browser-on-startup-for-tests";
+
+// Enables a frame context menu item that toggles the frame in and out of glass
+// mode (Windows Vista and up only).
+const char kDebugEnableFrameToggle[]        = "debug-enable-frame-toggle";
+
+// Adds debugging entries such as Inspect Element to context menus of packed
+// apps.
+const char kDebugPackedApps[]        = "debug-packed-apps";
+
+// Enables support to debug printing subsystem.
+const char kDebugPrint[]                    = "debug-print";
+
+// Specifies the URL at which to fetch configuration policy from the device
+// management backend. Specifying this switch turns on managed policy from the
+// device management backend.
+const char kDeviceManagementUrl[]           = "device-management-url";
+
+// Triggers a plethora of diagnostic modes.
+const char kDiagnostics[]                   = "diagnostics";
+
+// Replaces the audio IPC layer for <audio> and <video> with a mock audio
+// device, useful when using remote desktop or machines without sound cards.
+// This is temporary until we fix the underlying problem.
+
+// Disables the experimental asynchronous DNS client.
+const char kDisableAsyncDns[]               = "disable-async-dns";
+
+// Disables CNAME lookup of the host when generating the Kerberos SPN for a
+// Negotiate challenge. See HttpAuthHandlerNegotiate::CreateSPN for more
+// background.
+const char kDisableAuthNegotiateCnameLookup[] =
+    "disable-auth-negotiate-cname-lookup";
+
+// Disables background mode (background apps will not keep chrome running in
+// the background).
+const char kDisableBackgroundMode[]         = "disable-background-mode";
+
+// Disable several subsystems which run network requests in the background.
+// This is for use when doing network performance testing to avoid noise in the
+// measurements.
+const char kDisableBackgroundNetworking[]   = "disable-background-networking";
+
+// Disables the bundled PPAPI version of Flash (if it's enabled by default).
+const char kDisableBundledPpapiFlash[]      = "disable-bundled-ppapi-flash";
+
+// Disables the bookmark autocomplete provider (BookmarkProvider).
+const char kDisableBookmarkAutocompleteProvider[] =
+    "disable-bookmark-autocomplete-provider";
+
+// Disables the client-side phishing detection feature. Note that even if
+// client-side phishing detection is enabled, it will only be active if the
+// user has opted in to UMA stats and SafeBrowsing is enabled in the
+// preferences.
+const char kDisableClientSidePhishingDetection[] =
+    "disable-client-side-phishing-detection";
+
+const char kDisableComponentUpdate[]        = "disable-component-update";
+
+// Disables establishing certificate revocation information by downloading a
+// set of CRLs rather than performing on-line checks.
+const char kDisableCRLSets[]                = "disable-crl-sets";
+
+// Disables the custom JumpList on Windows 7.
+const char kDisableCustomJumpList[]         = "disable-custom-jumplist";
+
+// Disables installation of default apps on first run. This is used during
+// automated testing.
+const char kDisableDefaultApps[]            = "disable-default-apps";
+
+// Disables retrieval of PAC URLs from DHCP as per the WPAD standard.
+const char kDisableDhcpWpad[]               = "disable-dhcp-wpad";
+
+// Disable extensions.
+const char kDisableExtensions[]             = "disable-extensions";
+
+// Disable checking for user opt-in for extensions that want to inject script
+// into file URLs (ie, always allow it). This is used during automated testing.
+const char kDisableExtensionsFileAccessCheck[] =
+    "disable-extensions-file-access-check";
+
+// Disable the net::URLRequestThrottlerManager functionality for
+// requests originating from extensions.
+const char kDisableExtensionsHttpThrottling[] =
+    "disable-extensions-http-throttling";
+
+// Disable mandatory enforcement of web_accessible_resources in extensions.
+const char kDisableExtensionsResourceWhitelist[] =
+    "disable-extensions-resource-whitelist";
+
+// Disables improved SafeBrowsing download protection.
+const char kDisableImprovedDownloadProtection[] =
+    "disable-improved-download-protection";
+
+// Disable the Infinite Cache.
+const char kDisableInfiniteCache[]          = "disable-infinite-cache";
+
+// Disable the internal Flash Player.
+const char kDisableInternalFlash[]          = "disable-internal-flash";
+
+// Don't resolve hostnames to IPv6 addresses. This can be used when debugging
+// issues relating to IPv6, but shouldn't otherwise be needed. Be sure to file
+// bugs if something isn't working properly in the presence of IPv6. This flag
+// can be overidden by the "enable-ipv6" flag.
+const char kDisableIPv6[]                   = "disable-ipv6";
+
+// Disables IP Pooling within the networks stack (SPDY only). When a connection
+// is needed for a domain which shares an IP with an existing connection,
+// attempt to use the existing connection.
+const char kDisableIPPooling[]              = "disable-ip-pooling";
+
+// Disables the menu on the NTP for accessing sessions from other devices.
+const char kDisableNTPOtherSessionsMenu[]   = "disable-ntp-other-sessions-menu";
+
+// Disable pop-up blocking.
+const char kDisablePopupBlocking[]          = "disable-popup-blocking";
+
+// Disable speculative TCP/IP preconnection.
+const char kDisablePreconnect[]             = "disable-preconnect";
+
+// Normally when the user attempts to navigate to a page that was the result of
+// a post we prompt to make sure they want to. This switch may be used to
+// disable that check. This switch is used during automated testing.
+const char kDisablePromptOnRepost[]         = "disable-prompt-on-repost";
+
+// Prevents the URLs of BackgroundContents from being remembered and
+// re-launched when the browser restarts.
+const char kDisableRestoreBackgroundContents[] =
+    "disable-restore-background-contents";
+
+// Disables restoring session state (cookies, session storage, etc.) when
+// restoring the browsing session.
+const char kDisableRestoreSessionState[]    = "disable-restore-session-state";
+
+// Disables throttling prints initiated by scripts.
+const char kDisableScriptedPrintThrottling[] =
+    "disable-scripted-print-throttling";
+
+// Disables syncing browser data to a Google Account.
+const char kDisableSync[]                   = "disable-sync";
+
+// Disables syncing of app settings.
+const char kDisableSyncAppSettings[]        = "disable-sync-app-settings";
+
+// Disables syncing of apps.
+const char kDisableSyncApps[]               = "disable-sync-apps";
+
+// Disable syncing app notifications.
+const char kDisableSyncAppNotifications[]   = "disable-sync-app-notifications";
+
+// Disables syncing of autofill.
+const char kDisableSyncAutofill[]           = "disable-sync-autofill";
+
+// Disables syncing of autofill Profile.
+const char kDisableSyncAutofillProfile[]    = "disable-sync-autofill-profile";
+
+// Disables syncing of bookmarks.
+const char kDisableSyncBookmarks[]          = "disable-sync-bookmarks";
+
+// Disables syncing extension settings.
+const char kDisableSyncExtensionSettings[]  = "disable-sync-extension-settings";
+
+// Disables syncing of extensions.
+const char kDisableSyncExtensions[]         = "disable-sync-extensions";
+
+// Disables syncing browser passwords.
+const char kDisableSyncPasswords[]          = "disable-sync-passwords";
+
+// Disables syncing of preferences.
+const char kDisableSyncPreferences[]        = "disable-sync-preferences";
+
+// Disable syncing custom search engines.
+const char kDisableSyncSearchEngines[]      = "disable-sync-search-engines";
+
+// Disables syncing of themes.
+const char kDisableSyncThemes[]             = "disable-sync-themes";
+
+// Disables syncing browser typed urls.
+const char kDisableSyncTypedUrls[]          = "disable-sync-typed-urls";
+
+// Allows disabling of translate from the command line to assist with automated
+// browser testing (e.g. Selenium/WebDriver). Normal browser users should
+// disable translate with the preference.
+const char kDisableTranslate[]              = "disable-translate";
+
+// Disables TLS Channel ID extension.
+const char kDisableTLSChannelID[]           = "disable-tls-channel-id";
+
+// Disables the backend service for web resources.
+const char kDisableWebResources[]           = "disable-web-resources";
+
+// Disables the website settings UI.
+const char kDisableWebsiteSettings[]         = "disable-website-settings";
+
+// Some tests seem to require the application to close when the last
+// browser window is closed. Thus, we need a switch to force this behavior
+// for ChromeOS Aura, disable "zero window mode".
+// TODO(pkotwicz): Investigate if this bug can be removed.
+// (http://crbug.com/119175)
+extern const char kDisableZeroBrowsersOpenForTests[] =
+    "disable-zero-browsers-open-for-tests";
+
+// Use a specific disk cache location, rather than one derived from the
+// UserDatadir.
+const char kDiskCacheDir[]                  = "disk-cache-dir";
+
+// Forces the maximum disk space to be used by the disk cache, in bytes.
+const char kDiskCacheSize[]                 = "disk-cache-size";
+
+const char kDnsLogDetails[]                 = "dns-log-details";
+
+// Disables prefetching of DNS information.
+const char kDnsPrefetchDisable[]            = "dns-prefetch-disable";
+
+// Dump any accumualted histograms to the log when browser terminates (requires
+// logging to be enabled to really do anything). Used by developers and test
+// scripts.
+const char kDumpHistogramsOnExit[]          = "dump-histograms-on-exit";
+
+// Enables the experimental asynchronous DNS client.
+const char kEnableAsyncDns[]                = "enable-async-dns";
+
+// Enables the inclusion of non-standard ports when generating the Kerberos SPN
+// in response to a Negotiate challenge. See
+// HttpAuthHandlerNegotiate::CreateSPN for more background.
+const char kEnableAuthNegotiatePort[]       = "enable-auth-negotiate-port";
+
+// Enables the pre- and auto-login features. When a user signs in to sync, the
+// browser's cookie jar is pre-filled with GAIA cookies. When the user visits a
+// GAIA login page, an info bar can help the user login.
+const char kEnableAutologin[]               = "enable-autologin";
+
+// Enables the benchmarking extensions.
+const char kEnableBenchmarking[]            = "enable-benchmarking";
+
+// Enables the bundled PPAPI version of Flash.
+const char kEnableBundledPpapiFlash[]       = "enable-bundled-ppapi-flash";
+
+// Enables the new cloud policy stack.
+const char kEnableCloudPolicyService[]      = "enable-cloud-policy-service";
+
+// This applies only when the process type is "service". Enables the Cloud
+// Print Proxy component within the service process.
+const char kEnableCloudPrintProxy[]         = "enable-cloud-print-proxy";
+
+// Enables fetching the user's contacts from Google and showing them in the
+// Chrome OS apps list.
+const char kEnableContacts[]                = "enable-contacts";
+
+// Enables web developers to create apps for Chrome without using crx packages.
+const char kEnableCrxlessWebApps[]          = "enable-crxless-web-apps";
+
+// If true devtools experimental settings are enabled.
+const char kEnableDevToolsExperiments[]     = "enable-devtools-experiments";
+
+// Enables Drive v2 API instead of Google Documents List API.
+const char kEnableDriveV2Api[]              = "enable-drive-v2-api";
+
+// Enables an interactive autocomplete UI and a way to invoke this UI from
+// WebKit by enabling HTMLFormElement#requestAutocomplete (and associated
+// autocomplete* events and logic).
+const char kEnableInteractiveAutocomplete[] = "enable-interactive-autocomplete";
+
+// Enables extensions to be easily installed from sites other than the web
+// store. Without this flag, they can still be installed, but must be manually
+// dragged onto chrome://extensions/.
+const char kEasyOffStoreExtensionInstall[] = "easy-off-store-extension-install";
+
+// Enables extension APIs that are in development.
+const char kEnableExperimentalExtensionApis[] =
+    "enable-experimental-extension-apis";
+
+// Enable autofill for new elements like checkboxes. crbug.com/157636
+const char kEnableExperimentalFormFilling[] =
+    "enable-experimental-form-filling";
+
+// Enables logging for extension activity.
+const char kEnableExtensionActivityLogging[] =
+    "enable-extension-activity-logging";
+
+// Enables the extension activity UI.
+const char kEnableExtensionActivityUI[]     = "enable-extension-activity-ui";
+
+// Enables or disables showing extensions in the action box.
+const char kExtensionsInActionBox[]         = "extensions-in-action-box";
+
+// Enables experimental timeline API.
+const char kEnableExtensionTimelineApi[]    = "enable-extension-timeline-api";
+
+// Applies the chrome style to any dialog based on ConstrainedWindowViews.
+const char kEnableChromeStyleDialogs[]      = "enable-chrome-style-dialogs";
+
+// By default, cookies are not allowed on file://. They are needed for testing,
+// for example page cycler and layout tests. See bug 1157243.
+const char kEnableFileCookies[]             = "enable-file-cookies";
+
+// Enable HTTP pipelining. Attempt to pipeline HTTP connections. Heuristics will
+// try to figure out if pipelining can be used for a given host and request.
+// Without this flag, pipelining will never be used.
+const char kEnableHttpPipelining[]          = "enable-http-pipelining";
+
+// Enable Instant extended API.
+const char kEnableInstantExtendedAPI[]      = "enable-instant-extended-api";
+
+// Enables IPv6 support, even if probes suggest that it may not be fully
+// supported. Some probes may require internet connections, and this flag will
+// allow support independent of application testing. This flag overrides
+// "disable-ipv6" which appears elswhere in this file.
+const char kEnableIPv6[]                    = "enable-ipv6";
+
+/// Enables the IPC fuzzer for reliability testing
+const char kEnableIPCFuzzing[]              = "enable-ipc-fuzzing";
+
+// Enables IP Pooling within the networks stack (SPDY only). When a connection
+// is needed for a domain which shares an IP with an existing connection,
+// attempt to use the existing connection.
+const char kEnableIPPooling[]               = "enable-ip-pooling";
+
+// The managed storage extension API is disabled by default for now. This
+// flag can be used to enable it until http://crbug.com/108992 is fixed.
+extern const char kEnableManagedStorage[]   = "enable-managed-storage";
+
+// Allows reporting memory info (JS heap size) to page.
+const char kEnableMemoryInfo[]              = "enable-memory-info";
+
+// Enables metrics recording and reporting in the browser startup sequence, as
+// if this was an official Chrome build where the user allowed metrics
+// reporting. This is used for testing only.
+const char kEnableMetricsReportingForTesting[] =
+    "enable-metrics-reporting-for-testing";
+
+// Runs the Native Client inside the renderer process and enables GPU plugin
+// (internally adds lEnableGpuPlugin to the command line).
+const char kEnableNaCl[]                    = "enable-nacl";
+
+// Enables debugging via RSP over a socket.
+const char kEnableNaClDebug[]               = "enable-nacl-debug";
+
+// Uses NaCl manifest URL to choose whether NaCl program will be debugged by
+// debug stub.
+// Switch value format: [!]pattern1,pattern2,...,patternN. Each pattern uses
+// the same syntax as patterns in Chrome extension manifest. The only difference
+// is that * scheme matches all schemes instead of matching only http and https.
+// If the value doesn't start with !, a program will be debugged if manifest URL
+// matches any pattern. If the value starts with !, a program will be debugged
+// if manifest URL does not match any pattern.
+const char kNaClDebugMask[]                 = "nacl-debug-mask";
+
+// Enables the SRPC Proxy for NaCl. The default is the Chrome IPC based  proxy.
+// TODO(bbudge) remove this after we switch to IPC and remove SRPC.
+const char kEnableNaClSRPCProxy[]           = "enable-nacl-srpc-proxy";
+
+// Enables hardware exception handling via debugger process.
+const char kEnableNaClExceptionHandling[]   = "enable-nacl-exception-handling";
+
+// Enables the native messaging extensions API.
+const char kEnableNativeMessaging[]         = "enable-native-messaging";
+
+// Enables the new Autofill UI, which is part of the browser process rather than
+// part of the renderer process.  http://crbug.com/51644
+const char kEnableNewAutofillUi[]           = "enable-new-autofill-ui";
+
+// Enables new Autofill heuristics, such as adding support for new field types.
+const char kEnableNewAutofillHeuristics[]   = "enable-new-autofill-heuristics";
+
+// Enables NPN and SPDY. In case server supports SPDY, browser will use SPDY.
+const char kEnableNpn[]                     = "enable-npn";
+
+// Enables NPN with HTTP. It means NPN is enabled but SPDY won't be used.
+// HTTP is still used for all requests.
+const char kEnableNpnHttpOnly[]             = "enable-npn-http";
+
+// Enables panels (always on-top docked pop-up windows).
+const char kEnablePanels[]                  = "enable-panels";
+
+// Enables password generation when we detect that the user is going through
+// account creation.
+const char kEnablePasswordGeneration[]      = "enable-password-generation";
+
+// Enables content settings based on host *and* plug-in in the user
+// preferences.
+const char kEnableResourceContentSettings[] =
+    "enable-resource-content-settings";
+
+// Enables the installation and usage of Portable Native Client.
+const char kEnablePnacl[]                   = "enable-pnacl";
+
+// Enables tracking of tasks in profiler for viewing via about:profiler.
+// To predominantly disable tracking (profiling), use the command line switch:
+// --enable-profiling=0
+// Some tracking will still take place at startup, but it will be turned off
+// during chrome_browser_main.
+const char kEnableProfiling[]               = "enable-profiling";
+
+
+// Controls the support for SDCH filtering (dictionary based expansion of
+// content). By default SDCH filtering is enabled. To disable SDCH filtering,
+// use "--enable-sdch=0" as command line argument. SDCH is currently only
+// supported server-side for searches on google.com.
+const char kEnableSdch[]                    = "enable-sdch";
+
+// Enable the settings WebUI dialog that installs as a packaged app.
+const char kEnableSettingsApp[]             = "enable-settings-app";
+
+// Enable SPDY/3. This is a temporary testing flag.
+const char kEnableSpdy3[]                   = "enable-spdy3";
+
+// Enable SPDY CREDENTIAL frame support.  This is a temporary testing flag.
+const char kEnableSpdyCredentialFrames[]    = "enable-spdy-credential-frames";
+
+// Enables auto correction for misspelled words.
+const char kEnableSpellingAutoCorrect[]    = "enable-spelling-auto-correct";
+
+// Enables the stacked tabstrip.
+const char kEnableStackedTabStrip[]         = "enable-stacked-tab-strip";
+
+// Enables experimental suggestions pane in New Tab page.
+const char kEnableSuggestionsTabPage[]      = "enable-suggestions-ntp";
+
+// Enables syncing of history delete directives.
+const char kEnableSyncHistoryDeleteDirectives[] =
+    "enable-sync-history-delete-directives";
+
+// Disables syncing browser sessions. Will override kEnableSyncTabs.
+const char kDisableSyncTabs[]               = "disable-sync-tabs";
+
+// Enables context menu for selecting groups of tabs.
+const char kEnableTabGroupsContextMenu[]    = "enable-tab-groups-context-menu";
+
+// Spawns threads to watch for excessive delays in specified message loops.
+// User should set breakpoints on Alarm() to examine problematic thread.
+//
+// Usage:   -enable-watchdog=[ui][io]
+//
+// Order of the listed sub-arguments does not matter.
+const char kEnableWatchdog[]                = "enable-watchdog";
+
+// Uses WebSocket over SPDY.
+const char kEnableWebSocketOverSpdy[]       = "enable-websocket-over-spdy";
+
+// Explicitly allows additional ports using a comma-separated list of port
+// numbers.
+const char kExplicitlyAllowedPorts[]        = "explicitly-allowed-ports";
+
+// The time in seconds that an extension event page can be idle before it
+// is shut down.
+const char kEventPageIdleTime[]             = "event-page-idle-time";
+
+// The time in seconds that an extension event page has between being notified
+// of its impending unload and that unload happening.
+const char kEventPageUnloadingTime[]        = "event-page-unloading-time";
+
+// Marks a renderer as extension process.
+const char kExtensionProcess[]              = "extension-process";
+
+// Frequency in seconds for Extensions auto-update.
+const char kExtensionsUpdateFrequency[]     = "extensions-update-frequency";
+
+// These two flags are added around the switches about:flags adds to the
+// command line. This is useful to see which switches were added by about:flags
+// on about:version. They don't have any effect.
+const char kFlagSwitchesBegin[]             = "flag-switches-begin";
+const char kFlagSwitchesEnd[]               = "flag-switches-end";
+
+// Alternative feedback server to use when submitting user feedback
+const char kFeedbackServer[]                = "feedback-server";
+
+// The file descriptor limit is set to the value of this switch, subject to the
+// OS hard limits. Useful for testing that file descriptor exhaustion is
+// handled gracefully.
+const char kFileDescriptorLimit[]           = "file-descriptor-limit";
+
+// Displays the First Run experience when the browser is started, regardless of
+// whether or not it's actually the first run.
+const char kFirstRun[]                      = "first-run";
+
+// Force use of synchronous spell checking. If this is enabled, unified spell
+// checking will be disabled since it relies on asynchronous spellchecking.
+const char kForceSyncSpellCheck[]           = "force-sync-spellcheck";
+
+// Enables using GAIA information to populate profile name and icon.
+const char kGaiaProfileInfo[]               = "gaia-profile-info";
+
+// Specifies an alternate URL to use for retrieving the search domain for
+// Google. Useful for testing.
+const char kGoogleSearchDomainCheckURL[] = "google-search-domain-check-url";
+
+// Specifies a custom name for the GSSAPI library to load.
+const char kGSSAPILibraryName[]             = "gssapi-library-name";
+
+// These flags show the man page on Linux. They are equivalent to each
+// other.
+const char kHelp[]                          = "help";
+const char kHelpShort[]                     = "h";
+
+// Makes Windows happy by allowing it to show "Enable access to this program"
+// checkbox in Add/Remove Programs->Set Program Access and Defaults. This only
+// shows an error box because the only way to hide Chrome is by uninstalling
+// it.
+const char kHideIcons[]                     = "hide-icons";
+
+// Specifies which page will be displayed in newly-opened tabs. We need this
+// for testing purposes so that the UI tests don't depend on what comes up for
+// http://google.com.
+const char kHomePage[]                      = "homepage";
+
+// Comma-separated list of rules that control how hostnames are mapped.
+//
+// For example:
+//    "MAP * 127.0.0.1" --> Forces all hostnames to be mapped to 127.0.0.1
+//    "MAP *.google.com proxy" --> Forces all google.com subdomains to be
+//                                 resolved to "proxy".
+//    "MAP test.com [::1]:77 --> Forces "test.com" to resolve to IPv6 loopback.
+//                               Will also force the port of the resulting
+//                               socket address to be 77.
+//    "MAP * baz, EXCLUDE www.google.com" --> Remaps everything to "baz",
+//                                            except for "www.google.com".
+//
+// These mappings apply to the endpoint host in a net::URLRequest (the TCP
+// connect and host resolver in a direct connection, and the CONNECT in an http
+// proxy connection, and the endpoint host in a SOCKS proxy connection).
+const char kHostRules[]                     = "host-rules";
+
+// The maximum number of concurrent host resolve requests (i.e. DNS) to allow
+// (not counting backup attempts which would also consume threads).
+// --host-resolver-retry-attempts must be set to zero for this to be exact.
+const char kHostResolverParallelism[]       = "host-resolver-parallelism";
+
+// The maximum number of retry attempts to resolve the host. Set this to zero
+// to disable host resolver retry attempts.
+const char kHostResolverRetryAttempts[]     = "host-resolver-retry-attempts";
+
+// Takes the JSON-formatted HSTS specification and loads it as if it were a
+// preloaded HSTS entry. Takes precedence over both website-specified rules and
+// built-in rules. The JSON format is the same as that persisted in
+// <profile_dir>/Default/TransportSecurity
+const char kHstsHosts[]                     = "hsts-hosts";
+
+// Performs importing from another browser. The value associated with this
+// setting encodes the target browser and what items to import.
+const char kImport[]                        = "import";
+
+// Performs bookmark importing from an HTML file. The value associated with
+// this setting encodes the file path. It may be used jointly with kImport.
+const char kImportFromFile[]                = "import-from-file";
+
+// Causes the browser to launch directly in incognito mode.
+const char kIncognito[]                     = "incognito";
+
+// Causes Chrome to attempt to get metadata from the webstore for the
+// app/extension ID given, and then prompt the user to download and install it.
+const char kInstallFromWebstore[]    = "install-from-webstore";
+
+// URL to use for instant. If specified this overrides the url from the
+// TemplateURL.
+const char kInstantURL[]                    = "instant-url";
+
+// Used for testing - keeps browser alive after last browser window closes.
+const char kKeepAliveForTest[]              = "keep-alive-for-test";
+
+// Enable Kiosk mode.
+const char kKioskMode[]                     = "kiosk";
+
+// Print automatically in kiosk mode. |kKioskMode| must be set as well.
+// See http://crbug.com/31395.
+const char kKioskModePrinting[]             = "kiosk-printing";
+
+// Comma-separated list of directories with component extensions to load.
+const char kLoadComponentExtension[]        = "load-component-extension";
+
+// If present, cloud policy will be loaded and applied once the user is signed
+// in to the browser.
+const char kLoadCloudPolicyOnSignin[]        = "load-cloud-policy-on-signin";
+
+// Loads an extension from the specified directory.
+const char kLoadExtension[]                 = "load-extension";
+
+// Loads the opencryptoki library into NSS at startup. This is only needed
+// temporarily for developers who need to work on WiFi/VPN certificate code.
+//
+// TODO(gspencer): Remove this switch once cryptohomed work is finished:
+// http://crosbug.com/12295 and http://crosbug.com/12304
+const char kLoadOpencryptoki[]              = "load-opencryptoki";
+
+// Enables displaying net log events on the command line, or writing the events
+// to a separate file if a file name is given.
+const char kLogNetLog[]                     = "log-net-log";
+
+// Uninstalls an extension with the specified extension id.
+const char kUninstallExtension[]            = "uninstall-extension";
+
+// Starts the browser in managed mode.
+const char kManaged[]                       = "managed";
+
+// Makes Chrome default browser
+const char kMakeDefaultBrowser[]            = "make-default-browser";
+
+// Forces the maximum disk space to be used by the media cache, in bytes.
+const char kMediaCacheSize[]                = "media-cache-size";
+
+// Enables dynamic loading of the Memory Profiler DLL, which will trace all
+// memory allocations during the run.
+const char kMemoryProfiling[]               = "memory-profile";
+
+// Enables histograming of tasks served by MessageLoop. See
+// about:histograms/Loop for results, which show frequency of messages on each
+// thread, including APC count, object signalling count, etc.
+const char kMessageLoopHistogrammer[]       = "message-loop-histogrammer";
+
+// Enables the recording of metrics reports but disables reporting. In contrast
+// to kDisableMetrics, this executes all the code that a normal client would
+// use for reporting, except the report is dropped rather than sent to the
+// server. This is useful for finding issues in the metrics code during UI and
+// performance tests.
+const char kMetricsRecordingOnly[]          = "metrics-recording-only";
+
+// Enables multiprofile Chrome.
+const char kMultiProfiles[]                 = "multi-profiles";
+
+// Native Client GDB debugger for loader. It needs switches calculated
+// at run time in order to work correctly. That's why NaClLoadCmdPrefix
+// flag can't be used.
+const char kNaClGdb[]                       = "nacl-gdb";
+
+// GDB script to pass to the nacl-gdb debugger at startup.
+const char kNaClGdbScript[]                 = "nacl-gdb-script";
+
+// On POSIX only: the contents of this flag are prepended to the nacl-loader
+// command line. Useful values might be "valgrind" or "xterm -e gdb --args".
+const char kNaClLoaderCmdPrefix[]           = "nacl-loader-cmd-prefix";
+
+// Sets the base logging level for the net log. Log 0 logs the most data.
+// Intended primarily for use with --log-net-log.
+const char kNetLogLevel[]                   = "net-log-level";
+
+// Disables the default browser check. Useful for UI/browser tests where we
+// want to avoid having the default browser info-bar displayed.
+const char kNoDefaultBrowserCheck[]         = "no-default-browser-check";
+
+// By default, an https page can load images, fonts or frames from an http
+// page. This switch overrides this to block this lesser mixed-content problem.
+const char kNoDisplayingInsecureContent[]   = "no-displaying-insecure-content";
+
+// Don't record/playback events when using record & playback.
+const char kNoEvents[]                      = "no-events";
+
+// Disables all experiments set on about:flags. Does not disable about:flags
+// itself. Useful if an experiment makes chrome crash at startup: One can start
+// chrome with --no-experiments, disable the problematic lab at about:flags and
+// then restart chrome without this switch again.
+const char kNoExperiments[]                 = "no-experiments";
+
+// Whether or not it's actually the first run. Overrides kFirstRun in case
+// you're for some reason tempted to pass them both.
+const char kNoFirstRun[]                    = "no-first-run";
+
+// Support a separate switch that enables the v8 playback extension.
+// The extension causes javascript calls to Date.now() and Math.random()
+// to return consistent values, such that subsequent loads of the same
+// page will result in consistent js-generated data and XHR requests.
+// Pages may still be able to generate inconsistent data from plugins.
+const char kNoJsRandomness[]                = "no-js-randomness";
+
+// Starts the browser outside of managed mode.
+const char kNoManaged[]                     = "no-managed";
+
+// Whether or not the browser should warn if the profile is on a network share.
+// This flag is only relevant for Windows currently.
+const char kNoNetworkProfileWarning[]       = "no-network-profile-warning";
+
+// Don't send hyperlink auditing pings
+const char kNoPings[]                       = "no-pings";
+
+// Don't use a proxy server, always make direct connections. Overrides any
+// other proxy server flags that are passed.
+const char kNoProxyServer[]                 = "no-proxy-server";
+
+// Disables the service process from adding itself as an autorun process. This
+// does not delete existing autorun registrations, it just prevents the service
+// from registering a new one.
+const char kNoServiceAutorun[]              = "no-service-autorun";
+
+// Does not automatically open a browser window on startup (used when
+// launching Chrome for the purpose of hosting background apps).
+const char kNoStartupWindow[]               = "no-startup-window";
+
+// Shows a desktop notification that the cloud print token has expired and that
+// user needs to re-authenticate.
+const char kNotifyCloudPrintTokenExpired[]  = "notify-cp-token-expired";
+
+// Specifies the maximum number of threads to use for running the Proxy
+// Autoconfig (PAC) script.
+const char kNumPacThreads[]                 = "num-pac-threads";
+
+// Controls whether to use the fancy new scoring (takes into account
+// word breaks, does better balancing of topicality, recency, etc.) for
+// HistoryQuickProvider.
+const char kOmniboxHistoryQuickProviderNewScoring[] =
+    "omnibox-history-quick-provider-new-scoring";
+// The value the kOmniboxHistoryQuickProviderNewScoring switch may have,
+// as in "--omnibox-history-quick-provider-new-scoring=1".
+// 1 means use new scoring.
+const char kOmniboxHistoryQuickProviderNewScoringEnabled[] = "1";
+// 0 means use old scoring ( == current behavior as of 6/2012).
+const char kOmniboxHistoryQuickProviderNewScoringDisabled[] = "0";
+
+// Controls whether HistoryQuickProvider is allowed to reorder results
+// according to inlineability in order to more aggressively assign/keep
+// high relevance scores.
+const char kOmniboxHistoryQuickProviderReorderForInlining[] =
+    "omnibox-history-quick-provider-reorder-for-inlining";
+// The value the kOmniboxHistoryQuickProviderReorderForInlining switch may
+// have, as in "--omnibox-history-quick-provider-reorder-for-inlining=1".
+// 1 means allow reordering results.
+const char kOmniboxHistoryQuickProviderReorderForInliningEnabled[] = "1";
+// 0 means don't allow reordering results ( == current behavior as of 6/2012).
+const char kOmniboxHistoryQuickProviderReorderForInliningDisabled[] = "0";
+
+// Controls whether the omnibox's HistoryQuickProvider is allowed to
+// inline suggestions.
+const char kOmniboxInlineHistoryQuickProvider[] =
+    "omnibox-inline-history-quick-provider-allowed";
+// The values the kOmniboxInlineHistoryQuickProvider switch may have, as in
+// "--omnibox-inline-history-quick-provider-allowed=1"
+//   allowed: if HistoryQuickProvider thinks it appropriate, it can inline
+//   ( == current behavior as of 2/2012).
+const char kOmniboxInlineHistoryQuickProviderAllowed[] = "1";
+//   prohibited: never inline matches
+const char kOmniboxInlineHistoryQuickProviderProhibited[] = "0";
+//   auto: any other value => the code and field trial does what it wants.
+
+// When the option to block third-party cookies is enabled, only block
+// third-party cookies from being set.
+const char kOnlyBlockSettingThirdPartyCookies[] =
+    "only-block-setting-third-party-cookies";
+
+// Launches URL in new browser window.
+const char kOpenInNewWindow[]               = "new-window";
+
+// Simulates an organic Chrome install.
+const char kOrganicInstall[]                = "organic";
+
+// Packages an extension to a .crx installable file from a given directory.
+const char kPackExtension[]                 = "pack-extension";
+
+// Optional PEM private key to use in signing packaged .crx.
+const char kPackExtensionKey[]              = "pack-extension-key";
+
+// Specifies the path to the user data folder for the parent profile.
+const char kParentProfile[]                 = "parent-profile";
+
+// Launches PerformanceMonitor at startup, which will gather statistics about
+// Chrome's CPU and memory usage, page load times, startup times, and network
+// usage, and will also store information about events which may be of interest,
+// such as extension-related occurrences and crashes. Optionally, this may be
+// run with an integer value representing the interval between the timed
+// metric gatherings, measured in seconds (if invalid or not provided, the
+// default interval is used).
+const char kPerformanceMonitorGathering[]  = "performance-monitor-gathering";
+
+// Enable the post crash analyzer which uploads detailed crash information in
+// situations where a crash is determined to be particularly interesting.
+const char kPerformCrashAnalysis[]          = "perform-crash-analysis";
+
+// Read previously recorded data from the cache. Only cached data is read.
+// See kRecordMode.
+const char kPlaybackMode[]                  = "playback-mode";
+
+// Overrides the path to the location that PNaCl is installed.
+const char kPnaclDir[]                      = "pnacl-dir";
+
+// Forces the PPAPI version of Flash (if it's being used) to run in the
+// renderer process rather than in a separate plugin process.
+const char kPpapiFlashInProcess[]           = "ppapi-flash-in-process";
+
+// Use the PPAPI (Pepper) Flash found at the given path.
+const char kPpapiFlashPath[]                = "ppapi-flash-path";
+
+// Report the given version for the PPAPI (Pepper) Flash. The version should be
+// numbers separated by '.'s (e.g., "12.3.456.78"). If not specified, it
+// defaults to "10.2.999.999".
+const char kPpapiFlashVersion[]             = "ppapi-flash-version";
+
+// Triggers prerendering of pages from suggestions in the omnibox. Only has an
+// effect when Instant is either disabled or restricted to search, and when
+// prerender is enabled.
+const char kPrerenderFromOmnibox[]          = "prerender-from-omnibox";
+// These are the values the kPrerenderFromOmnibox switch may have, as in
+// "--prerender-from-omnibox=auto". auto: Allow field trial selection.
+const char kPrerenderFromOmniboxSwitchValueAuto[] = "auto";
+//   disabled: No prerendering.
+const char kPrerenderFromOmniboxSwitchValueDisabled[] = "disabled";
+//   enabled: Guaranteed prerendering.
+const char kPrerenderFromOmniboxSwitchValueEnabled[] = "enabled";
+// Controls speculative prerendering of pages, and content prefetching. Both
+// are dispatched from <link rel=prefetch href=...> elements.
+const char kPrerenderMode[]                 = "prerender";
+// These are the values the kPrerenderMode switch may have, as in
+// "--prerender=auto".
+//   auto: Allow field trial selection in both prerender and prefetch.
+const char kPrerenderModeSwitchValueAuto[]  = "auto";
+//   disabled: No prerendering or prefetching.
+const char kPrerenderModeSwitchValueDisabled[] = "disabled";
+//   enabled: Both prerendering and prefetching.
+const char kPrerenderModeSwitchValueEnabled[] = "enabled";
+//   prefetch_only: No prerendering, but enables prefetching.
+const char kPrerenderModeSwitchValuePrefetchOnly[] = "prefetch_only";
+
+// Enable conversion from vector to raster for any page.
+const char kPrintRaster[]              = "print-raster";
+
+// Outputs the product version information and quit. Used as an internal api to
+// detect the installed version of Chrome on Linux.
+const char kProductVersion[]                = "product-version";
+
+// Selects directory of profile to associate with the first browser launched.
+const char kProfileDirectory[]              = "profile-directory";
+
+// Starts the sampling based profiler for the browser process at startup. This
+// will only work if chrome has been built with the gyp variable profiling=1.
+// The output will go to the value of kProfilingFile.
+const char kProfilingAtStart[]              = "profiling-at-start";
+
+// Specifies a location for profiling output. This will only work if chrome has
+// been built with the gyp variable profiling=1.
+//
+//   {pid} if present will be replaced by the pid of the process.
+//   {count} if present will be incremented each time a profile is generated
+//           for this process.
+// The default is chrome-profile-{pid}.
+const char kProfilingFile[]                 = "profiling-file";
+
+// Specifies a path for the output of task-level profiling which can be loaded
+// and viewed in about:profiler.
+const char kProfilingOutputFile[]           = "profiling-output-file";
+
+// Controls whether profile data is periodically flushed to a file. Normally
+// the data gets written on exit but cases exist where chrome doesn't exit
+// cleanly (especially when using single-process). A time in seconds can be
+// specified.
+const char kProfilingFlush[]                = "profiling-flush";
+
+// Specifies a custom URL for fetching NTP promo data.
+const char kPromoServerURL[]                = "promo-server-url";
+
+// Should we prompt the user before allowing external extensions to install?
+// Default is yes.
+const char kPromptForExternalExtensions[]   = "prompt-for-external-extensions";
+
+// Enables the Protector feature.
+const char kProtector[]                     = "protector";
+
+// Forces proxy auto-detection.
+const char kProxyAutoDetect[]               = "proxy-auto-detect";
+
+// Specifies a list of hosts for whom we bypass proxy settings and use direct
+// connections. Ignored if --proxy-auto-detect or --no-proxy-server are also
+// specified. This is a comma-separated list of bypass rules. See:
+// "net/proxy/proxy_bypass_rules.h" for the format of these rules.
+const char kProxyBypassList[]               = "proxy-bypass-list";
+
+// Uses the pac script at the given URL
+const char kProxyPacUrl[]                   = "proxy-pac-url";
+
+// Uses a specified proxy server, overrides system settings. This switch only
+// affects HTTP and HTTPS requests.
+const char kProxyServer[]                   = "proxy-server";
+
+// Adds a "Purge memory" button to the Task Manager, which tries to dump as
+// much memory as possible. This is mostly useful for testing how well the
+// MemoryPurger functionality works.
+//
+// NOTE: This is only implemented for Views.
+const char kPurgeMemoryButton[]             = "purge-memory-button";
+
+// Capture resource consumption information through page cycling and output the
+// data to the specified file.
+const char kRecordStats[]                   = "record-stats";
+
+// Chrome supports a playback and record mode.  Record mode saves *everything*
+// to the cache.  Playback mode reads data exclusively from the cache.  This
+// allows us to record a session into the cache and then replay it at will.
+// See also kPlaybackMode.
+const char kRecordMode[]                    = "record-mode";
+
+// Reloads pages that have been killed when they are next focused by the user.
+const char kReloadKilledTabs[]              = "reload-killed-tabs";
+
+// Uses custom front-end URL for the remote debugging.
+const char kRemoteDebuggingFrontend[]       = "remote-debugging-frontend";
+
+// Enables print preview in the renderer. This flag is generated internally by
+// Chrome and does nothing when directly passed to the browser.
+const char kRendererPrintPreview[]          = "renderer-print-preview";
+
+// Forces a reset of the one-time-randomized FieldTrials on this client, also
+// known as the Chrome Variations state.
+const char kResetVariationState[]           = "reset-variation-state";
+
+// Indicates the last session should be restored on startup. This overrides the
+// preferences value and is primarily intended for testing. The value of this
+// switch is the number of tabs to wait until loaded before 'load completed' is
+// sent to the ui_test.
+const char kRestoreLastSession[]            = "restore-last-session";
+
+// Disable saving pages as HTML-only, disable saving pages as HTML Complete
+// (with a directory of sub-resources). Enable only saving pages as MHTML.
+// See http://crbug.com/120416 for how to remove this switch.
+const char kSavePageAsMHTML[]               = "save-page-as-mhtml";
+
+// URL prefix used by safebrowsing to fetch hash, download data and report
+// malware.
+const char kSbURLPrefix[]                   = "safebrowsing-url-prefix";
+
+// If present, safebrowsing only performs update when
+// SafeBrowsingProtocolManager::ForceScheduleNextUpdate() is explicitly called.
+// This is used for testing only.
+const char kSbDisableAutoUpdate[] = "safebrowsing-disable-auto-update";
+
+// TODO(lzheng): Remove this flag once the feature works fine
+// (http://crbug.com/74848).
+//
+// Disables safebrowsing feature that checks download url and downloads
+// content's hash to make sure the content are not malicious.
+const char kSbDisableDownloadProtection[] =
+    "safebrowsing-disable-download-protection";
+
+// Enables or disables extension scripts badges in the location bar.
+const char kScriptBadges[]                  = "script-badges";
+
+// Enable or diable the "script bubble" icon in the URL bar that tells you how
+// many extensions are running scripts on a page.
+const char kScriptBubble[]                  = "script-bubble";
+
+// Enables the showing of an info-bar instructing user they can search directly
+// from the omnibox.
+const char kSearchInOmniboxHint[]           = "search-in-omnibox-hint";
+
+// Sets a token in the token service, for testing.
+const char kSetToken[]                      = "set-token";
+
+// If true the app list will be shown.
+const char kShowAppList[]                   = "show-app-list";
+
+// If true an app list shortcut will be shown in the taskbar.
+const char kShowAppListShortcut[]           = "show-app-list-shortcut";
+
+// Annotates forms with Autofill field type predictions.
+const char kShowAutofillTypePredictions[]   = "show-autofill-type-predictions";
+
+// Makes component extensions appear in chrome://settings/extensions.
+const char kShowComponentExtensionOptions[] =
+    "show-component-extension-options";
+
+// See kHideIcons.
+const char kShowIcons[]                     = "show-icons";
+
+// If true the alignment of the launcher can be changed.
+const char kShowLauncherAlignmentMenu[]     = "show-launcher-alignment-menu";
+
+// Enables or disables sideload wipeout extension effort.
+const char kSideloadWipeout[]               = "sideload-wipeout";
+
+// Changes the DCHECKS to dump memory and continue instead of displaying error
+// dialog. This is valid only in Release mode when --enable-dcheck is
+// specified.
+const char kSilentDumpOnDCHECK[]            = "silent-dump-on-dcheck";
+
+// Simulates an update being available.
+const char kSimulateUpgrade[]               = "simulate-upgrade";
+
+// Replaces the buffered data source for <audio> and <video> with a simplified
+// resource loader that downloads the entire resource into memory.
+
+// Socket reuse policy. The value should be of type enum
+// ClientSocketReusePolicy.
+const char kSocketReusePolicy[]             = "socket-reuse-policy";
+
+// Origin for which SpdyProxy authentication is supported.
+const char kSpdyProxyOrigin[]               = "spdy-proxy-origin";
+
+// Speculative resource prefetching.
+const char kSpeculativeResourcePrefetching[] =
+    "speculative-resource-prefetching";
+
+// Speculative resource prefetching is disabled.
+const char kSpeculativeResourcePrefetchingDisabled[] = "disabled";
+
+// Speculative resource prefetching will only learn about resources that need to
+// be prefetched but will not prefetch them.
+const char kSpeculativeResourcePrefetchingLearning[] = "learning";
+
+// Speculative resource prefetching is enabled.
+const char kSpeculativeResourcePrefetchingEnabled[] = "enabled";
+
+// Specifies the maximum SSL/TLS version ("ssl3", "tls1", "tls1.1", or
+// "tls1.2").
+const char kSSLVersionMax[]                 = "ssl-version-max";
+
+// Specifies the minimum SSL/TLS version ("ssl3", "tls1", "tls1.1", or
+// "tls1.2").
+const char kSSLVersionMin[]                 = "ssl-version-min";
+
+// Starts the browser maximized, regardless of any previous settings.
+const char kStartMaximized[]                = "start-maximized";
+
+// Controls the width of time-of-day filters on the 'suggested' ntp page, in
+// minutes.
+const char kSuggestionNtpFilterWidth[]      = "suggestion-ntp-filter-width";
+
+// Enables a normal distribution dropoff to the relevancy of visits with respect
+// to the time of day.
+const char kSuggestionNtpGaussianFilter[]   = "suggestion-ntp-gaussian-filter";
+
+// Enables a linear dropoff to the relevancy of visits with respect to the time
+// of day.
+const char kSuggestionNtpLinearFilter[]     = "suggestion-ntp-linear-filter";
+
+// Allows insecure XMPP connections for sync (for testing).
+const char kSyncAllowInsecureXmppConnection[] =
+    "sync-allow-insecure-xmpp-connection";
+
+// Invalidates any login info passed into sync's XMPP connection.
+const char kSyncInvalidateXmppLogin[]       = "sync-invalidate-xmpp-login";
+
+// Enable support for keystore key based encryption.
+const char kSyncKeystoreEncryption[] = "sync-keystore-encryption";
+
+// This flag causes sync to retry very quickly (see polling_constants.h) the
+// when it encounters an error, as the first step towards exponential backoff.
+const char kSyncShortInitialRetryOverride[] =
+    "sync-short-initial-retry-override";
+
+// Overrides the default notification method for sync.
+const char kSyncNotificationMethod[]        = "sync-notification-method";
+
+// Overrides the default host:port used for sync notifications.
+const char kSyncNotificationHostPort[]      = "sync-notification-host-port";
+
+// Overrides the default server used for profile sync.
+const char kSyncServiceURL[]                = "sync-url";
+
+// Enables syncing of favicons as part of tab sync.
+const char kSyncTabFavicons[]                = "sync-tab-favicons";
+
+// Makes the sync code to throw an unrecoverable error after initialization.
+// Useful for testing unrecoverable error scenarios.
+const char kSyncThrowUnrecoverableError[]   = "sync-throw-unrecoverable-error";
+
+
+// Tries to connect to XMPP using SSLTCP first (for testing).
+const char kSyncTrySsltcpFirstForXmpp[]     = "sync-try-ssltcp-first-for-xmpp";
+
+// Enables tab dragging to create a real browser.
+const char kTabBrowserDragging[]            = "enable-tab-browser-dragging";
+
+// Enables tab capture.
+const char kTabCapture[]                    = "enable-tab-capture";
+
+// Passes the name of the current running automated test to Chrome.
+const char kTestName[]                      = "test-name";
+
+// Runs the security test for the NaCl loader sandbox.
+const char kTestNaClSandbox[]               = "test-nacl-sandbox";
+
+// Type of the current test harness ("browser" or "ui").
+const char kTestType[]                      = "test-type";
+
+// Tells the app to listen for and broadcast testing-related messages on IPC
+// channel with the given ID.
+const char kTestingChannelID[]              = "testing-channel";
+
+// Disables same-origin check on HTTP resources pushed via a SPDY proxy.
+// The value is the host:port of the trusted proxy.
+const char kTrustedSpdyProxy[] = "trusted-spdy-proxy";
+
+// Experimental. Shows a dialog asking the user to try chrome. This flag is to
+// be used only by the upgrade process.
+const char kTryChromeAgain[]                = "try-chrome-again";
+
+// Runs un-installation steps that were done by chrome first-run.
+const char kUninstall[]                     = "uninstall";
+
+// Use hardware gpu, if available, for tests.
+const char kUseGpuInTests[] = "use-gpu-in-tests";
+
+// Uses Spdy for the transport protocol instead of HTTP. This is a temporary
+// testing flag.
+const char kUseSpdy[]                       = "use-spdy";
+
+// Enables use of the spelling web service. This will only work if asynchronous
+// spell checking is not disabled.
+const char kUseSpellingService[]            = "use-spelling-service";
+
+// Sets the maximum SPDY sessions per domain.
+const char kMaxSpdySessionsPerDomain[]      = "max-spdy-sessions-per-domain";
+
+// Sets the maximum concurrent streams over a SPDY session.
+const char kMaxSpdyConcurrentStreams[]      = "max-spdy-concurrent-streams";
+
+// Specifies the user data directory, which is where the browser will look for
+// all of its state.
+const char kUserDataDir[]                   = "user-data-dir";
+
+// Uses the GAIA web-based signin flow instead of the native UI signin flow.
+const char kUseWebBasedSigninFlow[]         = "use-web-based-signin-flow";
+
+// Specifies a custom URL for the server which reports variation data to the
+// client. See variations_service.cc.
+const char kVariationsServerURL[]            = "variations-server-url";
+
+// Prints version information and quits.
+const char kVersion[]                       = "version";
+
+// Cycle through a series of URLs listed in the specified file.
+const char kVisitURLs[]                     = "visit-urls";
+
+// Enable the "native services" feature of web-intents.
+const char kWebIntentsNativeServicesEnabled[] =
+    "web-intents-native-services-enabled";
+
+// Enable invocation of web intents from web content.
+const char kWebIntentsInvocationEnabled[] =
+    "enable-web-intents-invocation";
+
+// Adds the given extension ID to all the permission whitelists.
+const char kWhitelistedExtensionID[]        = "whitelisted-extension-id";
+
+// Specify the initial window position: --window-position=x,y
+const char kWindowPosition[]                = "window-position";
+
+// Specify the initial window size: --window-size=w,h
+const char kWindowSize[]                    = "window-size";
+
+// Uses WinHTTP to fetch and evaluate PAC scripts. Otherwise the default is to
+// use Chromium's network stack to fetch, and V8 to evaluate.
+const char kWinHttpProxyResolver[]          = "winhttp-proxy-resolver";
+
+#if defined(ENABLE_PLUGIN_INSTALLATION)
+// Specifies a custom URL for fetching plug-ins metadata. Used for testing.
+const char kPluginsMetadataServerURL[]      = "plugins-metadata-server-url";
+#endif
+
+#if defined(OS_ANDROID)
+// Use the tablet specific UI components when available.
+const char kTabletUI[]                      = "tablet-ui";
+#endif
+
+#if defined(OS_CHROMEOS)
+// When wallpaper boot animation is not disabled this switch
+// is used to override OOBE/sign in WebUI init type.
+// Possible values: parallel|postpone. Default: parallel.
+const char kAshWebUIInit[]                  = "ash-webui-init";
+
+// Enables switching between different cellular carriers from the UI.
+const char kEnableCarrierSwitching[]        = "enable-carrier-switching";
+
+// Disables wallpaper boot animation (except of OOBE case).
+const char kDisableBootAnimation[]          = "disable-boot-animation";
+
+// Disables Chrome Captive Portal detector, which initiates Captive
+// Portal detection for new active networks.
+const char kDisableChromeCaptivePortalDetector[] =
+    "disable-chrome-captive-portal-detector";
+
+// Disables Google Drive integration.
+const char kDisableDrive[]                  = "disable-drive";
+
+// Disables file prefetching in Google Drive Client for Chrome OS.
+const char kDisableDrivePrefetch[]          = "disable-drive-prefetch";
+
+// Disables reset the device to its factory state in design.
+const char kDisableFactoryReset[]           = "disable-factory-reset";
+
+// Disables new WebRTC implementation of user image picker.
+const char kDisableHtml5Camera[]            = "disable-html5-camera";
+
+// Avoid doing expensive animations upon login.
+const char kDisableLoginAnimations[]        = "disable-login-animations";
+
+// Disables new OOBE/sign in design.
+const char kDisableNewOobe[]                = "disable-new-oobe";
+
+// Avoid doing animations upon oobe.
+const char kDisableOobeAnimation[]          = "disable-oobe-animation";
+
+// Enables component extension that initializes background pages of
+// certain hosted applications.
+const char kEnableBackgroundLoader[]        = "enable-background-loader";
+
+// Enables Chrome Captive Portal detector, which initiates Captive
+// Portal detection for new active networks.
+const char kEnableChromeCaptivePortalDetector[] =
+    "enable-chrome-captive-portal-detector";
+
+// Enables touchpad three-finger-click as middle button.
+const char kEnableTouchpadThreeFingerClick[]
+    = "enable-touchpad-three-finger-click";
+
+// Enables touchpad three-finger swipe.
+const char kEnableTouchpadThreeFingerSwipe[]
+    = "enable-touchpad-three-finger-swipe";
+
+// Skips OAuth part of ChromeOS login process.
+const char kSkipOAuthLogin[]                = "skip-oauth-login";
+
+// Enables the redirection of viewable document requests to the Google Document
+// Viewer.
+const char kEnableGView[]                   = "enable-gview";
+
+// Enable Kiosk mode for ChromeOS
+const char kEnableKioskMode[]               = "enable-kiosk-mode";
+
+// Enables request of tablet site (via user agent override).
+const char kEnableRequestTabletSite[]       = "enable-request-tablet-site";
+
+// Enables static ip configuration. This flag should be removed when it's on by
+// default.
+const char kEnableStaticIPConfig[]          = "enable-static-ip-config";
+
+// Passed to Chrome on first boot. Not passed on restart after sign out.
+const char kFirstBoot[] = "first-boot";
+
+// If true, the Chromebook has a Chrome OS keyboard. Don't use the flag for
+// Chromeboxes.
+const char kHasChromeOSKeyboard[]           = "has-chromeos-keyboard";
+
+// Path for the screensaver used in Kiosk mode
+const char kKioskModeScreensaverPath[]      = "kiosk-mode-screensaver-path";
+
+// Enables Chrome-as-a-login-manager behavior.
+const char kLoginManager[]                  = "login-manager";
+
+// Allows to override the first login screen. The value should be the name of
+// the first login screen to show (see
+// chrome/browser/chromeos/login/login_wizard_view.cc for actual names).
+// Ignored if kLoginManager is not specified. TODO(avayvod): Remove when the
+// switch is no longer needed for testing.
+const char kLoginScreen[]                   = "login-screen";
+
+// Controls the initial login screen size. Pass width,height.
+const char kLoginScreenSize[]               = "login-screen-size";
+
+// Specifies the profile to use once a chromeos user is logged in.
+const char kLoginProfile[]                  = "login-profile";
+
+// Specifies the user which is already logged in.
+const char kLoginUser[]                     = "login-user";
+
+// Specifies a password to be used to login (along with login-user).
+const char kLoginPassword[]                 = "login-password";
+
+// Enables natural scroll by default.
+const char kNaturalScrollDefault[]          = "enable-natural-scroll-default";
+
+// Disables tab discard in low memory conditions, a feature which silently
+// closes inactive tabs to free memory and to attempt to avoid the kernel's
+// out-of-memory process killer.
+const char kNoDiscardTabs[]                 = "no-discard-tabs";
+
+// Indicates that the browser is in "browse without sign-in" (Guest session)
+// mode. Should completely disable extensions, sync and bookmarks.
+const char kGuestSession[]                  = "bwsi";
+
+// Enables overriding the path for the default echo component extension.
+// Useful for testing.
+const char kEchoExtensionPath[]             = "echo-ext-path";
+
+// Indicates that a stub implementation of CrosSettings that stores settings in
+// memory without signing should be used, treating current user as the owner.
+// This option is for testing the chromeos build of chrome on the desktop only.
+const char kStubCrosSettings[]              = "stub-cros-settings";
+
+// Enables overriding the path for the default authentication extension.
+const char kAuthExtensionPath[]             = "auth-ext-path";
+
+// Power of the power-of-2 initial modulus that will be used by the
+// auto-enrollment client. E.g. "4" means the modulus will be 2^4 = 16.
+const char kEnterpriseEnrollmentInitialModulus[] =
+    "enterprise-enrollment-initial-modulus";
+
+// Power of the power-of-2 maximum modulus that will be used by the
+// auto-enrollment client.
+const char kEnterpriseEnrollmentModulusLimit[] =
+    "enterprise-enrollment-modulus-limit";
+
+// Loads the File Manager as a packaged app.
+const char kFileManagerPackaged[] = "file-manager-packaged";
+
+#ifndef NDEBUG
+// Skips all other OOBE pages after user login.
+const char kOobeSkipPostLogin[]             = "oobe-skip-postlogin";
+#endif  // NDEBUG
+#endif  // OS_CHROMEOS
+
+#if defined(OS_POSIX)
+// A flag, generated internally by Chrome for renderer and other helper process
+// command lines on Linux and Mac. It tells the helper process to enable crash
+// dumping and reporting, because helpers cannot access the profile or other
+// files needed to make this decision.
+const char kEnableCrashReporter[]           = "enable-crash-reporter";
+
+#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
+// Specifies which password store to use (detect, default, gnome, kwallet).
+const char kPasswordStore[]                 = "password-store";
+#endif
+#endif  // OS_POSIX
+
+#if defined(OS_MACOSX)
+// Enables the tabs expose feature ( http://crbug.com/50307 ).
+const char kEnableExposeForTabs[]           = "enable-expose-for-tabs";
+
+// Performs Keychain reauthorization from the command line on behalf of a
+// special Keychain reauthorization stub executable. Used during auto-update.
+const char kKeychainReauthorize[]           = "keychain-reauthorize";
+
+// A process type (switches::kProcessType) that relaunches the browser. See
+// chrome/browser/mac/relauncher.h.
+const char kRelauncherProcess[]             = "relauncher";
+
+// Uses mock keychain for testing purposes, which prevents blocking dialogs
+// from causing timeouts.
+const char kUseMockKeychain[]               = "use-mock-keychain";
+#endif
+
+#if defined(OS_WIN)
+// Disables profile desktop shortcuts handling, preventing their creation,
+// modification or removal.
+const char kDisableDesktopShortcuts[]       = "disable-desktop-shortcuts";
+
+// Enables sync credential caching on Windows 8.
+// See chrome/browser/sync/credential_cache_service_win.h.
+const char kEnableSyncCredentialCaching[]    = "enable-sync-credential-caching";
+
+// For the DelegateExecute verb handler to launch Chrome in metro mode on
+// Windows 8 and higher.  Used when relaunching metro Chrome.
+const char kForceImmersive[]                 = "force-immersive";
+
+// For the DelegateExecute verb handler to launch Chrome in desktop mode on
+// Windows 8 and higher.  Used when relaunching metro Chrome.
+const char kForceDesktop[]                   = "force-desktop";
+
+// Allows for disabling the overlapped I/O for TCP reads.
+// Possible values are "on" or "off".
+// The default is "on" which matches the existing behavior.
+// "off" switches to use non-blocking reads and WSAEventSelect.
+const char kOverlappedRead[]                 = "overlapped-reads";
+
+// Relaunches metro Chrome on Windows 8 and higher using a given shortcut.
+const char kRelaunchShortcut[]               = "relaunch-shortcut";
+
+// Waits for the given handle to be signaled before relaunching metro Chrome on
+// Windows 8 and higher.
+const char kWaitForMutex[]                  = "wait-for-mutex";
+#endif
+
+#ifndef NDEBUG
+// Enables overriding the path of file manager extension.
+const char kFileManagerExtensionPath[]      = "filemgr-ext-path";
+
+// Dumps dependency information about our profile services into a dot file in
+// the profile directory.
+const char kDumpProfileDependencyGraph[]    = "dump-profile-graph";
+#endif  // NDEBUG
+
+// Controls print preview in the browser process.
+#if defined(GOOGLE_CHROME_BUILD)
+// Disables print preview (For testing, and for users who don't like us. :[ )
+const char kDisablePrintPreview[]           = "disable-print-preview";
+#else
+// Enables print preview (Force enable on Chromium, which normally does not
+//                        have the PDF viewer required for print preview.)
+const char kEnablePrintPreview[]            = "enable-print-preview";
+#endif
+
+// -----------------------------------------------------------------------------
+// DO NOT ADD YOUR CRAP TO THE BOTTOM OF THIS FILE.
+//
+// You were going to just dump your switches here, weren't you? Instead, please
+// put them in alphabetical order above, or in order inside the appropriate
+// ifdef at the bottom. The order should match the header.
+// -----------------------------------------------------------------------------
+
+}  // namespace switches
+
+namespace chrome {
+
+bool UseChromeStyleDialogs() {
+#if defined(OS_MACOSX)
+  return true;
+#elif defined(OS_WIN)
+  return CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kEnableChromeStyleDialogs);
+#else
+  return CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kEnableChromeStyleDialogs);
+#endif
+}
+
+}  // namespace chrome
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
new file mode 100644
index 0000000..170f392
--- /dev/null
+++ b/chrome/common/chrome_switches.h
@@ -0,0 +1,472 @@
+// Copyright (c) 2012 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.
+
+// Defines all the command-line switches used by Chrome.
+
+#ifndef CHROME_COMMON_CHROME_SWITCHES_H_
+#define CHROME_COMMON_CHROME_SWITCHES_H_
+
+#include "build/build_config.h"
+
+#include "base/base_switches.h"
+#include "content/public/common/content_switches.h"
+
+namespace switches {
+
+// -----------------------------------------------------------------------------
+// Can't find the switch you are looking for? Try looking in
+// ui/gl/gl_switches.cc or base/base_switches.cc or
+// content/public/common/content_switches.cc or media/base/media_switches.cc
+// instead.
+// -----------------------------------------------------------------------------
+
+// All switches in alphabetical order. The switches should be documented
+// alongside the definition of their values in the .cc file.
+extern const char kActionBox[];
+extern const char kAllowCrossOriginAuthPrompt[];
+extern const char kAllowFileAccess[];
+extern const char kAllowHTTPBackgroundPage[];
+extern const char kAllowLegacyExtensionManifests[];
+extern const char kAllowNaClSocketAPI[];
+extern const char kAllowOutdatedPlugins[];
+extern const char kAllowRunningInsecureContent[];
+extern const char kAllowScriptingGallery[];
+extern const char kAlwaysAuthorizePlugins[];
+extern const char kAppId[];
+extern const char kApp[];
+extern const char kAppWindowSize[];
+extern const char kAppNotifyChannelServerURL[];
+extern const char kAppsCheckoutURL[];
+extern const char kAppsGalleryDownloadURL[];
+extern const char kAppsGalleryInstallAutoConfirmForTests[];
+extern const char kAppsGalleryReturnTokens[];
+extern const char kAppsGalleryURL[];
+extern const char kAppsGalleryUpdateURL[];
+extern const char kAppsNewInstallBubble[];
+extern const char kAppsNoThrob[];
+extern const char kAuthNegotiateDelegateWhitelist[];
+extern const char kAuthSchemes[];
+extern const char kAuthServerWhitelist[];
+extern const char kAutoLaunchAtStartup[];
+extern const char kAutofillServiceUrl[];
+extern const char kAutomationClientChannelID[];
+extern const char kAutomationReinitializeOnChannelError[];
+extern const char kCheckForUpdateIntervalSec[];
+extern const char kCheckCloudPrintConnectorPolicy[];
+extern const char kChromeFrameShutdownDelay[];
+extern const char kChromeVersion[];
+extern const char kCipherSuiteBlacklist[];
+extern const char kClearTokenService[];
+extern const char kCloudPrintDeleteFile[];
+extern const char kCloudPrintFile[];
+extern const char kCloudPrintJobTitle[];
+extern const char kCloudPrintFileType[];
+extern const char kCloudPrintPrintTicket[];
+extern const char kCloudPrintProxyId[];
+extern const char kCloudPrintServiceURL[];
+extern const char kComponentUpdaterDebug[];
+extern const char kConflictingModulesCheck[];
+extern const char kContentSettings2[];
+extern const char kCountry[];
+extern const char kCrashOnHangSeconds[];
+extern const char kCrashOnHangThreads[];
+extern const char kCrashOnLive[];
+extern const char kCreateBrowserOnStartupForTests[];
+extern const char kDebugEnableFrameToggle[];
+extern const char kDebugPackedApps[];
+extern const char kDebugPrint[];
+extern const char kDeviceManagementUrl[];
+extern const char kDiagnostics[];
+extern const char kDisableAsyncDns[];
+extern const char kDisableAuthNegotiateCnameLookup[];
+extern const char kDisableBackgroundMode[];
+extern const char kDisableBackgroundNetworking[];
+extern const char kDisableBundledPpapiFlash[];
+extern const char kDisableBookmarkAutocompleteProvider[];
+extern const char kDisableClientSidePhishingDetection[];
+extern const char kDisableComponentUpdate[];
+extern const char kDisableCRLSets[];
+extern const char kDisableCustomJumpList[];
+extern const char kDisableDefaultApps[];
+extern const char kDisableDhcpWpad[];
+extern const char kDisableExtensionsFileAccessCheck[];
+extern const char kDisableExtensionsHttpThrottling[];
+extern const char kDisableExtensionsResourceWhitelist[];
+extern const char kDisableExtensions[];
+extern const char kDisableImprovedDownloadProtection[];
+extern const char kDisableInfiniteCache[];
+extern const char kDisableInternalFlash[];
+extern const char kDisableIPv6[];
+extern const char kDisableIPPooling[];
+extern const char kDisableNTPOtherSessionsMenu[];
+extern const char kDisablePopupBlocking[];
+extern const char kDisablePreconnect[];
+extern const char kDisablePromptOnRepost[];
+extern const char kDisableRestoreBackgroundContents[];
+extern const char kDisableRestoreSessionState[];
+extern const char kDisableScriptedPrintThrottling[];
+extern const char kDisableSync[];
+extern const char kDisableSyncAppSettings[];
+extern const char kDisableSyncApps[];
+extern const char kDisableSyncAppNotifications[];
+extern const char kDisableSyncAutofill[];
+extern const char kDisableSyncAutofillProfile[];
+extern const char kDisableSyncBookmarks[];
+extern const char kDisableSyncExtensionSettings[];
+extern const char kDisableSyncExtensions[];
+extern const char kDisableSyncPasswords[];
+extern const char kDisableSyncPreferences[];
+extern const char kDisableSyncSearchEngines[];
+extern const char kDisableSyncThemes[];
+extern const char kDisableSyncTypedUrls[];
+extern const char kDisableTranslate[];
+extern const char kDisableTLSChannelID[];
+extern const char kDisableWebResources[];
+extern const char kDisableWebsiteSettings[];
+extern const char kDisableZeroBrowsersOpenForTests[];
+extern const char kDiskCacheDir[];
+extern const char kDiskCacheSize[];
+extern const char kDnsLogDetails[];
+extern const char kDnsPrefetchDisable[];
+extern const char kDumpHistogramsOnExit[];
+extern const char kDisableSyncTabs[];
+extern const char kEasyOffStoreExtensionInstall[];
+extern const char kEnableAsyncDns[];
+extern const char kEnableAuthNegotiatePort[];
+extern const char kEnableAutologin[];
+extern const char kEnableBenchmarking[];
+extern const char kEnableBundledPpapiFlash[];
+extern const char kEnableChromeStyleDialogs[];
+extern const char kEnableCloudPolicyService[];
+extern const char kEnableCloudPrintProxy[];
+extern const char kEnableContacts[];
+extern const char kEnableCrxlessWebApps[];
+extern const char kEnableDevToolsExperiments[];
+extern const char kEnableDriveV2Api[];
+extern const char kEnableExperimentalExtensionApis[];
+extern const char kEnableExperimentalFormFilling[];
+extern const char kEnableExtensionActivityLogging[];
+extern const char kEnableExtensionActivityUI[];
+extern const char kEnableExtensionTimelineApi[];
+extern const char kEnableFileCookies[];
+extern const char kEnableHttpPipelining[];
+extern const char kEnableInstantExtendedAPI[];
+extern const char kEnableInteractiveAutocomplete[];
+extern const char kEnableIPCFuzzing[];
+extern const char kEnableIPPooling[];
+extern const char kEnableIPv6[];
+extern const char kEnableManagedStorage[];
+extern const char kEnableMemoryInfo[];
+extern const char kEnableMetricsReportingForTesting[];
+extern const char kEnableNaCl[];
+extern const char kEnableNaClDebug[];
+extern const char kEnableNaClExceptionHandling[];
+extern const char kEnableNaClSRPCProxy[];
+extern const char kEnableNativeMessaging[];
+extern const char kEnableNewAutofillHeuristics[];
+extern const char kEnableNewAutofillUi[];
+extern const char kEnableNpn[];
+extern const char kEnableNpnHttpOnly[];
+extern const char kEnablePanels[];
+extern const char kEnablePasswordGeneration[];
+extern const char kEnablePnacl[];
+extern const char kEnableProfiling[];
+extern const char kEnableResourceContentSettings[];
+extern const char kEnableSdch[];
+extern const char kEnableSettingsApp[];
+extern const char kEnableSpdy3[];
+extern const char kEnableSpdyCredentialFrames[];
+extern const char kEnableSpellingAutoCorrect[];
+extern const char kEnableStackedTabStrip[];
+extern const char kEnableSuggestionsTabPage[];
+extern const char kEnableSyncHistoryDeleteDirectives[];
+extern const char kEnableTabGroupsContextMenu[];
+extern const char kEnableWatchdog[];
+extern const char kEnableWebSocketOverSpdy[];
+extern const char kExtensionsInActionBox[];
+extern const char kEventPageIdleTime[];
+extern const char kEventPageUnloadingTime[];
+extern const char kExplicitlyAllowedPorts[];
+extern const char kExtensionProcess[];
+extern const char kExtensionsUpdateFrequency[];
+extern const char kFlagSwitchesBegin[];
+extern const char kFlagSwitchesEnd[];
+extern const char kFeedbackServer[];
+extern const char kFileDescriptorLimit[];
+extern const char kFirstRun[];
+extern const char kForceSyncSpellCheck[];
+extern const char kGaiaProfileInfo[];
+extern const char kGoogleSearchDomainCheckURL[];
+extern const char kGSSAPILibraryName[];
+extern const char kHelp[];
+extern const char kHelpShort[];
+extern const char kHideIcons[];
+extern const char kHomePage[];
+extern const char kHostRules[];
+extern const char kHostResolverParallelism[];
+extern const char kHostResolverRetryAttempts[];
+extern const char kHstsHosts[];
+extern const char kImport[];
+extern const char kImportFromFile[];
+extern const char kIncognito[];
+extern const char kInstallFromWebstore[];
+extern const char kInstantURL[];
+extern const char kKeepAliveForTest[];
+extern const char kKioskMode[];
+extern const char kKioskModePrinting[];
+extern const char kLoadComponentExtension[];
+extern const char kLoadCloudPolicyOnSignin[];
+extern const char kLoadExtension[];
+extern const char kLoadOpencryptoki[];
+extern const char kUninstallExtension[];
+extern const char kLogNetLog[];
+extern const char kMakeDefaultBrowser[];
+extern const char kManaged[];
+extern const char kMediaCacheSize[];
+extern const char kMemoryProfiling[];
+extern const char kMessageLoopHistogrammer[];
+extern const char kMetricsRecordingOnly[];
+extern const char kMultiProfiles[];
+extern const char kNaClDebugMask[];
+extern const char kNaClGdb[];
+extern const char kNaClGdbScript[];
+extern const char kNaClLoaderCmdPrefix[];
+extern const char kNetLogLevel[];
+extern const char kNoDefaultBrowserCheck[];
+extern const char kNoDisplayingInsecureContent[];
+extern const char kNoEvents[];
+extern const char kNoExperiments[];
+extern const char kNoFirstRun[];
+extern const char kNoJsRandomness[];
+extern const char kNoManaged[];
+extern const char kNoNetworkProfileWarning[];
+extern const char kNoProxyServer[];
+extern const char kNoPings[];
+extern const char kNoServiceAutorun[];
+extern const char kNoStartupWindow[];
+extern const char kNotifyCloudPrintTokenExpired[];
+extern const char kNtpAppInstallHint[];
+extern const char kNumPacThreads[];
+extern const char kOmniboxHistoryQuickProviderNewScoring[];
+extern const char kOmniboxHistoryQuickProviderNewScoringEnabled[];
+extern const char kOmniboxHistoryQuickProviderNewScoringDisabled[];
+extern const char kOmniboxHistoryQuickProviderReorderForInlining[];
+extern const char kOmniboxHistoryQuickProviderReorderForInliningEnabled[];
+extern const char kOmniboxHistoryQuickProviderReorderForInliningDisabled[];
+extern const char kOmniboxInlineHistoryQuickProvider[];
+extern const char kOmniboxInlineHistoryQuickProviderAllowed[];
+extern const char kOmniboxInlineHistoryQuickProviderProhibited[];
+extern const char kOnlyBlockSettingThirdPartyCookies[];
+extern const char kOpenInNewWindow[];
+extern const char kOrganicInstall[];
+extern const char kPackExtension[];
+extern const char kPackExtensionKey[];
+extern const char kParentProfile[];
+extern const char kPerformanceMonitorGathering[];
+extern const char kPerformCrashAnalysis[];
+extern const char kPlaybackMode[];
+extern const char kPnaclDir[];
+extern const char kPpapiFlashInProcess[];
+extern const char kPpapiFlashPath[];
+extern const char kPpapiFlashVersion[];
+extern const char kPrerenderFromOmnibox[];
+extern const char kPrerenderFromOmniboxSwitchValueAuto[];
+extern const char kPrerenderFromOmniboxSwitchValueDisabled[];
+extern const char kPrerenderFromOmniboxSwitchValueEnabled[];
+extern const char kPrerenderMode[];
+extern const char kPrerenderModeSwitchValueAuto[];
+extern const char kPrerenderModeSwitchValueDisabled[];
+extern const char kPrerenderModeSwitchValueEnabled[];
+extern const char kPrerenderModeSwitchValuePrefetchOnly[];
+extern const char kProductVersion[];
+extern const char kProfileDirectory[];
+extern const char kProfilingAtStart[];
+extern const char kProfilingFile[];
+extern const char kProfilingFlush[];
+extern const char kProfilingOutputFile[];
+extern const char kPromoServerURL[];
+extern const char kPromptForExternalExtensions[];
+extern const char kProtector[];
+extern const char kProxyAutoDetect[];
+extern const char kProxyBypassList[];
+extern const char kProxyPacUrl[];
+extern const char kProxyServer[];
+extern const char kPurgeMemoryButton[];
+extern const char kRecordStats[];
+extern const char kRecordMode[];
+extern const char kReloadKilledTabs[];
+extern const char kRemoteDebuggingFrontend[];
+extern const char kRendererPrintPreview[];
+extern const char kResetVariationState[];
+extern const char kRestoreLastSession[];
+extern const char kSavePageAsMHTML[];
+extern const char kSbURLPrefix[];
+extern const char kSbDisableAutoUpdate[];
+extern const char kSbDisableDownloadProtection[];
+extern const char kScriptBadges[];
+extern const char kScriptBubble[];
+extern const char kSearchInOmniboxHint[];
+extern const char kSideloadWipeout[];
+extern const char kSetToken[];
+extern const char kShowAppList[];
+extern const char kShowAppListShortcut[];
+extern const char kShowAutofillTypePredictions[];
+extern const char kShowComponentExtensionOptions[];
+extern const char kShowIcons[];
+extern const char kShowLauncherAlignmentMenu[];
+extern const char kSilentDumpOnDCHECK[];
+extern const char kSimulateUpgrade[];
+extern const char kSocketReusePolicy[];
+extern const char kSpeculativeResourcePrefetching[];
+extern const char kSpeculativeResourcePrefetchingDisabled[];
+extern const char kSpeculativeResourcePrefetchingLearning[];
+extern const char kSpdyProxyOrigin[];
+extern const char kSpeculativeResourcePrefetchingEnabled[];
+extern const char kSSLVersionMax[];
+extern const char kSSLVersionMin[];
+extern const char kStartMaximized[];
+extern const char kSuggestionNtpFilterWidth[];
+extern const char kSuggestionNtpGaussianFilter[];
+extern const char kSuggestionNtpLinearFilter[];
+extern const char kSyncAllowInsecureXmppConnection[];
+extern const char kSyncInvalidateXmppLogin[];
+extern const char kSyncKeystoreEncryption[];
+extern const char kSyncShortInitialRetryOverride[];
+extern const char kSyncNotificationMethod[];
+extern const char kSyncNotificationHostPort[];
+extern const char kSyncServiceURL[];
+extern const char kSyncTabFavicons[];
+extern const char kSyncThrowUnrecoverableError[];
+extern const char kSyncTrySsltcpFirstForXmpp[];
+extern const char kTabBrowserDragging[];
+extern const char kTabCapture[];
+extern const char kTestNaClSandbox[];
+extern const char kTestName[];
+extern const char kTestType[];
+extern const char kTestingChannelID[];
+extern const char kTrustedSpdyProxy[];
+extern const char kTryChromeAgain[];
+extern const char kUninstall[];
+extern const char kUseSpdy[];
+extern const char kUseGpuInTests[];
+extern const char kUseSpellingService[];
+extern const char kMaxSpdySessionsPerDomain[];
+extern const char kMaxSpdyConcurrentStreams[];
+extern const char kUserDataDir[];
+extern const char kUseWebBasedSigninFlow[];
+extern const char kVariationsServerURL[];
+extern const char kVersion[];
+extern const char kVisitURLs[];
+extern const char kWebIntentsNativeServicesEnabled[];
+extern const char kWebIntentsInvocationEnabled[];
+extern const char kWhitelistedExtensionID[];
+extern const char kWindowPosition[];
+extern const char kWindowSize[];
+extern const char kWinHttpProxyResolver[];
+
+#if defined(ENABLE_PLUGIN_INSTALLATION)
+extern const char kPluginsMetadataServerURL[];
+#endif
+
+#if defined(OS_ANDROID)
+extern const char kTabletUI[];
+#endif
+
+#if defined(OS_CHROMEOS)
+// Keep switches in alphabetical order.
+extern const char kAshWebUIInit[];
+extern const char kEnableCarrierSwitching[];
+extern const char kDisableBootAnimation[];
+extern const char kDisableChromeCaptivePortalDetector[];
+extern const char kDisableDrive[];
+extern const char kDisableDrivePrefetch[];
+extern const char kDisableFactoryReset[];
+extern const char kDisableHtml5Camera[];
+extern const char kDisableLoginAnimations[];
+extern const char kDisableNewOobe[];
+extern const char kDisableOobeAnimation[];
+extern const char kEnableBackgroundLoader[];
+extern const char kEnableChromeCaptivePortalDetector[];
+extern const char kEnableTouchpadThreeFingerClick[];
+extern const char kEnableTouchpadThreeFingerSwipe[];
+extern const char kSkipOAuthLogin[];
+extern const char kEnableGView[];
+extern const char kEnableKioskMode[];
+extern const char kEnableRequestTabletSite[];
+extern const char kEnableStaticIPConfig[];
+extern const char kFirstBoot[];
+extern const char kHasChromeOSKeyboard[];
+extern const char kKioskModeScreensaverPath[];
+extern const char kLoginManager[];
+// TODO(avayvod): Remove this flag when it's unnecessary for testing
+// purposes.
+extern const char kLoginScreen[];
+extern const char kLoginScreenSize[];
+extern const char kLoginProfile[];
+extern const char kLoginUser[];
+extern const char kLoginPassword[];
+extern const char kNaturalScrollDefault[];
+extern const char kNoDiscardTabs[];
+extern const char kGuestSession[];
+extern const char kEchoExtensionPath[];
+extern const char kStubCrosSettings[];
+extern const char kAuthExtensionPath[];
+extern const char kEnterpriseEnrollmentInitialModulus[];
+extern const char kEnterpriseEnrollmentModulusLimit[];
+extern const char kFileManagerPackaged[];
+#ifndef NDEBUG
+extern const char kOobeSkipPostLogin[];
+#endif
+#endif
+
+#if defined(OS_POSIX)
+extern const char kEnableCrashReporter[];
+#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
+extern const char kPasswordStore[];
+#endif
+#endif
+
+#if defined(OS_MACOSX)
+extern const char kEnableExposeForTabs[];
+extern const char kKeychainReauthorize[];
+extern const char kRelauncherProcess[];
+extern const char kUseMockKeychain[];
+#endif
+
+#if defined(OS_WIN)
+extern const char kDisableDesktopShortcuts[];
+extern const char kEnableSyncCredentialCaching[];
+extern const char kForceImmersive[];
+extern const char kForceDesktop[];
+extern const char kOverlappedRead[];
+extern const char kPrintRaster[];
+extern const char kRelaunchShortcut[];
+extern const char kWaitForMutex[];
+#endif
+
+#ifndef NDEBUG
+extern const char kFileManagerExtensionPath[];
+extern const char kDumpProfileDependencyGraph[];
+#endif
+
+#if defined(GOOGLE_CHROME_BUILD)
+extern const char kDisablePrintPreview[];
+#else
+extern const char kEnablePrintPreview[];
+#endif
+
+// DON'T ADD RANDOM STUFF HERE. Put it in the main section above in
+// alphabetical order, or in one of the ifdefs (also in order in each section).
+
+}  // namespace switches
+
+namespace chrome {
+
+// Returns true if the chrome style dialog is enabled.
+// TODO(sail): Remove this once the feature is fully baked.
+bool UseChromeStyleDialogs();
+
+}  // namespace chrome
+
+#endif  // CHROME_COMMON_CHROME_SWITCHES_H_
diff --git a/chrome/common/chrome_utility_messages.h b/chrome/common/chrome_utility_messages.h
new file mode 100644
index 0000000..cc20690
--- /dev/null
+++ b/chrome/common/chrome_utility_messages.h
@@ -0,0 +1,179 @@
+// Copyright (c) 2012 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.
+
+// Multiply-included message file, so no include guard.
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/platform_file.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/update_manifest.h"
+#include "content/public/common/common_param_traits.h"
+#include "ipc/ipc_message_macros.h"
+#include "printing/backend/print_backend.h"
+#include "printing/page_range.h"
+#include "printing/pdf_render_settings.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/rect.h"
+
+#define IPC_MESSAGE_START ChromeUtilityMsgStart
+
+IPC_STRUCT_TRAITS_BEGIN(printing::PageRange)
+  IPC_STRUCT_TRAITS_MEMBER(from)
+  IPC_STRUCT_TRAITS_MEMBER(to)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(printing::PrinterCapsAndDefaults)
+  IPC_STRUCT_TRAITS_MEMBER(printer_capabilities)
+  IPC_STRUCT_TRAITS_MEMBER(caps_mime_type)
+  IPC_STRUCT_TRAITS_MEMBER(printer_defaults)
+  IPC_STRUCT_TRAITS_MEMBER(defaults_mime_type)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(UpdateManifest::Result)
+  IPC_STRUCT_TRAITS_MEMBER(extension_id)
+  IPC_STRUCT_TRAITS_MEMBER(version)
+  IPC_STRUCT_TRAITS_MEMBER(browser_min_version)
+  IPC_STRUCT_TRAITS_MEMBER(package_hash)
+  IPC_STRUCT_TRAITS_MEMBER(crx_url)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(UpdateManifest::Results)
+  IPC_STRUCT_TRAITS_MEMBER(list)
+  IPC_STRUCT_TRAITS_MEMBER(daystart_elapsed_seconds)
+IPC_STRUCT_TRAITS_END()
+
+//------------------------------------------------------------------------------
+// Utility process messages:
+// These are messages from the browser to the utility process.
+// Tell the utility process to unpack the given extension file in its
+// directory and verify that it is valid.
+IPC_MESSAGE_CONTROL4(ChromeUtilityMsg_UnpackExtension,
+                     FilePath /* extension_filename */,
+                     std::string /* extension_id */,
+                     int /* Extension::Location */,
+                     int /* InitFromValue flags */)
+
+// Tell the utility process to parse the given JSON data and verify its
+// validity.
+IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_UnpackWebResource,
+                     std::string /* JSON data */)
+
+// Tell the utility process to parse the given xml document.
+IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_ParseUpdateManifest,
+                     std::string /* xml document contents */)
+
+// Tell the utility process to decode the given image data.
+IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_DecodeImage,
+                     std::vector<unsigned char>)  // encoded image contents
+
+// Tell the utility process to decode the given image data, which is base64
+// encoded.
+IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_DecodeImageBase64,
+                     std::string)  // base64 encoded image contents
+
+// Tell the utility process to render the given PDF into a metafile.
+IPC_MESSAGE_CONTROL4(ChromeUtilityMsg_RenderPDFPagesToMetafile,
+                     base::PlatformFile,       // PDF file
+                     FilePath,                 // Location for output metafile
+                     printing::PdfRenderSettings,  // PDF render settitngs
+                     std::vector<printing::PageRange>)
+
+// Tell the utility process to decode the given JPEG image data with a robust
+// libjpeg codec.
+IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_RobustJPEGDecodeImage,
+                     std::vector<unsigned char>)  // encoded image contents
+
+// Tell the utility process to parse a JSON string into a Value object.
+IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_ParseJSON,
+                     std::string /* JSON to parse */)
+
+// Tells the utility process to get capabilities and defaults for the specified
+// printer. Used on Windows to isolate the service process from printer driver
+// crashes by executing this in a separate process. This does not run in a
+// sandbox.
+IPC_MESSAGE_CONTROL1(ChromeUtilityMsg_GetPrinterCapsAndDefaults,
+                     std::string /* printer name */)
+
+//------------------------------------------------------------------------------
+// Utility process host messages:
+// These are messages from the utility process to the browser.
+// Reply when the utility process is done unpacking an extension.  |manifest|
+// is the parsed manifest.json file.
+// The unpacker should also have written out files containing the decoded
+// images and message catalogs from the extension. See extensions::Unpacker for
+// details.
+IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_UnpackExtension_Succeeded,
+                     DictionaryValue /* manifest */)
+
+// Reply when the utility process has failed while unpacking an extension.
+// |error_message| is a user-displayable explanation of what went wrong.
+IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_UnpackExtension_Failed,
+                     string16 /* error_message, if any */)
+
+// Reply when the utility process is done unpacking and parsing JSON data
+// from a web resource.
+IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_UnpackWebResource_Succeeded,
+                     DictionaryValue /* json data */)
+
+// Reply when the utility process has failed while unpacking and parsing a
+// web resource.  |error_message| is a user-readable explanation of what
+// went wrong.
+IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_UnpackWebResource_Failed,
+                     std::string /* error_message, if any */)
+
+// Reply when the utility process has succeeded in parsing an update manifest
+// xml document.
+IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_ParseUpdateManifest_Succeeded,
+                     UpdateManifest::Results /* updates */)
+
+// Reply when an error occured parsing the update manifest. |error_message|
+// is a description of what went wrong suitable for logging.
+IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_ParseUpdateManifest_Failed,
+                     std::string /* error_message, if any */)
+
+// Reply when the utility process has succeeded in decoding the image.
+IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_DecodeImage_Succeeded,
+                     SkBitmap)  // decoded image
+
+// Reply when an error occured decoding the image.
+IPC_MESSAGE_CONTROL0(ChromeUtilityHostMsg_DecodeImage_Failed)
+
+// Reply when the utility process has succeeded in rendering the PDF.
+IPC_MESSAGE_CONTROL2(ChromeUtilityHostMsg_RenderPDFPagesToMetafile_Succeeded,
+                     int,          // Highest rendered page number
+                     double)       // Scale factor
+
+// Reply when an error occured rendering the PDF.
+IPC_MESSAGE_CONTROL0(ChromeUtilityHostMsg_RenderPDFPagesToMetafile_Failed)
+
+// Reply when the utility process successfully parsed a JSON string.
+//
+// WARNING: The result can be of any Value subclass type, but we can't easily
+// pass indeterminate value types by const object reference with our IPC macros,
+// so we put the result Value into a ListValue. Handlers should examine the
+// first (and only) element of the ListValue for the actual result.
+IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_ParseJSON_Succeeded,
+                     ListValue)
+
+// Reply when the utility process failed in parsing a JSON string.
+IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_ParseJSON_Failed,
+                     std::string /* error message, if any*/)
+
+#if defined(ENABLE_PRINTING)
+// Reply when the utility process has succeeded in obtaining the printer
+// capabilities and defaults.
+IPC_MESSAGE_CONTROL2(ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Succeeded,
+                     std::string /* printer name */,
+                     printing::PrinterCapsAndDefaults)
+#endif
+
+// Reply when the utility process has failed to obtain the printer
+// capabilities and defaults.
+IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Failed,
+                     std::string /* printer name */)
diff --git a/chrome/common/chrome_version_info.cc b/chrome/common/chrome_version_info.cc
new file mode 100644
index 0000000..376739b
--- /dev/null
+++ b/chrome/common/chrome_version_info.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/chrome_version_info.h"
+
+#include "base/basictypes.h"
+#include "base/file_version_info.h"
+#include "base/string_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace chrome {
+
+#if defined(OS_WIN) || defined(OS_MACOSX)
+// On Windows and Mac, we get the Chrome version info by querying
+// FileVersionInfo for the current module.
+
+VersionInfo::VersionInfo() {
+  // The current module is already loaded in memory, so this will be cheap.
+  base::ThreadRestrictions::ScopedAllowIO allow_io;
+  version_info_.reset(FileVersionInfo::CreateFileVersionInfoForCurrentModule());
+}
+
+VersionInfo::~VersionInfo() {
+}
+
+bool VersionInfo::is_valid() const {
+  return version_info_.get() != NULL;
+}
+
+std::string VersionInfo::Name() const {
+  if (!is_valid())
+    return std::string();
+  return UTF16ToUTF8(version_info_->product_name());
+}
+
+std::string VersionInfo::Version() const {
+  if (!is_valid())
+    return std::string();
+  return UTF16ToUTF8(version_info_->product_version());
+}
+
+std::string VersionInfo::LastChange() const {
+  if (!is_valid())
+    return std::string();
+  return UTF16ToUTF8(version_info_->last_change());
+}
+
+bool VersionInfo::IsOfficialBuild() const {
+  if (!is_valid())
+    return false;
+  return version_info_->is_official_build();
+}
+
+#elif defined(OS_POSIX)
+// We get chrome version information from chrome_version_info_posix.h,
+// a generated header.
+
+#include "chrome/common/chrome_version_info_posix.h"
+
+VersionInfo::VersionInfo() {
+}
+
+VersionInfo::~VersionInfo() {
+}
+
+bool VersionInfo::is_valid() const {
+  return true;
+}
+
+std::string VersionInfo::Name() const {
+  return PRODUCT_NAME;
+}
+
+std::string VersionInfo::Version() const {
+  return PRODUCT_VERSION;
+}
+
+std::string VersionInfo::LastChange() const {
+  return LAST_CHANGE;
+}
+
+bool VersionInfo::IsOfficialBuild() const {
+  return IS_OFFICIAL_BUILD;
+}
+
+#endif
+
+std::string VersionInfo::CreateVersionString() const {
+  std::string current_version;
+  if (is_valid()) {
+    current_version += Version();
+#if !defined(GOOGLE_CHROME_BUILD)
+    current_version += " (";
+    current_version += l10n_util::GetStringUTF8(IDS_ABOUT_VERSION_UNOFFICIAL);
+    current_version += " ";
+    current_version += LastChange();
+    current_version += " ";
+    current_version += OSType();
+    current_version += ")";
+#endif
+    std::string modifier = GetVersionStringModifier();
+    if (!modifier.empty())
+      current_version += " " + modifier;
+  }
+  return current_version;
+}
+
+std::string VersionInfo::OSType() const {
+#if defined(OS_WIN)
+  return "Windows";
+#elif defined(OS_MACOSX)
+  return "Mac OS X";
+#elif defined(OS_CHROMEOS)
+  if (ui::ResourceBundle::HasSharedInstance())
+    return UTF16ToASCII(l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_OS_NAME));
+  else
+    return "Chromium OS";
+#elif defined(OS_ANDROID)
+  return "Android";
+#elif defined(OS_LINUX)
+  return "Linux";
+#elif defined(OS_FREEBSD)
+  return "FreeBSD";
+#elif defined(OS_OPENBSD)
+  return "OpenBSD";
+#elif defined(OS_SOLARIS)
+  return "Solaris";
+#else
+  return "Unknown";
+#endif
+}
+
+}  // namespace chrome
diff --git a/chrome/common/chrome_version_info.h b/chrome/common/chrome_version_info.h
new file mode 100644
index 0000000..41cee4b
--- /dev/null
+++ b/chrome/common/chrome_version_info.h
@@ -0,0 +1,92 @@
+// 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.
+
+#ifndef CHROME_COMMON_CHROME_VERSION_INFO_H_
+#define CHROME_COMMON_CHROME_VERSION_INFO_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+
+class FileVersionInfo;
+
+namespace chrome {
+
+// An instance of chrome::VersionInfo has information about the
+// current running build of Chrome.
+class VersionInfo {
+ public:
+  // The possible channels for an installation, from most fun to most stable.
+  enum Channel {
+    CHANNEL_UNKNOWN = 0,  // Probably blue
+    CHANNEL_CANARY,       // Yellow
+    CHANNEL_DEV,          // Technicolor
+    CHANNEL_BETA,         // Rainbow
+    CHANNEL_STABLE        // Full-spectrum
+  };
+
+  VersionInfo();
+  ~VersionInfo();
+
+  // In the rare case where we fail to get the version info,
+  // is_valid() will return false.  The other functions will return
+  // the empty string in this case, so it's not harmful if you don't
+  // check is_valid().
+  bool is_valid() const;
+
+  // E.g. "Chromium" or "Google Chrome".
+  std::string Name() const;
+
+  // Version number, e.g. "6.0.490.1".
+  std::string Version() const;
+
+  // The SVN revision of this release.  E.g. "55800".
+  std::string LastChange() const;
+
+  // Whether this is an "official" release of the current Version():
+  // whether knowing Version() is enough to completely determine what
+  // LastChange() is.
+  bool IsOfficialBuild() const;
+
+  // OS type. E.g. "Windows", "Linux", "FreeBSD", ...
+  std::string OSType() const;
+
+  // Returns a human-readable modifier for the version string. For a branded
+  // build, this modifier is the channel ("canary", "dev", or "beta", but ""
+  // for stable). On Windows, this may be modified with additional information
+  // after a hyphen. For multi-user installations, it will return "canary-m",
+  // "dev-m", "beta-m", and for a stable channel multi-user installation, "m".
+  // In branded builds, when the channel cannot be determined, "unknown" will
+  // be returned. In unbranded builds, the modifier is usually an empty string
+  // (""), although on Linux, it may vary in certain distributions.
+  // GetVersionStringModifier() is intended to be used for display purposes.
+  // To simply test the channel, use GetChannel().
+  static std::string GetVersionStringModifier();
+
+  // Returns the channel for the installation. In branded builds, this will be
+  // CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV, or CHANNEL_CANARY. In unbranded
+  // builds, or in branded builds when the channel cannot be determined, this
+  // will be CHANNEL_UNKNOWN.
+  static Channel GetChannel();
+
+#if defined(OS_CHROMEOS)
+  // Sets channel before use.
+  static void SetChannel(const std::string& channel);
+#endif
+
+  // Returns a version string to be displayed in "About Chromium" dialog.
+  std::string CreateVersionString() const;
+
+ private:
+#if defined(OS_WIN) || defined(OS_MACOSX)
+  scoped_ptr<FileVersionInfo> version_info_;
+#endif
+
+  DISALLOW_COPY_AND_ASSIGN(VersionInfo);
+};
+
+}  // namespace chrome
+
+#endif  // CHROME_COMMON_CHROME_VERSION_INFO_H_
diff --git a/chrome/common/chrome_version_info_android.cc b/chrome/common/chrome_version_info_android.cc
new file mode 100644
index 0000000..a993672
--- /dev/null
+++ b/chrome/common/chrome_version_info_android.cc
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/chrome_version_info.h"
+
+#include "base/android/build_info.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+
+namespace chrome {
+
+// static
+std::string VersionInfo::GetVersionStringModifier() {
+  switch (GetChannel()) {
+    case CHANNEL_UNKNOWN: return "unknown";
+    case CHANNEL_CANARY: return "canary";
+    case CHANNEL_DEV: return "dev";
+    case CHANNEL_BETA: return "beta";
+    case CHANNEL_STABLE: return std::string();
+  }
+  NOTREACHED() << "Unknown channel " << GetChannel();
+  return std::string();
+}
+
+// static
+VersionInfo::Channel VersionInfo::GetChannel() {
+  const base::android::BuildInfo* bi = base::android::BuildInfo::GetInstance();
+  DCHECK(bi && bi->package_name());
+
+  if (!strcmp(bi->package_name(), "com.android.chrome"))
+    return CHANNEL_STABLE;
+  if (!strcmp(bi->package_name(), "com.google.android.apps.chrome_beta"))
+    return CHANNEL_BETA;
+  if (!strcmp(bi->package_name(), "com.google.android.apps.chrome_dev"))
+    return CHANNEL_DEV;
+
+  return CHANNEL_UNKNOWN;
+}
+
+}  // namespace chrome
diff --git a/chrome/common/chrome_version_info_chromeos.cc b/chrome/common/chrome_version_info_chromeos.cc
new file mode 100644
index 0000000..22d9ef7
--- /dev/null
+++ b/chrome/common/chrome_version_info_chromeos.cc
@@ -0,0 +1,50 @@
+// 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.
+
+#include "chrome/common/chrome_version_info.h"
+
+namespace chrome {
+
+static VersionInfo::Channel chromeos_channel = VersionInfo::CHANNEL_UNKNOWN;
+
+// static
+std::string VersionInfo::GetVersionStringModifier() {
+#if defined(GOOGLE_CHROME_BUILD)
+  switch (chromeos_channel) {
+    case CHANNEL_STABLE:
+      return "";
+    case CHANNEL_BETA:
+      return "beta";
+    case CHANNEL_DEV:
+      return "dev";
+    case CHANNEL_CANARY:
+      return "canary";
+    default:
+      return "unknown";
+  }
+#endif
+  return std::string();
+}
+
+// static
+VersionInfo::Channel VersionInfo::GetChannel() {
+  return chromeos_channel;
+}
+
+// static
+void VersionInfo::SetChannel(const std::string& channel) {
+#if defined(GOOGLE_CHROME_BUILD)
+  if (channel == "stable-channel") {
+    chromeos_channel = CHANNEL_STABLE;
+  } else if (channel == "beta-channel") {
+    chromeos_channel = CHANNEL_BETA;
+  } else if (channel == "dev-channel") {
+    chromeos_channel = CHANNEL_DEV;
+  } else if (channel == "canary-channel") {
+    chromeos_channel = CHANNEL_CANARY;
+  }
+#endif
+}
+
+}  // namespace chrome
diff --git a/chrome/common/chrome_version_info_mac.mm b/chrome/common/chrome_version_info_mac.mm
new file mode 100644
index 0000000..3db365b
--- /dev/null
+++ b/chrome/common/chrome_version_info_mac.mm
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/chrome_version_info.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/basictypes.h"
+#include "base/mac/bundle_locations.h"
+#include "base/sys_string_conversions.h"
+
+namespace chrome {
+
+// static
+std::string VersionInfo::GetVersionStringModifier() {
+#if defined(GOOGLE_CHROME_BUILD)
+  // Use the main Chrome application bundle and not the framework bundle.
+  // Keystone keys don't live in the framework.
+  NSBundle* bundle = base::mac::OuterBundle();
+  NSString* channel = [bundle objectForInfoDictionaryKey:@"KSChannelID"];
+
+  // Only ever return "", "unknown", "beta", "dev", or "canary" in a branded
+  // build.
+  if (![bundle objectForInfoDictionaryKey:@"KSProductID"]) {
+    // This build is not Keystone-enabled, it can't have a channel.
+    channel = @"unknown";
+  } else if (!channel) {
+    // For the stable channel, KSChannelID is not set.
+    channel = @"";
+  } else if ([channel isEqual:@"beta"] ||
+             [channel isEqual:@"dev"] ||
+             [channel isEqual:@"canary"]) {
+    // do nothing.
+  } else {
+    channel = @"unknown";
+  }
+
+  return base::SysNSStringToUTF8(channel);
+#else
+  return std::string();
+#endif
+}
+
+// static
+VersionInfo::Channel VersionInfo::GetChannel() {
+#if defined(GOOGLE_CHROME_BUILD)
+  std::string channel = GetVersionStringModifier();
+  if (channel.empty()) {
+    return CHANNEL_STABLE;
+  } else if (channel == "beta") {
+    return CHANNEL_BETA;
+  } else if (channel == "dev") {
+    return CHANNEL_DEV;
+  } else if (channel == "canary") {
+    return CHANNEL_CANARY;
+  }
+#endif
+
+  return CHANNEL_UNKNOWN;
+}
+
+}  // namespace chrome
diff --git a/chrome/common/chrome_version_info_posix.cc b/chrome/common/chrome_version_info_posix.cc
new file mode 100644
index 0000000..b657cbd
--- /dev/null
+++ b/chrome/common/chrome_version_info_posix.cc
@@ -0,0 +1,50 @@
+// 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.
+
+#include "chrome/common/chrome_version_info.h"
+
+namespace chrome {
+
+// static
+std::string VersionInfo::GetVersionStringModifier() {
+  char* env = getenv("CHROME_VERSION_EXTRA");
+  if (!env)
+    return std::string();
+  std::string modifier(env);
+
+#if defined(GOOGLE_CHROME_BUILD)
+  // Only ever return "", "unknown", "dev" or "beta" in a branded build.
+  if (modifier == "unstable")  // linux version of "dev"
+    modifier = "dev";
+  if (modifier == "stable") {
+    modifier = "";
+  } else if ((modifier == "dev") || (modifier == "beta")) {
+    // do nothing.
+  } else {
+    modifier = "unknown";
+  }
+#endif
+
+  return modifier;
+}
+
+// static
+VersionInfo::Channel VersionInfo::GetChannel() {
+#if defined(GOOGLE_CHROME_BUILD)
+  std::string channel = GetVersionStringModifier();
+  if (channel.empty()) {
+    return CHANNEL_STABLE;
+  } else if (channel == "beta") {
+    return CHANNEL_BETA;
+  } else if (channel == "dev") {
+    return CHANNEL_DEV;
+  } else if (channel == "canary") {
+    return CHANNEL_CANARY;
+  }
+#endif
+
+  return CHANNEL_UNKNOWN;
+}
+
+}  // namespace chrome
diff --git a/chrome/common/chrome_version_info_posix.h.version b/chrome/common/chrome_version_info_posix.h.version
new file mode 100644
index 0000000..7ce9982
--- /dev/null
+++ b/chrome/common/chrome_version_info_posix.h.version
@@ -0,0 +1,13 @@
+// Copyright (c) 2010 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.
+
+#ifndef CHROME_COMMON_CHROME_VERSION_INFO_POSIX_H_
+#define CHROME_COMMON_CHROME_VERSION_INFO_POSIX_H_
+
+#define PRODUCT_NAME "@PRODUCT_FULLNAME@"
+#define PRODUCT_VERSION "@MAJOR@.@MINOR@.@BUILD@.@PATCH@"
+#define LAST_CHANGE "@LASTCHANGE@"
+#define IS_OFFICIAL_BUILD @OFFICIAL_BUILD@
+
+#endif  // CHROME_COMMON_CHROME_VERSION_INFO_POSIX_H_
diff --git a/chrome/common/chrome_version_info_win.cc b/chrome/common/chrome_version_info_win.cc
new file mode 100644
index 0000000..2dd77e1
--- /dev/null
+++ b/chrome/common/chrome_version_info_win.cc
@@ -0,0 +1,60 @@
+// 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.
+
+#include "chrome/common/chrome_version_info.h"
+
+#include "base/base_paths.h"
+#include "base/file_path.h"
+#include "base/path_service.h"
+#include "base/string_util.h"
+#include "chrome/installer/util/install_util.h"
+#include "chrome/installer/util/google_update_settings.h"
+
+namespace chrome {
+
+// static
+std::string VersionInfo::GetVersionStringModifier() {
+#if defined(GOOGLE_CHROME_BUILD)
+  FilePath module;
+  string16 channel;
+  if (PathService::Get(base::FILE_MODULE, &module)) {
+    bool is_system_install =
+        !InstallUtil::IsPerUserInstall(module.value().c_str());
+
+    GoogleUpdateSettings::GetChromeChannelAndModifiers(is_system_install,
+                                                       &channel);
+  }
+  return UTF16ToASCII(channel);
+#else
+  return std::string();
+#endif
+}
+
+// static
+VersionInfo::Channel VersionInfo::GetChannel() {
+#if defined(GOOGLE_CHROME_BUILD)
+  std::wstring channel(L"unknown");
+
+  FilePath module;
+  if (PathService::Get(base::FILE_MODULE, &module)) {
+    bool is_system_install =
+        !InstallUtil::IsPerUserInstall(module.value().c_str());
+    channel = GoogleUpdateSettings::GetChromeChannel(is_system_install);
+  }
+
+  if (channel.empty()) {
+    return CHANNEL_STABLE;
+  } else if (channel == L"beta") {
+    return CHANNEL_BETA;
+  } else if (channel == L"dev") {
+    return CHANNEL_DEV;
+  } else if (channel == L"canary") {
+    return CHANNEL_CANARY;
+  }
+#endif
+
+  return CHANNEL_UNKNOWN;
+}
+
+}  // namespace chrome
diff --git a/chrome/common/cloud_print/cloud_print_class_mac.h b/chrome/common/cloud_print/cloud_print_class_mac.h
new file mode 100644
index 0000000..c2b7e44
--- /dev/null
+++ b/chrome/common/cloud_print/cloud_print_class_mac.h
@@ -0,0 +1,17 @@
+// 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.
+
+#ifndef CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_CLASS_MAC_H_
+#define CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_CLASS_MAC_H_
+
+#import <AppKit/AppKit.h>
+
+namespace cloud_print {
+
+// Four character constant to identify Cloud print IPC call.
+extern const AEEventClass kAECloudPrintClass;
+
+}  // namespace cloud_print
+
+#endif  // CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_CLASS_MAC_H_
diff --git a/chrome/common/cloud_print/cloud_print_class_mac.mm b/chrome/common/cloud_print/cloud_print_class_mac.mm
new file mode 100644
index 0000000..9905e89
--- /dev/null
+++ b/chrome/common/cloud_print/cloud_print_class_mac.mm
@@ -0,0 +1,11 @@
+// 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.
+
+#import "chrome/common/cloud_print/cloud_print_class_mac.h"
+
+namespace cloud_print {
+
+const AEEventClass kAECloudPrintClass = 'GCPp';
+
+}  // namespace cloud_print
diff --git a/chrome/common/cloud_print/cloud_print_helpers.cc b/chrome/common/cloud_print/cloud_print_helpers.cc
new file mode 100644
index 0000000..dbe7544
--- /dev/null
+++ b/chrome/common/cloud_print/cloud_print_helpers.cc
@@ -0,0 +1,93 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/cloud_print/cloud_print_helpers.h"
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/rand_util.h"
+#include "base/stringprintf.h"
+#include "base/values.h"
+#include "googleurl/src/gurl.h"
+
+namespace cloud_print {
+
+const char kPrinterListValue[] = "printers";
+const char kSuccessValue[] = "success";
+
+// Certain cloud print requests require Chrome's X-CloudPrint-Proxy header.
+const char kChromeCloudPrintProxyHeader[] = "X-CloudPrint-Proxy: Chrome";
+
+std::string AppendPathToUrl(const GURL& url, const std::string& path) {
+  DCHECK_NE(path[0], '/');
+  std::string ret = url.path();
+  if (url.has_path() && (ret[ret.length() - 1] != '/'))
+    ret += '/';
+  ret += path;
+  return ret;
+}
+
+GURL GetUrlForSearch(const GURL& cloud_print_server_url) {
+  std::string path(AppendPathToUrl(cloud_print_server_url, "search"));
+  GURL::Replacements replacements;
+  replacements.SetPathStr(path);
+  return cloud_print_server_url.ReplaceComponents(replacements);
+}
+
+GURL GetUrlForSubmit(const GURL& cloud_print_server_url) {
+  std::string path(AppendPathToUrl(cloud_print_server_url, "submit"));
+  GURL::Replacements replacements;
+  replacements.SetPathStr(path);
+  return cloud_print_server_url.ReplaceComponents(replacements);
+}
+
+bool ParseResponseJSON(const std::string& response_data,
+                       bool* succeeded,
+                       DictionaryValue** response_dict) {
+  scoped_ptr<Value> message_value(base::JSONReader::Read(response_data));
+  if (!message_value.get())
+    return false;
+
+  if (!message_value->IsType(Value::TYPE_DICTIONARY))
+    return false;
+
+  scoped_ptr<DictionaryValue> response_dict_local(
+      static_cast<DictionaryValue*>(message_value.release()));
+  if (succeeded &&
+      !response_dict_local->GetBoolean(cloud_print::kSuccessValue, succeeded))
+    *succeeded = false;
+  if (response_dict)
+    *response_dict = response_dict_local.release();
+  return true;
+}
+
+void AddMultipartValueForUpload(const std::string& value_name,
+                                const std::string& value,
+                                const std::string& mime_boundary,
+                                const std::string& content_type,
+                                std::string* post_data) {
+  DCHECK(post_data);
+  // First line is the boundary
+  post_data->append("--" + mime_boundary + "\r\n");
+  // Next line is the Content-disposition
+  post_data->append(StringPrintf("Content-Disposition: form-data; "
+                   "name=\"%s\"\r\n", value_name.c_str()));
+  if (!content_type.empty()) {
+    // If Content-type is specified, the next line is that
+    post_data->append(StringPrintf("Content-Type: %s\r\n",
+                      content_type.c_str()));
+  }
+  // Leave an empty line and append the value.
+  post_data->append(StringPrintf("\r\n%s\r\n", value.c_str()));
+}
+
+// Create a MIME boundary marker (27 '-' characters followed by 16 hex digits).
+void CreateMimeBoundaryForUpload(std::string* out) {
+  int r1 = base::RandInt(0, kint32max);
+  int r2 = base::RandInt(0, kint32max);
+  base::SStringPrintf(out, "---------------------------%08X%08X", r1, r2);
+}
+
+}  // namespace cloud_print
diff --git a/chrome/common/cloud_print/cloud_print_helpers.h b/chrome/common/cloud_print/cloud_print_helpers.h
new file mode 100644
index 0000000..1b4a15e
--- /dev/null
+++ b/chrome/common/cloud_print/cloud_print_helpers.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_HELPERS_H_
+#define CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_HELPERS_H_
+
+#include <string>
+
+class GURL;
+
+namespace base {
+class DictionaryValue;
+}
+
+// Helper consts and methods for both cloud print and chrome browser.
+namespace cloud_print {
+
+// Values in the respone JSON from the cloud print server
+extern const char kPrinterListValue[];
+extern const char kSuccessValue[];
+
+extern const char kChromeCloudPrintProxyHeader[];
+
+// Appends a relative path to the url making sure to append a '/' if the
+// URL's path does not end with a slash. It is assumed that |path| does not
+// begin with a '/'.
+// NOTE: Since we ALWAYS want to append here, we simply append the path string
+// instead of calling url_utils::ResolveRelative. The input |url| may or may not
+// contain a '/' at the end.
+std::string AppendPathToUrl(const GURL& url, const std::string& path);
+
+GURL GetUrlForSearch(const GURL& cloud_print_server_url);
+GURL GetUrlForSubmit(const GURL& cloud_print_server_url);
+
+// Parses the response data for any cloud print server request. The method
+// returns false if there was an error in parsing the JSON. The succeeded
+// value returns the value of the "success" value in the response JSON.
+// Returns the response as a dictionary value.
+bool ParseResponseJSON(const std::string& response_data,
+                       bool* succeeded,
+                       base::DictionaryValue** response_dict);
+
+// Prepares one value as part of a multi-part upload request.
+void AddMultipartValueForUpload(const std::string& value_name,
+                                const std::string& value,
+                                const std::string& mime_boundary,
+                                const std::string& content_type,
+                                std::string* post_data);
+
+// Create a MIME boundary marker (27 '-' characters followed by 16 hex digits).
+void CreateMimeBoundaryForUpload(std::string *out);
+
+}  // namespace cloud_print
+
+#endif  // CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_HELPERS_H_
diff --git a/chrome/common/cloud_print/cloud_print_proxy_info.cc b/chrome/common/cloud_print/cloud_print_proxy_info.cc
new file mode 100644
index 0000000..6e38663
--- /dev/null
+++ b/chrome/common/cloud_print/cloud_print_proxy_info.cc
@@ -0,0 +1,14 @@
+// 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.
+
+#include "chrome/common/cloud_print/cloud_print_proxy_info.h"
+
+namespace cloud_print {
+
+CloudPrintProxyInfo::CloudPrintProxyInfo() : enabled(false) {
+}
+
+CloudPrintProxyInfo::~CloudPrintProxyInfo() {}
+
+}  // namespace cloud_print
diff --git a/chrome/common/cloud_print/cloud_print_proxy_info.h b/chrome/common/cloud_print/cloud_print_proxy_info.h
new file mode 100644
index 0000000..2b97378
--- /dev/null
+++ b/chrome/common/cloud_print/cloud_print_proxy_info.h
@@ -0,0 +1,24 @@
+// 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.
+
+#ifndef CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_PROXY_INFO_H_
+#define CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_PROXY_INFO_H_
+
+#include <string>
+
+namespace cloud_print {
+
+// This struct is used for ServiceHostMsg_CloudPrint_Info IPC message.
+struct CloudPrintProxyInfo {
+  CloudPrintProxyInfo();
+  ~CloudPrintProxyInfo();
+
+  bool enabled;
+  std::string email;
+  std::string proxy_id;
+};
+
+}  // namespace cloud_print
+
+#endif  // CHROME_COMMON_CLOUD_PRINT_CLOUD_PRINT_PROXY_INFO_H_
diff --git a/chrome/common/common.vsprops b/chrome/common/common.vsprops
new file mode 100644
index 0000000..a834391
--- /dev/null
+++ b/chrome/common/common.vsprops
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+	ProjectType="Visual C++"
+	Version="8.00"
+	Name="common (chrome)"
+	InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\third_party\icu\build\using_icu.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops;$(SolutionDir)..\third_party\libpng\using_libpng.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;$(SolutionDir)..\tools\grit\build\using_generated_resources.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;$(SolutionDir)..\third_party\npapi\using_npapi.vsprops;$(SolutionDir)..\chrome\third_party\wtl\using_wtl.vsprops;$(SolutionDir)\common\extra_defines.vsprops"
+	>
+</VisualStudioPropertySheet>
diff --git a/chrome/common/common_message_generator.cc b/chrome/common/common_message_generator.cc
new file mode 100644
index 0000000..72e755e
--- /dev/null
+++ b/chrome/common/common_message_generator.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2010 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.
+
+// Get basic type definitions.
+#define IPC_MESSAGE_IMPL
+#include "chrome/common/common_message_generator.h"
+
+// Generate constructors.
+#include "ipc/struct_constructor_macros.h"
+#include "chrome/common/common_message_generator.h"
+
+// Generate destructors.
+#include "ipc/struct_destructor_macros.h"
+#include "chrome/common/common_message_generator.h"
+
+// Generate param traits write methods.
+#include "ipc/param_traits_write_macros.h"
+namespace IPC {
+#include "chrome/common/common_message_generator.h"
+}  // namespace IPC
+
+// Generate param traits read methods.
+#include "ipc/param_traits_read_macros.h"
+namespace IPC {
+#include "chrome/common/common_message_generator.h"
+}  // namespace IPC
+
+// Generate param traits log methods.
+#include "ipc/param_traits_log_macros.h"
+namespace IPC {
+#include "chrome/common/common_message_generator.h"
+}  // namespace IPC
+
diff --git a/chrome/common/common_message_generator.h b/chrome/common/common_message_generator.h
new file mode 100644
index 0000000..b6c06e9
--- /dev/null
+++ b/chrome/common/common_message_generator.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 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.
+
+// Multiply-included file, hence no include guard.
+
+#include "chrome/common/autofill_messages.h"
+#include "chrome/common/benchmarking_messages.h"
+#include "chrome/common/chrome_utility_messages.h"
+#include "chrome/common/extensions/extension_messages.h"
+#include "chrome/common/icon_messages.h"
+#include "chrome/common/prerender_messages.h"
+#include "chrome/common/print_messages.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/safe_browsing/safebrowsing_messages.h"
+#include "chrome/common/service_messages.h"
+#include "chrome/common/spellcheck_messages.h"
+
diff --git a/chrome/common/common_param_traits.cc b/chrome/common/common_param_traits.cc
new file mode 100644
index 0000000..466cfde
--- /dev/null
+++ b/chrome/common/common_param_traits.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 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.
+
+// Get basic type definitions.
+#include "chrome/common/common_param_traits.h"
+
+// Generate param traits write methods.
+#include "ipc/param_traits_write_macros.h"
+namespace IPC {
+#undef CHROME_COMMON_COMMON_PARAM_TRAITS_MACROS_H_
+#include "chrome/common/common_param_traits_macros.h"
+}  // namespace IPC
+
+// Generate param traits read methods.
+#include "ipc/param_traits_read_macros.h"
+namespace IPC {
+#undef CHROME_COMMON_COMMON_PARAM_TRAITS_MACROS_H_
+#include "chrome/common/common_param_traits_macros.h"
+}  // namespace IPC
+
+// Generate param traits log methods.
+#include "ipc/param_traits_log_macros.h"
+namespace IPC {
+#undef CHROME_COMMON_COMMON_PARAM_TRAITS_MACROS_H_
+#include "chrome/common/common_param_traits_macros.h"
+}  // namespace IPC
diff --git a/chrome/common/common_param_traits.h b/chrome/common/common_param_traits.h
new file mode 100644
index 0000000..9a05b2b
--- /dev/null
+++ b/chrome/common/common_param_traits.h
@@ -0,0 +1,13 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_COMMON_PARAM_TRAITS_H_
+#define CHROME_COMMON_COMMON_PARAM_TRAITS_H_
+
+// This file provides declarations of IPC serialization macros that are used
+// in more than one IPC message file.
+
+#include "chrome/common/common_param_traits_macros.h"
+
+#endif  // CHROME_COMMON_COMMON_PARAM_TRAITS_H_
diff --git a/chrome/common/common_param_traits_macros.h b/chrome/common/common_param_traits_macros.h
new file mode 100644
index 0000000..de209a8
--- /dev/null
+++ b/chrome/common/common_param_traits_macros.h
@@ -0,0 +1,17 @@
+// Copyright (c) 2012 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.
+
+// Singly or multiply-included shared traits file depending upon circumstances.
+// This allows the use of IPC serialization macros in more than one IPC message
+// file.
+#ifndef CHROME_COMMON_COMMON_PARAM_TRAITS_MACROS_H_
+#define CHROME_COMMON_COMMON_PARAM_TRAITS_MACROS_H_
+
+#include "chrome/common/content_settings.h"
+#include "ipc/ipc_message_macros.h"
+
+IPC_ENUM_TRAITS(ContentSetting)
+IPC_ENUM_TRAITS(ContentSettingsType)
+
+#endif  // CHROME_COMMON_COMMON_PARAM_TRAITS_MACROS_H_
diff --git a/chrome/common/common_param_traits_unittest.cc b/chrome/common/common_param_traits_unittest.cc
new file mode 100644
index 0000000..c956fae
--- /dev/null
+++ b/chrome/common/common_param_traits_unittest.cc
@@ -0,0 +1,186 @@
+// Copyright (c) 2012 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.
+
+#include <string.h>
+#include <utility>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "content/public/common/common_param_traits.h"
+#include "googleurl/src/gurl.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_utils.h"
+#include "net/base/host_port_pair.h"
+#include "printing/backend/print_backend.h"
+#include "printing/page_range.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/rect.h"
+
+// Tests that serialize/deserialize correctly understand each other
+TEST(IPCMessageTest, Serialize) {
+  const char* serialize_cases[] = {
+    "http://www.google.com/",
+    "http://user:pass@host.com:888/foo;bar?baz#nop",
+    "#inva://idurl/",
+  };
+
+  for (size_t i = 0; i < arraysize(serialize_cases); i++) {
+    GURL input(serialize_cases[i]);
+    IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+    IPC::ParamTraits<GURL>::Write(&msg, input);
+
+    GURL output;
+    PickleIterator iter(msg);
+    EXPECT_TRUE(IPC::ParamTraits<GURL>::Read(&msg, &iter, &output));
+
+    // We want to test each component individually to make sure its range was
+    // correctly serialized and deserialized, not just the spec.
+    EXPECT_EQ(input.possibly_invalid_spec(), output.possibly_invalid_spec());
+    EXPECT_EQ(input.is_valid(), output.is_valid());
+    EXPECT_EQ(input.scheme(), output.scheme());
+    EXPECT_EQ(input.username(), output.username());
+    EXPECT_EQ(input.password(), output.password());
+    EXPECT_EQ(input.host(), output.host());
+    EXPECT_EQ(input.port(), output.port());
+    EXPECT_EQ(input.path(), output.path());
+    EXPECT_EQ(input.query(), output.query());
+    EXPECT_EQ(input.ref(), output.ref());
+  }
+
+  // Also test the corrupt case.
+  IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+  msg.WriteInt(99);
+  GURL output;
+  PickleIterator iter(msg);
+  EXPECT_FALSE(IPC::ParamTraits<GURL>::Read(&msg, &iter, &output));
+}
+
+// Tests std::pair serialization
+TEST(IPCMessageTest, Pair) {
+  typedef std::pair<std::string, std::string> TestPair;
+
+  TestPair input("foo", "bar");
+  IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+  IPC::ParamTraits<TestPair>::Write(&msg, input);
+
+  TestPair output;
+  PickleIterator iter(msg);
+  EXPECT_TRUE(IPC::ParamTraits<TestPair>::Read(&msg, &iter, &output));
+  EXPECT_EQ(output.first, "foo");
+  EXPECT_EQ(output.second, "bar");
+
+}
+
+// Tests bitmap serialization.
+TEST(IPCMessageTest, Bitmap) {
+  SkBitmap bitmap;
+
+  bitmap.setConfig(SkBitmap::kARGB_8888_Config, 10, 5);
+  bitmap.allocPixels();
+  memset(bitmap.getPixels(), 'A', bitmap.getSize());
+
+  IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+  IPC::ParamTraits<SkBitmap>::Write(&msg, bitmap);
+
+  SkBitmap output;
+  PickleIterator iter(msg);
+  EXPECT_TRUE(IPC::ParamTraits<SkBitmap>::Read(&msg, &iter, &output));
+
+  EXPECT_EQ(bitmap.config(), output.config());
+  EXPECT_EQ(bitmap.width(), output.width());
+  EXPECT_EQ(bitmap.height(), output.height());
+  EXPECT_EQ(bitmap.rowBytes(), output.rowBytes());
+  EXPECT_EQ(bitmap.getSize(), output.getSize());
+  EXPECT_EQ(memcmp(bitmap.getPixels(), output.getPixels(), bitmap.getSize()),
+            0);
+
+  // Also test the corrupt case.
+  IPC::Message bad_msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+  // Copy the first message block over to |bad_msg|.
+  const char* fixed_data;
+  int fixed_data_size;
+  iter = PickleIterator(msg);
+  msg.ReadData(&iter, &fixed_data, &fixed_data_size);
+  bad_msg.WriteData(fixed_data, fixed_data_size);
+  // Add some bogus pixel data.
+  const size_t bogus_pixels_size = bitmap.getSize() * 2;
+  scoped_array<char> bogus_pixels(new char[bogus_pixels_size]);
+  memset(bogus_pixels.get(), 'B', bogus_pixels_size);
+  bad_msg.WriteData(bogus_pixels.get(), bogus_pixels_size);
+  // Make sure we don't read out the bitmap!
+  SkBitmap bad_output;
+  iter = PickleIterator(bad_msg);
+  EXPECT_FALSE(IPC::ParamTraits<SkBitmap>::Read(&bad_msg, &iter, &bad_output));
+}
+
+TEST(IPCMessageTest, ListValue) {
+  ListValue input;
+  input.Set(0, Value::CreateDoubleValue(42.42));
+  input.Set(1, Value::CreateStringValue("forty"));
+  input.Set(2, Value::CreateNullValue());
+
+  IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+  IPC::WriteParam(&msg, input);
+
+  ListValue output;
+  PickleIterator iter(msg);
+  EXPECT_TRUE(IPC::ReadParam(&msg, &iter, &output));
+
+  EXPECT_TRUE(input.Equals(&output));
+
+  // Also test the corrupt case.
+  IPC::Message bad_msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+  bad_msg.WriteInt(99);
+  iter = PickleIterator(bad_msg);
+  EXPECT_FALSE(IPC::ReadParam(&bad_msg, &iter, &output));
+}
+
+TEST(IPCMessageTest, DictionaryValue) {
+  DictionaryValue input;
+  input.Set("null", Value::CreateNullValue());
+  input.Set("bool", Value::CreateBooleanValue(true));
+  input.Set("int", Value::CreateIntegerValue(42));
+
+  scoped_ptr<DictionaryValue> subdict(new DictionaryValue());
+  subdict->Set("str", Value::CreateStringValue("forty two"));
+  subdict->Set("bool", Value::CreateBooleanValue(false));
+
+  scoped_ptr<ListValue> sublist(new ListValue());
+  sublist->Set(0, Value::CreateDoubleValue(42.42));
+  sublist->Set(1, Value::CreateStringValue("forty"));
+  sublist->Set(2, Value::CreateStringValue("two"));
+  subdict->Set("list", sublist.release());
+
+  input.Set("dict", subdict.release());
+
+  IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+  IPC::WriteParam(&msg, input);
+
+  DictionaryValue output;
+  PickleIterator iter(msg);
+  EXPECT_TRUE(IPC::ReadParam(&msg, &iter, &output));
+
+  EXPECT_TRUE(input.Equals(&output));
+
+  // Also test the corrupt case.
+  IPC::Message bad_msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+  bad_msg.WriteInt(99);
+  iter = PickleIterator(bad_msg);
+  EXPECT_FALSE(IPC::ReadParam(&bad_msg, &iter, &output));
+}
+
+// Tests net::HostPortPair serialization
+TEST(IPCMessageTest, HostPortPair) {
+  net::HostPortPair input("host.com", 12345);
+
+  IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+  IPC::ParamTraits<net::HostPortPair>::Write(&msg, input);
+
+  net::HostPortPair output;
+  PickleIterator iter(msg);
+  EXPECT_TRUE(IPC::ParamTraits<net::HostPortPair>::Read(&msg, &iter, &output));
+  EXPECT_EQ(input.host(), output.host());
+  EXPECT_EQ(input.port(), output.port());
+}
diff --git a/chrome/common/common_resources.grd b/chrome/common/common_resources.grd
new file mode 100644
index 0000000..8d014a8
--- /dev/null
+++ b/chrome/common/common_resources.grd
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1">
+  <outputs>
+    <output filename="grit/common_resources.h" type="rc_header">
+      <emit emit_type='prepend'></emit>
+    </output>
+    <output filename="common_resources.pak" type="data_package" />
+    <output filename="common_resources.rc" type="rc_all" />
+  </outputs>
+  <release seq="1">
+    <includes>
+      <include name="IDR_EXTENSION_MANIFEST_FEATURES" file="extensions\api\_manifest_features.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_PERMISSION_FEATURES" file="extensions\api\_permission_features.json" type="BINDATA" />
+      <include name="IDR_I18N_PROCESS_JS" file="..\browser\resources\shared\js\i18n_process.js" type="BINDATA" />
+      <include name="IDR_I18N_TEMPLATE_JS" file="..\browser\resources\shared\js\i18n_template.js" type="BINDATA" />
+      <include name="IDR_I18N_TEMPLATE2_JS" file="..\browser\resources\shared\js\i18n_template2.js" flattenhtml="true" type="BINDATA" />
+      <include name="IDR_JSTEMPLATE_JS" file="..\browser\resources\shared\js\jstemplate_compiled.js" flattenhtml="true" type="BINDATA" />
+      <include name="IDR_WEB_APP_SCHEMA" file="web_app_schema.json" type="BINDATA" />
+      <if expr="is_macosx">
+        <include name="IDR_NACL_SANDBOX_PROFILE" file="nacl_loader.sb" type="BINDATA" />
+      </if>
+    </includes>
+  </release>
+</grit>
diff --git a/chrome/common/content_settings.cc b/chrome/common/content_settings.cc
new file mode 100644
index 0000000..5d48bc9
--- /dev/null
+++ b/chrome/common/content_settings.cc
@@ -0,0 +1,31 @@
+// 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.
+
+#include "chrome/common/content_settings.h"
+
+ContentSetting IntToContentSetting(int content_setting) {
+  return ((content_setting < 0) ||
+          (content_setting >= CONTENT_SETTING_NUM_SETTINGS)) ?
+      CONTENT_SETTING_DEFAULT : static_cast<ContentSetting>(content_setting);
+}
+
+ContentSettingPatternSource::ContentSettingPatternSource(
+    const ContentSettingsPattern& primary_pattern,
+    const ContentSettingsPattern& secondary_pattern,
+    ContentSetting setting,
+    const std::string& source,
+    bool incognito)
+    : primary_pattern(primary_pattern),
+      secondary_pattern(secondary_pattern),
+      setting(setting),
+      source(source),
+      incognito(incognito) {}
+
+ContentSettingPatternSource::ContentSettingPatternSource()
+    : setting(CONTENT_SETTING_DEFAULT), incognito(false) {
+}
+
+RendererContentSettingRules::RendererContentSettingRules() {}
+
+RendererContentSettingRules::~RendererContentSettingRules() {}
diff --git a/chrome/common/content_settings.h b/chrome/common/content_settings.h
new file mode 100644
index 0000000..bf92413
--- /dev/null
+++ b/chrome/common/content_settings.h
@@ -0,0 +1,77 @@
+// 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.
+
+#ifndef CHROME_COMMON_CONTENT_SETTINGS_H_
+#define CHROME_COMMON_CONTENT_SETTINGS_H_
+
+#include <string>
+#include <vector>
+
+#include "chrome/common/content_settings_pattern.h"
+#include "chrome/common/content_settings_types.h"
+
+// Different settings that can be assigned for a particular content type.  We
+// give the user the ability to set these on a global and per-origin basis.
+enum ContentSetting {
+  CONTENT_SETTING_DEFAULT = 0,
+  CONTENT_SETTING_ALLOW,
+  CONTENT_SETTING_BLOCK,
+  CONTENT_SETTING_ASK,
+  CONTENT_SETTING_SESSION_ONLY,
+  CONTENT_SETTING_NUM_SETTINGS
+};
+
+// Range-checked conversion of an int to a ContentSetting, for use when reading
+// prefs off disk.
+ContentSetting IntToContentSetting(int content_setting);
+
+struct ContentSettingPatternSource {
+  ContentSettingPatternSource(const ContentSettingsPattern& primary_pattern,
+                              const ContentSettingsPattern& secondary_patttern,
+                              ContentSetting setting,
+                              const std::string& source,
+                              bool incognito);
+  ContentSettingPatternSource();
+  ContentSettingsPattern primary_pattern;
+  ContentSettingsPattern secondary_pattern;
+  ContentSetting setting;
+  std::string source;
+  bool incognito;
+};
+
+typedef std::vector<ContentSettingPatternSource> ContentSettingsForOneType;
+
+struct RendererContentSettingRules {
+  RendererContentSettingRules();
+  ~RendererContentSettingRules();
+  ContentSettingsForOneType image_rules;
+  ContentSettingsForOneType script_rules;
+};
+
+namespace content_settings {
+
+// Enum containing the various source for content settings. Settings can be
+// set by policy, extension or the user. Certain (internal) schemes are
+// whilelisted. For whilelisted schemes the source is
+// |SETTING_SOURCE_WHITELIST|.
+enum SettingSource {
+  SETTING_SOURCE_NONE,
+  SETTING_SOURCE_POLICY,
+  SETTING_SOURCE_EXTENSION,
+  SETTING_SOURCE_USER,
+  SETTING_SOURCE_WHITELIST,
+};
+
+// |SettingInfo| provides meta data for content setting values. |source|
+// contains the source of a value. |primary_pattern| and |secondary_pattern|
+// contains the patterns of the appling rule.
+struct SettingInfo {
+  SettingSource source;
+  ContentSettingsPattern primary_pattern;
+  ContentSettingsPattern secondary_pattern;
+};
+
+}  // namespace content_settings
+
+#endif  // CHROME_COMMON_CONTENT_SETTINGS_H_
diff --git a/chrome/common/content_settings_helper.cc b/chrome/common/content_settings_helper.cc
new file mode 100644
index 0000000..c98ed3f
--- /dev/null
+++ b/chrome/common/content_settings_helper.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/content_settings_helper.h"
+
+#include "base/string_piece.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/url_constants.h"
+#include "googleurl/src/gurl.h"
+
+namespace content_settings_helper {
+
+std::string OriginToString(const GURL& origin) {
+   std::string port_component(origin.IntPort() != url_parse::PORT_UNSPECIFIED ?
+      ":" + origin.port() : "");
+  std::string scheme_component(!origin.SchemeIs(chrome::kHttpScheme) ?
+      origin.scheme() + content::kStandardSchemeSeparator : "");
+  return scheme_component + origin.host() + port_component;
+}
+
+string16 OriginToString16(const GURL& origin) {
+  return UTF8ToUTF16(OriginToString(origin));
+}
+
+}  // namespace content_settings_helper
diff --git a/chrome/common/content_settings_helper.h b/chrome/common/content_settings_helper.h
new file mode 100644
index 0000000..a29e30d
--- /dev/null
+++ b/chrome/common/content_settings_helper.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2010 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.
+
+#ifndef CHROME_COMMON_CONTENT_SETTINGS_HELPER_H_
+#define CHROME_COMMON_CONTENT_SETTINGS_HELPER_H_
+
+#include <string>
+
+#include "base/string16.h"
+
+class GURL;
+
+namespace content_settings_helper {
+
+// Return simplified string representing origin. If origin is using http or
+// the standard port, those parts are not included in the output.
+std::string OriginToString(const GURL& origin);
+string16 OriginToString16(const GURL& origin);
+
+}  // namespace content_settings_helper
+
+#endif  // CHROME_COMMON_CONTENT_SETTINGS_HELPER_H_
diff --git a/chrome/common/content_settings_helper_unittest.cc b/chrome/common/content_settings_helper_unittest.cc
new file mode 100644
index 0000000..f20a1a2
--- /dev/null
+++ b/chrome/common/content_settings_helper_unittest.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2010 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.
+
+#include "chrome/common/content_settings_helper.h"
+
+#include "base/utf_string_conversions.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(ContentSettingsHelperTest, OriginToString16) {
+  // Urls with "http":
+  const GURL kUrl0("http://www.foo.com/bar");
+  const GURL kUrl1("http://foo.com/bar");
+
+  const GURL kUrl2("http://www.foo.com:81/bar");
+  const GURL kUrl3("http://foo.com:81/bar");
+
+  // Urls with "https":
+  const GURL kUrl4("https://www.foo.com/bar");
+  const GURL kUrl5("https://foo.com/bar");
+
+  const GURL kUrl6("https://www.foo.com:81/bar");
+  const GURL kUrl7("https://foo.com:81/bar");
+
+  // Now check the first group of urls with just "http":
+  EXPECT_EQ(ASCIIToUTF16("www.foo.com"),
+            content_settings_helper::OriginToString16(kUrl0));
+  EXPECT_EQ(ASCIIToUTF16("foo.com"),
+            content_settings_helper::OriginToString16(kUrl1));
+
+  EXPECT_EQ(ASCIIToUTF16("www.foo.com:81"),
+            content_settings_helper::OriginToString16(kUrl2));
+  EXPECT_EQ(ASCIIToUTF16("foo.com:81"),
+            content_settings_helper::OriginToString16(kUrl3));
+
+  // Now check the second group of urls with "https":
+  EXPECT_EQ(ASCIIToUTF16("https://www.foo.com"),
+            content_settings_helper::OriginToString16(kUrl4));
+  EXPECT_EQ(ASCIIToUTF16("https://foo.com"),
+            content_settings_helper::OriginToString16(kUrl5));
+
+  EXPECT_EQ(ASCIIToUTF16("https://www.foo.com:81"),
+            content_settings_helper::OriginToString16(kUrl6));
+  EXPECT_EQ(ASCIIToUTF16("https://foo.com:81"),
+            content_settings_helper::OriginToString16(kUrl7));
+}
diff --git a/chrome/common/content_settings_pattern.cc b/chrome/common/content_settings_pattern.cc
new file mode 100644
index 0000000..f5aa898
--- /dev/null
+++ b/chrome/common/content_settings_pattern.cc
@@ -0,0 +1,668 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/content_settings_pattern.h"
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "chrome/common/content_settings_pattern_parser.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/url_constants.h"
+#include "googleurl/src/gurl.h"
+#include "googleurl/src/url_canon.h"
+#include "ipc/ipc_message_utils.h"
+#include "net/base/dns_util.h"
+#include "net/base/net_util.h"
+
+namespace {
+
+std::string GetDefaultPort(const std::string& scheme) {
+  if (scheme == chrome::kHttpScheme)
+    return "80";
+  if (scheme == chrome::kHttpsScheme)
+    return "443";
+  return "";
+}
+
+// Returns true if |sub_domain| is a sub domain or equls |domain|.  E.g.
+// "mail.google.com" is a sub domain of "google.com" but "evilhost.com" is not a
+// subdomain of "host.com".
+bool IsSubDomainOrEqual(const std::string& sub_domain,
+                        const std::string& domain) {
+  // The empty string serves as wildcard. Each domain is a subdomain of the
+  // wildcard.
+  if (domain.empty())
+    return true;
+  const size_t match = sub_domain.rfind(domain);
+  if (match == std::string::npos ||
+      (match > 0 && sub_domain[match - 1] != '.') ||
+      (match + domain.length() != sub_domain.length())) {
+    return false;
+  }
+  return true;
+}
+
+// Compares two domain names.
+int CompareDomainNames(const std::string& str1, const std::string& str2) {
+  std::vector<std::string> domain_name1;
+  std::vector<std::string> domain_name2;
+
+  base::SplitString(str1, '.', &domain_name1);
+  base::SplitString(str2, '.', &domain_name2);
+
+  int i1 = domain_name1.size() - 1;
+  int i2 = domain_name2.size() - 1;
+  int rv;
+  while (i1 >= 0 && i2 >= 0) {
+    // domain names are stored in puny code. So it's fine to use the compare
+    // method.
+    rv = domain_name1[i1].compare(domain_name2[i2]);
+    if (rv != 0)
+      return rv;
+    --i1;
+    --i2;
+  }
+
+  if (i1 > i2)
+    return 1;
+
+  if (i1 < i2)
+    return -1;
+
+  // The domain names are identical.
+  return 0;
+}
+
+typedef ContentSettingsPattern::BuilderInterface BuilderInterface;
+
+}  // namespace
+
+// ////////////////////////////////////////////////////////////////////////////
+// ContentSettingsPattern::Builder
+//
+ContentSettingsPattern::Builder::Builder(bool use_legacy_validate)
+    : is_valid_(true),
+      use_legacy_validate_(use_legacy_validate) {}
+
+ContentSettingsPattern::Builder::~Builder() {}
+
+BuilderInterface* ContentSettingsPattern::Builder::WithPort(
+    const std::string& port) {
+  parts_.port = port;
+  parts_.is_port_wildcard = false;
+  return this;
+}
+
+BuilderInterface* ContentSettingsPattern::Builder::WithPortWildcard() {
+  parts_.port = "";
+  parts_.is_port_wildcard = true;
+  return this;
+}
+
+BuilderInterface* ContentSettingsPattern::Builder::WithHost(
+    const std::string& host) {
+  parts_.host = host;
+  return this;
+}
+
+BuilderInterface* ContentSettingsPattern::Builder::WithDomainWildcard() {
+  parts_.has_domain_wildcard = true;
+  return this;
+}
+
+BuilderInterface* ContentSettingsPattern::Builder::WithScheme(
+    const std::string& scheme) {
+  parts_.scheme = scheme;
+  parts_.is_scheme_wildcard = false;
+  return this;
+}
+
+BuilderInterface* ContentSettingsPattern::Builder::WithSchemeWildcard() {
+  parts_.scheme = "";
+  parts_.is_scheme_wildcard = true;
+  return this;
+}
+
+BuilderInterface* ContentSettingsPattern::Builder::WithPath(
+    const std::string& path) {
+  parts_.path = path;
+  parts_.is_path_wildcard = false;
+  return this;
+}
+
+BuilderInterface* ContentSettingsPattern::Builder::WithPathWildcard() {
+  parts_.path = "";
+  parts_.is_path_wildcard = true;
+  return this;
+}
+
+BuilderInterface* ContentSettingsPattern::Builder::Invalid() {
+  is_valid_ = false;
+  return this;
+}
+
+ContentSettingsPattern ContentSettingsPattern::Builder::Build() {
+  if (!is_valid_)
+    return ContentSettingsPattern();
+  if (!Canonicalize(&parts_))
+    return ContentSettingsPattern();
+  if (use_legacy_validate_) {
+    is_valid_ = LegacyValidate(parts_);
+  } else {
+    is_valid_ = Validate(parts_);
+  }
+  return ContentSettingsPattern(parts_, is_valid_);
+}
+
+// static
+bool ContentSettingsPattern::Builder::Canonicalize(PatternParts* parts) {
+  // Canonicalize the scheme part.
+  const std::string scheme(StringToLowerASCII(parts->scheme));
+  parts->scheme = scheme;
+
+  if (parts->scheme == std::string(chrome::kFileScheme) &&
+      !parts->is_path_wildcard) {
+      GURL url(std::string(chrome::kFileScheme) +
+               std::string(content::kStandardSchemeSeparator) + parts->path);
+      parts->path = url.path();
+  }
+
+  // Canonicalize the host part.
+  const std::string host(parts->host);
+  url_canon::CanonHostInfo host_info;
+  std::string canonicalized_host(net::CanonicalizeHost(host, &host_info));
+  if (host_info.IsIPAddress() && parts->has_domain_wildcard)
+    return false;
+  canonicalized_host = net::TrimEndingDot(canonicalized_host);
+
+  parts->host = "";
+  if ((host.find('*') == std::string::npos) &&
+      !canonicalized_host.empty()) {
+    // Valid host.
+    parts->host += canonicalized_host;
+  }
+  return true;
+}
+
+// static
+bool ContentSettingsPattern::Builder::Validate(const PatternParts& parts) {
+  // Sanity checks first: {scheme, port} wildcards imply empty {scheme, port}.
+  if ((parts.is_scheme_wildcard && !parts.scheme.empty()) ||
+      (parts.is_port_wildcard && !parts.port.empty())) {
+    NOTREACHED();
+    return false;
+  }
+
+  // file:// URL patterns have an empty host and port.
+  if (parts.scheme == std::string(chrome::kFileScheme)) {
+    if (parts.has_domain_wildcard || !parts.host.empty() || !parts.port.empty())
+      return false;
+    if (parts.is_path_wildcard)
+      return parts.path.empty();
+    return (!parts.path.empty() &&
+            parts.path != "/" &&
+            parts.path.find("*") == std::string::npos);
+  }
+
+  // If the pattern is for an extension URL test if it is valid.
+  if (parts.scheme == std::string(chrome::kExtensionScheme) &&
+      parts.port.empty() &&
+      !parts.is_port_wildcard) {
+    return true;
+  }
+
+  // Non-file patterns are invalid if either the scheme, host or port part is
+  // empty.
+  if ((parts.scheme.empty() && !parts.is_scheme_wildcard) ||
+      (parts.host.empty() && !parts.has_domain_wildcard) ||
+      (parts.port.empty() && !parts.is_port_wildcard)) {
+    return false;
+  }
+
+  if (parts.host.find("*") != std::string::npos)
+    return false;
+
+  // Test if the scheme is supported or a wildcard.
+  if (!parts.is_scheme_wildcard &&
+      parts.scheme != std::string(chrome::kHttpScheme) &&
+      parts.scheme != std::string(chrome::kHttpsScheme)) {
+    return false;
+  }
+  return true;
+}
+
+// static
+bool ContentSettingsPattern::Builder::LegacyValidate(
+    const PatternParts& parts) {
+  // If the pattern is for a "file-pattern" test if it is valid.
+  if (parts.scheme == std::string(chrome::kFileScheme) &&
+      !parts.is_scheme_wildcard &&
+      parts.host.empty() &&
+      parts.port.empty())
+    return true;
+
+  // If the pattern is for an extension URL test if it is valid.
+  if (parts.scheme == std::string(chrome::kExtensionScheme) &&
+      !parts.is_scheme_wildcard &&
+      !parts.host.empty() &&
+      !parts.has_domain_wildcard &&
+      parts.port.empty() &&
+      !parts.is_port_wildcard)
+    return true;
+
+  // Non-file patterns are invalid if either the scheme, host or port part is
+  // empty.
+  if ((!parts.is_scheme_wildcard) ||
+      (parts.host.empty() && !parts.has_domain_wildcard) ||
+      (!parts.is_port_wildcard))
+    return false;
+
+  // Test if the scheme is supported or a wildcard.
+  if (!parts.is_scheme_wildcard &&
+      parts.scheme != std::string(chrome::kHttpScheme) &&
+      parts.scheme != std::string(chrome::kHttpsScheme)) {
+    return false;
+  }
+  return true;
+}
+
+// ////////////////////////////////////////////////////////////////////////////
+// ContentSettingsPattern::PatternParts
+//
+ContentSettingsPattern::PatternParts::PatternParts()
+        : is_scheme_wildcard(false),
+          has_domain_wildcard(false),
+          is_port_wildcard(false),
+          is_path_wildcard(false) {}
+
+ContentSettingsPattern::PatternParts::~PatternParts() {}
+
+// ////////////////////////////////////////////////////////////////////////////
+// ContentSettingsPattern
+//
+
+// The version of the pattern format implemented. Version 1 includes the
+// following patterns:
+//   - [*.]domain.tld (matches domain.tld and all sub-domains)
+//   - host (matches an exact hostname)
+//   - a.b.c.d (matches an exact IPv4 ip)
+//   - [a:b:c:d:e:f:g:h] (matches an exact IPv6 ip)
+//   - file:///tmp/test.html (a complete URL without a host)
+// Version 2 adds a resource identifier for plugins.
+// TODO(jochen): update once this feature is no longer behind a flag.
+const int ContentSettingsPattern::kContentSettingsPatternVersion = 1;
+
+// TODO(markusheintz): These two constants were moved to the Pattern Parser.
+// Remove once the dependency of the ContentSettingsBaseProvider is removed.
+const char* ContentSettingsPattern::kDomainWildcard = "[*.]";
+const size_t ContentSettingsPattern::kDomainWildcardLength = 4;
+
+// static
+BuilderInterface* ContentSettingsPattern::CreateBuilder(
+    bool validate) {
+  return new Builder(validate);
+}
+
+// static
+ContentSettingsPattern ContentSettingsPattern::FromURL(
+    const GURL& url) {
+  scoped_ptr<ContentSettingsPattern::BuilderInterface> builder(
+      ContentSettingsPattern::CreateBuilder(false));
+
+  const GURL* local_url = &url;
+  if (url.SchemeIsFileSystem() && url.inner_url()) {
+    local_url = url.inner_url();
+  }
+  if (local_url->SchemeIsFile()) {
+    builder->WithScheme(local_url->scheme())->WithPath(local_url->path());
+  } else {
+    // Please keep the order of the ifs below as URLs with an IP as host can
+    // also have a "http" scheme.
+    if (local_url->HostIsIPAddress()) {
+      builder->WithScheme(local_url->scheme())->WithHost(local_url->host());
+    } else if (local_url->SchemeIs(chrome::kHttpScheme)) {
+      builder->WithSchemeWildcard()->WithDomainWildcard()->WithHost(
+          local_url->host());
+    } else if (local_url->SchemeIs(chrome::kHttpsScheme)) {
+      builder->WithScheme(local_url->scheme())->WithDomainWildcard()->WithHost(
+          local_url->host());
+    } else {
+      // Unsupported scheme
+    }
+    if (local_url->port().empty()) {
+      if (local_url->SchemeIs(chrome::kHttpsScheme))
+        builder->WithPort(GetDefaultPort(chrome::kHttpsScheme));
+      else
+        builder->WithPortWildcard();
+    } else {
+      builder->WithPort(local_url->port());
+    }
+  }
+  return builder->Build();
+}
+
+// static
+ContentSettingsPattern ContentSettingsPattern::FromURLNoWildcard(
+    const GURL& url) {
+  scoped_ptr<ContentSettingsPattern::BuilderInterface> builder(
+      ContentSettingsPattern::CreateBuilder(false));
+
+  const GURL* local_url = &url;
+  if (url.SchemeIsFileSystem() && url.inner_url()) {
+    local_url = url.inner_url();
+  }
+  if (local_url->SchemeIsFile()) {
+    builder->WithScheme(local_url->scheme())->WithPath(local_url->path());
+  } else {
+    builder->WithScheme(local_url->scheme())->WithHost(local_url->host());
+    if (local_url->port().empty()) {
+      builder->WithPort(GetDefaultPort(local_url->scheme()));
+    } else {
+      builder->WithPort(local_url->port());
+    }
+  }
+  return builder->Build();
+}
+
+// static
+ContentSettingsPattern ContentSettingsPattern::FromString(
+    const std::string& pattern_spec) {
+  scoped_ptr<ContentSettingsPattern::BuilderInterface> builder(
+      ContentSettingsPattern::CreateBuilder(false));
+  content_settings::PatternParser::Parse(pattern_spec, builder.get());
+  return builder->Build();
+}
+
+// static
+ContentSettingsPattern ContentSettingsPattern::LegacyFromString(
+    const std::string& pattern_spec) {
+  scoped_ptr<ContentSettingsPattern::BuilderInterface> builder(
+      ContentSettingsPattern::CreateBuilder(true));
+  content_settings::PatternParser::Parse(pattern_spec, builder.get());
+  return builder->Build();
+}
+
+// static
+ContentSettingsPattern ContentSettingsPattern::Wildcard() {
+  scoped_ptr<ContentSettingsPattern::BuilderInterface> builder(
+      ContentSettingsPattern::CreateBuilder(true));
+  builder->WithSchemeWildcard()->WithDomainWildcard()->WithPortWildcard()->
+           WithPathWildcard();
+  return builder->Build();
+}
+
+ContentSettingsPattern::ContentSettingsPattern()
+  : is_valid_(false) {
+}
+
+ContentSettingsPattern::ContentSettingsPattern(
+    const PatternParts& parts,
+    bool valid)
+    : parts_(parts),
+      is_valid_(valid) {
+}
+
+void ContentSettingsPattern::WriteToMessage(IPC::Message* m) const {
+  IPC::WriteParam(m, is_valid_);
+  IPC::WriteParam(m, parts_);
+}
+
+bool ContentSettingsPattern::ReadFromMessage(const IPC::Message* m,
+                                             PickleIterator* iter) {
+  return IPC::ReadParam(m, iter, &is_valid_) &&
+         IPC::ReadParam(m, iter, &parts_);
+}
+
+bool ContentSettingsPattern::Matches(
+    const GURL& url) const {
+  // An invalid pattern matches nothing.
+  if (!is_valid_)
+    return false;
+
+  const GURL* local_url = &url;
+  if (url.SchemeIsFileSystem() && url.inner_url()) {
+    local_url = url.inner_url();
+  }
+
+  // Match the scheme part.
+  const std::string scheme(local_url->scheme());
+  if (!parts_.is_scheme_wildcard &&
+      parts_.scheme != scheme) {
+    return false;
+  }
+
+  // File URLs have no host. Matches if the pattern has the path wildcard set,
+  // or if the path in the URL is identical to the one in the pattern.
+  // For filesystem:file URLs, the path used is the filesystem type, so all
+  // filesystem:file:///temporary/... are equivalent.
+  // TODO(markusheintz): Content settings should be defined for all files on
+  // a machine. Unless there is a good use case for supporting paths for file
+  // patterns, stop supporting path for file patterns.
+  if (!parts_.is_scheme_wildcard && scheme == chrome::kFileScheme)
+    return parts_.is_path_wildcard ||
+        parts_.path == std::string(local_url->path());
+
+  // Match the host part.
+  const std::string host(net::TrimEndingDot(local_url->host()));
+  if (!parts_.has_domain_wildcard) {
+    if (parts_.host != host)
+      return false;
+  } else {
+    if (!IsSubDomainOrEqual(host, parts_.host))
+      return false;
+  }
+
+  // For chrome extensions URLs ignore the port.
+  if (parts_.scheme == std::string(chrome::kExtensionScheme))
+    return true;
+
+  // Match the port part.
+  std::string port(local_url->port());
+
+  // Use the default port if the port string is empty. GURL returns an empty
+  // string if no port at all was specified or if the default port was
+  // specified.
+  if (port.empty()) {
+    port = GetDefaultPort(scheme);
+  }
+
+  if (!parts_.is_port_wildcard &&
+      parts_.port != port ) {
+    return false;
+  }
+
+  return true;
+}
+
+bool ContentSettingsPattern::MatchesAllHosts() const {
+  return parts_.has_domain_wildcard && parts_.host.empty();
+}
+
+const std::string ContentSettingsPattern::ToString() const {
+  if (IsValid())
+    return content_settings::PatternParser::ToString(parts_);
+  else
+    return "";
+}
+
+ContentSettingsPattern::Relation ContentSettingsPattern::Compare(
+    const ContentSettingsPattern& other) const {
+  // Two invalid patterns are identical in the way they behave. They don't match
+  // anything and are represented as an empty string. So it's fair to treat them
+  // as identical.
+  if ((this == &other) ||
+      (!is_valid_ && !other.is_valid_))
+    return IDENTITY;
+
+  if (!is_valid_ && other.is_valid_)
+    return DISJOINT_ORDER_POST;
+  if (is_valid_ && !other.is_valid_)
+    return DISJOINT_ORDER_PRE;
+
+  // If either host, port or scheme are disjoint return immediately.
+  Relation host_relation = CompareHost(parts_, other.parts_);
+  if (host_relation == DISJOINT_ORDER_PRE ||
+      host_relation == DISJOINT_ORDER_POST)
+    return host_relation;
+
+  Relation port_relation = ComparePort(parts_, other.parts_);
+  if (port_relation == DISJOINT_ORDER_PRE ||
+      port_relation == DISJOINT_ORDER_POST)
+    return port_relation;
+
+  Relation scheme_relation = CompareScheme(parts_, other.parts_);
+  if (scheme_relation == DISJOINT_ORDER_PRE ||
+      scheme_relation == DISJOINT_ORDER_POST)
+    return scheme_relation;
+
+  if (host_relation != IDENTITY)
+    return host_relation;
+  if (port_relation != IDENTITY)
+    return port_relation;
+  return scheme_relation;
+}
+
+bool ContentSettingsPattern::operator==(
+    const ContentSettingsPattern& other) const {
+  return Compare(other) == IDENTITY;
+}
+
+bool ContentSettingsPattern::operator!=(
+    const ContentSettingsPattern& other) const {
+  return !(*this == other);
+}
+
+bool ContentSettingsPattern::operator<(
+    const ContentSettingsPattern& other) const {
+  return Compare(other) < 0;
+}
+
+bool ContentSettingsPattern::operator>(
+    const ContentSettingsPattern& other) const {
+  return Compare(other) > 0;
+}
+
+// static
+ContentSettingsPattern::Relation ContentSettingsPattern::CompareHost(
+    const ContentSettingsPattern::PatternParts& parts,
+    const ContentSettingsPattern::PatternParts& other_parts) {
+  if (!parts.has_domain_wildcard && !other_parts.has_domain_wildcard) {
+    // Case 1: No host starts with a wild card
+    int result = CompareDomainNames(parts.host, other_parts.host);
+    if (result == 0)
+      return ContentSettingsPattern::IDENTITY;
+    if (result < 0)
+      return ContentSettingsPattern::DISJOINT_ORDER_PRE;
+    return ContentSettingsPattern::DISJOINT_ORDER_POST;
+  } else if (parts.has_domain_wildcard && !other_parts.has_domain_wildcard) {
+    // Case 2: |host| starts with a domain wildcard and |other_host| does not
+    // start with a domain wildcard.
+    // Examples:
+    // "this" host:   [*.]google.com
+    // "other" host:  google.com
+    //
+    // [*.]google.com
+    // mail.google.com
+    //
+    // [*.]mail.google.com
+    // google.com
+    //
+    // [*.]youtube.com
+    // google.de
+    //
+    // [*.]youtube.com
+    // mail.google.com
+    //
+    // *
+    // google.de
+    if (IsSubDomainOrEqual(other_parts.host, parts.host)) {
+      return ContentSettingsPattern::SUCCESSOR;
+    } else {
+       if (CompareDomainNames(parts.host, other_parts.host) < 0)
+         return ContentSettingsPattern::DISJOINT_ORDER_PRE;
+       return ContentSettingsPattern::DISJOINT_ORDER_POST;
+    }
+  } else if (!parts.has_domain_wildcard && other_parts.has_domain_wildcard) {
+    // Case 3: |host| starts NOT with a domain wildcard and |other_host| starts
+    // with a domain wildcard.
+    if (IsSubDomainOrEqual(parts.host, other_parts.host)) {
+      return ContentSettingsPattern::PREDECESSOR;
+    } else {
+      if (CompareDomainNames(parts.host, other_parts.host) < 0)
+        return ContentSettingsPattern::DISJOINT_ORDER_PRE;
+      return ContentSettingsPattern::DISJOINT_ORDER_POST;
+    }
+  } else if (parts.has_domain_wildcard && other_parts.has_domain_wildcard) {
+    // Case 4: |host| and |other_host| both start with a domain wildcard.
+    // Examples:
+    // [*.]google.com
+    // [*.]google.com
+    //
+    // [*.]google.com
+    // [*.]mail.google.com
+    //
+    // [*.]youtube.com
+    // [*.]google.de
+    //
+    // [*.]youtube.com
+    // [*.]mail.google.com
+    //
+    // [*.]youtube.com
+    // *
+    //
+    // *
+    // [*.]youtube.com
+    if (parts.host == other_parts.host) {
+      return ContentSettingsPattern::IDENTITY;
+    } else if (IsSubDomainOrEqual(other_parts.host, parts.host)) {
+      return ContentSettingsPattern::SUCCESSOR;
+    } else if (IsSubDomainOrEqual(parts.host, other_parts.host)) {
+      return ContentSettingsPattern::PREDECESSOR;
+    } else {
+      if (CompareDomainNames(parts.host, other_parts.host) < 0)
+        return ContentSettingsPattern::DISJOINT_ORDER_PRE;
+      return ContentSettingsPattern::DISJOINT_ORDER_POST;
+    }
+  }
+
+  NOTREACHED();
+  return ContentSettingsPattern::IDENTITY;
+}
+
+// static
+ContentSettingsPattern::Relation ContentSettingsPattern::CompareScheme(
+    const ContentSettingsPattern::PatternParts& parts,
+    const ContentSettingsPattern::PatternParts& other_parts) {
+  if (parts.is_scheme_wildcard && !other_parts.is_scheme_wildcard)
+    return ContentSettingsPattern::SUCCESSOR;
+  if (!parts.is_scheme_wildcard && other_parts.is_scheme_wildcard)
+    return ContentSettingsPattern::PREDECESSOR;
+
+  int result = parts.scheme.compare(other_parts.scheme);
+  if (result == 0)
+    return ContentSettingsPattern::IDENTITY;
+  if (result > 0)
+    return ContentSettingsPattern::DISJOINT_ORDER_PRE;
+  return ContentSettingsPattern::DISJOINT_ORDER_POST;
+}
+
+// static
+ContentSettingsPattern::Relation ContentSettingsPattern::ComparePort(
+    const ContentSettingsPattern::PatternParts& parts,
+    const ContentSettingsPattern::PatternParts& other_parts) {
+  if (parts.is_port_wildcard && !other_parts.is_port_wildcard)
+    return ContentSettingsPattern::SUCCESSOR;
+  if (!parts.is_port_wildcard && other_parts.is_port_wildcard)
+    return ContentSettingsPattern::PREDECESSOR;
+
+  int result = parts.port.compare(other_parts.port);
+  if (result == 0)
+    return ContentSettingsPattern::IDENTITY;
+  if (result > 0)
+    return ContentSettingsPattern::DISJOINT_ORDER_PRE;
+  return ContentSettingsPattern::DISJOINT_ORDER_POST;
+}
diff --git a/chrome/common/content_settings_pattern.h b/chrome/common/content_settings_pattern.h
new file mode 100644
index 0000000..a7b4861
--- /dev/null
+++ b/chrome/common/content_settings_pattern.h
@@ -0,0 +1,280 @@
+// Copyright (c) 2012 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.
+
+// Patterns used in content setting rules.
+
+#ifndef CHROME_COMMON_CONTENT_SETTINGS_PATTERN_H_
+#define CHROME_COMMON_CONTENT_SETTINGS_PATTERN_H_
+
+#include <ostream>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+
+class GURL;
+class Pickle;
+class PickleIterator;
+
+namespace content_settings {
+class PatternParser;
+}
+
+namespace IPC {
+class Message;
+}
+
+// A pattern used in content setting rules. See |IsValid| for a description of
+// possible patterns.
+class ContentSettingsPattern {
+ public:
+  // Each content settings pattern describes a set of origins. Patterns, and the
+  // sets they describe, have specific relations. |Relation| describes the
+  // relation of two patterns A and B. When pattern A is compared with pattern B
+  // (A compare B) interesting relations are:
+  // - IDENTITY:
+  //   Pattern A and B are identical. The patterns are equal.
+  //
+  // - DISJOINT_ORDER_PRE:
+  //   Pattern A and B have no intersection. A and B never match the origin of
+  //   a URL at the same time. But pattern A has a higher precedence than
+  //   pattern B when patterns are sorted.
+  //
+  // - DISJOINT_ORDER_POST:
+  //   Pattern A and B have no intersection. A and B never match the origin of
+  //   a URL at the same time. But pattern A has a lower precedence than
+  //   pattern B when patterns are sorted.
+  //
+  // - SUCCESSOR:
+  //   Pattern A and B have an intersection. But pattern B has a higher
+  //   precedence than pattern A for URLs that are matched by both pattern.
+  //
+  // - PREDECESSOR:
+  //   Pattern A and B have an intersection. But pattern A has a higher
+  //   precedence than pattern B for URLs that are matched by both pattern.
+  enum Relation {
+    DISJOINT_ORDER_POST = -2,
+    SUCCESSOR = -1,
+    IDENTITY = 0,
+    PREDECESSOR = 1,
+    DISJOINT_ORDER_PRE = 2,
+  };
+
+  struct PatternParts {
+    PatternParts();
+    ~PatternParts();
+
+    // Lowercase string of the URL scheme to match. This string is empty if the
+    // |is_scheme_wildcard| flag is set.
+    std::string scheme;
+
+    // True if the scheme wildcard is set.
+    bool is_scheme_wildcard;
+
+    // Normalized string that is either of the following:
+    // - IPv4 or IPv6
+    // - hostname
+    // - domain
+    // - empty string if the |is_host_wildcard flag is set.
+    std::string host;
+
+    // True if the domain wildcard is set.
+    bool has_domain_wildcard;
+
+    // String with the port to match. This string is empty if the
+    // |is_port_wildcard| flag is set.
+    std::string port;
+
+    // True if the port wildcard is set.
+    bool is_port_wildcard;
+
+    // TODO(markusheintz): Needed for legacy reasons. Remove. Path
+    // specification. Only used for content settings pattern with a "file"
+    // scheme part.
+    std::string path;
+
+    // True if the path wildcard is set.
+    bool is_path_wildcard;
+  };
+
+  class BuilderInterface {
+   public:
+    virtual ~BuilderInterface() {}
+
+    virtual BuilderInterface* WithPort(const std::string& port) = 0;
+
+    virtual BuilderInterface* WithPortWildcard() = 0;
+
+    virtual BuilderInterface* WithHost(const std::string& host) = 0;
+
+    virtual BuilderInterface* WithDomainWildcard() = 0;
+
+    virtual BuilderInterface* WithScheme(const std::string& scheme) = 0;
+
+    virtual BuilderInterface* WithSchemeWildcard() = 0;
+
+    virtual BuilderInterface* WithPath(const std::string& path) = 0;
+
+    virtual BuilderInterface* WithPathWildcard() = 0;
+
+    virtual BuilderInterface* Invalid() = 0;
+
+    // Returns a content settings pattern according to the current configuration
+    // of the builder.
+    virtual ContentSettingsPattern Build() = 0;
+  };
+
+  static BuilderInterface* CreateBuilder(bool use_legacy_validate);
+
+  // The version of the pattern format implemented.
+  static const int kContentSettingsPatternVersion;
+
+  // The format of a domain wildcard.
+  static const char* kDomainWildcard;
+
+  // The length of kDomainWildcard (without the trailing '\0').
+  static const size_t kDomainWildcardLength;
+
+  // Returns a wildcard content settings pattern that matches all possible valid
+  // origins.
+  static ContentSettingsPattern Wildcard();
+
+  // Returns a pattern that matches the scheme and host of this URL, as well as
+  // all subdomains and ports.
+  static ContentSettingsPattern FromURL(const GURL& url);
+
+  // Returns a pattern that matches exactly this URL.
+  static ContentSettingsPattern FromURLNoWildcard(const GURL& url);
+
+  // Returns a pattern that matches the given pattern specification.
+  // Valid patterns specifications are:
+  //   - [*.]domain.tld (matches domain.tld and all sub-domains)
+  //   - host (matches an exact hostname)
+  //   - scheme://host:port (supported schemes: http,https)
+  //   - scheme://[*.]domain.tld:port (supported schemes: http,https)
+  //   - file://path (The path has to be an absolute path and start with a '/')
+  //   - a.b.c.d (matches an exact IPv4 ip)
+  //   - [a:b:c:d:e:f:g:h] (matches an exact IPv6 ip)
+  static ContentSettingsPattern FromString(const std::string& pattern_spec);
+
+  static ContentSettingsPattern LegacyFromString(
+      const std::string& pattern_spec);
+
+  // Constructs an empty pattern. Empty patterns are invalid patterns. Invalid
+  // patterns match nothing.
+  ContentSettingsPattern();
+
+  // Serializes the pattern to an IPC message or deserializes it.
+  void WriteToMessage(IPC::Message* m) const;
+  bool ReadFromMessage(const IPC::Message* m, PickleIterator* iter);
+
+  // True if this is a valid pattern.
+  bool IsValid() const { return is_valid_; }
+
+  // True if |url| matches this pattern.
+  bool Matches(const GURL& url) const;
+
+  // True if this pattern matches all hosts (i.e. it has a host wildcard).
+  bool MatchesAllHosts() const;
+
+  // Returns a std::string representation of this pattern.
+  const std::string ToString() const;
+
+  // Compares the pattern with a given |other| pattern and returns the
+  // |Relation| of the two patterns.
+  Relation Compare(const ContentSettingsPattern& other) const;
+
+  // Returns true if the pattern and the |other| pattern are identical.
+  bool operator==(const ContentSettingsPattern& other) const;
+
+  // Returns true if the pattern and the |other| pattern are not identical.
+  bool operator!=(const ContentSettingsPattern& other) const;
+
+  // Returns true if the pattern has a lower priority than the |other| pattern.
+  bool operator<(const ContentSettingsPattern& other) const;
+
+  // Returns true if the pattern has a higher priority than the |other| pattern.
+  bool operator>(const ContentSettingsPattern& other) const;
+
+ private:
+  friend class content_settings::PatternParser;
+  friend class Builder;
+  FRIEND_TEST_ALL_PREFIXES(ContentSettingsPatternParserTest, SerializePatterns);
+
+  class Builder : public BuilderInterface {
+    public:
+     explicit Builder(bool use_legacy_validate);
+     virtual ~Builder();
+
+     // Overrides BuilderInterface
+     virtual BuilderInterface* WithPort(const std::string& port) OVERRIDE;
+
+     virtual BuilderInterface* WithPortWildcard() OVERRIDE;
+
+     virtual BuilderInterface* WithHost(const std::string& host) OVERRIDE;
+
+     virtual BuilderInterface* WithDomainWildcard() OVERRIDE;
+
+     virtual BuilderInterface* WithScheme(const std::string& scheme) OVERRIDE;
+
+     virtual BuilderInterface* WithSchemeWildcard() OVERRIDE;
+
+     virtual BuilderInterface* WithPath(const std::string& path) OVERRIDE;
+
+     virtual BuilderInterface* WithPathWildcard() OVERRIDE;
+
+     virtual BuilderInterface* Invalid() OVERRIDE;
+
+     virtual ContentSettingsPattern Build() OVERRIDE;
+
+    private:
+     // Canonicalizes the pattern parts so that they are ASCII only, either
+     // in original (if it was already ASCII) or punycode form. Returns true if
+     // the canonicalization was successful.
+     static bool Canonicalize(PatternParts* parts);
+
+     // Returns true when the pattern |parts| represent a valid pattern.
+     static bool Validate(const PatternParts& parts);
+
+     static bool LegacyValidate(const PatternParts& parts);
+
+     bool is_valid_;
+
+     bool use_legacy_validate_;
+
+     PatternParts parts_;
+
+     DISALLOW_COPY_AND_ASSIGN(Builder);
+  };
+
+  static Relation CompareScheme(
+      const ContentSettingsPattern::PatternParts& parts,
+      const ContentSettingsPattern::PatternParts& other_parts);
+
+  static Relation CompareHost(
+      const ContentSettingsPattern::PatternParts& parts,
+      const ContentSettingsPattern::PatternParts& other_parts);
+
+  static Relation ComparePort(
+      const ContentSettingsPattern::PatternParts& parts,
+      const ContentSettingsPattern::PatternParts& other_parts);
+
+  static bool Validate(const PatternParts& parts);
+
+  ContentSettingsPattern(const PatternParts& parts, bool valid);
+
+  PatternParts parts_;
+
+  bool is_valid_;
+};
+
+// Stream operator so ContentSettingsPattern can be used in assertion
+// statements.
+inline std::ostream& operator<<(
+    std::ostream& out, const ContentSettingsPattern& pattern) {
+  return out << pattern.ToString();
+}
+
+#endif  // CHROME_COMMON_CONTENT_SETTINGS_PATTERN_H_
diff --git a/chrome/common/content_settings_pattern_parser.cc b/chrome/common/content_settings_pattern_parser.cc
new file mode 100644
index 0000000..1498586
--- /dev/null
+++ b/chrome/common/content_settings_pattern_parser.cc
@@ -0,0 +1,231 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/content_settings_pattern_parser.h"
+
+#include "base/string_util.h"
+#include "chrome/common/url_constants.h"
+#include "googleurl/src/gurl.h"
+#include "googleurl/src/url_canon.h"
+#include "net/base/net_util.h"
+
+namespace {
+
+const char* kUrlPathSeparator = "/";
+const char* kUrlPortSeparator = ":";
+
+class Component {
+ public:
+  Component() : start(0), len(0) {}
+  Component(size_t s, size_t l) : start(s), len(l) {}
+
+  bool IsNonEmpty() {
+    return len > 0;
+  }
+
+  size_t start;
+  size_t len;
+};
+
+}  // namespace
+
+namespace content_settings {
+
+const char* PatternParser::kDomainWildcard = "[*.]";
+
+const size_t PatternParser::kDomainWildcardLength = 4;
+
+const char* PatternParser::kSchemeWildcard = "*";
+
+const char* PatternParser::kHostWildcard = "*";
+
+const char* PatternParser::kPortWildcard = "*";
+
+const char* PatternParser::kPathWildcard = "*";
+
+// static
+void PatternParser::Parse(const std::string& pattern_spec,
+                          ContentSettingsPattern::BuilderInterface* builder) {
+  if (pattern_spec == "*") {
+    builder->WithSchemeWildcard();
+    builder->WithDomainWildcard();
+    builder->WithPortWildcard();
+    return;
+  }
+
+  // Initialize components for the individual patterns parts to empty
+  // sub-strings.
+  Component scheme_component;
+  Component host_component;
+  Component port_component;
+  Component path_component;
+
+  size_t start = 0;
+  size_t current_pos = 0;
+
+  if (pattern_spec.empty())
+    return;
+
+  // Test if a scheme pattern is in the spec.
+  current_pos = pattern_spec.find(
+      std::string(content::kStandardSchemeSeparator), start);
+  if (current_pos != std::string::npos) {
+    scheme_component = Component(start, current_pos);
+    start = current_pos + strlen(content::kStandardSchemeSeparator);
+    current_pos = start;
+  } else {
+    current_pos = start;
+  }
+
+  if (start >= pattern_spec.size())
+    return;  // Bad pattern spec.
+
+  // Jump to the end of domain wildcards or an IPv6 addresses. IPv6 addresses
+  // contain ':'. So first move to the end of an IPv6 address befor searching
+  // for the ':' that separates the port form the host.
+  if (pattern_spec[current_pos] == '[')
+    current_pos = pattern_spec.find("]", start);
+
+  if (current_pos == std::string::npos)
+    return;  // Bad pattern spec.
+
+  current_pos = pattern_spec.find(std::string(kUrlPortSeparator), current_pos);
+  if (current_pos == std::string::npos) {
+    // No port spec found
+    current_pos = pattern_spec.find(std::string(kUrlPathSeparator), start);
+    if (current_pos == std::string::npos) {
+      current_pos = pattern_spec.size();
+      host_component = Component(start, current_pos - start);
+    } else {
+      // Pattern has a path spec.
+      host_component = Component(start, current_pos - start);
+    }
+    start = current_pos;
+  } else {
+    // Port spec found.
+    host_component = Component(start, current_pos - start);
+    start = current_pos + 1;
+    if (start < pattern_spec.size()) {
+      current_pos = pattern_spec.find(std::string(kUrlPathSeparator), start);
+      if (current_pos == std::string::npos) {
+        current_pos = pattern_spec.size();
+      }
+      port_component = Component(start, current_pos - start);
+      start = current_pos;
+    }
+  }
+
+  current_pos = pattern_spec.size();
+  if (start < current_pos) {
+    // Pattern has a path spec.
+    path_component = Component(start, current_pos - start);
+  }
+
+  // Set pattern parts.
+  std::string scheme;
+  if (scheme_component.IsNonEmpty()) {
+    scheme = pattern_spec.substr(scheme_component.start, scheme_component.len);
+    if (scheme == kSchemeWildcard) {
+      builder->WithSchemeWildcard();
+    } else {
+      builder->WithScheme(scheme);
+    }
+  } else {
+    builder->WithSchemeWildcard();
+  }
+
+  if (host_component.IsNonEmpty()) {
+    std::string host = pattern_spec.substr(host_component.start,
+                                           host_component.len);
+    if (host == kHostWildcard) {
+      builder->WithDomainWildcard();
+    } else if (StartsWithASCII(host, kDomainWildcard, true)) {
+      host = host.substr(kDomainWildcardLength);
+      builder->WithDomainWildcard();
+      builder->WithHost(host);
+    } else {
+      // If the host contains a wildcard symbol then it is invalid.
+      if (host.find(kHostWildcard) != std::string::npos) {
+        builder->Invalid();
+        return;
+      }
+      builder->WithHost(host);
+    }
+  }
+
+  if (port_component.IsNonEmpty()) {
+    const std::string port = pattern_spec.substr(port_component.start,
+                                                 port_component.len);
+    if (port == kPortWildcard) {
+      builder->WithPortWildcard();
+    } else {
+      // Check if the port string represents a valid port.
+      for (size_t i = 0; i < port.size(); ++i) {
+        if (!IsAsciiDigit(port[i])) {
+          builder->Invalid();
+          return;
+        }
+      }
+      // TODO(markusheintz): Check port range.
+      builder->WithPort(port);
+    }
+  } else {
+    if (scheme != std::string(chrome::kExtensionScheme) &&
+        scheme != std::string(chrome::kFileScheme))
+      builder->WithPortWildcard();
+  }
+
+  if (path_component.IsNonEmpty()) {
+    const std::string path = pattern_spec.substr(path_component.start,
+                                                 path_component.len);
+    if (path.substr(1) == kPathWildcard)
+      builder->WithPathWildcard();
+    else
+      builder->WithPath(path);
+  }
+}
+
+// static
+std::string PatternParser::ToString(
+    const ContentSettingsPattern::PatternParts& parts) {
+  // Return the most compact form to support legacy code and legacy pattern
+  // strings.
+  if (parts.is_scheme_wildcard &&
+      parts.has_domain_wildcard &&
+      parts.host.empty() &&
+      parts.is_port_wildcard)
+    return "*";
+
+  std::string str = "";
+  if (!parts.is_scheme_wildcard)
+    str += parts.scheme + content::kStandardSchemeSeparator;
+
+  if (parts.scheme == chrome::kFileScheme) {
+    if (parts.is_path_wildcard)
+      return str + kUrlPathSeparator + kPathWildcard;
+    else
+      return str + parts.path;
+  }
+
+  if (parts.has_domain_wildcard) {
+    if (parts.host.empty())
+      str += kHostWildcard;
+    else
+      str += kDomainWildcard;
+  }
+  str += parts.host;
+
+  if (parts.scheme == std::string(chrome::kExtensionScheme)) {
+    str += parts.path.empty() ? std::string(kUrlPathSeparator) : parts.path;
+    return str;
+  }
+
+  if (!parts.is_port_wildcard) {
+    str += std::string(kUrlPortSeparator) + parts.port;
+  }
+
+  return str;
+}
+
+}  // namespace content_settings
diff --git a/chrome/common/content_settings_pattern_parser.h b/chrome/common/content_settings_pattern_parser.h
new file mode 100644
index 0000000..19f8d27
--- /dev/null
+++ b/chrome/common/content_settings_pattern_parser.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_CONTENT_SETTINGS_PATTERN_PARSER_H_
+#define CHROME_COMMON_CONTENT_SETTINGS_PATTERN_PARSER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "chrome/common/content_settings_pattern.h"
+
+namespace content_settings {
+
+struct PatternParts;
+
+class PatternParser {
+ public:
+  static void Parse(const std::string& pattern_spec,
+                    ContentSettingsPattern::BuilderInterface* builder);
+
+  static std::string ToString(
+      const ContentSettingsPattern::PatternParts& parts);
+
+ private:
+  static const char* kDomainWildcard;
+
+  static const size_t kDomainWildcardLength;
+
+  static const char* kSchemeWildcard;
+
+  static const char* kHostWildcard;
+
+  static const char* kPortWildcard;
+
+  static const char* kPathWildcard;
+
+  DISALLOW_COPY_AND_ASSIGN(PatternParser);
+};
+
+}  // namespace content_settings
+
+#endif  // CHROME_COMMON_CONTENT_SETTINGS_PATTERN_PARSER_H_
diff --git a/chrome/common/content_settings_pattern_parser_unittest.cc b/chrome/common/content_settings_pattern_parser_unittest.cc
new file mode 100644
index 0000000..b6d5f34
--- /dev/null
+++ b/chrome/common/content_settings_pattern_parser_unittest.cc
@@ -0,0 +1,230 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/content_settings_pattern.h"
+#include "chrome/common/content_settings_pattern_parser.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+typedef ContentSettingsPattern::BuilderInterface BuilderInterface;
+}  // namespace
+
+class MockBuilder : public ContentSettingsPattern::BuilderInterface {
+ public:
+  MOCK_METHOD0(WithSchemeWildcard, BuilderInterface*());
+  MOCK_METHOD0(WithDomainWildcard, BuilderInterface*());
+  MOCK_METHOD0(WithPortWildcard, BuilderInterface*());
+  MOCK_METHOD1(WithScheme, BuilderInterface*(const std::string& scheme));
+  MOCK_METHOD1(WithHost, BuilderInterface*(const std::string& host));
+  MOCK_METHOD1(WithPort, BuilderInterface*(const std::string& port));
+  MOCK_METHOD1(WithPath, BuilderInterface*(const std::string& path));
+  MOCK_METHOD0(WithPathWildcard, BuilderInterface*());
+  MOCK_METHOD0(Invalid, BuilderInterface*());
+  MOCK_METHOD0(Build, ContentSettingsPattern());
+};
+
+TEST(ContentSettingsPatternParserTest, ParsePatterns) {
+  // Test valid patterns
+  ::testing::StrictMock<MockBuilder> builder;
+
+  // WithPathWildcard() is not called for "*". (Need a strict Mock for this
+  // case.)
+  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithDomainWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPortWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse("*", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  EXPECT_CALL(builder, WithScheme("http")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("www.youtube.com")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPort("8080")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse(
+      "http://www.youtube.com:8080", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("www.gmail.com")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPort("80")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse("*://www.gmail.com:80", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  EXPECT_CALL(builder, WithScheme("http")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("www.gmail.com")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPortWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse("http://www.gmail.com:*", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  EXPECT_CALL(builder, WithScheme("http")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithDomainWildcard()).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("google.com")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPort("80")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse("http://[*.]google.com:80", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  EXPECT_CALL(builder, WithScheme("https")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("[::1]")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPort("8080")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse("https://[::1]:8080", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  EXPECT_CALL(builder, WithScheme("http")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("127.0.0.1")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPort("8080")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse("http://127.0.0.1:8080", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  // Test valid pattern short forms
+  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("www.youtube.com")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPort("8080")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse("www.youtube.com:8080", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("www.youtube.com")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPortWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse("www.youtube.com", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithDomainWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithHost("youtube.com")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPortWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse("[*.]youtube.com", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  // Test invalid patterns
+  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, Invalid()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse("*youtube.com", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, Invalid()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse("*.youtube.com", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  EXPECT_CALL(builder, WithSchemeWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, Invalid()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse("www.youtube.com*", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+}
+
+TEST(ContentSettingsPatternParserTest, ParseFilePatterns) {
+  ::testing::StrictMock<MockBuilder> builder;
+
+  EXPECT_CALL(builder, WithScheme("file")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPath("/foo/bar/test.html")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse(
+      "file:///foo/bar/test.html", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  EXPECT_CALL(builder, WithScheme("file")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithDomainWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse(
+      "file://*", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  EXPECT_CALL(builder, WithScheme("file")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithDomainWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPath("/")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse(
+      "file://*/", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  EXPECT_CALL(builder, WithScheme("file")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithDomainWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPathWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse(
+      "file://*/*", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  EXPECT_CALL(builder, WithScheme("file")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, WithPathWildcard()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse(
+      "file:///*", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+
+  // Invalid file patterns.
+  EXPECT_CALL(builder, WithScheme("file")).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  EXPECT_CALL(builder, Invalid()).Times(1).WillOnce(
+      ::testing::Return(&builder));
+  content_settings::PatternParser::Parse(
+      "file://**", &builder);
+  ::testing::Mock::VerifyAndClear(&builder);
+}
+
+TEST(ContentSettingsPatternParserTest, SerializePatterns) {
+  ContentSettingsPattern::PatternParts parts;
+  parts.scheme = "http";
+  parts.host = "www.youtube.com";
+  parts.port = "8080";
+  EXPECT_STREQ("http://www.youtube.com:8080",
+               content_settings::PatternParser::ToString(parts).c_str());
+
+  parts = ContentSettingsPattern::PatternParts();
+  parts.scheme = "file";
+  parts.path = "/foo/bar/test.html";
+  EXPECT_STREQ("file:///foo/bar/test.html",
+               content_settings::PatternParser::ToString(parts).c_str());
+
+  parts = ContentSettingsPattern::PatternParts();
+  parts.scheme = "file";
+  parts.path = "";
+  parts.is_path_wildcard = true;
+  EXPECT_EQ("file:///*", content_settings::PatternParser::ToString(parts));
+}
diff --git a/chrome/common/content_settings_pattern_unittest.cc b/chrome/common/content_settings_pattern_unittest.cc
new file mode 100644
index 0000000..bd79338
--- /dev/null
+++ b/chrome/common/content_settings_pattern_unittest.cc
@@ -0,0 +1,666 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/content_settings_pattern.h"
+
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+ContentSettingsPattern Pattern(const std::string& str) {
+  return ContentSettingsPattern::FromString(str);
+}
+
+}  // namespace
+
+TEST(ContentSettingsPatternTest, RealWorldPatterns) {
+  // This is the place for real world patterns that unveiled bugs.
+  EXPECT_STREQ("[*.]ikea.com",
+               Pattern("[*.]ikea.com").ToString().c_str());
+}
+
+TEST(ContentSettingsPatternTest, GURL) {
+  // Document and verify GURL behavior.
+  GURL url("http://mail.google.com:80");
+  EXPECT_EQ(-1, url.IntPort());
+  EXPECT_EQ("", url.port());
+
+  url = GURL("http://mail.google.com");
+  EXPECT_EQ(-1, url.IntPort());
+  EXPECT_EQ("", url.port());
+
+  url = GURL("https://mail.google.com:443");
+  EXPECT_EQ(-1, url.IntPort());
+  EXPECT_EQ("", url.port());
+
+  url = GURL("https://mail.google.com");
+  EXPECT_EQ(-1, url.IntPort());
+  EXPECT_EQ("", url.port());
+
+  url = GURL("http://mail.google.com");
+  EXPECT_EQ(-1, url.IntPort());
+  EXPECT_EQ("", url.port());
+}
+
+TEST(ContentSettingsPatternTest, FromURL) {
+  // NOTICE: When content settings pattern are created from a GURL the following
+  // happens:
+  // - If the GURL scheme is "http" the scheme wildcard is used. Otherwise the
+  //   GURL scheme is used.
+  // - A domain wildcard is added to the GURL host.
+  // - A port wildcard is used instead of the schemes default port.
+  //   In case of non-default ports the specific GURL port is used.
+  ContentSettingsPattern pattern = ContentSettingsPattern::FromURL(
+      GURL("http://www.youtube.com"));
+  EXPECT_TRUE(pattern.IsValid());
+  EXPECT_STREQ("[*.]www.youtube.com", pattern.ToString().c_str());
+
+  // Patterns created from a URL.
+  pattern = ContentSettingsPattern::FromURL(GURL("http://www.google.com"));
+  EXPECT_TRUE(pattern.Matches(GURL("http://www.google.com")));
+  EXPECT_TRUE(pattern.Matches(GURL("http://foo.www.google.com")));
+  EXPECT_TRUE(pattern.Matches(GURL("http://www.google.com:80")));
+  EXPECT_TRUE(pattern.Matches(GURL("http://www.google.com:81")));
+  EXPECT_FALSE(pattern.Matches(GURL("https://mail.google.com")));
+  EXPECT_TRUE(pattern.Matches(GURL("https://www.google.com")));
+
+  pattern = ContentSettingsPattern::FromURL(GURL("http://www.google.com:80"));
+  EXPECT_TRUE(pattern.Matches(GURL("http://www.google.com")));
+  EXPECT_TRUE(pattern.Matches(GURL("http://www.google.com:80")));
+  EXPECT_TRUE(pattern.Matches(GURL("http://www.google.com:81")));
+
+  pattern = ContentSettingsPattern::FromURL(GURL("https://www.google.com:443"));
+  EXPECT_TRUE(pattern.Matches(GURL("https://www.google.com")));
+  EXPECT_TRUE(pattern.Matches(GURL("https://foo.www.google.com")));
+  EXPECT_TRUE(pattern.Matches(GURL("https://www.google.com:443")));
+  EXPECT_FALSE(pattern.Matches(GURL("https://www.google.com:444")));
+  EXPECT_FALSE(pattern.Matches(GURL("http://www.google.com:443")));
+
+  pattern = ContentSettingsPattern::FromURL(GURL("https://127.0.0.1"));
+  EXPECT_TRUE(pattern.IsValid());
+  EXPECT_STREQ("https://127.0.0.1:443", pattern.ToString().c_str());
+
+  pattern = ContentSettingsPattern::FromURL(GURL("http://[::1]"));
+  EXPECT_TRUE(pattern.IsValid());
+
+  pattern = ContentSettingsPattern::FromURL(GURL("file:///foo/bar.html"));
+  EXPECT_TRUE(pattern.IsValid());
+  EXPECT_EQ("file:///foo/bar.html", pattern.ToString());
+}
+
+TEST(ContentSettingsPatternTest, FilesystemUrls) {
+  ContentSettingsPattern pattern =
+      ContentSettingsPattern::FromURL(GURL("http://www.google.com"));
+  EXPECT_TRUE(pattern.Matches(
+      GURL("filesystem:http://www.google.com/temporary/")));
+  EXPECT_TRUE(pattern.Matches(
+      GURL("filesystem:http://foo.www.google.com/temporary/")));
+  EXPECT_TRUE(pattern.Matches(
+      GURL("filesystem:http://www.google.com:80/temporary/")));
+  EXPECT_TRUE(pattern.Matches(
+      GURL("filesystem:http://www.google.com:81/temporary/")));
+
+  pattern = ContentSettingsPattern::FromURL(GURL("https://www.google.com"));
+  EXPECT_TRUE(pattern.Matches(
+      GURL("filesystem:https://www.google.com/temporary/")));
+  EXPECT_TRUE(pattern.Matches(
+      GURL("filesystem:https://www.google.com:443/temporary/")));
+  EXPECT_TRUE(pattern.Matches(
+      GURL("filesystem:https://foo.www.google.com/temporary/")));
+  EXPECT_FALSE(pattern.Matches(
+      GURL("filesystem:https://www.google.com:81/temporary/")));
+
+  // A pattern from a filesystem URLs is equivalent to a pattern from the inner
+  // URL of the filesystem URL.
+  ContentSettingsPattern pattern2 = ContentSettingsPattern::FromURL(
+      GURL("filesystem:https://www.google.com/temporary/"));
+  EXPECT_EQ(ContentSettingsPattern::IDENTITY, pattern.Compare(pattern2));
+
+  EXPECT_STREQ("https://[*.]www.google.com:443", pattern2.ToString().c_str());
+
+  pattern =
+      ContentSettingsPattern::FromURL(
+          GURL("filesystem:file:///temporary/foo/bar"));
+  EXPECT_TRUE(pattern.Matches(GURL("filesystem:file:///temporary/")));
+  EXPECT_TRUE(pattern.Matches(GURL("filesystem:file:///temporary/test.txt")));
+  EXPECT_TRUE(pattern.Matches(GURL("file:///temporary")));
+  EXPECT_FALSE(pattern.Matches(GURL("file://foo/bar")));
+  pattern2 =
+      ContentSettingsPattern::FromURL(
+          GURL("filesystem:file:///persistent/foo2/bar2"));
+  EXPECT_EQ(ContentSettingsPattern::IDENTITY, pattern.Compare(pattern2));
+}
+
+TEST(ContentSettingsPatternTest, FromURLNoWildcard) {
+  // If no port is specifed GURLs always use the default port for the schemes
+  // HTTP and HTTPS. Hence a GURL always carries a port specification either
+  // explicitly or implicitly. Therefore if a content settings pattern is
+  // created from a GURL with no wildcard, specific values are used for the
+  // scheme, host and port part of the pattern.
+  // Creating content settings patterns from strings behaves different. Pattern
+  // parts that are omitted in pattern specifications (strings), are completed
+  // with a wildcard.
+  ContentSettingsPattern pattern = ContentSettingsPattern::FromURLNoWildcard(
+      GURL("http://www.example.com"));
+  EXPECT_TRUE(pattern.IsValid());
+  EXPECT_STREQ("http://www.example.com:80", pattern.ToString().c_str());
+  EXPECT_TRUE(pattern.Matches(GURL("http://www.example.com")));
+  EXPECT_FALSE(pattern.Matches(GURL("https://www.example.com")));
+  EXPECT_FALSE(pattern.Matches(GURL("http://foo.www.example.com")));
+
+  pattern = ContentSettingsPattern::FromURLNoWildcard(
+      GURL("https://www.example.com"));
+  EXPECT_TRUE(pattern.IsValid());
+  EXPECT_STREQ("https://www.example.com:443", pattern.ToString().c_str());
+  EXPECT_FALSE(pattern.Matches(GURL("http://www.example.com")));
+  EXPECT_TRUE(pattern.Matches(GURL("https://www.example.com")));
+  EXPECT_FALSE(pattern.Matches(GURL("http://foo.www.example.com")));
+
+   // Pattern for filesystem URLs
+  pattern =
+      ContentSettingsPattern::FromURLNoWildcard(
+          GURL("filesystem:http://www.google.com/temporary/"));
+  EXPECT_TRUE(pattern.IsValid());
+  EXPECT_TRUE(pattern.Matches(GURL("http://www.google.com")));
+  EXPECT_FALSE(pattern.Matches(GURL("http://foo.www.google.com")));
+  EXPECT_TRUE(pattern.Matches(
+      GURL("filesystem:http://www.google.com/persistent/")));
+  EXPECT_FALSE(pattern.Matches(
+      GURL("filesystem:https://www.google.com/persistent/")));
+  EXPECT_FALSE(pattern.Matches(
+      GURL("filesystem:https://www.google.com:81/temporary/")));
+  EXPECT_FALSE(pattern.Matches(
+      GURL("filesystem:https://foo.www.google.com/temporary/")));
+}
+
+TEST(ContentSettingsPatternTest, Wildcard) {
+  EXPECT_TRUE(ContentSettingsPattern::Wildcard().IsValid());
+
+  EXPECT_TRUE(ContentSettingsPattern::Wildcard().Matches(
+      GURL("http://www.google.com")));
+  EXPECT_TRUE(ContentSettingsPattern::Wildcard().Matches(
+      GURL("https://www.google.com")));
+  EXPECT_TRUE(ContentSettingsPattern::Wildcard().Matches(
+      GURL("https://myhost:8080")));
+  EXPECT_TRUE(ContentSettingsPattern::Wildcard().Matches(
+      GURL("file:///foo/bar.txt")));
+
+  EXPECT_STREQ("*", ContentSettingsPattern::Wildcard().ToString().c_str());
+
+  EXPECT_EQ(ContentSettingsPattern::IDENTITY,
+            ContentSettingsPattern::Wildcard().Compare(
+                ContentSettingsPattern::Wildcard()));
+}
+
+TEST(ContentSettingsPatternTest, TrimEndingDotFromHost) {
+  EXPECT_TRUE(Pattern("www.example.com").IsValid());
+  EXPECT_TRUE(Pattern("www.example.com").Matches(
+      GURL("http://www.example.com")));
+  EXPECT_TRUE(Pattern("www.example.com").Matches(
+      GURL("http://www.example.com.")));
+
+  EXPECT_TRUE(Pattern("www.example.com.").IsValid());
+  EXPECT_STREQ("www.example.com",
+               Pattern("www.example.com.").ToString().c_str());
+
+  EXPECT_TRUE(Pattern("www.example.com.") == Pattern("www.example.com"));
+}
+
+TEST(ContentSettingsPatternTest, FromString_WithNoWildcards) {
+  // HTTP patterns with default port.
+  EXPECT_TRUE(Pattern("http://www.example.com:80").IsValid());
+  EXPECT_STREQ("http://www.example.com:80",
+               Pattern("http://www.example.com:80").ToString().c_str());
+  // HTTP patterns with none default port.
+  EXPECT_TRUE(Pattern("http://www.example.com:81").IsValid());
+  EXPECT_STREQ("http://www.example.com:81",
+               Pattern("http://www.example.com:81").ToString().c_str());
+
+  // HTTPS patterns with default port.
+  EXPECT_TRUE(Pattern("https://www.example.com:443").IsValid());
+  EXPECT_STREQ("https://www.example.com:443",
+               Pattern("https://www.example.com:443").ToString().c_str());
+  // HTTPS patterns with none default port.
+  EXPECT_TRUE(Pattern("https://www.example.com:8080").IsValid());
+  EXPECT_STREQ("https://www.example.com:8080",
+               Pattern("https://www.example.com:8080").ToString().c_str());
+}
+
+TEST(ContentSettingsPatternTest, FromString_FilePatterns) {
+  // "/" is an invalid file path.
+  EXPECT_FALSE(Pattern("file:///").IsValid());
+
+  // Non-empty domains aren't allowed in file patterns.
+  EXPECT_FALSE(Pattern("file://foo/").IsValid());
+  EXPECT_FALSE(Pattern("file://localhost/foo/bar/test.html").IsValid());
+  EXPECT_FALSE(Pattern("file://*").IsValid());
+  EXPECT_FALSE(Pattern("file://*/").IsValid());
+  EXPECT_FALSE(Pattern("file://*/*").IsValid());
+  EXPECT_FALSE(Pattern("file://*/foo/bar/test.html").IsValid());
+  EXPECT_FALSE(Pattern("file://[*.]/").IsValid());
+
+  // This is the only valid file path wildcard format.
+  EXPECT_TRUE(Pattern("file:///*").IsValid());
+  EXPECT_EQ("file:///*", Pattern("file:///*").ToString());
+
+  // Wildcards are not allowed anywhere in the file path.
+  EXPECT_FALSE(Pattern("file:///f*o/bar/file.html").IsValid());
+  EXPECT_FALSE(Pattern("file:///*/bar/file.html").IsValid());
+  EXPECT_FALSE(Pattern("file:///foo/*").IsValid());
+  EXPECT_FALSE(Pattern("file:///foo/bar/*").IsValid());
+  EXPECT_FALSE(Pattern("file:///foo/*/file.html").IsValid());
+  EXPECT_FALSE(Pattern("file:///foo/bar/*.html").IsValid());
+  EXPECT_FALSE(Pattern("file:///foo/bar/file.*").IsValid());
+
+  EXPECT_TRUE(Pattern("file:///tmp/test.html").IsValid());
+  EXPECT_EQ("file:///tmp/file.html",
+            Pattern("file:///tmp/file.html").ToString());
+  EXPECT_TRUE(Pattern("file:///tmp/test.html").Matches(
+      GURL("file:///tmp/test.html")));
+  EXPECT_FALSE(Pattern("file:///tmp/test.html").Matches(
+      GURL("file:///tmp/other.html")));
+  EXPECT_FALSE(Pattern("file:///tmp/test.html").Matches(
+      GURL("http://example.org/")));
+
+  EXPECT_TRUE(Pattern("file:///*").Matches(GURL("file:///tmp/test.html")));
+  EXPECT_TRUE(Pattern("file:///*").Matches(
+      GURL("file://localhost/tmp/test.html")));
+}
+
+TEST(ContentSettingsPatternTest, FromString_ExtensionPatterns) {
+  EXPECT_TRUE(Pattern("chrome-extension://peoadpeiejnhkmpaakpnompolbglelel/")
+      .IsValid());
+  EXPECT_EQ("chrome-extension://peoadpeiejnhkmpaakpnompolbglelel/",
+      Pattern("chrome-extension://peoadpeiejnhkmpaakpnompolbglelel/")
+          .ToString());
+  EXPECT_TRUE(Pattern("chrome-extension://peoadpeiejnhkmpaakpnompolbglelel/")
+      .Matches(GURL("chrome-extension://peoadpeiejnhkmpaakpnompolbglelel/")));
+}
+
+TEST(ContentSettingsPatternTest, FromString_WithIPAdresses) {
+  // IPv4
+  EXPECT_TRUE(Pattern("192.168.0.1").IsValid());
+  EXPECT_STREQ("192.168.1.1", Pattern("192.168.1.1").ToString().c_str());
+  EXPECT_TRUE(Pattern("https://192.168.0.1:8080").IsValid());
+  EXPECT_STREQ("https://192.168.0.1:8080",
+               Pattern("https://192.168.0.1:8080").ToString().c_str());
+
+  // Subdomain wildcards should be only valid for hosts, not for IP addresses.
+  EXPECT_FALSE(Pattern("[*.]127.0.0.1").IsValid());
+
+  // IPv6
+  EXPECT_TRUE(Pattern("[::1]").IsValid());
+  EXPECT_STREQ("[::1]", Pattern("[::1]").ToString().c_str());
+  EXPECT_TRUE(Pattern("https://[::1]:8080").IsValid());
+  EXPECT_STREQ("https://[::1]:8080",
+               Pattern("https://[::1]:8080").ToString().c_str());
+}
+
+TEST(ContentSettingsPatternTest, FromString_WithWildcards) {
+  // Creating content settings patterns from strings completes pattern parts
+  // that are omitted in pattern specifications (strings) with a wildcard.
+
+  // The wildcard pattern.
+  EXPECT_TRUE(Pattern("*").IsValid());
+  EXPECT_STREQ("*", Pattern("*").ToString().c_str());
+  EXPECT_EQ(ContentSettingsPattern::IDENTITY,
+            Pattern("*").Compare(ContentSettingsPattern::Wildcard()));
+
+  // Patterns with port wildcard.
+  EXPECT_TRUE(Pattern("http://example.com:*").IsValid());
+  EXPECT_STREQ("http://example.com",
+               Pattern("http://example.com:*").ToString().c_str());
+
+  EXPECT_TRUE(Pattern("https://example.com").IsValid());
+  EXPECT_STREQ("https://example.com",
+               Pattern("https://example.com").ToString().c_str());
+
+  EXPECT_TRUE(Pattern("*://www.google.com.com:8080").IsValid());
+  EXPECT_STREQ("www.google.com:8080",
+               Pattern("*://www.google.com:8080").ToString().c_str());
+  EXPECT_TRUE(Pattern("*://www.google.com:8080").Matches(
+      GURL("http://www.google.com:8080")));
+  EXPECT_TRUE(Pattern("*://www.google.com:8080").Matches(
+      GURL("https://www.google.com:8080")));
+  EXPECT_FALSE(
+      Pattern("*://www.google.com").Matches(GURL("file:///foo/bar.html")));
+
+  EXPECT_TRUE(Pattern("www.example.com:8080").IsValid());
+
+  // Patterns with port and scheme wildcard.
+  EXPECT_TRUE(Pattern("*://www.example.com:*").IsValid());
+  EXPECT_STREQ("www.example.com",
+               Pattern("*://www.example.com:*").ToString().c_str());
+
+  EXPECT_TRUE(Pattern("*://www.example.com").IsValid());
+  EXPECT_STREQ("www.example.com",
+               Pattern("*://www.example.com").ToString().c_str());
+
+  EXPECT_TRUE(Pattern("www.example.com:*").IsValid());
+  EXPECT_STREQ("www.example.com",
+               Pattern("www.example.com:*").ToString().c_str());
+
+  EXPECT_TRUE(Pattern("www.example.com").IsValid());
+  EXPECT_STREQ("www.example.com",
+               Pattern("www.example.com").ToString().c_str());
+  EXPECT_TRUE(Pattern("www.example.com").Matches(
+      GURL("http://www.example.com/")));
+  EXPECT_FALSE(Pattern("example.com").Matches(
+      GURL("http://example.org/")));
+
+  // Patterns with domain wildcard.
+  EXPECT_TRUE(Pattern("[*.]example.com").IsValid());
+  EXPECT_STREQ("[*.]example.com",
+               Pattern("[*.]example.com").ToString().c_str());
+  EXPECT_TRUE(Pattern("[*.]example.com").Matches(
+      GURL("http://example.com/")));
+  EXPECT_TRUE(Pattern("[*.]example.com").Matches(
+      GURL("http://foo.example.com/")));
+  EXPECT_FALSE(Pattern("[*.]example.com").Matches(
+      GURL("http://example.org/")));
+
+  EXPECT_TRUE(Pattern("[*.]google.com:80").Matches(
+      GURL("http://mail.google.com:80")));
+  EXPECT_FALSE(Pattern("[*.]google.com:80").Matches(
+      GURL("http://mail.google.com:81")));
+  EXPECT_TRUE(Pattern("[*.]google.com:80").Matches(
+      GURL("http://www.google.com")));
+
+  EXPECT_TRUE(Pattern("[*.]google.com:8080").Matches(
+      GURL("http://mail.google.com:8080")));
+
+  EXPECT_TRUE(Pattern("[*.]google.com:443").Matches(
+      GURL("https://mail.google.com:443")));
+  EXPECT_TRUE(Pattern("[*.]google.com:443").Matches(
+      GURL("https://www.google.com")));
+
+  EXPECT_TRUE(Pattern("[*.]google.com:4321").Matches(
+      GURL("https://mail.google.com:4321")));
+  EXPECT_TRUE(Pattern("[*.]example.com").Matches(
+      GURL("http://example.com/")));
+  EXPECT_TRUE(Pattern("[*.]example.com").Matches(
+      GURL("http://www.example.com/")));
+
+  // Patterns with host wildcard
+  EXPECT_TRUE(Pattern("[*.]").IsValid());
+  EXPECT_TRUE(Pattern("http://*").IsValid());
+  EXPECT_TRUE(Pattern("http://[*.]").IsValid());
+  EXPECT_EQ(std::string("http://*"), Pattern("http://[*.]").ToString());
+  EXPECT_TRUE(Pattern("http://*:8080").IsValid());
+  EXPECT_TRUE(Pattern("*://*").IsValid());
+  EXPECT_STREQ("*", Pattern("*://*").ToString().c_str());
+}
+
+TEST(ContentSettingsPatternTest, FromString_Canonicalized) {
+  // UTF-8 patterns.
+  EXPECT_TRUE(Pattern("[*.]\xC4\x87ira.com").IsValid());
+  EXPECT_STREQ("[*.]xn--ira-ppa.com",
+               Pattern("[*.]\xC4\x87ira.com").ToString().c_str());
+  EXPECT_TRUE(Pattern("\xC4\x87ira.com").IsValid());
+  EXPECT_STREQ("xn--ira-ppa.com",
+               Pattern("\xC4\x87ira.com").ToString().c_str());
+  EXPECT_TRUE(Pattern("file:///\xC4\x87ira.html").IsValid());
+  EXPECT_STREQ("file:///%C4%87ira.html",
+               Pattern("file:///\xC4\x87ira.html").ToString().c_str());
+
+  // File path normalization.
+  EXPECT_TRUE(Pattern("file:///tmp/bar/../test.html").IsValid());
+  EXPECT_STREQ("file:///tmp/test.html",
+               Pattern("file:///tmp/bar/../test.html").ToString().c_str());
+}
+
+TEST(ContentSettingsPatternTest, InvalidPatterns) {
+  // StubObserver expects an empty pattern top be returned as empty string.
+  EXPECT_FALSE(ContentSettingsPattern().IsValid());
+  EXPECT_STREQ("", ContentSettingsPattern().ToString().c_str());
+
+  // Empty pattern string
+  EXPECT_FALSE(Pattern("").IsValid());
+  EXPECT_STREQ("", Pattern("").ToString().c_str());
+
+  // Pattern strings with invalid scheme part.
+  EXPECT_FALSE(Pattern("ftp://myhost.org").IsValid());
+  EXPECT_STREQ("", Pattern("ftp://myhost.org").ToString().c_str());
+
+  // Pattern strings with invalid host part.
+  EXPECT_FALSE(Pattern("*example.com").IsValid());
+  EXPECT_STREQ("", Pattern("*example.com").ToString().c_str());
+  EXPECT_FALSE(Pattern("example.*").IsValid());
+  EXPECT_STREQ("", Pattern("example.*").ToString().c_str());
+  EXPECT_FALSE(Pattern("*\xC4\x87ira.com").IsValid());
+  EXPECT_STREQ("", Pattern("*\xC4\x87ira.com").ToString().c_str());
+  EXPECT_FALSE(Pattern("\xC4\x87ira.*").IsValid());
+  EXPECT_STREQ("", Pattern("\xC4\x87ira.*").ToString().c_str());
+
+  // Pattern strings with invalid port parts.
+  EXPECT_FALSE(Pattern("example.com:abc").IsValid());
+  EXPECT_STREQ("", Pattern("example.com:abc").ToString().c_str());
+
+  // Invalid file pattern strings.
+  EXPECT_FALSE(Pattern("file://").IsValid());
+  EXPECT_STREQ("", Pattern("file://").ToString().c_str());
+  EXPECT_FALSE(Pattern("file:///foo/bar.html:8080").IsValid());
+  EXPECT_STREQ("", Pattern("file:///foo/bar.html:8080").ToString().c_str());
+}
+
+TEST(ContentSettingsPatternTest, UnequalOperator) {
+  EXPECT_TRUE(Pattern("http://www.foo.com") != Pattern("http://www.foo.com*"));
+  EXPECT_TRUE(Pattern("http://www.foo.com*") !=
+              ContentSettingsPattern::Wildcard());
+
+  EXPECT_TRUE(Pattern("http://www.foo.com") !=
+              ContentSettingsPattern::Wildcard());
+
+  EXPECT_TRUE(Pattern("http://www.foo.com") != Pattern("www.foo.com"));
+  EXPECT_TRUE(Pattern("http://www.foo.com") !=
+              Pattern("http://www.foo.com:80"));
+
+  EXPECT_FALSE(Pattern("http://www.foo.com") != Pattern("http://www.foo.com"));
+  EXPECT_TRUE(Pattern("http://www.foo.com") == Pattern("http://www.foo.com"));
+}
+
+TEST(ContentSettingsPatternTest, Compare) {
+  // Test identical patterns patterns.
+  ContentSettingsPattern pattern1 =
+      Pattern("http://www.google.com");
+  EXPECT_EQ(ContentSettingsPattern::IDENTITY, pattern1.Compare(pattern1));
+  EXPECT_EQ(ContentSettingsPattern::IDENTITY,
+            Pattern("http://www.google.com:80").Compare(
+                Pattern("http://www.google.com:80")));
+  EXPECT_EQ(ContentSettingsPattern::IDENTITY,
+            Pattern("*://[*.]google.com:*").Compare(
+                Pattern("*://[*.]google.com:*")));
+
+  ContentSettingsPattern invalid_pattern1;
+  ContentSettingsPattern invalid_pattern2 =
+      ContentSettingsPattern::FromString("google.com*");
+
+  // Compare invalid patterns.
+  EXPECT_TRUE(!invalid_pattern1.IsValid());
+  EXPECT_TRUE(!invalid_pattern2.IsValid());
+  EXPECT_EQ(ContentSettingsPattern::IDENTITY,
+            invalid_pattern1.Compare(invalid_pattern2));
+  EXPECT_TRUE(invalid_pattern1 == invalid_pattern2);
+
+  // Compare a pattern with an IPv4 addresse to a pattern with a domain name.
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_POST,
+            Pattern("http://www.google.com").Compare(
+                Pattern("127.0.0.1")));
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
+            Pattern("127.0.0.1").Compare(
+                Pattern("http://www.google.com")));
+  EXPECT_TRUE(Pattern("127.0.0.1") > Pattern("http://www.google.com"));
+  EXPECT_TRUE(Pattern("http://www.google.com") < Pattern("127.0.0.1"));
+
+  // Compare a pattern with an IPv6 address to a patterns with a domain name.
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_POST,
+            Pattern("http://www.google.com").Compare(
+                Pattern("[::1]")));
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
+            Pattern("[::1]").Compare(
+                Pattern("http://www.google.com")));
+  EXPECT_TRUE(Pattern("[::1]") > Pattern("http://www.google.com"));
+  EXPECT_TRUE(Pattern("http://www.google.com") < Pattern("[::1]"));
+
+  // Compare a pattern with an IPv6 addresse to a pattern with an IPv4 addresse.
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
+            Pattern("127.0.0.1").Compare(
+                Pattern("[::1]")));
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_POST,
+            Pattern("[::1]").Compare(
+                Pattern("127.0.0.1")));
+  EXPECT_TRUE(Pattern("[::1]") < Pattern("127.0.0.1"));
+  EXPECT_TRUE(Pattern("127.0.0.1") > Pattern("[::1]"));
+
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
+            Pattern("http://www.google.com").Compare(
+                Pattern("http://www.youtube.com")));
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
+            Pattern("http://[*.]google.com").Compare(
+                Pattern("http://[*.]youtube.com")));
+
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_POST,
+            Pattern("http://[*.]host.com").Compare(
+                Pattern("http://[*.]evilhost.com")));
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_POST,
+            Pattern("*://www.google.com:80").Compare(
+                Pattern("*://www.google.com:8080")));
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
+            Pattern("https://www.google.com:80").Compare(
+                Pattern("http://www.google.com:80")));
+
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
+            Pattern("http://[*.]google.com:90").Compare(
+                Pattern("http://mail.google.com:80")));
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
+            Pattern("https://[*.]google.com:80").Compare(
+                Pattern("http://mail.google.com:80")));
+  EXPECT_EQ(ContentSettingsPattern::DISJOINT_ORDER_PRE,
+            Pattern("https://mail.google.com:*").Compare(
+                Pattern("http://mail.google.com:80")));
+
+  // Test patterns with different precedences.
+  EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
+            Pattern("mail.google.com").Compare(
+                Pattern("[*.]google.com")));
+  EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
+            Pattern("[*.]google.com").Compare(
+                Pattern("mail.google.com")));
+
+  EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
+            Pattern("[*.]mail.google.com").Compare(
+                Pattern("[*.]google.com")));
+  EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
+            Pattern("[*.]google.com").Compare(
+                Pattern("[*.]mail.google.com")));
+
+  EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
+            Pattern("mail.google.com:80").Compare(
+                Pattern("mail.google.com:*")));
+  EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
+            Pattern("mail.google.com:*").Compare(
+                Pattern("mail.google.com:80")));
+
+  EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
+            Pattern("https://mail.google.com:*").Compare(
+                Pattern("*://mail.google.com:*")));
+  EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
+            Pattern("*://mail.google.com:*").Compare(
+                Pattern("https://mail.google.com:*")));
+
+  EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
+            Pattern("*://mail.google.com:80").Compare(
+                Pattern("https://mail.google.com:*")));
+  EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
+            Pattern("https://mail.google.com:*").Compare(
+                Pattern("*://mail.google.com:80")));
+
+  // Test the wildcard pattern.
+  EXPECT_EQ(ContentSettingsPattern::IDENTITY,
+            ContentSettingsPattern::Wildcard().Compare(
+                ContentSettingsPattern::Wildcard()));
+
+  EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
+            Pattern("[*.]google.com").Compare(
+                ContentSettingsPattern::Wildcard()));
+  EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
+            ContentSettingsPattern::Wildcard().Compare(
+                 Pattern("[*.]google.com")));
+
+  EXPECT_EQ(ContentSettingsPattern::PREDECESSOR,
+            Pattern("mail.google.com").Compare(
+                ContentSettingsPattern::Wildcard()));
+  EXPECT_EQ(ContentSettingsPattern::SUCCESSOR,
+            ContentSettingsPattern::Wildcard().Compare(
+                 Pattern("mail.google.com")));
+}
+
+// Legacy tests to ensure backwards compatibility.
+
+TEST(ContentSettingsPatternTest, PatternSupport_Legacy) {
+  EXPECT_TRUE(Pattern("[*.]example.com").IsValid());
+  EXPECT_TRUE(Pattern("example.com").IsValid());
+  EXPECT_TRUE(Pattern("192.168.0.1").IsValid());
+  EXPECT_TRUE(Pattern("[::1]").IsValid());
+  EXPECT_TRUE(
+      Pattern("file:///tmp/test.html").IsValid());
+  EXPECT_FALSE(Pattern("*example.com").IsValid());
+  EXPECT_FALSE(Pattern("example.*").IsValid());
+
+  EXPECT_TRUE(
+      Pattern("http://example.com").IsValid());
+  EXPECT_TRUE(
+      Pattern("https://example.com").IsValid());
+
+  EXPECT_TRUE(Pattern("[*.]example.com").Matches(
+              GURL("http://example.com/")));
+  EXPECT_TRUE(Pattern("[*.]example.com").Matches(
+              GURL("http://www.example.com/")));
+  EXPECT_TRUE(Pattern("www.example.com").Matches(
+              GURL("http://www.example.com/")));
+  EXPECT_TRUE(
+      Pattern("file:///tmp/test.html").Matches(
+              GURL("file:///tmp/test.html")));
+  EXPECT_FALSE(Pattern("").Matches(
+               GURL("http://www.example.com/")));
+  EXPECT_FALSE(Pattern("[*.]example.com").Matches(
+               GURL("http://example.org/")));
+  EXPECT_FALSE(Pattern("example.com").Matches(
+               GURL("http://example.org/")));
+  EXPECT_FALSE(
+      Pattern("file:///tmp/test.html").Matches(
+               GURL("file:///tmp/other.html")));
+  EXPECT_FALSE(
+      Pattern("file:///tmp/test.html").Matches(
+               GURL("http://example.org/")));
+}
+
+TEST(ContentSettingsPatternTest, CanonicalizePattern_Legacy) {
+  // Basic patterns.
+  EXPECT_STREQ("[*.]ikea.com", Pattern("[*.]ikea.com").ToString().c_str());
+  EXPECT_STREQ("example.com", Pattern("example.com").ToString().c_str());
+  EXPECT_STREQ("192.168.1.1", Pattern("192.168.1.1").ToString().c_str());
+  EXPECT_STREQ("[::1]", Pattern("[::1]").ToString().c_str());
+  EXPECT_STREQ("file:///tmp/file.html",
+               Pattern("file:///tmp/file.html").ToString().c_str());
+
+  // UTF-8 patterns.
+  EXPECT_STREQ("[*.]xn--ira-ppa.com",
+               Pattern("[*.]\xC4\x87ira.com").ToString().c_str());
+  EXPECT_STREQ("xn--ira-ppa.com",
+               Pattern("\xC4\x87ira.com").ToString().c_str());
+  EXPECT_STREQ("file:///%C4%87ira.html",
+               Pattern("file:///\xC4\x87ira.html").ToString().c_str());
+
+  // file:/// normalization.
+  EXPECT_STREQ("file:///tmp/test.html",
+               Pattern("file:///tmp/bar/../test.html").ToString().c_str());
+
+  // Invalid patterns.
+  EXPECT_STREQ("", Pattern("*example.com").ToString().c_str());
+  EXPECT_STREQ("", Pattern("example.*").ToString().c_str());
+  EXPECT_STREQ("", Pattern("*\xC4\x87ira.com").ToString().c_str());
+  EXPECT_STREQ("", Pattern("\xC4\x87ira.*").ToString().c_str());
+}
diff --git a/chrome/common/content_settings_types.h b/chrome/common/content_settings_types.h
new file mode 100644
index 0000000..7b4d16b
--- /dev/null
+++ b/chrome/common/content_settings_types.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_CONTENT_SETTINGS_TYPES_H_
+#define CHROME_COMMON_CONTENT_SETTINGS_TYPES_H_
+
+// A particular type of content to care about.  We give the user various types
+// of controls over each of these.
+enum ContentSettingsType {
+  // "DEFAULT" is only used as an argument to the Content Settings Window
+  // opener; there it means "whatever was last shown".
+  CONTENT_SETTINGS_TYPE_DEFAULT = -1,
+  CONTENT_SETTINGS_TYPE_COOKIES = 0,
+  CONTENT_SETTINGS_TYPE_IMAGES,
+  CONTENT_SETTINGS_TYPE_JAVASCRIPT,
+  CONTENT_SETTINGS_TYPE_PLUGINS,
+  CONTENT_SETTINGS_TYPE_POPUPS,
+  CONTENT_SETTINGS_TYPE_GEOLOCATION,
+  CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
+  CONTENT_SETTINGS_TYPE_INTENTS,
+  CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE,
+  CONTENT_SETTINGS_TYPE_FULLSCREEN,
+  CONTENT_SETTINGS_TYPE_MOUSELOCK,
+  CONTENT_SETTINGS_TYPE_MIXEDSCRIPT,
+  CONTENT_SETTINGS_TYPE_MEDIASTREAM,
+  CONTENT_SETTINGS_TYPE_PROTOCOL_HANDLERS,
+  CONTENT_SETTINGS_TYPE_PPAPI_BROKER,
+  CONTENT_SETTINGS_NUM_TYPES,
+};
+
+#endif  // CHROME_COMMON_CONTENT_SETTINGS_TYPES_H_
diff --git a/chrome/common/custom_handlers/protocol_handler.cc b/chrome/common/custom_handlers/protocol_handler.cc
new file mode 100644
index 0000000..e1bc8cc
--- /dev/null
+++ b/chrome/common/custom_handlers/protocol_handler.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/custom_handlers/protocol_handler.h"
+
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "net/base/escape.h"
+
+
+ProtocolHandler::ProtocolHandler(const std::string& protocol,
+                                 const GURL& url,
+                                 const string16& title)
+    : protocol_(protocol),
+      url_(url),
+      title_(title) {
+}
+
+ProtocolHandler ProtocolHandler::CreateProtocolHandler(
+    const std::string& protocol,
+    const GURL& url,
+    const string16& title) {
+  std::string lower_protocol = StringToLowerASCII(protocol);
+  return ProtocolHandler(lower_protocol, url, title);
+}
+
+ProtocolHandler::ProtocolHandler() {
+}
+
+bool ProtocolHandler::IsValidDict(const DictionaryValue* value) {
+  return value->HasKey("protocol") && value->HasKey("url") &&
+    value->HasKey("title");
+}
+
+bool ProtocolHandler::IsSameOrigin(
+    const ProtocolHandler& handler) const {
+  return handler.url().GetOrigin() == url_.GetOrigin();
+}
+
+const ProtocolHandler& ProtocolHandler::EmptyProtocolHandler() {
+  static const ProtocolHandler* const kEmpty = new ProtocolHandler();
+  return *kEmpty;
+}
+
+ProtocolHandler ProtocolHandler::CreateProtocolHandler(
+    const DictionaryValue* value) {
+  if (!IsValidDict(value)) {
+    return EmptyProtocolHandler();
+  }
+  std::string protocol, url;
+  string16 title;
+  value->GetString("protocol", &protocol);
+  value->GetString("url", &url);
+  value->GetString("title", &title);
+  return ProtocolHandler::CreateProtocolHandler(protocol, GURL(url), title);
+}
+
+GURL ProtocolHandler::TranslateUrl(const GURL& url) const {
+  std::string translatedUrlSpec(url_.spec());
+  ReplaceSubstringsAfterOffset(&translatedUrlSpec, 0, "%s",
+      net::EscapeQueryParamValue(url.spec(), true));
+  return GURL(translatedUrlSpec);
+}
+
+DictionaryValue* ProtocolHandler::Encode() const {
+  DictionaryValue* d = new DictionaryValue();
+  d->Set("protocol", Value::CreateStringValue(protocol_));
+  d->Set("url", Value::CreateStringValue(url_.spec()));
+  d->Set("title", Value::CreateStringValue(title_));
+  return d;
+}
+
+#if !defined(NDEBUG)
+std::string ProtocolHandler::ToString() const {
+  return "{ protocol=" + protocol_ +
+         ", url=" + url_.spec() +
+         ", title=" + UTF16ToASCII(title_) +
+         " }";
+}
+#endif
+
+bool ProtocolHandler::operator==(const ProtocolHandler& other) const {
+  return protocol_ == other.protocol_ &&
+    url_ == other.url_ &&
+    title_ == other.title_;
+}
+
+bool ProtocolHandler::IsEquivalent(const ProtocolHandler& other) const {
+  return protocol_ == other.protocol_ && url_ == other.url_;
+}
+
+bool ProtocolHandler::operator<(const ProtocolHandler& other) const {
+  return title_ < other.title_;
+}
diff --git a/chrome/common/custom_handlers/protocol_handler.h b/chrome/common/custom_handlers/protocol_handler.h
new file mode 100644
index 0000000..dd26b48
--- /dev/null
+++ b/chrome/common/custom_handlers/protocol_handler.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_CUSTOM_HANDLERS_PROTOCOL_HANDLER_H_
+#define CHROME_COMMON_CUSTOM_HANDLERS_PROTOCOL_HANDLER_H_
+
+#include <string>
+
+#include "base/values.h"
+#include "googleurl/src/gurl.h"
+
+// A single tuple of (protocol, url, title) that indicates how URLs of the
+// given protocol should be rewritten to be handled.
+
+class ProtocolHandler {
+ public:
+  static ProtocolHandler CreateProtocolHandler(const std::string& protocol,
+                                               const GURL& url,
+                                               const string16& title);
+
+  // Creates a ProtocolHandler with fields from the dictionary. Returns an
+  // empty ProtocolHandler if the input is invalid.
+  static ProtocolHandler CreateProtocolHandler(const DictionaryValue* value);
+
+  // Returns true if the dictionary value has all the necessary fields to
+  // define a ProtocolHandler.
+  static bool IsValidDict(const DictionaryValue* value);
+
+  // Returns true if this handler's url has the same origin as the given one.
+  bool IsSameOrigin(const ProtocolHandler& handler) const;
+
+  // Canonical empty ProtocolHandler.
+  static const ProtocolHandler& EmptyProtocolHandler();
+
+  // Interpolates the given URL into the URL template of this handler.
+  GURL TranslateUrl(const GURL& url) const;
+
+  // Returns true if the handlers are considered equivalent when determining
+  // if both handlers can be registered, or if a handler has previously been
+  // ignored.
+  bool IsEquivalent(const ProtocolHandler& other) const;
+
+  // Encodes this protocol handler as a DictionaryValue. The caller is
+  // responsible for deleting the returned value.
+  DictionaryValue* Encode() const;
+
+  const std::string& protocol() const { return protocol_; }
+  const GURL& url() const { return url_;}
+  const string16& title() const { return title_; }
+
+  bool IsEmpty() const {
+    return protocol_.empty();
+  }
+
+#if !defined(NDEBUG)
+  // Returns a string representation suitable for use in debugging.
+  std::string ToString() const;
+#endif
+
+
+  bool operator==(const ProtocolHandler& other) const;
+  bool operator<(const ProtocolHandler& other) const;
+
+ private:
+  ProtocolHandler(const std::string& protocol,
+                  const GURL& url,
+                  const string16& title);
+  ProtocolHandler();
+
+  std::string protocol_;
+  GURL url_;
+  string16 title_;
+};
+
+#endif  // CHROME_COMMON_CUSTOM_HANDLERS_PROTOCOL_HANDLER_H_
diff --git a/chrome/common/descriptors_android.h b/chrome/common/descriptors_android.h
new file mode 100644
index 0000000..09d9a9b
--- /dev/null
+++ b/chrome/common/descriptors_android.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_DESCRIPTORS_ANDROID_H_
+#define CHROME_COMMON_DESCRIPTORS_ANDROID_H_
+
+#include "content/public/common/content_descriptors.h"
+
+enum {
+#if defined(OS_ANDROID)
+  kAndroidChromePakDescriptor = kContentIPCDescriptorMax + 1,
+  kAndroidLocalePakDescriptor,
+  kAndroidUIResourcesPakDescriptor,
+  kAndroidMinidumpDescriptor,
+#endif
+};
+
+#endif  // CHROME_COMMON_DESCRIPTORS_ANDROID_H_
diff --git a/chrome/common/env_vars.cc b/chrome/common/env_vars.cc
new file mode 100644
index 0000000..06e10f8
--- /dev/null
+++ b/chrome/common/env_vars.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/env_vars.h"
+
+namespace env_vars {
+
+// We call running in unattended mode (for automated testing) "headless".
+// This mode can be enabled using this variable or by the kNoErrorDialogs
+// switch.
+const char kHeadless[] = "CHROME_HEADLESS";
+
+// The name of the log file.
+const char kLogFileName[] = "CHROME_LOG_FILE";
+
+// The name of the session log directory when logged in to ChromeOS.
+const char kSessionLogDir[] = "CHROMEOS_SESSION_LOG_DIR";
+
+// CHROME_CRASHED exists if a previous instance of chrome has crashed. This
+// triggers the 'restart chrome' dialog. CHROME_RESTART contains the strings
+// that are needed to show the dialog.
+const char kShowRestart[] = "CHROME_CRASHED";
+const char kRestartInfo[] = "CHROME_RESTART";
+
+// The strings RIGHT_TO_LEFT and LEFT_TO_RIGHT indicate the locale direction.
+// For example, for Hebrew and Arabic locales, we use RIGHT_TO_LEFT so that the
+// dialog is displayed using the right orientation.
+const char kRtlLocale[] = "RIGHT_TO_LEFT";
+const char kLtrLocale[] = "LEFT_TO_RIGHT";
+
+// Number of times to run a given startup_tests unit test.
+const char kStartupTestsNumCycles[] = "STARTUP_TESTS_NUMCYCLES";
+
+}  // namespace env_vars
diff --git a/chrome/common/env_vars.h b/chrome/common/env_vars.h
new file mode 100644
index 0000000..0b3ba6f
--- /dev/null
+++ b/chrome/common/env_vars.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 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.
+
+// Defines all the environment variables used by Chrome.
+
+#ifndef CHROME_COMMON_ENV_VARS_H__
+#define CHROME_COMMON_ENV_VARS_H__
+
+namespace env_vars {
+
+extern const char kHeadless[];
+extern const char kLogFileName[];
+extern const char kSessionLogDir[];
+extern const char kShowRestart[];
+extern const char kRestartInfo[];
+extern const char kRtlLocale[];
+extern const char kLtrLocale[];
+extern const char kStartupTestsNumCycles[];
+
+}  // namespace env_vars
+
+#endif  // CHROME_COMMON_ENV_VARS_H__
diff --git a/chrome/common/extensions/OWNERS b/chrome/common/extensions/OWNERS
new file mode 100644
index 0000000..25639fd
--- /dev/null
+++ b/chrome/common/extensions/OWNERS
@@ -0,0 +1,26 @@
+# This should match chrome/browser/extensions/OWNERS
+aa@chromium.org
+asargent@chromium.org
+benwells@chromium.org
+erikkay@chromium.org
+finnur@chromium.org
+jeremya@chromium.org
+kalman@chromium.org
+koz@chromium.org
+mihaip@chromium.org
+miket@chromium.org
+mpcomplete@chromium.org
+yoz@chromium.org
+
+# For API documentation
+mkearney@chromium.org
+kathyw@chromium.org
+
+# For security review of IPC message files.
+per-file *_messages.h=set noparent
+per-file *_messages.h=cdn@chromium.org
+per-file *_messages.h=cevans@chromium.org
+per-file *_messages.h=inferno@chromium.org
+per-file *_messages.h=jschuh@chromium.org
+per-file *_messages.h=palmer@chromium.org
+per-file *_messages.h=tsepez@chromium.org
diff --git a/chrome/common/extensions/PRESUBMIT.py b/chrome/common/extensions/PRESUBMIT.py
new file mode 100644
index 0000000..2e9a114
--- /dev/null
+++ b/chrome/common/extensions/PRESUBMIT.py
@@ -0,0 +1,130 @@
+# Copyright (c) 2012 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.
+
+"""Presubmit script for changes affecting extensions.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details about the presubmit API built into gcl.
+"""
+import fnmatch
+import os
+import re
+
+EXTENSIONS_PATH = os.path.join('chrome', 'common', 'extensions')
+DOCS_PATH = os.path.join(EXTENSIONS_PATH, 'docs')
+SERVER2_PATH = os.path.join(DOCS_PATH, 'server2')
+API_PATH = os.path.join(EXTENSIONS_PATH, 'api')
+TEMPLATES_PATH = os.path.join(DOCS_PATH, 'templates')
+PRIVATE_TEMPLATES_PATH = os.path.join(TEMPLATES_PATH, 'private')
+PUBLIC_TEMPLATES_PATH = os.path.join(TEMPLATES_PATH, 'public')
+INTROS_PATH = os.path.join(TEMPLATES_PATH, 'intros')
+ARTICLES_PATH = os.path.join(TEMPLATES_PATH, 'articles')
+
+LOCAL_PUBLIC_TEMPLATES_PATH = os.path.join('docs',
+                                           'templates',
+                                           'public')
+
+def _ListFilesInPublic():
+  all_files = []
+  for path, dirs, files in os.walk(LOCAL_PUBLIC_TEMPLATES_PATH):
+    all_files.extend(
+        os.path.join(path, filename)[len(LOCAL_PUBLIC_TEMPLATES_PATH + os.sep):]
+        for filename in files)
+  return all_files
+
+def _UnixName(name):
+  name = os.path.splitext(name)[0]
+  s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name)
+  s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1)
+  return s2.replace('.', '_').lower()
+
+def _FindMatchingTemplates(template_name, template_path_list):
+  matches = []
+  unix_name = _UnixName(template_name)
+  for template in template_path_list:
+    if unix_name == _UnixName(template.split(os.sep)[-1]):
+      matches.append(template)
+  return matches
+
+def _SanitizeAPIName(name, api_path):
+  if not api_path.endswith(os.sep):
+    api_path += os.sep
+  filename = os.path.splitext(name)[0][len(api_path):].replace(os.sep, '_')
+  if 'experimental' in filename:
+    filename = 'experimental_' + filename.replace('experimental_', '')
+  return filename
+
+def _CreateIntegrationTestArgs(affected_files):
+  if (any(fnmatch.fnmatch(name, '%s*.py' % SERVER2_PATH)
+         for name in affected_files) or
+      any(fnmatch.fnmatch(name, '%s*' % PRIVATE_TEMPLATES_PATH)
+          for name in affected_files)):
+    return ['-a']
+  args = []
+  for name in affected_files:
+    if (fnmatch.fnmatch(name, '%s*' % PUBLIC_TEMPLATES_PATH) or
+        fnmatch.fnmatch(name, '%s*' % INTROS_PATH) or
+        fnmatch.fnmatch(name, '%s*' % ARTICLES_PATH)):
+      args.extend(_FindMatchingTemplates(name.split(os.sep)[-1],
+                                         _ListFilesInPublic()))
+    if fnmatch.fnmatch(name, '%s*' % API_PATH):
+      args.extend(_FindMatchingTemplates(_SanitizeAPIName(name, API_PATH),
+                                         _ListFilesInPublic()))
+  return args
+
+def _CheckHeadingIDs(input_api):
+  ids_re = re.compile('<h[23].*id=.*?>')
+  headings_re = re.compile('<h[23].*?>')
+  bad_files = []
+  for name in input_api.AbsoluteLocalPaths():
+    if (fnmatch.fnmatch(name, '*%s*' % INTROS_PATH) or
+        fnmatch.fnmatch(name, '*%s*' % ARTICLES_PATH)):
+      contents = input_api.ReadFile(name)
+      if (len(re.findall(headings_re, contents)) !=
+          len(re.findall(ids_re, contents))):
+        bad_files.append(name)
+  return bad_files
+
+def _CheckVersions(input_api, output_api, results):
+  version = '_VERSION ='
+  for affected_file in input_api.AffectedFiles():
+    local_path = affected_file.LocalPath()
+    if not fnmatch.fnmatch(local_path, '%s*' % SERVER2_PATH):
+      continue
+    if local_path.endswith('PRESUBMIT.py'):
+      continue
+    if any(version in line for line in affected_file.NewContents()):
+      found = False
+      for _, text in affected_file.ChangedContents():
+        if version in text:
+          found = True
+          break
+      if not found:
+        results.append(output_api.PresubmitError(
+            '_VERSION of %s needs to be incremented.' % affected_file))
+
+def _CheckChange(input_api, output_api):
+  results = [
+      output_api.PresubmitError('File %s needs an id for each heading.' % name)
+      for name in _CheckHeadingIDs(input_api)]
+  try:
+    integration_test = []
+    # From depot_tools/presubmit_canned_checks.py:529
+    if input_api.platform == 'win32':
+      integration_test = [input_api.python_executable]
+    integration_test.append(
+        os.path.join('docs', 'server2', 'integration_test.py'))
+    integration_test.extend(_CreateIntegrationTestArgs(input_api.LocalPaths()))
+    input_api.subprocess.check_call(integration_test,
+                                    cwd=input_api.PresubmitLocalPath())
+  except input_api.subprocess.CalledProcessError:
+    results.append(output_api.PresubmitError('IntegrationTest failed!'))
+  _CheckVersions(input_api, output_api, results)
+  return results
+
+def CheckChangeOnUpload(input_api, output_api):
+  return _CheckChange(input_api, output_api)
+
+def CheckChangeOnCommit(input_api, output_api):
+  return _CheckChange(input_api, output_api)
diff --git a/chrome/common/extensions/PRESUBMIT_test.py b/chrome/common/extensions/PRESUBMIT_test.py
new file mode 100755
index 0000000..7ac79b0
--- /dev/null
+++ b/chrome/common/extensions/PRESUBMIT_test.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+import os
+import unittest
+
+import PRESUBMIT
+
+EXTENSIONS_PATH = os.path.join('chrome', 'common', 'extensions')
+DOCS_PATH = os.path.join(EXTENSIONS_PATH, 'docs')
+SERVER2_PATH = os.path.join(DOCS_PATH, 'server2')
+PUBLIC_PATH = os.path.join(DOCS_PATH, 'templates', 'public')
+PRIVATE_PATH = os.path.join(DOCS_PATH, 'templates', 'private')
+INTROS_PATH = os.path.join(DOCS_PATH, 'templates', 'intros')
+ARTICLES_PATH = os.path.join(DOCS_PATH, 'templates', 'articles')
+
+class PRESUBMITTest(unittest.TestCase):
+  def testCreateIntegrationTestArgs(self):
+    input_files = [
+      os.path.join(EXTENSIONS_PATH, 'test.cc'),
+      os.path.join(EXTENSIONS_PATH, 'test2.cc'),
+      os.path.join('test', 'test.py')
+    ]
+    expected_files = []
+    self.assertEqual(expected_files,
+                     PRESUBMIT._CreateIntegrationTestArgs(input_files))
+    expected_files.append(os.path.join('apps', 'fileSystem.html'))
+    input_files.append(os.path.join(EXTENSIONS_PATH, 'api', 'file_system.idl'))
+    self.assertEqual(expected_files,
+                     PRESUBMIT._CreateIntegrationTestArgs(input_files))
+    expected_files.append(os.path.join('extensions', 'alarms.html'))
+    expected_files.append(os.path.join('apps', 'alarms.html'))
+    input_files.append(os.path.join(EXTENSIONS_PATH, 'api', 'alarms.json'))
+    self.assertEqual(expected_files,
+                     PRESUBMIT._CreateIntegrationTestArgs(input_files))
+    expected_files.append('extensions/devtools_network.html')
+    input_files.append(os.path.join(EXTENSIONS_PATH,
+                                    'api',
+                                    'devtools',
+                                    'network.json'))
+    self.assertEqual(expected_files,
+                     PRESUBMIT._CreateIntegrationTestArgs(input_files))
+    expected_files.append(os.path.join('extensions', 'docs.html'))
+    expected_files.append(os.path.join('apps', 'docs.html'))
+    input_files.append(os.path.join(PUBLIC_PATH, 'extensions', 'docs.html'))
+    self.assertEqual(expected_files,
+                     PRESUBMIT._CreateIntegrationTestArgs(input_files))
+    expected_files.append(os.path.join('extensions', 'bookmarks.html'))
+    input_files.append(os.path.join(INTROS_PATH, 'bookmarks.html'))
+    self.assertEqual(expected_files,
+                     PRESUBMIT._CreateIntegrationTestArgs(input_files))
+    expected_files.append(os.path.join('extensions', 'i18n.html'))
+    expected_files.append(os.path.join('apps', 'i18n.html'))
+    input_files.append(os.path.join(INTROS_PATH, 'i18n.html'))
+    self.assertEqual(expected_files,
+                     PRESUBMIT._CreateIntegrationTestArgs(input_files))
+    expected_files.append(os.path.join('apps', 'about_apps.html'))
+    input_files.append(os.path.join(ARTICLES_PATH, 'about_apps.html'))
+    self.assertEqual(expected_files,
+                     PRESUBMIT._CreateIntegrationTestArgs(input_files))
+    input_files.append(os.path.join(PRIVATE_PATH, 'type.html'))
+    self.assertEqual([ '-a' ],
+                     PRESUBMIT._CreateIntegrationTestArgs(input_files))
+    input_files.pop()
+    input_files.append(os.path.join(SERVER2_PATH, 'test.txt'))
+    self.assertEqual(expected_files,
+                     PRESUBMIT._CreateIntegrationTestArgs(input_files))
+    input_files.append(os.path.join(SERVER2_PATH, 'handler.py'))
+    self.assertEqual([ '-a' ],
+                     PRESUBMIT._CreateIntegrationTestArgs(input_files))
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/chrome/common/extensions/api/OWNERS b/chrome/common/extensions/api/OWNERS
new file mode 100644
index 0000000..a1a5a17
--- /dev/null
+++ b/chrome/common/extensions/api/OWNERS
@@ -0,0 +1,12 @@
+# *.json
+# See chrome/common/extensions/OWNERS
+
+# devtools_api.json
+caseq@chromium.org
+pfeldman@chromium.org
+
+# For API documentation, primary contacts.
+mkearney@chromium.org
+mkwst@chromium.org
+# Secondary for API documentation, please try to contact primary first.
+kathyw@chromium.org
diff --git a/chrome/common/extensions/api/_manifest_features.json b/chrome/common/extensions/api/_manifest_features.json
new file mode 100644
index 0000000..adb4c65
--- /dev/null
+++ b/chrome/common/extensions/api/_manifest_features.json
@@ -0,0 +1,267 @@
+// Copyright (c) 2012 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.
+
+{
+  "app": {
+    "channel": "stable",
+    "extension_types": ["packaged_app", "hosted_app", "platform_app"]
+  },
+  // The default platform app CSP can only be overridden by whitelisted apps.
+  // This is a separate key from the top-level content_security_policy one since
+  // we can't combine type restrictions with whitelisted ID restrictions. If
+  // there is a need for additional whitelisted entries, the feature system
+  // should instead be extended to support OR-ing of restrictions.
+  "app.content_security_policy": {
+    "channel": "stable",
+    "extension_types": ["platform_app"],
+    "min_manifest_version": 2,
+    "whitelist": [
+      "nckgahadagoaajjgafhacjanaoiihapd",  // Google Talk prod
+      "eggnbpckecmjlblplehfpjjdhhidfdoj",  // Google Talk beta
+      "ppleadejekpmccmnpjdimmlfljlkdfej",  // Google Talk alpha
+      "ljclpkphhpbpinifbeabbhlfddcpfdde"   // Google Talk debug
+    ]
+  },
+  "app.background": {
+    "channel": "stable",
+    "extension_types": ["platform_app"],
+    "min_manifest_version": 2
+  },
+  "app.launch": {
+    "channel": "stable",
+    "extension_types": ["packaged_app", "hosted_app"]
+  },
+  "app.isolation": {
+    "channel": "stable",
+    // Platform apps always have isolated storage, thus they cannot specify it
+    // via the manifest.
+    "extension_types": ["packaged_app", "hosted_app"]
+  },
+  "author": {
+    "channel": "stable",
+    "extension_types": "all"
+  },
+  "background": {
+    "channel": "stable",
+    "extension_types": [
+      // Platform apps specify their background page via app.background.
+      "extension", "packaged_app", "hosted_app"
+    ]
+  },
+  "background.persistent": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app"
+    ],
+    "min_manifest_version": 2
+  },
+  "background_page": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app"
+    ],
+    "max_manifest_version": 1
+  },
+  "browser_action": {
+    "channel": "stable",
+    "extension_types": ["extension"]
+  },
+  "chrome_url_overrides": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "commands": {
+    "channel": "beta",
+    "extension_types": ["extension", "packaged_app", "platform_app"],
+    "min_manifest_version": 2
+  },
+  "content_security_policy": {
+    "channel": "stable",
+    // Platform apps have a restricted content security policy that cannot be
+    // overriden (except for a whitelist of exceptions, see the
+    // app.content_security_policy whitelist).
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "content_scripts": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "converted_from_user_script": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app"
+    ],
+    "no_doc": true
+  },
+  "current_locale": {
+    "channel": "stable",
+    "extension_types": "all"
+  },
+  "default_locale": {
+    "channel": "stable",
+    "extension_types": "all"
+  },
+  "description": {
+    "channel": "stable",
+    "extension_types": "all"
+  },
+  "devtools_page": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "display_in_launcher": {
+    "channel": "stable",
+    "extension_types": ["packaged_app", "platform_app"],
+    "location": "component"
+  },
+  "display_in_new_tab_page": {
+    "channel": "stable",
+    "extension_types": ["packaged_app", "platform_app"],
+    "location": "component"
+  },
+  "file_browser_handlers": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "file_handlers": {
+    "channel": "stable",
+    "extension_types": ["platform_app"]
+  },
+  "homepage_url": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "icons": {
+    "channel": "stable",
+    "extension_types": "all"
+  },
+  "incognito": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app", "platform_app"]
+  },
+  "input_components": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "intents": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app", "platform_app"
+    ]
+  },
+  "key": {
+    "channel": "stable",
+    "extension_types": "all"
+  },
+  "manifest_version": {
+    "channel": "stable",
+    "extension_types": "all"
+  },
+  "minimum_chrome_version": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app", "platform_app"
+    ]
+  },
+  "nacl_modules": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app", "platform_app"
+    ]
+  },
+  "name": {
+    "channel": "stable",
+    "extension_types": "all"
+  },
+  "offline_enabled": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app", "platform_app"
+    ]
+  },
+  "omnibox": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "optional_permissions": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app", "platform_app"
+    ]
+  },
+  "options_page": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app", "platform_app"
+    ]
+  },
+  "oauth2": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "platform_app"
+    ]
+  },
+  "page_action": {
+    "channel": "stable",
+    "extension_types": ["extension"]
+  },
+  "page_actions": {
+    "channel": "stable",
+    "extension_types": ["extension"],
+    "max_manifest_version": 1
+  },
+  "permissions": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app", "platform_app"
+    ]
+  },
+  "plugins": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app", "hosted_app"]
+  },
+  "requirements": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app", "platform_app"
+    ]
+  },
+  "sandbox": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "platform_app", "packaged_app"
+    ],
+    "min_manifest_version": 2
+  },
+  "script_badge": {
+    "channel": "trunk",
+    "extension_types": ["extension"]
+  },
+  "signature": {
+    "channel": "stable",
+    "extension_types": "all"
+  },
+  "theme": {
+    "channel": "stable",
+    "extension_types": ["theme"]
+  },
+  "tts_engine": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "update_url": {
+    "channel": "stable",
+    "extension_types": "all"
+  },
+  "version": {
+    "channel": "stable",
+    "extension_types": "all"
+  },
+  "web_accessible_resources": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app"
+    ]
+  }
+}
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json
new file mode 100644
index 0000000..b9b2607
--- /dev/null
+++ b/chrome/common/extensions/api/_permission_features.json
@@ -0,0 +1,375 @@
+// Copyright (c) 2012 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.
+
+{
+  "activeTab": {
+    "channel": "dev",
+    "extension_types": ["extension", "packaged_app"],
+    "min_manifest_version": 2
+  },
+  "alarms": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app", "platform_app"],
+    "min_manifest_version": 2
+  },
+  "app.runtime": {
+    "channel": "stable",
+    "extension_types": ["platform_app"]
+  },
+  "app.window": {
+    "channel": "stable",
+    "extension_types": ["platform_app"]
+  },
+  "appNotifications": {
+    "channel": "stable",
+    "extension_types": ["packaged_app", "hosted_app"]
+  },
+  "audioCapture": {
+    "channel": "stable",
+    "extension_types": ["platform_app"]
+  },
+  "background": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app", "platform_app"
+    ]
+  },
+  "bluetooth": {
+    "channel": "dev",
+    "extension_types": ["platform_app"]
+  },
+  "bookmarkManagerPrivate": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"],
+    "location": "component"
+  },
+  "bookmarks": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "browsingData": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "chromePrivate": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"],
+    "whitelist": [
+      "haiffjcadagjlijoggckpgfnoeiflnem",  // Citrix Receiver
+      "gnedhmakppccajfpfiihfcdlnpgomkcf",  // Citrix Receiver Beta
+      "fjcibdnjlbfnbfdjneajpipnlcppleek"   // Citrix Receiver Dev
+    ]
+  },
+  "chromeosInfoPrivate": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"],
+    "location": "component"
+  },
+  "clipboardRead": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app", "platform_app"
+    ]
+  },
+  "clipboardWrite": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app", "platform_app"
+    ]
+  },
+  "cloudPrintPrivate": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"],
+    // CloudPrint
+    "whitelist": ["mfehgcgbbipciphmccgaenjidiccnmng"]
+  },
+  "contentSettings": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "contextMenus": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app", "platform_app"]
+  },
+  "cookies": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "debugger": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "devtools": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "declarativeWebRequest": {
+    "channel": "beta",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "downloads": {
+    "channel": "dev",
+    "extension_types": [
+      "extension", "packaged_app"
+    ]
+  },
+  "experimental": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app", "platform_app"
+    ]
+  },
+  "fileBrowserHandler": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app", "platform_app"]
+  },
+  "fileBrowserPrivate": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"],
+    "location": "component"
+  },
+  "fileSystem": {
+    "channel": "stable",
+    "extension_types": ["platform_app"]
+  },
+  "fileSystem.write": {
+    "channel": "stable",
+    "extension_types": ["platform_app"]
+  },
+  "fontSettings": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "geolocation": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app", "platform_app"
+    ]
+  },
+  "history": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "idle": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app", "platform_app"]
+  },
+  "input": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"],
+    "inplies_full_url_access": true
+  },
+  "inputMethodPrivate": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app", "platform_app"],
+    "whitelist": [
+      "haiffjcadagjlijoggckpgfnoeiflnem",  // Citrix Receiver
+      "gnedhmakppccajfpfiihfcdlnpgomkcf",  // Citrix Receiver Beta
+      "fjcibdnjlbfnbfdjneajpipnlcppleek",  // Citrix Receiver Dev
+      "pnhechapfaindjhompbnflcldabbghjo",  // HTerm
+      "okddffdblfhhnmhodogpojmfkjmhinfp"   // HTerm dev
+    ]
+  },
+  "managedModePrivate": {
+    "channel": "dev",
+    "extension_types": ["extension", "packaged_app"],
+    "location": "component"
+  },
+  "management": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "mediaGalleries": {
+    "channel": "stable",
+    "extension_types": [ "platform_app" ]
+  },
+  "mediaGalleries.allAutoDetected": {
+    "channel": "stable",
+    "extension_types": [ "platform_app" ]
+  },
+  "mediaGalleries.read": {
+    "channel": "stable",
+    "extension_types": [ "platform_app" ]
+  },
+  // TODO(thestig) Remove this as part of http:://crbug.com/144496
+  "mediaGalleriesPrivate": {
+    "channel": "stable",
+    "extension_types": [ "platform_app" ],
+    "whitelist": [
+      "ebpbnabdhheoknfklmpddcdijjkmklkp",
+      "efjnaogkjbogokcnohkmnjdojkikgobo",
+      "ejegoaikibpmikoejfephaneibodccma"
+    ]
+  },
+  "mediaPlayerPrivate": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"],
+    "location": "component"
+  },
+  "metricsPrivate": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app", "platform_app"],
+    "whitelist": [
+      // The file manager is a component extension, and it can currently use
+      // whitelisted interfaces without being on the corresponding whitelist.
+      // Adding it to this whitelist documents its dependency, however, and also
+      // doesn't hurt anything.
+      "hhaomjibdihmijegdhdafkllkbggdgoj",  // File manager
+      "gbkeegbaiigmenfmjfclcdgdpimamgkj",  // Quickoffice
+      "ionpfmkccalenbmnddpbmocokhaknphg"   // Quickoffice dev
+    ]
+  },
+  "notifications": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app", "platform_app"
+    ]
+  },
+  "echoPrivate": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"],
+    "location": "component"
+  },
+  "pageCapture": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "plugin": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "privacy": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "proxy": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "pushMessaging": {
+    "channel": "stable",
+    "extension_types": ["extension", "platform_app"]
+  },
+  "rtcPrivate": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"],
+    "whitelist": [
+      "nckgahadagoaajjgafhacjanaoiihapd",  // Google Talk prod
+      "eggnbpckecmjlblplehfpjjdhhidfdoj",  // Google Talk beta
+      "ppleadejekpmccmnpjdimmlfljlkdfej",  // Google Talk alpha
+      "ljclpkphhpbpinifbeabbhlfddcpfdde"   // Google Talk debug
+    ]
+  },
+  // Note: runtime is not actually a permission, but some systems check these
+  // values to verify restrictions.
+  "runtime": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app", "platform_app"],
+    "min_manifest_version": 2
+  },
+  "serial": {
+    "channel": "stable",
+    "extension_types": ["platform_app"]
+  },
+  "socket": {
+    "channel": "stable",
+    "extension_types": ["platform_app"]
+  },
+  "syncFileSystem": {
+    "channel": "trunk",
+    "extension_types": ["platform_app"]
+  },
+  "storage": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app", "platform_app"],
+    "min_manifest_version": 2
+  },
+  "systemPrivate": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"],
+    "location": "component"
+  },
+  "tabs": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "tabCapture": {
+    "channel": "canary",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "terminalPrivate": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app", "platform_app"],
+    "whitelist": [
+      "pnhechapfaindjhompbnflcldabbghjo",  // HTerm
+      "okddffdblfhhnmhodogpojmfkjmhinfp"   // HTerm dev
+    ]
+  },
+  "topSites": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "tts": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app", "platform_app"]
+  },
+  "ttsEngine": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "unlimitedStorage": {
+    "channel": "stable",
+    "extension_types": [
+      "extension", "packaged_app", "hosted_app", "platform_app"
+    ]
+  },
+  "usb": {
+    "channel": "dev",
+    "extension_types": ["platform_app"]
+  },
+  "videoCapture": {
+    "channel": "stable",
+    "extension_types": ["platform_app"]
+  },
+  "wallpaperPrivate": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"],
+    "location": "component"
+  },
+  "webNavigation": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "webSocketProxyPrivate": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"],
+    "whitelist": [
+      "haiffjcadagjlijoggckpgfnoeiflnem",  // Citrix Receiver
+      "gnedhmakppccajfpfiihfcdlnpgomkcf",  // Citrix Receiver Beta
+      "fjcibdnjlbfnbfdjneajpipnlcppleek",  // Citrix Receiver Dev
+      "pnhechapfaindjhompbnflcldabbghjo",  // HTerm
+      "okddffdblfhhnmhodogpojmfkjmhinfp"   // HTerm dev
+    ]
+  },
+  "webstorePrivate": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"],
+    "whitelist": [
+      "ahfgeienlihckogmohjhadlkjgocpleb",  // Web Store
+      "afchcafgojfnemjkcbhfekplkmjaldaa"   // Enterprise Web Store
+    ]
+  },
+  "webRequest": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "webRequestBlocking": {
+    "channel": "stable",
+    "extension_types": ["extension", "packaged_app"]
+  },
+  "webview": {
+    "channel": "dev",
+    "extension_types": ["platform_app"]
+  }
+}
diff --git a/chrome/common/extensions/api/alarms.idl b/chrome/common/extensions/api/alarms.idl
new file mode 100644
index 0000000..ce153f6
--- /dev/null
+++ b/chrome/common/extensions/api/alarms.idl
@@ -0,0 +1,88 @@
+// Copyright (c) 2012 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.
+
+// TODO(mpcomplete): We need documentation before we can release this.
+
+namespace alarms {
+  dictionary Alarm {
+    // Name of this alarm.
+    DOMString name;
+
+    // Time at which this alarm was scheduled to fire, in milliseconds past the
+    // epoch (e.g. <code>Date.now() + n</code>).  For performance reasons, the
+    // alarm may have been delayed an arbitrary amount beyond this.
+    double scheduledTime;
+
+    // If not null, the alarm is a repeating alarm and will fire again in
+    // <var>periodInMinutes</var> minutes.
+    double? periodInMinutes;
+  };
+
+  // TODO(mpcomplete): rename to CreateInfo when http://crbug.com/123073 is
+  // fixed.
+  dictionary AlarmCreateInfo {
+    // Time at which the alarm should fire, in milliseconds past the epoch
+    // (e.g. <code>Date.now() + n</code>).
+    double? when;
+
+    // Length of time in minutes after which the <code>onAlarm</code> event
+    // should fire.
+    //
+    // <!-- TODO: need minimum=0 -->
+    double? delayInMinutes;
+
+    // If set, the onAlarm event should fire every <var>periodInMinutes</var>
+    // minutes after the initial event specified by <var>when</var> or
+    // <var>delayInMinutes</var>.  If not set, the alarm will only fire once.
+    //
+    // <!-- TODO: need minimum=0 -->
+    double? periodInMinutes;
+  };
+
+  callback AlarmCallback = void (Alarm alarm);
+  callback AlarmListCallback = void (Alarm[] alarms);
+
+  interface Functions {
+    // Creates an alarm.  Near the time(s) specified by <var>alarmInfo</var>,
+    // the <code>onAlarm</code> event is fired. If there is another alarm with
+    // the same name (or no name if none is specified), it will be cancelled and
+    // replaced by this alarm.
+    //
+    // Note that granularity is not guaranteed: times are more of a hint to the
+    // browser. For performance reasons, alarms may be delayed an arbitrary
+    // amount of time before firing.
+    //
+    // |name|: Optional name to identify this alarm. Defaults to the empty
+    // string.
+    //
+    // |alarmInfo|: Describes when the alarm should fire.  The initial time must
+    // be specified by either <var>when</var> or <var>delayInMinutes</var> (but
+    // not both).  If <var>periodInMinutes</var> is set, the alarm will repeat
+    // every <var>periodInMinutes</var> minutes after the initial event.  If
+    // neither <var>when</var> or <var>delayInMinutes</var> is set for a
+    // repeating alarm, <var>periodInMinutes</var> is used as the default for
+    // <var>delayInMinutes</var>.
+    static void create(optional DOMString name, AlarmCreateInfo alarmInfo);
+
+    // Retrieves details about the specified alarm.
+    // |name|: The name of the alarm to get. Defaults to the empty string.
+    static void get(optional DOMString name, AlarmCallback callback);
+
+    // Gets an array of all the alarms.
+    static void getAll(AlarmListCallback callback);
+
+    // Clears the alarm with the given name.
+    // |name|: The name of the alarm to clear. Defaults to the empty string.
+    static void clear(optional DOMString name);
+
+    // Clears all alarms.
+    static void clearAll();
+  };
+
+  interface Events {
+    // Fired when an alarm has elapsed. Useful for event pages.
+    // |alarm|: The alarm that has elapsed.
+    static void onAlarm(Alarm alarm);
+  };
+};
diff --git a/chrome/common/extensions/api/api.gyp b/chrome/common/extensions/api/api.gyp
new file mode 100644
index 0000000..d2806d4
--- /dev/null
+++ b/chrome/common/extensions/api/api.gyp
@@ -0,0 +1,90 @@
+# Copyright (c) 2012 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.
+
+{
+  'targets': [
+    {
+      'target_name': 'api',
+      'type': 'static_library',
+      'sources': [
+        '<@(idl_schema_files)',
+        '<@(json_schema_files)',
+      ],
+      'includes': [
+        '../../../../build/json_schema_bundle_compile.gypi',
+        '../../../../build/json_schema_compile.gypi',
+      ],
+      'variables': {
+        'chromium_code': 1,
+        'json_schema_files': [
+          'bookmarks.json',
+          'cloud_print_private.json',
+          'content_settings.json',
+          'context_menus.json',
+          'cookies.json',
+          'debugger.json',
+          'events.json',
+          'experimental_history.json',
+          'experimental_record.json',
+          'file_browser_handler_internal.json',
+          'i18n.json',
+          'font_settings.json',
+          'history.json',
+          'management.json',
+          'page_capture.json',
+          'permissions.json',
+          'storage.json',
+          'tabs.json',
+          'web_navigation.json',
+          'web_request.json',
+          'windows.json',
+        ],
+        'idl_schema_files': [
+          'alarms.idl',
+          'app_current_window_internal.idl',
+          'app_runtime.idl',
+          'app_window.idl',
+          'bluetooth.idl',
+          'downloads.idl',
+          'experimental_discovery.idl',
+          'experimental_dns.idl',
+          'experimental_identity.idl',
+          'experimental_idltest.idl',
+          'experimental_media_galleries.idl',
+          'experimental_notification.idl',
+          'experimental_system_info_cpu.idl',
+          'experimental_system_info_memory.idl',
+          'experimental_system_info_storage.idl',
+          'file_system.idl',
+          'media_galleries.idl',
+          'media_galleries_private.idl',
+          'push_messaging.idl',
+          'rtc_private.idl',
+          'serial.idl',
+          'socket.idl',
+          'sync_file_system.idl',
+          'tab_capture.idl',
+          'usb.idl',
+        ],
+        'cc_dir': 'chrome/common/extensions/api',
+        'root_namespace': 'extensions::api',
+      },
+      'conditions': [
+        ['OS=="android"', {
+          'idl_schema_files!': [
+            'usb.idl',
+          ],
+        }],
+        ['OS!="chromeos"', {
+          'json_schema_files!': [
+            'file_browser_handler_internal.json',
+          ],
+          'idl_schema_files!': [
+            'rtc_private.idl',
+          ],
+        }],
+      ],
+    },
+  ],
+}
diff --git a/chrome/common/extensions/api/app.json b/chrome/common/extensions/api/app.json
new file mode 100644
index 0000000..52e9327
--- /dev/null
+++ b/chrome/common/extensions/api/app.json
@@ -0,0 +1,101 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "app",
+    "nodoc": true,
+    "unprivileged": true,
+    "matches": [ "<all_urls>" ],
+    "types": [
+      {
+        "id": "Details",
+        "description": "TODO (it's a manifest)",
+        "type": "object",
+        "properties": {},
+        "additionalProperties": { "type": "any" }
+      },
+      {
+        "id": "DOMWindow",
+        "type": "object",
+        "properties": {},
+        "additionalProperties": { "type": "any" }
+      }
+    ],
+    "functions": [
+      {
+        "name": "getIsInstalled",
+        "description": "TODO",
+        "type": "function",
+        "parameters": [],
+        "returns": {
+          "name": "isInstalled",
+          "description": "TODO",
+          "type": "boolean"
+        }
+      },
+      {
+        "name": "installState",
+        "description": "TODO",
+        "type": "function",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "type": "string",
+                "enum": [ "not_installed", "installed", "disabled" ]
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "runningState",
+        "description": "TODO",
+        "type": "function",
+        "parameters": [],
+        "returns": {
+          "type": "string",
+          "enum": [ "running", "cannot_run", "ready_to_run" ]
+        }
+      },
+      {
+        "name": "install",
+        "description": "TODO",
+        "type": "function",
+        "parameters": []
+      },
+      {
+        "name": "getDetails",
+        "description": "TODO",
+        "type": "function",
+        "parameters": [],
+        "returns": {
+          "$ref": "Details",
+          "optional": true,
+          "description": "TODO"
+        }
+      },
+      {
+        "name": "getDetailsForFrame",
+        "description": "TODO",
+        "type": "function",
+        "parameters": [
+          {
+            "name": "frame",
+            "description": "TODO",
+            "$ref": "DOMWindow"
+          }
+        ],
+        "returns": {
+          "$ref": "Details",
+          "optional": true,
+          "description": "TODO"
+        }
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/app_current_window_internal.idl b/chrome/common/extensions/api/app_current_window_internal.idl
new file mode 100644
index 0000000..2bf231b
--- /dev/null
+++ b/chrome/common/extensions/api/app_current_window_internal.idl
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 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.
+
+// This is used by the app window API internally to pass through messages to
+// the shell window.
+[internal, nodoc] namespace app.currentWindowInternal {
+  interface Functions {
+    static void focus();
+    static void minimize();
+    static void maximize();
+    static void restore();
+    static void drawAttention();
+    static void clearAttention();
+    static void show();
+    static void hide();
+  };
+};
diff --git a/chrome/common/extensions/api/app_runtime.idl b/chrome/common/extensions/api/app_runtime.idl
new file mode 100644
index 0000000..6e2c71a
--- /dev/null
+++ b/chrome/common/extensions/api/app_runtime.idl
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 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.
+
+namespace app.runtime {
+
+  callback NullCallback = void ();
+
+  // A WebIntents intent object.
+  [inline_doc] dictionary Intent {
+    // The WebIntent being invoked.
+    DOMString action;
+
+    // The MIME type of the data.
+    DOMString type;
+
+    // Data associated with the intent.
+    any data;
+
+    // Callback to be compatible with WebIntents.
+    NullCallback postResult;
+
+    // Callback to be compatible with WebIntents.
+    NullCallback postFailure;
+  };
+
+  // Optional data for the launch.
+  [inline_doc] dictionary LaunchData {
+    Intent intent;
+  };
+
+  interface Events {
+    // Fired when an app is launched from the launcher or in response to a web
+    // intent.
+    static void onLaunched(optional LaunchData launchData);
+
+    // Fired at Chrome startup to apps that were running when Chrome last shut
+    // down.
+    static void onRestarted();
+  };
+
+  dictionary IntentResponse {
+    // Identifies the intent.
+    long intentId;
+
+    // Was this intent successful? (i.e., postSuccess vs postFailure).
+    boolean success;
+
+    // Data associated with the intent response.
+    any data;
+  };
+
+  interface Functions {
+    // postIntentResponse is an internal method to responds to an intent
+    // previously sent to a packaged app. This is identified by intentId, and
+    // should only be invoked at most once per intentId.
+    [nodoc] static void postIntentResponse(IntentResponse intentResponse);
+  };
+};
diff --git a/chrome/common/extensions/api/app_window.idl b/chrome/common/extensions/api/app_window.idl
new file mode 100644
index 0000000..2d3037b
--- /dev/null
+++ b/chrome/common/extensions/api/app_window.idl
@@ -0,0 +1,144 @@
+// Copyright (c) 2012 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.
+
+namespace app.window {
+  dictionary CreateWindowOptions {
+    // Id to identify the window. This will be used to remember the size
+    // and position of the window and restore that geometry when a window
+    // with the same id (and no explicit size or position) is later opened.
+    DOMString? id;
+
+    // Default width of the window.
+    long? defaultWidth;
+
+    // Default height of the window.
+    long? defaultHeight;
+
+    // Default X coordinate of the window.
+    long? defaultLeft;
+
+    // Default Y coordinate of the window.
+    long? defaultTop;
+
+    // Width of the window.
+    long? width;
+
+    // Height of the window.
+    long? height;
+
+    // X coordinate of the window.
+    long? left;
+
+    // Y coordinate of the window.
+    long? top;
+
+    // Minimium width of the window.
+    long? minWidth;
+
+    // Minimum height of the window.
+    long? minHeight;
+
+    // Maximum width of the window.
+    long? maxWidth;
+
+    // Maximum height of the window.
+    long? maxHeight;
+
+    // Window type: 'shell' (the default) is the only currently supported value.
+    DOMString? type;
+
+    // Frame type: 'none' or 'chrome' (defaults to 'chrome').
+    DOMString? frame;
+
+    // If true, the window will be created in a hidden state. Call show() on
+    // the window to show it once it has been created. Defaults to false.
+    boolean? hidden;
+  };
+
+  callback CreateWindowCallback =
+      void ([instanceOf=AppWindow] object created_window);
+
+  dictionary Bounds {
+    long? left;
+    long? top;
+    long? width;
+    long? height;
+  };
+
+  dictionary AppWindow {
+    // Focus the window.
+    static void focus();
+
+    // Minimize the window.
+    static void minimize();
+
+    // Maximize the window.
+    static void maximize();
+
+    // Restore the window.
+    static void restore();
+
+    // Move the window to the position (|left|, |top|).
+    static void moveTo(long left, long top);
+
+    // Resize the window to |width|x|height| pixels in size.
+    static void resizeTo(long width, long height);
+
+    // Draw attention to the window.
+    static void drawAttention();
+
+    // Clear attention to the window.
+    static void clearAttention();
+
+    // Close the window.
+    static void close();
+
+    // Show the window. Does nothing if the window is already visible.
+    static void show();
+
+    // Hide the window. Does nothing if the window is already hidden.
+    static void hide();
+
+    // The JavaScript 'window' object for the created child.
+    [instanceOf=global] object contentWindow;
+  };
+
+  interface Functions {
+    // The size and position of a window can be specified in a number of
+    // different ways. The most simple option is not specifying anything at
+    // all, in which case a default size and platform dependent position will
+    // be used.
+    //
+    // Another option is to use the top/left and width/height properties,
+    // which will always put the window at the specified coordinates with the
+    // specified size.
+    //
+    // Yet another option is to give the window a (unique) id. This id is then
+    // used to remember the size and position of the window whenever it is
+    // moved or resized. This size and position is then used on subsequent
+    // opening of a window with the same id. The defaultLeft/defaultTop and
+    // defaultWidth/defaultHeight properties can be used to specify a position
+    // and size when no geometry has been stored for the window yet.
+    //
+    // You can also combine these various options, explicitly specifying for
+    // example the size while having the position be remembered or other
+    // combinations like that. Size and position are dealt with seperately,
+    // but individual coordinates are not. So if you specify a top (or left)
+    // coordinate, you should also specify a left (or top) coordinate, and
+    // similar for size.
+    //
+    // If you specify both a regular and a default value for the same option
+    // the regular value is the only one that takes effect.
+    static void create(DOMString url,
+                       optional CreateWindowOptions options,
+                       optional CreateWindowCallback callback);
+
+    // Returns an <a href="#type-AppWindow">AppWindow</a> object for the
+    // current script context (ie JavaScript 'window' object). This can also be
+    // called on a handle to a script context for another page, for example:
+    // otherWindow.chrome.app.window.current().
+    [nocompile] static AppWindow current();
+    [nocompile, nodoc] static void initializeAppWindow(object state);
+  };
+};
diff --git a/chrome/common/extensions/api/bluetooth.idl b/chrome/common/extensions/api/bluetooth.idl
new file mode 100644
index 0000000..9380e14
--- /dev/null
+++ b/chrome/common/extensions/api/bluetooth.idl
@@ -0,0 +1,237 @@
+// Copyright (c) 2012 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.
+
+// Bluetooth API.
+// TODO(bryeung): mark this API as ChromeOS only (see crbug.com/119398).
+
+namespace bluetooth {
+  dictionary Device {
+    // The address of the device, in the format 'XX:XX:XX:XX:XX:XX'.
+    DOMString address;
+
+    // The human-readable name of the device.
+    DOMString name;
+
+    // Indicates whether or not the device is paired with the system.
+    boolean paired;
+
+    // Indicates whether or not the device is bonded with the system. A device
+    // is bonded if it is paired and high-security link keys have been
+    // exchanged so that connections may be encrypted.
+    boolean bonded;
+
+    // Indicates whether the device is currently connected to the system.
+    boolean connected;
+  };
+
+  dictionary ServiceRecord {
+    // The name of the service.
+    DOMString name;
+
+    // The UUID of the service.
+    DOMString? uuid;
+  };
+
+  dictionary Socket {
+    Device device;
+    DOMString serviceUuid;
+    long id;
+  };
+
+  dictionary OutOfBandPairingData {
+    // Simple Pairing Hash C.
+    // Always 16 octets long.
+    ArrayBuffer hash;
+
+    // Simple Pairing Randomizer R.
+    // Always 16 octets long.
+    ArrayBuffer randomizer;
+  };
+
+  callback AddressCallback = void (DOMString result);
+  callback BooleanCallback = void (boolean result);
+  callback DataCallback = void (optional ArrayBuffer result);
+  callback DeviceCallback = void (Device device);
+  callback DevicesCallback = void (Device[] result);
+  callback NameCallback = void (DOMString result);
+  callback OutOfBandPairingDataCallback = void (OutOfBandPairingData data);
+  callback ResultCallback = void ();
+  callback ServicesCallback = void(ServiceRecord[] result);
+  callback SizeCallback = void (long result);
+  callback SocketCallback = void (Socket result);
+
+  // Options for the getDevices function. If neither |uuid| or |name| are
+  // provided, all devices known to the system are returned.
+  dictionary GetDevicesOptions {
+    // Only devices providing a service with a UUID that matches |uuid| will be
+    // returned.
+    DOMString? uuid;
+
+    // Only devices providing a service with a name that matches |name| will be
+    // returned.
+    DOMString? name;
+
+    // Called for each matching device.  Note that a service discovery request
+    // must be made to each non-matching device before it can be definitively
+    // excluded.  This can take some time.
+    DeviceCallback deviceCallback;
+  };
+
+  // Options for the getServices function.
+  dictionary GetServicesOptions {
+    // The address of the device to inquire about. |deviceAddress| should be
+    // in the format 'XX:XX:XX:XX:XX:XX'.
+    DOMString deviceAddress;
+  };
+
+  // Options for the connect function.
+  dictionary ConnectOptions {
+    // The connection is made to the device at |deviceAddress|.
+    // |deviceAddress| should be in the format 'XX:XX:XX:XX:XX:XX'.
+    DOMString deviceAddress;
+
+    // The connection is made to the service with UUID |serviceUuid|.
+    DOMString serviceUuid;
+  };
+
+  // Options for the disconnect function.
+  dictionary DisconnectOptions {
+    // The socket to disconnect.
+    long socketId;
+  };
+
+  // Options for the read function.
+  dictionary ReadOptions {
+    // The socket to read from.
+    long socketId;
+  };
+
+  // Options for the write function.
+  dictionary WriteOptions {
+    // The socket to write to.
+    long socketId;
+
+    // The data to write.
+    ArrayBuffer data;
+  };
+
+  // Options for the setOutOfBandPairingData function.
+  dictionary SetOutOfBandPairingDataOptions {
+    // The address of the remote device that the data should be associated
+    // with. |deviceAddress| should be in the format 'XX:XX:XX:XX:XX:XX'.
+    DOMString address;
+
+    // The Out Of Band Pairing Data. If this is omitted, the data for the
+    // device is cleared instead.
+    OutOfBandPairingData? data;
+  };
+
+  // Options for the startDiscovery function.
+  dictionary StartDiscoveryOptions {
+    // Called for each device that is discovered.
+    DeviceCallback deviceCallback;
+  };
+
+  // These functions all report failures via chrome.extension.lastError.
+  interface Functions {
+    // Checks if the system has Bluetooth support.
+    // |callback| : Called with the boolean result.
+    static void isAvailable(BooleanCallback callback);
+
+    // Checks if the system's Bluetooth module has power.
+    // |callback| : Called with the boolean result.
+    static void isPowered(BooleanCallback callback);
+
+    // Get the Bluetooth address of the system's Bluetooth module.
+    // |callback| : Called with the address of the Bluetooth adapter, or "" if
+    //              there is no adapater available.
+    static void getAddress(AddressCallback callback);
+
+    // Get the name of the Bluetooth adapter.
+    // |callback| : Called with the name of the Bluetooth adapter, or "" if
+    //              there is no adapater available.
+    static void getName(NameCallback callback);
+
+    // Get a bluetooth devices known to the system.  Known devices are either
+    // currently bonded, or have been bonded in the past.
+    // |options|  : Controls which devices are returned and provides
+    //              |deviceCallback|, which is called for each matching device.
+    // |callback| : Called when the search is completed.
+    //              |options.deviceCallback| will not be called after
+    //              |callback| has been called.
+    static void getDevices(GetDevicesOptions options,
+                           ResultCallback callback);
+
+    // Get a list of services provided by a device.
+    static void getServices(GetServicesOptions options,
+                            ServicesCallback callback);
+
+    // Connect to a service on a device.
+    // |options|  : The options for the connection.
+    // |callback| : Called when the connection is established with a Socket
+    //              that can be used to communicate with |device|.
+    static void connect(ConnectOptions options,
+                        SocketCallback callback);
+
+    // Close a Bluetooth connection.
+    // |options|  : The options for this function.
+    // |callback| : Called to indicate success or failure.
+    static void disconnect(DisconnectOptions options,
+                           optional ResultCallback callback);
+
+    // Read data from a Bluetooth connection.
+    // |options|  : The options for this function.
+    // |callback| : Called with the data when it is available.
+    static void read(ReadOptions options,
+                     DataCallback callback);
+
+    // Write data to a Bluetooth connection.
+    // |options|  : The options for this function.
+    // |callback| : Called with the number of bytes written.
+    static void write(WriteOptions options,
+                      optional SizeCallback callback);
+
+    // Get the local Out of Band Pairing data.
+    // |callback| : Called with the data.
+    static void getLocalOutOfBandPairingData(
+        OutOfBandPairingDataCallback callback);
+
+    // Set the Out of Band Pairing data for a remote device.
+    // Any previous Out Of Band Pairing Data for this device is overwritten.
+    // |options|  : The options for this function.
+    // |callback| : Called to indicate success or failure.
+    static void setOutOfBandPairingData(SetOutOfBandPairingDataOptions options,
+                                        optional ResultCallback callback);
+
+    // Start discovery. Discovered devices will be returned via the
+    // |onDeviceDiscovered| callback.  Discovery will fail to start if it is
+    // already in progress.  Discovery can be resource intensive: stopDiscovery
+    // should be called as soon as possible.
+    // |options|  : The options for this function.
+    // |callback| : Called to indicate success or failure.
+    static void startDiscovery(
+        StartDiscoveryOptions options,
+        optional ResultCallback callback);
+
+    // Stop discovery.
+    // |callback| : Called to indicate success or failure.
+    static void stopDiscovery(
+        optional ResultCallback callback);
+  };
+
+  interface Events {
+    // Fired when the availability of Bluetooth on the system changes.
+    // |available| : True if Bluetooth is available, false otherwise.
+    static void onAvailabilityChanged(boolean available);
+
+    // Fired when the power state of Bluetooth on the system changes.
+    // |powered| : True if Bluetooth is powered, false otherwise.
+    static void onPowerChanged(boolean has_power);
+
+    // Fired when the discovering state of the system changes.
+    // |discovering| : True if the system is currently in discovery mode, false
+    //                 otherwise.
+    static void onDiscoveringChanged(boolean discovering);
+  };
+};
diff --git a/chrome/common/extensions/api/bookmark_manager_private.json b/chrome/common/extensions/api/bookmark_manager_private.json
new file mode 100644
index 0000000..c6a35f8
--- /dev/null
+++ b/chrome/common/extensions/api/bookmark_manager_private.json
@@ -0,0 +1,252 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "bookmarkManagerPrivate",
+    "nodoc": true,
+    "dependencies": [ "bookmarks" ],
+    "types": [
+      {
+        "id": "BookmarkNodeDataElement",
+        "nodoc": true,
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "string",
+            "optional": true,
+            "description": "The ID of the bookmark. This is only provided if the data is from the same profile."
+          },
+          "parentId": {
+            "type": "string",
+            "optional": true,
+            "description": "The ID of the parent of the bookmark. This is only provided if the data is from the same profile."
+          },
+          "title": {"type": "string"},
+          "url": {
+            "type": "string",
+            "optional": true
+          },
+          "children": {
+            "type": "array",
+            "items": {"$ref": "BookmarkNodeDataElement"}
+          }
+        }
+      },
+      {
+        "id": "BookmarkNodeData",
+        "nodoc": true,
+        "type": "object",
+        "description": "Information about the drag and drop data for use with drag and drop events.",
+        "properties": {
+          "sameProfile": {"type": "boolean"},
+          "elements": {
+            "type": "array",
+            "items": {"$ref": "BookmarkNodeDataElement"}
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "copy",
+        "type": "function",
+        "description": "Copies the given bookmarks into the clipboard",
+        "nodoc": "true",
+        "parameters": [
+          {
+            "name": "idList",
+            "description": "An array of string-valued ids",
+            "type": "array",
+            "items": {"type": "string"},
+            "minItems": 1
+          },
+          {"type": "function", "name": "callback", "optional": true, "parameters": []}
+        ]
+      },
+      {
+        "name": "cut",
+        "type": "function",
+        "description": "Cuts the given bookmarks into the clipboard",
+        "nodoc": "true",
+        "parameters": [
+          {
+            "name": "idList",
+            "description": "An array of string-valued ids",
+            "type": "array",
+            "items": {"type": "string"},
+            "minItems": 1
+          },
+          {"type": "function", "name": "callback", "optional": true, "parameters": []}
+        ]
+      },
+      {
+        "name": "paste",
+        "type": "function",
+        "description": "Pastes bookmarks from the clipboard into the parent folder after the last selected node",
+        "nodoc": "true",
+        "parameters": [
+          {"type": "string", "name": "parentId"},
+          {
+            "name": "selectedIdList",
+            "description": "An array of string-valued ids for selected bookmarks",
+            "optional": true,
+            "type": "array",
+            "items": {"type": "string"},
+            "minItems": 0
+          },
+          {"type": "function", "name": "callback", "optional": true, "parameters": []}
+        ]
+      },
+      {
+        "name": "canPaste",
+        "type": "function",
+        "description": "Whether there are any bookmarks that can be pasted",
+        "nodoc": "true",
+        "parameters": [
+          {"type": "string", "name": "parentId", "description": "The ID of the folder to paste into"},
+          {"type": "function", "name": "callback", "parameters": [
+            {"name": "result", "type": "boolean"}
+          ]}
+        ]
+      },
+      {
+        "name": "sortChildren",
+        "type": "function",
+        "description": "Sorts the children of a given folder",
+        "nodoc": "true",
+        "parameters": [
+          {"type": "string", "name": "parentId", "description": "The ID of the folder to sort the children of"}
+        ]
+      },
+      {
+        "name": "getStrings",
+        "type": "function",
+        "description": "Gets the i18n strings for the bookmark manager",
+        "nodoc": "true",
+        "parameters": [
+          {
+            "type": "function",
+             "name": "callback",
+             "parameters": [
+              {
+                "name": "result",
+                "type": "object",
+                "additionalProperties": {"type": "string"}
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "startDrag",
+        "type": "function",
+        "description": "Begins dragging a set of bookmarks",
+        "nodoc": "true",
+        "parameters": [
+          {
+            "name": "idList",
+            "description": "An array of string-valued ids",
+            "type": "array",
+            "items": {"type": "string"},
+            "minItems": 1
+          }
+        ]
+      },
+      {
+        "name": "drop",
+        "type": "function",
+        "description": "Performs the drop action of the drag and drop session",
+        "nodoc": "true",
+        "parameters": [
+          {
+            "name": "parentId",
+            "description": "The ID of the folder that the drop was made",
+            "type": "string"
+          },
+          {
+            "name": "index",
+            "description": "The index of the position to drop at. If left out the dropped items will be placed at the end of the existing children",
+            "type": "integer",
+            "minimum": 0,
+            "optional": true
+          }
+        ]
+      },
+      {
+        "name": "getSubtree",
+        "type": "function",
+        "description": "Retrieves a bookmark hierarchy from the given node.  If the node id is empty, it is the full tree.  If foldersOnly is true, it will only return folders, not actual bookmarks.",
+        "nodoc": "true",
+        "parameters": [
+          {
+            "name": "id",
+            "type": "string",
+            "description": "ID of the root of the tree to pull.  If empty, the entire tree will be returned."
+          },
+          {
+            "name": "foldersOnly",
+            "type": "boolean",
+            "description": "Pass true to only return folders."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {"name": "results", "type": "array", "items": { "$ref": "bookmarks.BookmarkTreeNode"} }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "canEdit",
+        "type": "function",
+        "description": "Whether bookmarks can be modified",
+        "nodoc": "true",
+        "parameters": [
+          {"type": "function", "name": "callback", "parameters": [
+            {"name": "result", "type": "boolean"}
+          ]}
+        ]
+      },
+      {
+        "name": "canOpenNewWindows",
+        "type": "function",
+        "description": "Whether bookmarks can be opened in new windows",
+        "nodoc": "true",
+        "parameters": [
+          {"type": "function", "name": "callback", "parameters": [
+            {"name": "result", "type": "boolean"}
+          ]}
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onDragEnter",
+        "type": "function",
+        "description": "Fired when dragging bookmarks over the document",
+        "parameters": [
+          {"name": "bookmarkNodeData", "$ref": "BookmarkNodeData"}
+        ]
+      },
+      {
+        "name": "onDragLeave",
+        "type": "function",
+        "description": "Fired when the drag and drop leaves the document",
+        "parameters": [
+          {"name": "bookmarkNodeData", "$ref": "BookmarkNodeData"}
+        ]
+      },
+      {
+        "name": "onDrop",
+        "type": "function",
+        "description": "Fired when the user drops bookmarks on the document",
+        "parameters": [
+          {"name": "bookmarkNodeData", "$ref": "BookmarkNodeData"}
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/bookmarks.json b/chrome/common/extensions/api/bookmarks.json
new file mode 100644
index 0000000..bad4bd3
--- /dev/null
+++ b/chrome/common/extensions/api/bookmarks.json
@@ -0,0 +1,516 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "bookmarks",
+    "uses_feature_system": true,
+    "channel": "stable",
+    "dependencies": ["permission:bookmarks"],
+    "contexts": ["blessed_extension"],
+    "properties": {
+      "MAX_WRITE_OPERATIONS_PER_HOUR": {
+        "value": 100,
+        "description": "The maximum number of <code>move</code>, <code>update</code>, <code>create</code>, or <code>remove</code> operations that can be performed each hour. Updates that would cause this limit to be exceeded fail."
+      },
+      "MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE": {
+        "value": 2,
+        "description": "The maximum number of <code>move</code>, <code>update</code>, <code>create</code>, or <code>remove</code> operations that can be performed each minute, sustained over 10 minutes. Updates that would cause this limit to be exceeded fail."
+      }
+    },
+    "types": [
+      {
+        "id": "BookmarkTreeNode",
+        "type": "object",
+        "description": "A node (either a bookmark or a folder) in the bookmark tree.  Child nodes are ordered within their parent folder.",
+        "properties": {
+          "id": {
+            "type": "string",
+            "minimum": 0,
+            "description": "The unique identifier for the node. IDs are unique within the current profile, and they remain valid even after the browser is restarted."
+          },
+          "parentId": {
+            "type": "string",
+            "minimum": 0,
+            "optional": true,
+            "description": "The <code>id</code> of the parent folder.  Omitted for the root node."
+          },
+          "index": {
+            "type": "integer",
+            "optional": true,
+            "description": "The 0-based position of this node within its parent folder."
+          },
+          "url": {
+            "type": "string",
+            "optional": true,
+            "description": "The URL navigated to when a user clicks the bookmark. Omitted for folders."
+          },
+          "title": {
+            "type": "string",
+            "description": "The text displayed for the node."
+          },
+          "dateAdded": {
+            "type": "number",
+            "optional": true,
+            "description": "When this node was created, in milliseconds since the epoch (<code>new Date(dateAdded)</code>)."
+          },
+          "dateGroupModified": {
+            "type": "number",
+            "optional": true,
+            "description": "When the contents of this folder last changed, in milliseconds since the epoch."
+          },
+          "children": {
+            "type": "array",
+            "optional": true,
+            "items": { "$ref": "BookmarkTreeNode" },
+            "description": "An ordered list of children of this node."
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "get",
+        "type": "function",
+        "description": "Retrieves the specified BookmarkTreeNode(s).",
+        "parameters": [
+          {
+            "name": "idOrIdList",
+            "description": "A single string-valued id, or an array of string-valued ids",
+            "choices": [
+              {
+                "type": "string",
+                "serialized_type": "int64"
+              },
+              {
+                "type": "array",
+                "items": {
+                  "type": "string",
+                  "serialized_type": "int64"
+                },
+                "minItems": 1
+              }
+            ]
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "results",
+                "type": "array",
+                "items": { "$ref": "BookmarkTreeNode" }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getChildren",
+        "type": "function",
+        "description": "Retrieves the children of the specified BookmarkTreeNode id.",
+        "parameters": [
+          {
+            "type": "string",
+            "serialized_type": "int64",
+            "name": "id"
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "results",
+                "type": "array",
+                "items": { "$ref": "BookmarkTreeNode"}
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getRecent",
+        "type": "function",
+        "description": "Retrieves the recently added bookmarks.",
+        "parameters": [
+          {
+            "type": "integer",
+            "minimum": 1,
+            "name": "numberOfItems",
+            "description": "The maximum number of items to return."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "results",
+                "type": "array",
+                "items": { "$ref": "BookmarkTreeNode" }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getTree",
+        "type": "function",
+        "description": "Retrieves the entire Bookmarks hierarchy.",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "results",
+                "type": "array",
+                "items": { "$ref": "BookmarkTreeNode" }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getSubTree",
+        "type": "function",
+        "description": "Retrieves part of the Bookmarks hierarchy, starting at the specified node.",
+        "parameters": [
+          {
+            "type": "string",
+            "serialized_type": "int64",
+            "name": "id",
+            "description": "The ID of the root of the subtree to retrieve."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "results",
+                "type": "array",
+                "items": { "$ref": "BookmarkTreeNode" }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "search",
+        "type": "function",
+        "description": "Searches for BookmarkTreeNodes matching the given query.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "query"
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "results",
+                "type": "array",
+                "items": { "$ref": "BookmarkTreeNode" }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "create",
+        "type": "function",
+        "description": "Creates a bookmark or folder under the specified parentId.  If url is NULL or missing, it will be a folder.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "bookmark",
+            "properties": {
+              "parentId": {
+                "type": "string",
+                "serialized_type": "int64",
+                "optional": true,
+                "description": "Defaults to the Other Bookmarks folder."
+              },
+              "index": {
+                "type": "integer",
+                "minimum": 0,
+                "optional": true
+              },
+              "title": {
+                "type": "string",
+                "optional": true
+              },
+              "url": {
+                "type": "string",
+                "optional": true
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "result",
+                "$ref": "BookmarkTreeNode"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "move",
+        "type": "function",
+        "description": "Moves the specified BookmarkTreeNode to the provided location.",
+        "parameters": [
+          {
+            "type": "string",
+            "serialized_type": "int64",
+            "name": "id"
+          },
+          {
+            "type": "object",
+            "name": "destination",
+            "properties": {
+              "parentId": {
+                "type": "string",
+                "optional": true
+              },
+              "index": {
+                "type": "integer",
+                "minimum": 0,
+                "optional": true
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "result",
+                "$ref": "BookmarkTreeNode"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "update",
+        "type": "function",
+        "description": "Updates the properties of a bookmark or folder. Specify only the properties that you want to change; unspecified properties will be left unchanged.  <b>Note:</b> Currently, only 'title' and 'url' are supported.",
+        "parameters": [
+          {
+            "type": "string",
+            "serialized_type": "int64",
+            "name": "id"
+          },
+          {
+            "type": "object",
+            "name": "changes",
+            "properties": {
+              "title": {
+                "type": "string",
+                "optional": true
+              },
+              "url": {
+                "type": "string",
+                "optional": true
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "result",
+                "$ref": "BookmarkTreeNode"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "remove",
+        "type": "function",
+        "description": "Removes a bookmark or an empty bookmark folder.",
+        "parameters": [
+          {
+            "type": "string",
+            "serialized_type": "int64",
+            "name": "id"
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeTree",
+        "type": "function",
+        "description": "Recursively removes a bookmark folder.",
+        "parameters": [
+          {
+            "type": "string",
+            "serialized_type": "int64",
+            "name": "id"
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "import",
+        "type": "function",
+        "description": "Imports bookmarks from a chrome html bookmark file",
+        "nodoc": "true",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "export",
+        "type": "function",
+        "description": "Exports bookmarks to a chrome html bookmark file",
+        "nodoc": "true",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onCreated",
+        "type": "function",
+        "description": "Fired when a bookmark or folder is created.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "id"
+          },
+          {
+            "$ref": "BookmarkTreeNode",
+            "name": "bookmark"
+          }
+        ]
+      },
+      {
+        "name": "onRemoved",
+        "type": "function",
+        "description": "Fired when a bookmark or folder is removed.  When a folder is removed recursively, a single notification is fired for the folder, and none for its contents.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "id"
+          },
+          {
+            "type": "object",
+            "name": "removeInfo",
+            "properties": {
+              "parentId": { "type": "string" },
+              "index": { "type": "integer" }
+            }
+          }
+        ]
+      },
+      {
+        "name": "onChanged",
+        "type": "function",
+        "description": "Fired when a bookmark or folder changes.  <b>Note:</b> Currently, only title and url changes trigger this.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "id"
+          },
+          {
+            "type": "object",
+            "name": "changeInfo",
+            "properties": {
+              "title": { "type": "string" },
+              "url": {
+                "type": "string",
+                "optional": true
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "onMoved",
+        "type": "function",
+        "description": "Fired when a bookmark or folder is moved to a different parent folder.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "id"
+          },
+          {
+            "type": "object",
+            "name": "moveInfo",
+            "properties": {
+              "parentId": { "type": "string" },
+              "index": { "type": "integer" },
+              "oldParentId": { "type": "string" },
+              "oldIndex": { "type": "integer" }
+            }
+          }
+        ]
+      },
+      {
+        "name": "onChildrenReordered",
+        "type": "function",
+        "description": "Fired when the children of a folder have changed their order due to the order being sorted in the UI.  This is not called as a result of a move().",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "id"
+          },
+          {
+            "type": "object",
+            "name": "reorderInfo",
+            "properties": {
+              "childIds": {
+                "type": "array",
+                "items": { "type": "string" }
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "onImportBegan",
+        "type": "function",
+        "description": "Fired when a bookmark import session is begun.  Expensive observers should ignore handleCreated updates until onImportEnded is fired.  Observers should still handle other notifications immediately.",
+        "parameters": []
+      },
+      {
+        "name": "onImportEnded",
+        "type": "function",
+        "description": "Fired when a bookmark import session is ended.",
+        "parameters": []
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/browser_action.json b/chrome/common/extensions/api/browser_action.json
new file mode 100644
index 0000000..0b30073
--- /dev/null
+++ b/chrome/common/extensions/api/browser_action.json
@@ -0,0 +1,329 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "browserAction",
+    "dependencies": [ "tabs" ],
+    "types": [
+      {
+        "id": "ColorArray",
+        "type": "array",
+        "items": {
+          "type": "integer",
+          "minimum": 0,
+          "maximum": 255
+        },
+        "minItems": 4,
+        "maxItems": 4
+      },
+      {
+        "id": "ImageDataType",
+        "type": "object",
+        "isInstanceOf": "ImageData",
+        "additionalProperties": { "type": "any" },
+        "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)."
+      }
+    ],
+    "functions": [
+      {
+        "name": "setTitle",
+        "type": "function",
+        "description": "Sets the title of the browser action. This shows up in the tooltip.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "title": {
+                "type": "string",
+                "description": "The string the browser action should display when moused over."
+              },
+              "tabId": {
+                "type": "integer",
+                "optional": true,
+                "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "getTitle",
+        "type": "function",
+        "description": "Gets the title of the browser action.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {
+                "type": "integer",
+                "optional": true,
+                "description": "Specify the tab to get the title from. If no tab is specified, the non-tab-specific title is returned."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "string"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setIcon",
+        "type": "function",
+        "description": "Sets the icon for the browser action. The icon can be specified either as the path to an image file or as the pixel data from a canvas element, or as dictionary of either one of those. Either the <b>path</b> or the <b>imageData</b> property must be specified.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "imageData": {
+                "choices": [
+                  { "$ref": "ImageDataType" },
+                  {
+                    "type": "object",
+                    "properties": {
+                      "19": {"$ref": "ImageDataType", "optional": true},
+                      "38": {"$ref": "ImageDataType", "optional": true}
+                     }
+                  }
+                ],
+                "optional": true,
+                "description": "Either an ImageData object or a dictionary {size -> ImageData} representing icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.imageData = foo' is equivalent to 'details.imageData = {'19': foo}'"
+              },
+              "path": {
+                "choices": [
+                  { "type": "string" },
+                  {
+                    "type": "object",
+                    "properties": {
+                      "19": {"type": "string", "optional": true},
+                      "38": {"type": "string", "optional": true}
+                    }
+                  }
+                ],
+                "optional": true,
+                "description": "Either a relative image path or a dictionary {size -> relative image path} pointing to icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.path = foo' is equivalent to 'details.imageData = {'19': foo}'"
+              },
+              "tabId": {
+                "type": "integer",
+                "optional": true,
+                "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "setPopup",
+        "type": "function",
+        "description": "Sets the html document to be opened as a popup when the user clicks on the browser action's icon.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {
+                "type": "integer",
+                "optional": true,
+                "minimum": 0,
+                "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
+              },
+              "popup": {
+                "type": "string",
+                "description": "The html file to show in a popup.  If set to the empty string (''), no popup is shown."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "getPopup",
+        "type": "function",
+        "description": "Gets the html document set as the popup for this browser action.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {
+                "type": "integer",
+                "optional": true,
+                "description": "Specify the tab to get the popup from. If no tab is specified, the non-tab-specific popup is returned."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "string"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setBadgeText",
+        "type": "function",
+        "description": "Sets the badge text for the browser action. The badge is displayed on top of the icon.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "text": {
+                "type": "string",
+                "description": "Any number of characters can be passed, but only about four can fit in the space."
+              },
+              "tabId": {
+                "type": "integer",
+                "optional": true,
+                "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "getBadgeText",
+        "type": "function",
+        "description": "Gets the badge text of the browser action. If no tab is specified, the non-tab-specific badge text is returned.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {
+                "type": "integer",
+                "optional": true,
+                "description": "Specify the tab to get the badge text from. If no tab is specified, the non-tab-specific badge text is returned."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "string"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setBadgeBackgroundColor",
+        "type": "function",
+        "description": "Sets the background color for the badge.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "color": {
+                "description": "An array of four integers in the range [0,255] that make up the RGBA color of the badge. For example, opaque red is <code>[255, 0, 0, 255]</code>. Can also be a string with a CSS value, with opaque red being <code>#FF0000</code> or <code>#F00</code>.",
+                "choices": [
+                  {"type": "string"},
+                  {"$ref": "ColorArray"}
+                ]
+              },
+              "tabId": {
+                "type": "integer",
+                "optional": true,
+                "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "getBadgeBackgroundColor",
+        "type": "function",
+        "description": "Gets the background color of the browser action.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {
+                "type": "integer",
+                "optional": true,
+                "description": "Specify the tab to get the badge background color from. If no tab is specified, the non-tab-specific badge background color is returned."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "result",
+                "$ref": "ColorArray"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "enable",
+        "type": "function",
+        "description": "Enables the browser action for a tab. By default, browser actions are enabled.",
+        "parameters": [
+          {
+            "type": "integer",
+            "optional": true,
+            "name": "tabId",
+            "minimum": 0,
+            "description": "The id of the tab for which you want to modify the browser action."
+          }
+        ]
+      },
+      {
+        "name": "disable",
+        "type": "function",
+        "description": "Disables the browser action for a tab.",
+        "parameters": [
+          {
+            "type": "integer",
+            "optional": true,
+            "name": "tabId",
+            "minimum": 0,
+            "description": "The id of the tab for which you want to modify the browser action."
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onClicked",
+        "type": "function",
+        "description": "Fired when a browser action icon is clicked.  This event will not fire if the browser action has a popup.",
+        "parameters": [
+          {
+            "name": "tab",
+            "$ref": "tabs.Tab"
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/browsing_data.json b/chrome/common/extensions/api/browsing_data.json
new file mode 100644
index 0000000..66ff45e
--- /dev/null
+++ b/chrome/common/extensions/api/browsing_data.json
@@ -0,0 +1,353 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "browsingData",
+    "types": [
+      {
+        "id": "RemovalOptions",
+        "type": "object",
+        "description": "Options that determine exactly what data will be removed.",
+        "properties": {
+          "since": {
+            "type": "number",
+            "optional": true,
+            "description": "Remove data accumulated on or after this date, represented in milliseconds since the epoch (accessible via the <code>getTime</code> method of the JavaScript <code>Date</code> object). If absent, defaults to 0 (which would remove all browsing data)."
+          },
+          "originTypes": {
+            "type": "object",
+            "optional": true,
+            "description": "An object whose properties specify which origin types ought to be cleared. If this object isn't specified, it defaults to clearing only \"unprotected\" origins. Please ensure that you <em>really</em> want to remove application data before adding 'protectedWeb' or 'extensions'.",
+            "properties": {
+              "unprotectedWeb": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Normal websites."
+              },
+              "protectedWeb": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Websites that have been installed as hosted applications (be careful!)."
+              },
+              "extension": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Extensions and packaged applications a user has installed (be _really_ careful!)."
+              }
+            }
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "remove",
+        "description": "Clears various types of browsing data stored in a user's profile.",
+        "type": "function",
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "dataToRemove",
+            "type": "object",
+            "description": "An object whose properties specify which browsing data types ought to be cleared. You may set as many or as few as you like in a single call, each is optional (defaulting to <code>false</code>).",
+            "properties": {
+              "appcache": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Should websites' appcaches be cleared?"
+              },
+              "cache": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Should the browser's cache be cleared? Note: this clears the <em>entire</em> cache: it is not limited to the range you specify."
+              },
+              "cookies": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Should the browser's cookies be cleared?"
+              },
+              "downloads": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Should the browser's download list be cleared?"
+              },
+              "fileSystems": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Should websites' file systems be cleared?"
+              },
+              "formData": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Should the browser's stored form data be cleared?"
+              },
+              "history": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Should the browser's history be cleared?"
+              },
+              "indexedDB": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Should websites' IndexedDB data be cleared?"
+              },
+              "localStorage": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Should websites' local storage data be cleared?"
+              },
+              "serverBoundCertificates": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Should server-bound certificates be removed?"
+              },
+              "pluginData": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Should plugins' data be cleared?"
+              },
+              "passwords": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Should the stored passwords be cleared?"
+              },
+              "webSQL": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Should websites' WebSQL data be cleared?"
+              }
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when deletion has completed.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeAppcache",
+        "description": "Clears websites' appcache data.",
+        "type": "function",
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when websites' appcache data has been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeCache",
+        "description": "Clears the browser's cache.",
+        "type": "function",
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the browser's cache has been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeCookies",
+        "description": "Clears the browser's cookies and server-bound certificates modified within a particular timeframe.",
+        "type": "function",
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the browser's cookies and server-bound certificates have been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeDownloads",
+        "description": "Clears the browser's list of downloaded files (<em>not</em> the downloaded files themselves).",
+        "type": "function",
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the browser's list of downloaded files has been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeFileSystems",
+        "description": "Clears websites' file system data.",
+        "type": "function",
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when websites' file systems have been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeFormData",
+        "description": "Clears the browser's stored form data (autofill).",
+        "type": "function",
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the browser's form data has been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeHistory",
+        "description": "Clears the browser's history.",
+        "type": "function",
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the browser's history has cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeIndexedDB",
+        "description": "Clears websites' IndexedDB data.",
+        "type": "function",
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when websites' IndexedDB data has been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeLocalStorage",
+        "description": "Clears websites' local storage data.",
+        "type": "function",
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when websites' local storage has been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removePluginData",
+        "description": "Clears plugins' data.",
+        "type": "function",
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when plugins' data has been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removePasswords",
+        "description": "Clears the browser's stored passwords.",
+        "type": "function",
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the browser's passwords have been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "removeWebSQL",
+        "description": "Clears websites' WebSQL data.",
+        "type": "function",
+        "parameters": [
+          {
+            "$ref": "RemovalOptions",
+            "name": "options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when websites' WebSQL databases have been cleared.",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/chromeos_info_private.json b/chrome/common/extensions/api/chromeos_info_private.json
new file mode 100644
index 0000000..9e7038f
--- /dev/null
+++ b/chrome/common/extensions/api/chromeos_info_private.json
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "chromeosInfoPrivate",
+    "nodoc": "true",
+    "functions": [
+      {
+        "name": "get",
+        "description": "Fetches customization values for the given property names. See property names in the declaration of the returned dictionary.",
+        "type": "function",
+        "parameters": [
+          {
+            "name": "propertyNames",
+            "type": "array",
+            "description": "Chrome OS Property names",
+            "items": {"type": "string"}
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name": "propertiesDictionary",
+                "type": "object",
+                "description": "Dictionary which contains all requested properties",
+                "properties": {
+                  "hwid": {"type": "string", "optional": "true", "description": "Hardware ID"},
+                  "homeProvider" : {"type": "string", "optional": "true", "description": "Home provider which is used by the cellular device"},
+                  "initialLocale" : {"type": "string", "optional": "true", "description": "Initial locale for the device"},
+                  "board" : {"type": "string", "optional": "true", "description": "Board name"},
+                  "isOwner" : {"type": "boolean", "optional": "true", "description": "True if current logged in user is device owner"}
+                }
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/cloud_print_private.json b/chrome/common/extensions/api/cloud_print_private.json
new file mode 100644
index 0000000..cf7714c
--- /dev/null
+++ b/chrome/common/extensions/api/cloud_print_private.json
@@ -0,0 +1,84 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "cloudPrintPrivate",
+    "nodoc": "true",
+    "functions": [
+      {
+        "name": "setupConnector",
+        "description": "Setup Cloud Print Connector.",
+        "type": "function",
+        "parameters": [
+          {
+            "name": "userEmail",
+            "type": "string",
+            "description": "The email address of the user."
+          },
+          {
+            "name": "robotEmail",
+            "type": "string",
+            "description": "The email address of the robot account."
+          },
+          {
+            "name": "credentials",
+            "type": "string",
+            "description": "The login credentials(OAuth2 Auth code)."
+          },
+          {
+            "name": "connectNewPrinters",
+            "type": "boolean",
+            "description": "True if new printers should be connected."
+          },
+          {
+            "name": "printerBlacklist",
+            "description": "Printers that should not be connected.",
+            "type": "array",
+            "items": {"type": "string"}
+          }
+        ]
+      },
+      {
+        "name": "getHostName",
+        "description": "Returns local hostname.",
+        "type": "function",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called to return host name.",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "string",
+                "description": "Host name."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getPrinters",
+        "description": "Returns local printers.",
+        "type": "function",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called to return printers.",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "array",
+                "items": {"type": "string"},
+                "description": "List of printer names."
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/commands.json b/chrome/common/extensions/api/commands.json
new file mode 100644
index 0000000..b064c9a
--- /dev/null
+++ b/chrome/common/extensions/api/commands.json
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "commands",
+    "types": [
+      {
+        "id": "Command",
+        "type": "object",
+        "properties": {
+          "name":        {
+            "type": "string",
+            "optional": true,
+            "description": "The name of the Extension Command"
+          },
+          "description": {
+            "type": "string",
+            "optional": true,
+            "description": "The Extension Command description"
+          },
+          "shortcut": {
+            "type": "string",
+            "optional": true,
+            "description": "The shortcut active for this command, or blank if not active."
+          }
+        }
+      }
+    ],
+    "events": [
+      {
+        "name": "onCommand",
+        "description": "Fired when a registered command is activated using a keyboard shortcut.",
+        "type": "function",
+        "parameters": [
+          {
+            "name": "command",
+            "type": "string"
+          }
+        ]
+      }
+    ],
+    "functions": [
+      {
+        "name": "getAll",
+        "type": "function",
+        "description": "Returns all the registered extension commands for this extension and their shortcut (if active).",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "commands",
+                "type": "array",
+                "items": {
+                  "$ref": "Command"
+                }
+              }
+            ],
+            "description": "Called to return the registered commands."
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/content_settings.json b/chrome/common/extensions/api/content_settings.json
new file mode 100644
index 0000000..2d6e803
--- /dev/null
+++ b/chrome/common/extensions/api/content_settings.json
@@ -0,0 +1,221 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "contentSettings",
+    "types": [
+      {
+        "id": "ResourceIdentifier",
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "string",
+            "description": "The resource identifier for the given content type."
+          },
+          "description": {
+            "type": "string",
+            "optional": true,
+            "description": "A human readable description of the resource."
+          }
+        },
+        "description": "The only content type using resource identifiers is <a href=\"contentSettings.html#property-plugins\"><var>plugins</var></a>. For more information, see <a href=\"contentSettings.html#resource-identifiers\">Resource Identifiers</a>."
+      },
+      {
+        "id": "ContentSetting",
+        "type": "object",
+        "functions": [
+          {
+            "name": "clear",
+            "type": "function",
+            "description": "Clear all content setting rules set by this extension.",
+            "parameters": [
+              {
+                "name": "details",
+                "type": "object",
+                "properties": {
+                  "scope": {
+                    "type": "string",
+                    "enum": ["regular", "incognito_session_only"],
+                    "optional": true,
+                    "description": "Where to set the setting (default: regular). One of<br><var>regular</var>: setting for regular profile (which is inherited by the incognito profile if not overridden elsewhere),<br><var>incognito_session_only</var>: setting for incognito profile that can only be set during an incognito session and is deleted when the incognito session ends (overrides regular settings)."
+                  }
+                }
+              },
+              {
+                "type": "function",
+                "name": "callback",
+                "optional": true,
+                "parameters": []
+              }
+            ]
+          },
+          {
+            "name": "get",
+            "type": "function",
+            "description": "Gets the current content setting for a given pair of URLs.",
+            "parameters": [
+              {
+                "name": "details",
+                "type": "object",
+                "properties": {
+                  "primaryUrl": {
+                    "type": "string",
+                    "description": "The primary URL for which the content setting should be retrieved. Note that the meaning of a primary URL depends on the content type."
+                  },
+                  "secondaryUrl": {
+                    "type": "string",
+                    "description": "The secondary URL for which the content setting should be retrieved. Defaults to the primary URL. Note that the meaning of a secondary URL depends on the content type, and not all content types use secondary URLs.",
+                    "optional": true
+                  },
+                  "resourceIdentifier": {
+                    "$ref": "ResourceIdentifier",
+                    "optional": true,
+                    "description": "A more specific identifier of the type of content for which the settings should be retrieved."
+                  },
+                  "incognito": {
+                    "type": "boolean",
+                    "optional": true,
+                    "description": "Whether to check the content settings for an incognito session. (default false)"
+                  }
+                }
+              },
+              {
+                "type": "function",
+                "name": "callback",
+                "parameters": [
+                  {
+                    "name": "details",
+                    "type": "object",
+                    "properties": {
+                      "setting": {
+                        "type": "any",
+                        "description": "The content setting. See the description of the individual ContentSetting objects for the possible values."
+                      }
+                    }
+                  }
+                ]
+              }
+            ]
+          },
+          {
+            "name": "set",
+            "type": "function",
+            "description": "Applies a new content setting rule.",
+            "parameters": [
+              {
+                "name": "details",
+                "type": "object",
+                "properties": {
+                  "primaryPattern": {
+                    "type": "string",
+                    "description": "The pattern for the primary URL. For details on the format of a pattern, see <a href='contentSettings.html#patterns'>Content Setting Patterns</a>."
+                  },
+                  "secondaryPattern": {
+                    "type": "string",
+                    "description": "The pattern for the secondary URL. Defaults to matching all URLs. For details on the format of a pattern, see <a href='contentSettings.html#patterns'>Content Setting Patterns</a>.",
+                    "optional": true
+                  },
+                  "resourceIdentifier": {
+                    "$ref": "ResourceIdentifier",
+                    "optional": true,
+                    "description": "The resource identifier for the content type."
+                  },
+                  "setting": {
+                    "type": "any",
+                    "description": "The setting applied by this rule. See the description of the individual ContentSetting objects for the possible values."
+                  },
+                  "scope": {
+                    "type": "string",
+                    "enum": ["regular", "incognito_session_only"],
+                    "optional": true,
+                    "description": "Where to clear the setting (default: regular). One of<br><var>regular</var>: setting for regular profile (which is inherited by the incognito profile if not overridden elsewhere),<br><var>incognito_session_only</var>: setting for incognito profile that can only be set during an incognito session and is deleted when the incognito session ends (overrides regular settings)."
+                  }
+                }
+              },
+              {
+                "type": "function",
+                "name": "callback",
+                "optional": true,
+                "parameters": []
+              }
+            ]
+          },
+          {
+            "name": "getResourceIdentifiers",
+            "type": "function",
+            "description": "",
+            "parameters": [
+              {
+                "name": "callback",
+                "type": "function",
+                "parameters": [
+                  {
+                    "name": "resourceIdentifiers",
+                    "type": "array",
+                    "description": "A list of resource identifiers for this content type, or <var>undefined</var> if this content type does not use resource identifiers.",
+                    "optional": true,
+                    "items": {
+                      "$ref": "ResourceIdentifier"
+                    }
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "properties": {
+      "cookies": {
+        "$ref": "ContentSetting",
+        "description": "Whether to allow cookies and other local data to be set by websites. One of<br><var>allow</var>: Accept cookies,<br><var>block</var>: Block cookies,<br><var>session_only</var>: Accept cookies only for the current session. <br>Default is <var>allow</var>.<br>The primary URL is the URL representing the cookie origin. The secondary URL is the URL of the top-level frame.",
+        "value": [
+          "cookies",
+          {"type":"string", "enum": ["allow", "block", "session_only"]}
+        ]
+      },
+      "images": {
+        "$ref": "ContentSetting",
+        "description": "Whether to show images. One of<br><var>allow</var>: Show images,<br><var>block</var>: Don't show images. <br>Default is <var>allow</var>.<br>The primary URL is the main-frame URL. The secondary URL is the URL of the image.",
+        "value": [
+          "images",
+          {"type":"string", "enum": ["allow", "block"]}
+        ]
+      },
+      "javascript": {
+        "$ref": "ContentSetting",
+        "description": "Whether to run JavaScript. One of<br><var>allow</var>: Run JavaScript,<br><var>block</var>: Don't run JavaScript. <br>Default is <var>allow</var>.<br>The primary URL is the main-frame URL. The secondary URL is not used.",
+        "value": [
+          "javascript",
+          {"type":"string", "enum": ["allow", "block"]}
+        ]
+      },
+      "plugins": {
+        "$ref": "ContentSetting",
+        "description": "Whether to run plug-ins. One of<br><var>allow</var>: Run plug-ins automatically,<br><var>block</var>: Don't run plug-ins automatically. <br>Default is <var>allow</var>.<br>The primary URL is the main-frame URL. The secondary URL is not used.",
+        "value": [
+          "plugins",
+          {"type":"string", "enum": ["allow", "block"]}
+        ]
+      },
+      "popups": {
+        "$ref": "ContentSetting",
+        "description": "Whether to allow sites to show pop-ups. One of<br><var>allow</var>: Allow sites to show pop-ups,<br><var>block</var>: Don't allow sites to show pop-ups. <br>Default is <var>block</var>.<br>The primary URL is the main-frame URL. The secondary URL is not used.",
+        "value": [
+          "popups",
+          {"type":"string", "enum": ["allow", "block"]}
+        ]
+      },
+      "notifications": {
+        "$ref": "ContentSetting",
+        "description": "Whether to allow sites to show desktop notifications. One of<br><var>allow</var>: Allow sites to show desktop notifications,<br><var>block</var>: Don't allow sites to show desktop notifications,<br><var>ask</var>: Ask when a site wants to show desktop notifications. <br>Default is <var>ask</var>.<br>The primary URL is the main-frame URL. The secondary URL is not used.",
+        "value": [
+          "notifications",
+          {"type":"string", "enum": ["allow", "block", "ask"]}
+        ]
+      }
+    }
+  }
+]
diff --git a/chrome/common/extensions/api/context_menus.json b/chrome/common/extensions/api/context_menus.json
new file mode 100644
index 0000000..c4113ea
--- /dev/null
+++ b/chrome/common/extensions/api/context_menus.json
@@ -0,0 +1,313 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "contextMenus",
+    "dependencies": [ "tabs" ],
+    "types": [
+      {
+        "id": "OnClickData",
+        "type": "object",
+        "description": "Information sent when a context menu item is clicked.",
+        "properties": {
+          "menuItemId": {
+            "choices": [
+              { "type": "integer" },
+              { "type": "string" }
+            ],
+            "description": "The ID of the menu item that was clicked."
+          },
+          "parentMenuItemId": {
+            "choices": [
+              { "type": "integer" },
+              { "type": "string" }
+            ],
+            "optional": true,
+            "description": "The parent ID, if any, for the item clicked."
+          },
+          "mediaType": {
+            "type": "string",
+            "optional": true,
+            "description": "One of 'image', 'video', or 'audio' if the context menu was activated on one of these types of elements."
+          },
+          "linkUrl": {
+            "type": "string",
+            "optional": true,
+            "description": "If the element is a link, the URL it points to."
+          },
+          "srcUrl": {
+            "type": "string",
+            "optional": true,
+            "description": "Will be present for elements with a 'src' URL."
+          },
+          "pageUrl": {
+            "type": "string",
+            "optional": true,
+            "description": "The URL of the page where the menu item was clicked. This property is not set if the click occured in a context where there is no current page, such as in a launcher context menu."
+          },
+          "frameUrl": {
+            "type": "string",
+            "optional": true,
+            "description": " The URL of the frame of the element where the context menu was clicked, if it was in a frame."
+          },
+          "selectionText": {
+            "type": "string",
+            "optional": true,
+            "description": "The text for the context selection, if any."
+          },
+          "editable": {
+            "type": "boolean",
+            "description": "A flag indicating whether the element is editable (text input, textarea, etc.)."
+          },
+          "wasChecked": {
+            "type": "boolean",
+            "optional": true,
+            "description": "A flag indicating the state of a checkbox or radio item before it was clicked."
+          },
+          "checked": {
+            "type": "boolean",
+            "optional": true,
+            "description": "A flag indicating the state of a checkbox or radio item after it is clicked."
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "create",
+        "type": "function",
+        "description": "Creates a new context menu item. Note that if an error occurs during creation, you may not find out until the creation callback fires (the details will be in chrome.extension.lastError).",
+        "returns": {
+          "choices": [
+            { "type": "integer" },
+            { "type": "string" }
+          ],
+          "description": "The ID of the newly created item."
+        },
+        "parameters": [
+          {
+            "type": "object",
+            "name": "createProperties",
+            "properties": {
+              "type": {
+                "type": "string",
+                "enum": ["normal", "checkbox", "radio", "separator"],
+                "optional": true,
+                "description": "The type of menu item. Defaults to 'normal' if not specified."
+              },
+              "id": {
+                "type": "string",
+                "optional": true,
+                "description": "The unique ID to assign to this item. Mandatory for event pages. Cannot be the same as another ID for this extension."
+              },
+              "title": {
+                "type": "string",
+                "optional": true,
+                "description": "The text to be displayed in the item; this is <em>required</em> unless <em>type</em> is 'separator'. When the context is 'selection', you can use <code>%s</code> within the string to show the selected text. For example, if this parameter's value is \"Translate '%s' to Pig Latin\" and the user selects the word \"cool\", the context menu item for the selection is \"Translate 'cool' to Pig Latin\"."
+              },
+              "checked": {
+                "type": "boolean",
+                "optional": true,
+                "description": "The initial state of a checkbox or radio item: true for selected and false for unselected. Only one radio item can be selected at a time in a given group of radio items."
+              },
+              "contexts": {
+                "type": "array",
+                "items": {
+                  "type": "string",
+                  "enum": ["all", "page", "frame", "selection", "link", "editable", "image", "video", "audio", "launcher"]
+                },
+                "minItems": 1,
+                "optional": true,
+                "description": "List of contexts this menu item will appear in. Defaults to ['page'] if not specified. Specifying ['all'] is equivalent to the combination of all other contexts except for 'launcher'. The 'launcher' context is only supported by apps and is used to add menu items to the context menu that appears when clicking on the app icon in the launcher/taskbar/dock/etc. Different platforms might put limitations on what is actually supported in a launcher context menu."
+              },
+              "onclick": {
+                "type": "function",
+                "optional": true,
+                "description": "A function that will be called back when the menu item is clicked. Event pages cannot use this; instead, they should register a listener for chrome.contextMenus.onClicked.",
+                "parameters": [
+                  {
+                    "name": "info",
+                    "$ref": "OnClickData",
+                    "description": "Information about the item clicked and the context where the click happened."
+                  },
+                  {
+                    "name": "tab",
+                    "$ref": "tabs.Tab",
+                    "description": "The details of the tab where the click took place."
+                  }
+                ]
+              },
+              "parentId": {
+                "choices": [
+                  { "type": "integer" },
+                  { "type": "string" }
+                ],
+                "optional": true,
+                "description": "The ID of a parent menu item; this makes the item a child of a previously added item."
+              },
+              "documentUrlPatterns": {
+                "type": "array",
+                "items": {"type": "string"},
+                "optional": true,
+                "description": "Lets you restrict the item to apply only to documents whose URL matches one of the given patterns. (This applies to frames as well.) For details on the format of a pattern, see <a href='match_patterns.html'>Match Patterns</a>."
+              },
+              "targetUrlPatterns": {
+                "type": "array",
+                "items": {"type": "string"},
+                "optional": true,
+                "description": "Similar to documentUrlPatterns, but lets you filter based on the src attribute of img/audio/video tags and the href of anchor tags."
+              },
+              "enabled": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether this context menu item is enabled or disabled. Defaults to true."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "description": "Called when the item has been created in the browser. If there were any problems creating the item, details will be available in chrome.extension.lastError.",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "update",
+        "type": "function",
+        "description": "Updates a previously created context menu item.",
+        "parameters": [
+          {
+            "choices": [
+              { "type": "integer" },
+              { "type": "string" }
+            ],
+            "name": "id",
+            "description": "The ID of the item to update."
+          },
+          {
+            "type": "object",
+            "name": "updateProperties",
+            "description": "The properties to update. Accepts the same values as the create function.",
+            "properties": {
+              "type": {
+                "type": "string",
+                "enum": ["normal", "checkbox", "radio", "separator"],
+                "optional": true
+              },
+              "title": {
+                "type": "string",
+                "optional": true
+              },
+              "checked": {
+                "type": "boolean",
+                "optional": true
+              },
+              "contexts": {
+                "type": "array",
+                "items": {
+                  "type": "string",
+                  "enum": ["all", "page", "frame", "selection", "link", "editable", "image", "video", "audio", "launcher"]
+                },
+                "minItems": 1,
+                "optional": true
+              },
+              "onclick": {
+                "type": "function",
+                "optional": true
+              },
+              "parentId": {
+                "choices": [
+                  { "type": "integer" },
+                  { "type": "string" }
+                ],
+                "optional": true,
+                "description": "Note: You cannot change an item to be a child of one of its own descendants."
+              },
+              "documentUrlPatterns": {
+                "type": "array",
+                "items": {"type": "string"},
+                "optional": true
+              },
+              "targetUrlPatterns": {
+                "type": "array",
+                "items": {"type": "string"},
+                "optional": true
+              },
+              "enabled": {
+                "type": "boolean",
+                "optional": true
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [],
+            "description": "Called when the context menu has been updated."
+          }
+        ]
+      },
+      {
+        "name": "remove",
+        "type": "function",
+        "description": "Removes a context menu item.",
+        "parameters": [
+          {
+            "choices": [
+              { "type": "integer" },
+              { "type": "string" }
+            ],
+            "name": "menuItemId",
+            "description": "The ID of the context menu item to remove."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [],
+            "description": "Called when the context menu has been removed."
+          }
+        ]
+      },
+      {
+        "name": "removeAll",
+        "type": "function",
+        "description": "Removes all context menu items added by this extension.",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [],
+            "description": "Called when removal is complete."
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onClicked",
+        "type": "function",
+        "description": "Fired when a context menu item is clicked.",
+        "parameters": [
+          {
+            "name": "info",
+            "$ref": "OnClickData",
+            "description": "Information about the item clicked and the context where the click happened."
+          },
+          {
+            "name": "tab",
+            "$ref": "tabs.Tab",
+            "description": "The details of the tab where the click took place. If the click did not take place in a tab, this parameter will be missing.",
+            "optional": true
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/cookies.json b/chrome/common/extensions/api/cookies.json
new file mode 100644
index 0000000..fd3a633
--- /dev/null
+++ b/chrome/common/extensions/api/cookies.json
@@ -0,0 +1,199 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "cookies",
+    "types": [
+      {
+        "id": "Cookie",
+        "type": "object",
+        "description": "Represents information about an HTTP cookie.",
+        "properties": {
+          "name": {"type": "string", "description": "The name of the cookie."},
+          "value": {"type": "string", "description": "The value of the cookie."},
+          "domain": {"type": "string", "description": "The domain of the cookie (e.g. \"www.google.com\", \"example.com\")."},
+          "hostOnly": {"type": "boolean", "description": "True if the cookie is a host-only cookie (i.e. a request's host must exactly match the domain of the cookie)."},
+          "path": {"type": "string", "description": "The path of the cookie."},
+          "secure": {"type": "boolean", "description": "True if the cookie is marked as Secure (i.e. its scope is limited to secure channels, typically HTTPS)."},
+          "httpOnly": {"type": "boolean", "description": "True if the cookie is marked as HttpOnly (i.e. the cookie is inaccessible to client-side scripts)."},
+          "session": {"type": "boolean", "description": "True if the cookie is a session cookie, as opposed to a persistent cookie with an expiration date."},
+          "expirationDate": {"type": "number", "optional": true, "description": "The expiration date of the cookie as the number of seconds since the UNIX epoch. Not provided for session cookies."},
+          "storeId": {"type": "string", "description": "The ID of the cookie store containing this cookie, as provided in getAllCookieStores()."}
+        }
+      },
+      {
+        "id": "CookieStore",
+        "type": "object",
+        "description": "Represents a cookie store in the browser. An incognito mode window, for instance, uses a separate cookie store from a non-incognito window.",
+        "properties": {
+          "id": {"type": "string", "description": "The unique identifier for the cookie store."},
+          "tabIds": {"type": "array", "items": {"type": "integer"}, "description": "Identifiers of all the browser tabs that share this cookie store."}
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "get",
+        "type": "function",
+        "description": "Retrieves information about a single cookie. If more than one cookie of the same name exists for the given URL, the one with the longest path will be returned. For cookies with the same path length, the cookie with the earliest creation time will be returned.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "description": "Details to identify the cookie being retrieved.",
+            "properties": {
+              "url": {"type": "string", "description": "The URL with which the cookie to retrieve is associated. This argument may be a full URL, in which case any data following the URL path (e.g. the query string) is simply ignored. If host permissions for this URL are not specified in the manifest file, the API call will fail."},
+              "name": {"type": "string", "description": "The name of the cookie to retrieve."},
+              "storeId": {"type": "string", "optional": true, "description": "The ID of the cookie store in which to look for the cookie. By default, the current execution context's cookie store will be used."}
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "cookie", "$ref": "Cookie", "optional": true, "description": "Contains details about the cookie. This parameter is null if no such cookie was found."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getAll",
+        "type": "function",
+        "description": "Retrieves all cookies from a single cookie store that match the given information.  The cookies returned will be sorted, with those with the longest path first.  If multiple cookies have the same path length, those with the earliest creation time will be first.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "description": "Information to filter the cookies being retrieved.",
+            "properties": {
+              "url": {"type": "string", "optional": true, "description": "Restricts the retrieved cookies to those that would match the given URL."},
+              "name": {"type": "string", "optional": true, "description": "Filters the cookies by name."},
+              "domain": {"type": "string", "optional": true, "description": "Restricts the retrieved cookies to those whose domains match or are subdomains of this one."},
+              "path": {"type": "string", "optional": true, "description": "Restricts the retrieved cookies to those whose path exactly matches this string."},
+              "secure": {"type": "boolean", "optional": true, "description": "Filters the cookies by their Secure property."},
+              "session": {"type": "boolean", "optional": true, "description": "Filters out session vs. persistent cookies."},
+              "storeId": {"type": "string", "optional": true, "description": "The cookie store to retrieve cookies from. If omitted, the current execution context's cookie store will be used."}
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "cookies", "type": "array", "items": {"$ref": "Cookie"}, "description": "All the existing, unexpired cookies that match the given cookie info."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "set",
+        "type": "function",
+        "description": "Sets a cookie with the given cookie data; may overwrite equivalent cookies if they exist.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "description": "Details about the cookie being set.",
+            "properties": {
+              "url": {"type": "string", "description": "The request-URI to associate with the setting of the cookie. This value can affect the default domain and path values of the created cookie. If host permissions for this URL are not specified in the manifest file, the API call will fail."},
+              "name": {"type": "string", "optional": true, "description": "The name of the cookie. Empty by default if omitted."},
+              "value": {"type": "string", "optional": true, "description": "The value of the cookie. Empty by default if omitted."},
+              "domain": {"type": "string", "optional": true, "description": "The domain of the cookie. If omitted, the cookie becomes a host-only cookie."},
+              "path": {"type": "string", "optional": true, "description": "The path of the cookie. Defaults to the path portion of the url parameter."},
+              "secure": {"type": "boolean", "optional": true, "description": "Whether the cookie should be marked as Secure. Defaults to false."},
+              "httpOnly": {"type": "boolean", "optional": true, "description": "Whether the cookie should be marked as HttpOnly. Defaults to false."},
+              "expirationDate": {"type": "number", "optional": true, "description": "The expiration date of the cookie as the number of seconds since the UNIX epoch. If omitted, the cookie becomes a session cookie."},
+              "storeId": {"type": "string", "optional": true, "description": "The ID of the cookie store in which to set the cookie. By default, the cookie is set in the current execution context's cookie store."}
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "min_version": "11.0.674.0",
+            "parameters": [
+              {
+                "name": "cookie", "$ref": "Cookie", "optional": true, "description": "Contains details about the cookie that's been set.  If setting failed for any reason, this will be \"null\", and \"chrome.extension.lastError\" will be set."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "remove",
+        "type": "function",
+        "description": "Deletes a cookie by name.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "description": "Information to identify the cookie to remove.",
+            "properties": {
+              "url": {"type": "string", "description": "The URL associated with the cookie. If host permissions for this URL are not specified in the manifest file, the API call will fail."},
+              "name": {"type": "string", "description": "The name of the cookie to remove."},
+              "storeId": {"type": "string", "optional": true, "description": "The ID of the cookie store to look in for the cookie. If unspecified, the cookie is looked for by default in the current execution context's cookie store."}
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "min_version": "11.0.674.0",
+            "parameters": [
+              {
+                "name": "details",
+                "type": "object",
+                "description": "Contains details about the cookie that's been removed.  If removal failed for any reason, this will be \"null\", and \"chrome.extension.lastError\" will be set.",
+                "optional": true,
+                "properties": {
+                  "url": {"type": "string", "description": "The URL associated with the cookie that's been removed."},
+                  "name": {"type": "string", "description": "The name of the cookie that's been removed."},
+                  "storeId": {"type": "string", "description": "The ID of the cookie store from which the cookie was removed."}
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getAllCookieStores",
+        "type": "function",
+        "description": "Lists all existing cookie stores.",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "cookieStores", "type": "array", "items": {"$ref": "CookieStore"}, "description": "All the existing cookie stores."
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onChanged",
+        "type": "function",
+        "description": "Fired when a cookie is set or removed. As a special case, note that updating a cookie's properties is implemented as a two step process: the cookie to be updated is first removed entirely, generating a notification with \"cause\" of \"overwrite\" .  Afterwards, a new cookie is written with the updated values, generating a second notification with \"cause\" \"explicit\".",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "changeInfo",
+            "properties": {
+              "removed": {"type": "boolean", "description": "True if a cookie was removed."},
+              "cookie": {"$ref": "Cookie", "description": "Information about the cookie that was set or removed."},
+              "cause": {"min_version": "12.0.707.0", "type": "string", "enum": ["evicted", "expired", "explicit", "expired_overwrite", "overwrite"], "description": "The underlying reason behind the cookie's change. If a cookie was inserted, or removed via an explicit call to \"chrome.cookies.remove\", \"cause\" will be \"explicit\". If a cookie was automatically removed due to expiry, \"cause\" will be \"expired\". If a cookie was removed due to being overwritten with an already-expired expiration date, \"cause\" will be set to \"expired_overwrite\".  If a cookie was automatically removed due to garbage collection, \"cause\" will be \"evicted\".  If a cookie was automatically removed due to a \"set\" call that overwrote it, \"cause\" will be \"overwrite\". Plan your response accordingly."}
+            }
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/debugger.json b/chrome/common/extensions/api/debugger.json
new file mode 100644
index 0000000..2ad4135
--- /dev/null
+++ b/chrome/common/extensions/api/debugger.json
@@ -0,0 +1,147 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "debugger",
+    "types": [
+      {
+        "id": "Debuggee",
+        "type": "object",
+        "description": "Debuggee identifier.",
+        "properties": {
+          "tabId": { "type": "integer", "description": "The id of the tab which you intend to debug." }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "attach",
+        "type": "function",
+        "description": "Attaches debugger to the given target.",
+        "parameters": [
+          {
+            "$ref": "Debuggee",
+            "name": "target",
+            "description": "Debugging target to which you want to attach."
+          },
+          {
+            "type": "string",
+            "name": "requiredVersion",
+            "description": "Required debugging protocol version (\"0.1\"). One can only attach to the debuggee with matching major version and greater or equal minor version. List of the protocol versions can be obtained <a href='http://code.google.com/chrome/devtools/docs/remote-debugging.html'>here</a>."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [],
+            "description": "Called once the attach operation succeeds or fails. Callback receives no arguments. If the attach fails, <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will be set to the error message."
+          }
+        ]
+      },
+      {
+        "name": "detach",
+        "type": "function",
+        "description": "Detaches debugger from the given target.",
+        "parameters": [
+          {
+            "$ref": "Debuggee",
+            "name": "target",
+            "description": "Debugging target from which you want to detach."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [],
+            "description": "Called once the detach operation succeeds or fails. Callback receives no arguments. If the detach fails, <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will be set to the error message."
+          }
+        ]
+      },
+      {
+        "name": "sendCommand",
+        "type": "function",
+        "description": "Sends given command to the debugging target.",
+        "parameters": [
+          {
+            "$ref": "Debuggee",
+            "name": "target",
+            "description": "Debugging target to which you want to send the command."
+          },
+          {
+            "type": "string",
+            "name": "method",
+            "description": "Method name. Should be one of the methods defined by the <a href='http://code.google.com/chrome/devtools/docs/remote-debugging.html'>remote debugging protocol</a>."
+          },
+          {
+            "type": "object",
+            "name": "commandParams",
+            "optional": true,
+            "additionalProperties": { "type": "any" },
+            "description": "JSON object with request parameters. This object must conform to the remote debugging params scheme for given method."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "type": "object",
+                "name": "result",
+                "optional": true,
+                "additionalProperties": { "type": "any" },
+                "description": "JSON object with the response. Structure of the response varies depending on the method and is defined by the remote debugging protocol."
+              }
+            ],
+            "description": "Response body. If an error occurs while posting the message, the callback will be called with no arguments and <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will be set to the error message."
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onEvent",
+        "type": "function",
+        "description": "Fired whenever debugging target issues instrumentation event.",
+        "parameters": [
+          {
+            "$ref": "Debuggee",
+            "name": "source",
+            "description": "The debuggee that generated this event."
+          },
+          {
+            "type": "string",
+            "name": "method",
+            "description": "Method name. Should be one of the notifications defined by the <a href='http://code.google.com/chrome/devtools/docs/remote-debugging.html'>remote debugging protocol</a>."
+          },
+          {
+            "type": "object",
+            "name": "params",
+            "optional": true,
+            "additionalProperties": { "type": "any" },
+            "description": "JSON object with the response. Structure of the response varies depending on the method and is defined by the remote debugging protocol."
+          }
+        ]
+      },
+      {
+        "name": "onDetach",
+        "type": "function",
+        "description": "Fired when browser terminates debugging session for the tab. This happens when either the tab is being closed or Chrome DevTools is being invoked for the attached tab.",
+        "parameters": [
+          {
+            "$ref": "Debuggee",
+            "name": "source",
+            "description": "The debuggee that was detached."
+          },
+          {
+            "type": "string",
+            "name": "reason",
+            "description": "Connection termination reason.",
+            "enum": [ "target_closed", "canceled_by_user", "replaced_with_devtools" ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/declarative_web_request.json b/chrome/common/extensions/api/declarative_web_request.json
new file mode 100644
index 0000000..4f29782
--- /dev/null
+++ b/chrome/common/extensions/api/declarative_web_request.json
@@ -0,0 +1,545 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "declarativeWebRequest",
+    "documentation_permissions_required": ["declarative", "declarativeWebRequest"],
+    "types": [
+      {
+        "id": "HeaderFilter",
+        "type": "object",
+        "description": "Filters request headers for various criteria. Multiple criteria are evaluated as a conjunction.",
+        "properties": {
+          "namePrefix": {
+            "description" : "Matches if the header name starts with the specified string.",
+            "type": "string",
+            "optional": true
+          },
+          "nameSuffix": {
+            "type": "string",
+            "optional": true,
+            "description" : "Matches if the header name ends with the specified string."
+          },
+          "nameContains": {
+            "choices": [
+             {"type": "array", "items": {"type": "string"}},
+             {"type": "string"}
+            ],
+            "optional": true,
+            "description" : "Matches if the header name contains all of the specified strings."
+          },
+          "nameEquals": {
+            "type": "string",
+            "optional": true,
+            "description" : "Matches if the header name is equal to the specified string."
+          },
+          "valuePrefix": {
+            "type": "string",
+            "optional": true,
+            "description" : "Matches if the header value starts with the specified string."
+          },
+          "valueSuffix": {
+            "type": "string",
+            "optional": true,
+            "description" : "Matches if the header value ends with the specified string."
+          },
+          "valueContains": {
+            "choices": [
+             {"type": "array", "items": {"type": "string"}},
+             {"type": "string"}
+            ],
+            "optional": true,
+            "description" : "Matches if the header value contains all of the specified strings."
+          },
+          "valueEquals": {
+            "type": "string",
+            "optional": true,
+            "description" : "Matches if the header value is equal to the specified string."
+          }
+        }
+      },
+      {
+        "id": "RequestMatcher",
+        "type": "object",
+        "description": "Matches network events by various criteria.",
+        "properties": {
+          "url": {
+            "$ref": "events.UrlFilter",
+            "description": "Matches if the condition of the UrlFilter are fulfilled for the URL of the request.",
+            "optional": true
+          },
+          "resourceType": {
+            "type": "array",
+            "optional": true,
+            "description": "Matches if the request type of a request is contained in the list. Requests that cannot match any of the types will be filtered out.",
+            "items": { "type": "string", "enum": ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"] }
+          },
+          "contentType": {
+            "type": "array",
+            "optional": true,
+            "description": "Matches if the MIME media type of a response (from the HTTP Content-Type header) is contained in the list.",
+            "items": { "type": "string" }
+          },
+          "excludeContentType": {
+            "type": "array",
+            "optional": true,
+            "description": "Matches if the MIME media type of a response (from the HTTP Content-Type header) is <em>not</em> contained in the list.",
+            "items": { "type": "string" }
+          },
+          "requestHeaders": {
+            "type": "array",
+            "optional": true,
+            "description": "Matches if some of the request headers is matched by one of the HeaderFilters.",
+            "items": { "$ref": "HeaderFilter" }
+          },
+          "excludeRequestHeaders": {
+            "type": "array",
+            "optional": true,
+            "description": "Matches if none of the request headers is matched by any of the HeaderFilters.",
+            "items": { "$ref": "HeaderFilter" }
+          },
+          "responseHeaders": {
+            "type": "array",
+            "optional": true,
+            "description": "Matches if some of the response headers is matched by one of the HeaderFilters.",
+            "items": { "$ref": "HeaderFilter" }
+          },
+          "excludeResponseHeaders": {
+            "type": "array",
+            "optional": true,
+            "description": "Matches if none of the response headers is matched by any of the HeaderFilters.",
+            "items": { "$ref": "HeaderFilter" }
+          },
+          "thirdPartyForCookies": {
+            "type": "boolean",
+            "optional": true,
+            "description": "If set to true, matches requests that are subject to third-party cookie policies. If set to false, matches all other requests."
+          },
+          "stages": {
+            "type": "array",
+            "items": {
+              "type": "string",
+              "enum": ["onBeforeRequest", "onBeforeSendHeaders", "onHeadersReceived", "onAuthRequired"]
+            },
+            "optional": true,
+            "description": "Contains a list of strings describing stages. Allowed values are 'onBeforeRequest', 'onBeforeSendHeaders', 'onHeadersReceived', 'onAuthRequired'. If this attribute is present, then it limits the applicable stages to those listed. Note that the whole condition is only applicable in stages compatible with all attributes."
+          },
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.RequestMatcher"],
+            "nodoc": true
+          }
+        }
+      },
+      {
+        "id": "CancelRequest",
+        "description": "Declarative event action that cancels a network request.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.CancelRequest"],
+            "nodoc": true
+          }
+        }
+      },
+      {
+        "id": "RedirectRequest",
+        "description": "Declarative event action that redirects a network request.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.RedirectRequest"],
+            "nodoc": true
+          },
+          "redirectUrl": { "type": "string", "description": "Destination to where the request is redirected."}
+        }
+      },
+      {
+        "id": "declarativeWebRequest.RedirectToTransparentImage",
+        "description": "Declarative event action that redirects a network request to a transparent image.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.RedirectToTransparentImage"],
+            "nodoc": true
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.RedirectToEmptyDocument",
+        "description": "Declarative event action that redirects a network request to an empty document.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.RedirectToEmptyDocument"],
+            "nodoc": true
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.RedirectByRegEx",
+        "description": "Redirects a request by applying a regular expression on the URL. The regular expressions use the <a href=\"http://code.google.com/p/re2/wiki/Syntax\">RE2 syntax</a>.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.RedirectByRegEx"],
+            "nodoc": true
+          },
+          "from": {
+            "type": "string",
+            "description": "A match pattern that may contain capture groups. Capture groups are referenced in the Perl syntax ($1, $2, ...) instead of the RE2 syntax (\\1, \\2, ...) in order to be closer to JavaScript Regular Expressions."
+          },
+          "to": {
+            "type": "string",
+            "description": "Destination pattern."
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.SetRequestHeader",
+        "description": "Sets the request header of the specified name to the specified value. If a header with the specified name did not exist before, a new one is created. Header name comparison is always case-insensitive. Each request header name occurs only once in each request.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.SetRequestHeader"],
+            "nodoc": true
+          },
+          "name": {
+            "type": "string",
+            "description": "HTTP request header name."
+          },
+          "value": {
+            "type": "string",
+            "description": "HTTP request header value."
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.RemoveRequestHeader",
+        "description": "Removes the request header of the specified name. Do not use SetRequestHeader and RemoveRequestHeader with the same header name on the same request. Each request header name occurs only once in each request.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.RemoveRequestHeader"],
+            "nodoc": true
+          },
+          "name": {
+            "type": "string",
+            "description": "HTTP request header name (case-insensitive)."
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.AddResponseHeader",
+        "description": "Adds the response header to the response of this web request. As multiple response headers may share the same name, you need to first remove and then add a new response header in order to replace one.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.AddResponseHeader"],
+            "nodoc": true
+          },
+          "name": {
+            "type": "string",
+            "description": "HTTP response header name."
+          },
+          "value": {
+            "type": "string",
+            "description": "HTTP response header value."
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.RemoveResponseHeader",
+        "description": "Removes all response headers of the specified names and values.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.RemoveResponseHeader"],
+            "nodoc": true
+          },
+          "name": {
+            "type": "string",
+            "description": "HTTP request header name (case-insensitive)."
+          },
+          "value": {
+            "type": "string",
+            "description": "HTTP request header value (case-insensitive).",
+            "optional": true
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.IgnoreRules",
+        "description": "Masks all rules that match the specified criteria.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.IgnoreRules"],
+            "nodoc": true
+          },
+          "lowerPriorityThan": {
+            "type": "integer",
+            "description": "If set, rules with a lower priority than the specified value are ignored. This boundary is not persisted, it affects only rules and their actions of the same network request stage."
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.RequestCookie",
+        "description": "A filter or specification of a cookie in HTTP Requests.",
+        "type": "object",
+        "properties": {
+          "name": {
+            "type": "string",
+            "description": "Name of a cookie.",
+            "optional": true
+          },
+          "value": {
+            "type": "string",
+            "description": "Value of a cookie, may be padded in double-quotes.",
+            "optional": true
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.ResponseCookie",
+        "description": "A specification of a cookie in HTTP Responses.",
+        "type": "object",
+        "properties": {
+          "name": {
+            "type": "string",
+            "description": "Name of a cookie.",
+            "optional": true
+          },
+          "value": {
+            "type": "string",
+            "description": "Value of a cookie, may be padded in double-quotes.",
+            "optional": true
+          },
+          "expires": {
+            "type": "string",
+            "description": "Value of the Expires cookie attribute.",
+            "optional": true
+          },
+          "maxAge": {
+            "type": "number",
+            "description": "Value of the Max-Age cookie attribute",
+            "optional": true
+          },
+          "domain": {
+            "type": "string",
+            "description": "Value of the Domain cookie attribute.",
+            "optional": true
+          },
+          "path": {
+            "type": "string",
+            "description": "Value of the Path cookie attribute.",
+            "optional": true
+          },
+          "secure": {
+            "type": "string",
+            "description": "Existence of the Secure cookie attribute.",
+            "optional": true
+          },
+          "httpOnly": {
+            "type": "string",
+            "description": "Existence of the HttpOnly cookie attribute.",
+            "optional": true
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.FilterResponseCookie",
+        "description": "A filter of a cookie in HTTP Responses.",
+        "type": "object",
+        "properties": {
+          "name": {
+            "type": "string",
+            "description": "Name of a cookie.",
+            "optional": true
+          },
+          "value": {
+            "type": "string",
+            "description": "Value of a cookie, may be padded in double-quotes.",
+            "optional": true
+          },
+          "expires": {
+            "type": "string",
+            "description": "Value of the Expires cookie attribute.",
+            "optional": true
+          },
+          "maxAge": {
+            "type": "number",
+            "description": "Value of the Max-Age cookie attribute",
+            "optional": true
+          },
+          "domain": {
+            "type": "string",
+            "description": "Value of the Domain cookie attribute.",
+            "optional": true
+          },
+          "path": {
+            "type": "string",
+            "description": "Value of the Path cookie attribute.",
+            "optional": true
+          },
+          "secure": {
+            "type": "string",
+            "description": "Existence of the Secure cookie attribute.",
+            "optional": true
+          },
+          "httpOnly": {
+            "type": "string",
+            "description": "Existence of the HttpOnly cookie attribute.",
+            "optional": true
+          },
+          "ageUpperBound": {
+            "type": "integer",
+            "description": "Inclusive upper bound on the cookie lifetime (specified in seconds after current time). Only cookies whose expiration date-time is in the interval [now, now + ageUpperBound] fulfill this criterion. Session cookies and cookies whose expiration date-time is in the past do not meet the criterion of this filter. The cookie lifetime is calculated from either 'max-age' or 'expires' cookie attributes. If both are specified, 'max-age' is used to calculate the cookie lifetime.",
+            "minimum": 0,
+            "optional": true
+          },
+          "ageLowerBound": {
+            "type": "integer",
+            "description": "Inclusive lower bound on the cookie lifetime (specified in seconds after current time). Only cookies whose expiration date-time is set to 'now + ageLowerBound' or later fulfill this criterion. Session cookies do not meet the criterion of this filter. The cookie lifetime is calculated from either 'max-age' or 'expires' cookie attributes. If both are specified, 'max-age' is used to calculate the cookie lifetime.",
+            "minimum": 0,
+            "optional": true
+          },
+          "sessionCookie": {
+            "type": "boolean",
+            "description": "Filters session cookies. Session cookies have no lifetime specified in any of 'max-age' or 'expires' attributes.",
+            "optional": true
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.AddRequestCookie",
+        "description": "Adds a cookie to the request or overrides a cookie, in case another cookie of the same name exists already. Note that it is preferred to use the Cookies API because this is computationally less expensive.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.AddRequestCookie"],
+            "nodoc": true
+          },
+          "cookie": {
+            "$ref": "declarativeWebRequest.RequestCookie",
+            "description": "Cookie to be added to the request. No field may be undefined."
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.AddResponseCookie",
+        "description": "Adds a cookie to the response or overrides a cookie, in case another cookie of the same name exists already. Note that it is preferred to use the Cookies API because this is computationally less expensive.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.AddResponseCookie"],
+            "nodoc": true
+          },
+          "cookie": {
+            "$ref": "declarativeWebRequest.ResponseCookie",
+            "description": "Cookie to be added to the response. The name and value need to be specified."
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.EditRequestCookie",
+        "description": "Edits one or more cookies of request. Note that it is preferred to use the Cookies API because this is computationally less expensive.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.EditRequestCookie"],
+            "nodoc": true
+          },
+          "filter": {
+            "$ref": "declarativeWebRequest.RequestCookie",
+            "description": "Filter for cookies that will be modified. All empty entries are ignored."
+          },
+          "modification": {
+            "$ref": "declarativeWebRequest.RequestCookie",
+            "description": "Attributes that shall be overridden in cookies that machted the filter. Attributes that are set to an empty string are removed."
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.EditResponseCookie",
+        "description": "Edits one or more cookies of response. Note that it is preferred to use the Cookies API because this is computationally less expensive.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.EditResponseCookie"],
+            "nodoc": true
+          },
+          "filter": {
+            "$ref": "declarativeWebRequest.FilterResponseCookie",
+            "description": "Filter for cookies that will be modified. All empty entries are ignored."
+          },
+          "modification": {
+            "$ref": "declarativeWebRequest.ResponseCookie",
+            "description": "Attributes that shall be overridden in cookies that machted the filter. Attributes that are set to an empty string are removed."
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.RemoveRequestCookie",
+        "description": "Removes one or more cookies of request. Note that it is preferred to use the Cookies API because this is computationally less expensive.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.RemoveRequestCookie"],
+            "nodoc": true
+          },
+          "filter": {
+            "$ref": "declarativeWebRequest.RequestCookie",
+            "description": "Filter for cookies that will be removed. All empty entries are ignored."
+          }
+        }
+      },
+      {
+        "id": "declarativeWebRequest.RemoveResponseCookie",
+        "description": "Removes one or more cookies of response. Note that it is preferred to use the Cookies API because this is computationally less expensive.",
+        "type": "object",
+        "properties": {
+          "instanceType": {
+            "type": "string", "enum": ["declarativeWebRequest.RemoveResponseCookie"],
+            "nodoc": true
+          },
+          "filter": {
+            "$ref": "declarativeWebRequest.FilterResponseCookie",
+            "description": "Filter for cookies that will be removed. All empty entries are ignored."
+          }
+        }
+      }
+    ],
+    "functions": [
+    ],
+    "events": [
+      {
+        "name": "onRequest",
+        "options": {
+          "supportsListeners": false,
+          "supportsRules": true,
+          "conditions": ["declarativeWebRequest.RequestMatcher"],
+          "actions": [
+            "declarativeWebRequest.AddRequestCookie",
+            "declarativeWebRequest.AddResponseCookie",
+            "declarativeWebRequest.AddResponseHeader",
+            "declarativeWebRequest.CancelRequest",
+            "declarativeWebRequest.EditRequestCookie",
+            "declarativeWebRequest.EditResponseCookie",
+            "declarativeWebRequest.RedirectRequest",
+            "declarativeWebRequest.RedirectToTransparentImage",
+            "declarativeWebRequest.RedirectToEmptyDocument",
+            "declarativeWebRequest.RedirectByRegEx",
+            "declarativeWebRequest.RemoveRequestCookie",
+            "declarativeWebRequest.RemoveResponseCookie",
+            "declarativeWebRequest.RemoveRequestHeader",
+            "declarativeWebRequest.RemoveResponseHeader",
+            "declarativeWebRequest.SetRequestHeader",
+            "declarativeWebRequest.IgnoreRules"
+          ]
+        }
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/devtools.json b/chrome/common/extensions/api/devtools.json
new file mode 100644
index 0000000..4a6d9df
--- /dev/null
+++ b/chrome/common/extensions/api/devtools.json
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "devtools",
+    "nodoc": "true",
+    "types": [],
+    "functions": [
+      {
+        "name": "getTabEvents",
+        "type": "function",
+        "description": "Experimental support for timeline API",
+        "nodoc": "true",
+        "parameters": [
+          {
+            "name": "tab_id",
+            "type": "integer"
+          }
+        ],
+        "returns": {
+          "type": "object",
+          "additionalProperties": { "type": "any" },
+          "description": "DevTools tab events object"
+        }
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/devtools/experimental_audits.json b/chrome/common/extensions/api/devtools/experimental_audits.json
new file mode 100644
index 0000000..89003ac
--- /dev/null
+++ b/chrome/common/extensions/api/devtools/experimental_audits.json
@@ -0,0 +1,190 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "experimental.devtools.audits",
+    "nocompile": true,
+    "functions": [
+      {
+        "name": "addCategory",
+        "type": "function",
+        "description": "Adds an audit category.",
+        "parameters": [
+          { "name": "displayName", "type": "string", "description": "A display name for the category." },
+          { "name": "resultCount", "type": "number", "description": "The expected number of audit results in the category." }
+        ],
+        "returns": {
+          "$ref": "AuditCategory"
+        }
+      }
+    ],
+    "types": [
+      {
+        "id": "AuditCategory",
+        "type": "object",
+        "description": "A group of logically related audit checks.",
+        "events": [
+          {
+            "name": "onAuditStarted",
+            "type": "function",
+            "description": "If the category is enabled, this event is fired when the audit is started. The event handler is expected to initiate execution of the audit logic that will populate the <code>results</code> collection.",
+            "parameters": [
+              { "name": "results", "$ref": "AuditResults" }
+            ]
+          }
+        ]
+      },
+      {
+        "id": "FormattedValue",
+        "type": "object",
+        "additionalProperties": { "type": "any" },
+        "description": "A value returned from one of the formatters (a URL, code snippet etc), to be passed to <code>createResult()</code> or <code>addChild()</code>. See <a href=\"#method-AuditResults-createSnippet\"><code>createSnippet()</code></a> and <a href=\"#method-AuditResults-createURL\"><code>createURL()</code></a>."
+      },
+      {
+        "id": "AuditResults",
+        "type": "object",
+        "description": "A collection of audit results for the current run of the audit category.",
+        "functions": [
+          {
+            "name": "addResult",
+            "type": "function",
+            "description": "Adds an audit result. The results are rendered as bulleted items under the audit category assoicated with the <code>AuditResults</code> object.",
+            "parameters": [
+              {
+                "name": "displayName",
+                "type": "string",
+                "description": "A concise, high-level description of the result."
+              },
+              {
+                "name": "description",
+                "type": "string",
+                "description": "A detailed description of what the displayName means."
+              },
+              {
+                "name": "severity",
+                "$ref": "AuditResultSeverity"
+              },
+              {
+                "name": "details",
+                "$ref": "AuditResultNode",
+                "optional": true,
+                "description": "A subtree that appears under the added result that may provide additional details on the violations found."
+              }
+            ]
+          },
+          {
+            "name": "createResult",
+            "type": "function",
+            "description": "Creates a result node that may be used as the <code>details</code> parameters to the <code>addResult()</code> method.",
+            "parameters": [
+              {
+                "name": "content ...",
+                "choices": [
+                  { "type": "string" },
+                  { "$ref": "FormattedValue" }
+                ],
+                "description": "Either string or formatted values returned by one of the AuditResult formatters (a URL, a snippet etc). If multiple arguments are passed, these will be concatenated into a single node."
+              }
+            ],
+            "returns": {
+              "$ref": "AuditResultNode"
+            }
+          },
+          {
+            "name": "done",
+            "type": "function",
+            "description": "Signals the DevTools Audits panel that the run of this category is over. The audit run also completes automatically when the number of added top-level results is equal to that declared when AuditCategory was created."
+          },
+          {
+            "name": "createURL",
+            "type": "function",
+            "description": "Render passed value as a URL in the Audits panel.",
+            "parameters": [
+              { "name": "href", "type": "string", "description": "A URL that appears as the href value on the resulting link." },
+              { "name": "displayText", "type": "string", "description": "Text that appears to the user.", "optional": true }
+            ],
+            "returns": { "$ref": "FormattedValue" }
+          },
+          {
+            "name": "createSnippet",
+            "type": "function",
+            "description": "Render passed text as a code snippet in the Audits panel.",
+            "parameters": [
+              { "name": "text", "type": "string", "description": "Snippet text." }
+            ],
+            "returns": { "$ref": "FormattedValue" }
+          }
+        ],
+        "properties": {
+          "Severity": {
+            "$ref": "AuditResultSeverity",
+            "description": "A class that contains possible values for the audit result severities."
+          },
+          "text": {
+            "type": "string",
+            "description": "The contents of the node."
+          },
+          "children": {
+            "optional": true,
+            "type": "array",
+            "items": { "$ref": "AuditResultNode" },
+            "description": "Children of this node."
+          },
+          "expanded": {
+            "optional": "true",
+            "type": "boolean",
+            "description": "Whether the node is expanded by default."
+          }
+        }
+      },
+      {
+        "id": "AuditResultNode",
+        "type": "object",
+        "description": "A node in the audit result tree. Displays content and may optionally have children nodes.",
+        "functions": [
+          {
+            "name": "addChild",
+            "description": "Adds a child node to this node.",
+            "parameters": [
+              {
+                "name": "content ...",
+                "choices": [
+                  { "type": "string" },
+                  { "$ref": "FormattedValue" }
+                ],
+                "description": "Either string or formatted values returned by one of the AuditResult formatters (URL, snippet etc). If multiple arguments are passed, these will be concatenated into a single node."
+              }
+            ],
+            "returns": {
+              "$ref": "AuditResultNode"
+            }
+          }
+        ],
+        "properties": {
+          "expanded": {
+            "type": "boolean",
+            "description": "If set, the subtree will always be expanded."
+          }
+        }
+      },
+      {
+        "id": "AuditResultSeverity",
+        "type": "object",
+        "description": "This type contains possible values for a result severity. The results of different severities are distinguished by colored bullets near the result's display name.",
+        "properties": {
+          "Info": {
+            "type": "string"
+          },
+          "Warning": {
+            "type": "string"
+          },
+          "Severe": {
+            "type": "string"
+          }
+        }
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/devtools/experimental_console.json b/chrome/common/extensions/api/devtools/experimental_console.json
new file mode 100644
index 0000000..35ea7f0
--- /dev/null
+++ b/chrome/common/extensions/api/devtools/experimental_console.json
@@ -0,0 +1,99 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "experimental.devtools.console",
+    "nocompile": true,
+    "functions": [
+      {
+        "name": "addMessage",
+        "type": "function",
+        "description": "Adds a message to the console.",
+        "parameters": [
+          { "name": "severity", "$ref": "Severity", "description": "The severity of the message." },
+          { "name": "text", "type": "string", "description": "The text of the message." }
+        ]
+      },
+      {
+        "name": "getMessages",
+        "type": "function",
+        "description": "Retrieves console messages.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "A function that receives console messages when the request completes.",
+            "parameters": [
+              {
+                "name": "messages",
+                "type": "array",
+                "items": { "$ref": "ConsoleMessage" },
+                "description": "Console messages."
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "types": [
+      {
+        "id": "ConsoleMessage",
+        "type": "object",
+        "description": "A console message.",
+        "properties": {
+          "severity": {
+            "$ref": "Severity",
+            "description": "Message severity."
+          },
+          "text": {
+            "type": "string",
+            "description": "The text of the console message, as represented by the first argument to the console.log() or a similar method (no parameter substitution  performed)."
+          },
+          "url": {
+            "type": "string",
+            "optional": true,
+            "description": "The URL of the script that originated the message, if available."
+          },
+          "line": {
+            "type": "number",
+            "optional": true,
+            "description": "The number of the line where the message originated, if available."
+          }
+        }
+      },
+      {
+        "id": "Severity",
+        "type": "object",
+        "properties": {
+          "Tip": {
+            "type": "string"
+          },
+          "Debug": {
+            "type": "string"
+          },
+          "Log": {
+            "type": "string"
+          },
+          "Warning": {
+            "type": "string"
+          },
+          "Error": {
+            "type": "string"
+          }
+        }
+      }
+    ],
+    "events": [
+      {
+        "name": "onMessageAdded",
+        "type": "function",
+        "description": "Fired when a new message is added to the console.",
+        "parameters": [
+          { "name": "message", "$ref": "ConsoleMessage" }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/devtools/inspected_window.json b/chrome/common/extensions/api/devtools/inspected_window.json
new file mode 100644
index 0000000..48346f8
--- /dev/null
+++ b/chrome/common/extensions/api/devtools/inspected_window.json
@@ -0,0 +1,196 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "devtools.inspectedWindow",
+    "nocompile": true,
+    "types": [
+      {
+        "id": "Resource",
+        "type": "object",
+        "description": "A resource within the inspected page, such as a document, a script, or an image.",
+        "properties": {
+          "url": {
+            "type": "string",
+            "description": "The URL of the resource."
+          }
+        },
+        "functions": [
+          {
+            "name": "getContent",
+            "type": "function",
+            "description": "Gets the content of the resource.",
+            "parameters": [
+              {
+                "name": "callback",
+                "type": "function",
+                "description": "A function that receives resource content when the request completes.",
+                "parameters": [
+                  {
+                    "name": "content",
+                    "type": "string",
+                    "description": "Content of the resource (potentially encoded)."
+                  },
+                  {
+                    "name": "encoding",
+                    "type": "string",
+                    "description": "Empty if content is not encoded, encoding name otherwise. Currently, only base64 is supported."
+                  }
+                ]
+              }
+            ]
+          },
+          {
+            "name": "setContent",
+            "type": "function",
+            "description": "Sets the content of the resource.",
+            "parameters": [
+              {
+                "name": "content",
+                "type": "string",
+                "description": "New content of the resource. Only resources with the text type are currently supported."
+              },
+              {
+                "name": "commit",
+                "type": "boolean",
+                "description": "True if the user has finished editing the resource, and the new content of the resource should be persisted; false if this is a minor change sent in progress of the user editing the resource."
+              },
+              {
+                "name": "callback",
+                "type": "function",
+                "description": "A function called upon request completion.",
+                "optional": true,
+                "parameters": [
+                  {
+                    "name": "error",
+                    "type": "object",
+                    "additionalProperties": {"type": "any"},
+                    "optional": true,
+                    "description": "Set to undefined if the resource content was set successfully; describes error otherwise."
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "properties": {
+      "tabId": {
+        "description": "The ID of the tab being inspected. This ID may be used with chrome.tabs.* API.",
+        "type": "integer"
+      }
+    },
+    "functions": [
+      {
+        "name": "eval",
+        "type": "function",
+        "description": "Evaluates a JavaScript expression in the context of the main frame of the inspected page. The expression must evaluate to a JSON-compliant object, otherwise an exception is thrown.",
+        "parameters": [
+          {
+            "name": "expression",
+            "type": "string",
+            "description": "An expression to evaluate."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "A function called when evaluation completes.",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "result",
+                "type": "object",
+                "additionalProperties": {"type": "any"},
+                "description": "The result of evaluation."
+              },
+              {
+                "name": "isException",
+                "type": "boolean",
+                "description": "Set if an exception was caught while evaluating the expression."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "reload",
+        "type": "function",
+        "description": "Reloads the inspected page.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "reloadOptions",
+            "optional": true,
+            "properties": {
+              "ignoreCache": {
+                "type": "boolean",
+                "optional": true,
+                "description": "When true, the loader will ignore the cache for all inspected page resources loaded before the <code>load</code> event is fired. The effect is similar to pressing Ctrl+Shift+R in the inspected window or within the Developer Tools window."
+              },
+              "userAgent": {
+                "type": "string",
+                "optional": true,
+                "description": "If specified, the string will override the value of the <code>User-Agent</code> HTTP header that's sent while loading the resources of the inspected page. The string will also override the value of the <code>navigator.userAgent</code> property that's returned to any scripts that are running within the inspected page."
+              },
+              "injectedScript": {
+                "type": "string",
+                "optional": true,
+                "description": "If specified, the script will be injected into every frame of the inspected page immediately upon load, before any of the frame's scripts. The script will not be injected after subsequent reloads&mdash;for example, if the user presses Ctrl+R."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "getResources",
+        "type": "function",
+        "description": "Retrieves the list of resources from the inspected page.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "A function that receives the list of resources when the request completes.",
+            "parameters": [
+              {
+                "name": "resources",
+                "type": "array",
+                "items": { "$ref": "Resource" },
+                "description": "The resources within the page."
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onResourceAdded",
+        "description": "Fired when a new resource is added to the inspected page.",
+        "parameters": [
+          {
+            "name": "resource",
+            "$ref": "Resource"
+          }
+        ]
+      },
+      {
+        "name": "onResourceContentCommitted",
+        "description": "Fired when a new revision of the resource is committed (e.g. user saves an edited version of the resource in the Developer Tools).",
+        "parameters": [
+          {
+            "name": "resource",
+            "$ref": "Resource"
+          },
+          {
+            "name": "content",
+            "type": "string",
+            "description": "New content of the resource."
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/devtools/network.json b/chrome/common/extensions/api/devtools/network.json
new file mode 100644
index 0000000..c7dc56e
--- /dev/null
+++ b/chrome/common/extensions/api/devtools/network.json
@@ -0,0 +1,87 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "devtools.network",
+    "nocompile": true,
+    "types": [
+      {
+        "id": "Request",
+        "type": "object",
+        "description": "Represents a network request for a document resource (script, image and so on). See HAR Specification for reference.",
+        "functions": [
+          {
+            "name": "getContent",
+            "type": "function",
+            "description": "Returns content of the response body.",
+            "parameters": [
+              {
+                "name": "callback",
+                "type": "function",
+                "description": "A function that receives the response body when the request completes.",
+                "parameters": [
+                  {
+                    "name": "content",
+                    "type": "string",
+                    "description": "Content of the response body (potentially encoded)."
+                  },
+                  {
+                    "name": "encoding",
+                    "type": "string",
+                    "description": "Empty if content is not encoded, encoding name otherwise. Currently, only base64 is supported."
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "functions": [
+      {
+        "name": "getHAR",
+        "type": "function",
+        "description": "Returns HAR log that contains all known network requests.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "A function that receives the HAR log when the request completes.",
+            "parameters": [
+              {
+                "name": "harLog",
+                "type": "object",
+                "additionalProperties": {"type": "any"},
+                "description": "A HAR log. See HAR specification for details."
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onRequestFinished",
+        "type": "function",
+        "description": "Fired when a network request is finished and all request data are available.",
+        "parameters": [
+          { "name": "request", "$ref": "Request", "description": "Description of a network request in the form of a HAR entry. See HAR specification for details." }
+        ]
+      },
+      {
+        "name": "onNavigated",
+        "type": "function",
+        "description": "Fired when the inspected window navigates to a new page.",
+        "parameters": [
+          {
+            "name": "url",
+            "type": "string",
+            "description": "URL of the new page."
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/devtools/panels.json b/chrome/common/extensions/api/devtools/panels.json
new file mode 100644
index 0000000..77f30fa
--- /dev/null
+++ b/chrome/common/extensions/api/devtools/panels.json
@@ -0,0 +1,316 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "devtools.panels",
+    "dependencies": [ "windows" ],
+    "nocompile": true,
+    "types": [
+      {
+        "id": "ElementsPanel",
+        "type": "object",
+        "description": "Represents the Elements panel.",
+        "events": [
+          {
+            "name": "onSelectionChanged",
+            "description": "Fired when an object is selected in the panel."
+          }
+        ],
+        "functions": [
+          {
+            "name": "createSidebarPane",
+            "type": "function",
+            "description": "Creates a pane within panel's sidebar.",
+            "parameters": [
+              {
+                "name": "title",
+                "type": "string",
+                "description": "Text that is displayed in sidebar caption."
+              },
+              {
+                "name": "callback",
+                "type": "function",
+                "description": "A callback invoked when the sidebar is created.",
+                "optional": true,
+                "parameters": [
+                  {
+                    "name": "result",
+                    "description": "An ExtensionSidebarPane object for created sidebar pane.",
+                    "$ref": "ExtensionSidebarPane"
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "id": "ExtensionPanel",
+        "type": "object",
+        "description": "Represents a panel created by extension.",
+        "functions": [
+          {
+            "name": "createStatusBarButton",
+            "description": "Appends a button to the status bar of the panel.",
+            "parameters": [
+              {
+                "name": "iconPath",
+                "type": "string",
+                "description": "Path to the icon of the button. The file should contain a 64x24-pixel image composed of two 32x24 icons. The left icon is used when the button is inactive; the right icon is displayed when the button is pressed."
+              },
+              {
+                "name": "tooltipText",
+                "type": "string",
+                "description": "Text shown as a tooltip when user hovers the mouse over the button."
+              },
+              {
+                "name": "disabled",
+                "type": "boolean",
+                "description": "Whether the button is disabled."
+              }
+            ],
+            "returns": { "$ref": "Button" }
+          }
+        ],
+        "events": [
+          {
+            "name": "onSearch",
+            "description": "Fired upon a search action (start of a new search, search result navigation, or search being canceled).",
+            "parameters": [
+              {
+                "name": "action",
+                "type": "string",
+                "description": "Type of search action being performed."
+              },
+              {
+                "name": "queryString",
+                "type": "string",
+                "optional": true,
+                "description": "Query string (only for 'performSearch')."
+              }
+            ]
+          },
+          {
+            "name": "onShown",
+            "type": "function",
+            "description": "Fired when the user switches to the panel.",
+            "parameters": [
+              {
+                "name": "window",
+                "$ref": "windows.Window",
+                "description": "The <code>window</code> object of panel's page."
+              }
+            ]
+          },
+          {
+            "name": "onHidden",
+            "type": "function",
+            "description": "Fired when the user switches away from the panel."
+          }
+        ]
+      },
+      {
+        "id": "ExtensionSidebarPane",
+        "type": "object",
+        "description": "A sidebar created by the extension.",
+        "functions": [
+          {
+            "name": "setHeight",
+            "type": "function",
+            "description": "Sets the height of the sidebar.",
+            "parameters": [
+              {
+                "name": "height",
+                "type": "string",
+                "description": "A CSS-like size specification, such as <code>'100px'</code> or <code>'12ex'</code>."
+              }
+            ]
+          },
+          {
+            "name": "setExpression",
+            "type": "function",
+            "description": "Sets an expression that is evaluated within the inspected page. The result is displayed in the sidebar pane.",
+            "parameters": [
+              {
+                "name": "expression",
+                "type": "string",
+                "description": "An expression to be evaluated in context of the inspected page. JavaScript objects and DOM nodes are displayed in an expandable tree similar to the console/watch."
+              },
+              {
+                "name": "rootTitle",
+                "type": "string",
+                "optional": true,
+                "description": "An optional title for the root of the expression tree."
+              },
+              {
+                "name": "callback",
+                "type": "function",
+                "optional": true,
+                "description": "A callback invoked after the sidebar pane is updated with the expression evaluation results."
+              }
+            ]
+          },
+          {
+            "name": "setObject",
+            "type": "function",
+            "description": "Sets a JSON-compliant object to be displayed in the sidebar pane.",
+            "parameters": [
+              {
+                "name": "jsonObject",
+                "type": "string",
+                "description": "An object to be displayed in context of the inspected page. Evaluated in the context of the caller (API client)."
+              },
+              {
+                "name": "rootTitle",
+                "type": "string",
+                "optional": true,
+                "description": "An optional title for the root of the expression tree."
+              },
+              {
+                "name": "callback",
+                "type": "function",
+                "optional": true,
+                "description": "A callback invoked after the sidebar is updated with the object."
+              }
+            ]
+          },
+          {
+            "name": "setPage",
+            "type": "function",
+            "description": "Sets an HTML page to be displayed in the sidebar pane.",
+            "parameters": [
+              {
+                "name": "path",
+                "type": "string",
+                "description": "Relative path of an extension page to display within the sidebar."
+              }
+            ]
+          }
+        ],
+        "events": [
+          {
+            "name": "onShown",
+            "type": "function",
+            "description": "Fired when the sidebar pane becomes visible as a result of user switching to the panel that hosts it.",
+            "parameters": [
+              {
+                "name": "window",
+                "$ref": "windows.Window",
+                "optional": true,
+                "description": "The <code>window</code> object of the sidebar page, if one was set with the <code>setPage()</code> method."
+              }
+            ]
+          },
+          {
+            "name": "onHidden",
+            "type": "function",
+            "description": "Fired when the sidebar pane becomes hidden as a result of the user switching away from the panel that hosts the sidebar pane."
+          }
+        ]
+      },
+      {
+        "id": "Button",
+        "type": "object",
+        "description": "A button created by the extension.",
+        "functions": [
+          {
+            "name": "update",
+            "description": "Updates the attributes of the button. If some of the arguments are omitted or <code>null</code>, the corresponding attributes are not updated.",
+            "parameters": [
+              {
+                "name": "iconPath",
+                "type": "string",
+                "optional": true,
+                "description": "Path to the new icon of the button."
+              },
+              {
+                "name": "tooltipText",
+                "type": "string",
+                "optional": true,
+                "description": "Text shown as a tooltip when user hovers the mouse over the button."
+              },
+              {
+                "name": "disabled",
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the button is disabled."
+              }
+            ]
+          }
+        ],
+        "events": [
+          {
+            "name": "onClicked",
+            "type": "function",
+            "description": "Fired when the button is clicked."
+          }
+        ]
+      }
+    ],
+    "properties": {
+      "elements": {
+        "$ref": "ElementsPanel",
+        "description": "Elements panel."
+      }
+    },
+    "functions": [
+      {
+        "name": "create",
+        "type": "function",
+        "description": "Creates an extension panel.",
+        "parameters": [
+          {
+            "name": "title",
+            "type": "string",
+            "description": "Title that is displayed next to the extension icon in the Developer Tools toolbar."
+          },
+          {
+            "name": "iconPath",
+            "type": "string",
+            "description": "Path of the panel's icon relative to the extension directory."
+          },
+          {
+            "name": "pagePath",
+            "type": "string",
+            "description": "Path of the panel's HTML page relative to the extension directory."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "description": "A function that is called when the panel is created.",
+            "parameters": [
+              {
+                "name": "panel",
+                "description": "An ExtensionPanel object representing the created panel.",
+                "$ref": "ExtensionPanel"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setOpenResourceHandler",
+        "type": "function",
+        "description": "Specifies the function to be called when the user clicks a resource link in the Developer Tools window. To unset the handler, either call the method with no parameters or pass null as the parameter.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "description": "A function that is called when the user clicks on a valid resource link in Developer Tools window. Note that if the user clicks an invalid URL or an XHR, this function is not called.",
+            "parameters": [
+              {
+                "name": "resource",
+                "$ref": "devtools.inspectedWindow.Resource",
+                "description": "A <a href=\"devtools.inspectedWindow.html#type-Resource\">Resource</a> object for the resource that was clicked."
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/downloads.idl b/chrome/common/extensions/api/downloads.idl
new file mode 100644
index 0000000..0d750e4
--- /dev/null
+++ b/chrome/common/extensions/api/downloads.idl
@@ -0,0 +1,399 @@
+// Copyright (c) 2012 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.
+
+[permissions=downloads]
+namespace downloads {
+  [inline_doc] dictionary HeaderNameValuePair {
+    // Name of the HTTP header.
+    DOMString name;
+
+    // Value of the HTTP header.
+    DOMString value;
+  };
+
+  [inline_doc] enum HttpMethod {GET, POST};
+
+  [inline_doc] dictionary DownloadOptions {
+    // The URL to download.
+    DOMString url;
+
+    // A file path relative to the Downloads directory to contain the downloaded
+    // file.
+    DOMString? filename;
+
+    // Use a file-chooser to allow the user to select a filename.
+    boolean? saveAs;
+
+    // The HTTP method to use if the URL uses the HTTP[S] protocol.
+    HttpMethod? method;
+
+    // Extra HTTP headers to send with the request if the URL uses the HTTP[s]
+    // protocol. Each header is represented as a dictionary containing the keys
+    // <code>name</code> and either <code>value</code> or
+    // <code>binaryValue</code>, restricted to those allowed by XMLHttpRequest.
+    HeaderNameValuePair[]? headers;
+
+    // Post body.
+    DOMString? body;
+  };
+
+  // <dl><dt>file</dt>
+  //     <dd>The download's filename is suspicious.</dd>
+  //     <dt>url</dt>
+  //     <dd>The download's URL is known to be malicious.</dd>
+  //     <dt>content</dt>
+  //     <dd>The downloaded file is known to be malicious.</dd>
+  //     <dt>uncommon</dt>
+  //     <dd>The download's URL is not commonly downloaded and could be
+  //     dangerous.</dd>
+  //     <dt>safe</dt>
+  //     <dd>The download presents no known danger to the user's computer.</dd>
+  // </dl>
+  // These string constants will never change, however the set of DangerTypes
+  // may change.
+  enum DangerType {file, url, content, uncommon, safe};
+
+  // <dl><dt>in_progress</dt>
+  //     <dd>The download is currently receiving data from the server.</dd>
+  //     <dt>interrupted</dt>
+  //     <dd>An error broke the connection with the file host.</dd>
+  //     <dt>complete</dt>
+  //     <dd>The download completed successfully.</dd>
+  // </dl>
+  // These string constants will never change, however the set of States may
+  // change.
+  enum State {in_progress, interrupted, complete};
+
+  // The state of the process of downloading a file.
+  dictionary DownloadItem {
+    // An identifier that is persistent across browser sessions.
+    long id;
+
+    // Absolute URL.
+    DOMString url;
+
+    // Absolute local path.
+    DOMString filename;
+
+    // False if this download is recorded in the history, true if it is not
+    // recorded.
+    boolean incognito;
+
+    // Indication of whether this download is thought to be safe or known to be
+    // suspicious.
+    DangerType danger;
+
+    // True if the user has accepted the download's danger.
+    boolean? dangerAccepted;
+
+    // The file's MIME type.
+    DOMString mime;
+
+    // Number of milliseconds between the unix epoch and when this download
+    // began.
+    long startTime;
+
+    // Number of milliseconds between the unix epoch and when this download
+    // ended.
+    long? endTime;
+
+    // Indicates whether the download is progressing, interrupted, or complete.
+    State state;
+
+    // True if the download has stopped reading data from the host, but kept the
+    // connection open.
+    boolean paused;
+
+    // Number indicating why a download was interrupted.
+    long? error;
+
+    // Number of bytes received so far from the host, without considering file
+    // compression.
+    long bytesReceived;
+
+    // Number of bytes in the whole file, without considering file compression,
+    // or -1 if unknown.
+    long totalBytes;
+
+    // Number of bytes in the whole file post-decompression, or -1 if unknown.
+    long fileSize;
+  };
+
+  [inline_doc] dictionary DownloadQuery {
+    // This space-separated string of search terms that may be grouped using
+    // quotation marks limits results to <a
+    // href='#type-DownloadItem'>DownloadItems</a> whose <code>filename</code>
+    // or <code>url</code> contain all of the search terms that do not begin with a dash '-'
+    // and none of the search terms that do begin with a dash.
+    DOMString? query;
+
+    // Limits results to <a href='#type-DownloadItem'>DownloadItems</a> that
+    // started before the given ms since the epoch.
+    long? startedBefore;
+
+    // Limits results to <a href='#type-DownloadItem'>DownloadItems</a> that
+    // started after the given ms since the epoch.
+    long? startedAfter;
+
+    // Limits results to <a href='#type-DownloadItem'>DownloadItems</a> that ended before the given ms since the
+    // epoch.
+    long? endedBefore;
+
+    // Limits results to <a href='#type-DownloadItem'>DownloadItems</a> that ended after the given ms since the
+    // epoch.
+    long? endedAfter;
+
+    // Limits results to <a href='#type-DownloadItem'>DownloadItems</a> whose
+    // <code>totalBytes</code> is greater than the given integer.
+    long? totalBytesGreater;
+
+    // Limits results to <a href='#type-DownloadItem'>DownloadItems</a> whose
+    // <code>totalBytes</code> is less than the given integer.
+    long? totalBytesLess;
+
+    // Limits results to <a href='#type-DownloadItem'>DownloadItems</a> whose
+    // <code>filename</code> matches the given regular expression.
+    DOMString? filenameRegex;
+
+    // Limits results to <a href='#type-DownloadItem'>DownloadItems</a> whose
+    // <code>url</code> matches the given regular expression.
+    DOMString? urlRegex;
+
+    // Setting this integer limits the number of results. Otherwise, all
+    // matching <a href='#type-DownloadItem'>DownloadItems</a> will be returned.
+    long? limit;
+
+    // Setting this string to a <a href='#type-DownloadItem'>DownloadItem</a>
+    // property sorts the <a href='#type-DownloadItem'>DownloadItems</a> prior
+    // to applying the above filters. For example, setting
+    // <code>orderBy='startTime'</code> sorts the <a
+    // href='#type-DownloadItem'>DownloadItems</a> by their start time in
+    // ascending order. To specify descending order, prefix <code>orderBy</code>
+    // with a hyphen: '-startTime'.
+    DOMString? orderBy;
+
+    // The <code>id</code> of the <a href="#type-DownloadItem">DownloadItem</a>
+    // that changed.
+    long? id;
+
+    // Absolute URL.
+    DOMString? url;
+
+    // Absolute local path.
+    DOMString? filename;
+
+    // Indication of whether this download is thought to be safe or known to be
+    // suspicious.
+    DangerType? danger;
+
+    // True if the user has accepted the download's danger.
+    boolean? dangerAccepted;
+
+    // The file's MIME type.
+    DOMString? mime;
+
+    // Number of milliseconds between the unix epoch and when this download
+    // began.
+    long? startTime;
+
+    // Number of milliseconds between the unix epoch and when this download
+    // ended.
+    long? endTime;
+
+    // Indicates whether the download is progressing, interrupted, or complete.
+    State? state;
+
+    // True if the download has stopped reading data from the host, but kept the
+    // connection open.
+    boolean? paused;
+
+    // Number indicating why a download was interrupted.
+    long? error;
+
+    // Number of bytes received so far from the host, without considering file
+    // compression.
+    long? bytesReceived;
+
+    // Number of bytes in the whole file, without considering file compression,
+    // or -1 if unknown.
+    long? totalBytes;
+
+    // Number of bytes in the whole file post-decompression, or -1 if unknown.
+    long? fileSize;
+  };
+
+  [inline_doc] dictionary DownloadStringDiff {
+    DOMString? previous;
+    DOMString? current;
+  };
+
+  [inline_doc] dictionary DownloadLongDiff {
+    long? previous;
+    long? current;
+  };
+
+  [inline_doc] dictionary DownloadBooleanDiff {
+    boolean? previous;
+    boolean? current;
+  };
+
+  // Encapsulates a change in a DownloadItem.
+  [inline_doc] dictionary DownloadDelta {
+    // An identifier that is persistent across browser sessions.
+    long id;
+
+    // The change in <code>url</code>, if any.
+    DownloadStringDiff? url;
+
+    // The change in <code>filename</code>, if any.
+    DownloadStringDiff? filename;
+
+    // The change in <code>danger</code>, if any.
+    DownloadStringDiff? danger;
+
+    // The change in <code>dangerAccepted</code>, if any.
+    DownloadBooleanDiff? dangerAccepted;
+
+    // The change in <code>mime</code>, if any.
+    DownloadStringDiff? mime;
+
+    // The change in <code>startTime</code>, if any.
+    DownloadLongDiff? startTime;
+
+    // The change in <code>endTime</code>, if any.
+    DownloadLongDiff? endTime;
+
+    // The change in <code>state</code>, if any.
+    DownloadStringDiff? state;
+
+    // The change in <code>paused</code>, if any.
+    DownloadBooleanDiff? paused;
+
+    // The change in <code>error</code>, if any.
+    DownloadLongDiff? error;
+
+    // The change in <code>totalBytes</code>, if any.
+    DownloadLongDiff? totalBytes;
+
+    // The change in <code>fileSize</code>, if any.
+    DownloadLongDiff? fileSize;
+  };
+
+  [inline_doc] dictionary GetFileIconOptions {
+    // The size of the icon.  The returned icon will be square with dimensions
+    // size * size pixels.  The default size for the icon is 32x32 pixels.
+    [legalValues=(16,32)] long? size;
+  };
+
+  callback DownloadCallback = void(long downloadId);
+  callback SearchCallback = void(DownloadItem[] results);
+  callback EraseCallback = void(long[] erasedIds);
+  callback NullCallback = void();
+  callback GetFileIconCallback = void(optional DOMString iconURL);
+
+  interface Functions {
+    // Download a URL. If the URL uses the HTTP[S] protocol, then the request
+    // will include all cookies currently set for its hostname. If both
+    // <code>filename</code> and <code>saveAs</code> are specified, then the
+    // Save As dialog will be displayed, pre-populated with the specified
+    // <code>filename</code>. If the download started successfully,
+    // <code>callback</code> will be called with the new <a href='#type-DownloadItem'>DownloadItem</a>'s
+    // <code>downloadId</code>. If there was an error starting the download,
+    // then <code>callback</code> will be called with
+    // <code>downloadId=undefined</code> and <a
+    // href='extension.html#property-lastError'>chrome.extension.lastError</a>
+    // will contain a descriptive string. The error strings are not guaranteed
+    // to remain backwards compatible between releases. You must not parse it.
+    // |options|: What to download and how.
+    // |callback|: Called with the id of the new <a href='#type-DownloadItem'>DownloadItem</a>.
+    static void download(DownloadOptions options,
+                         optional DownloadCallback callback);
+
+    // Find <a href='#type-DownloadItem'>DownloadItems</a>. Set
+    // <code>query</code> to the empty object to get all <a
+    // href='#type-DownloadItem'>DownloadItems</a>. To get a specific <a
+    // href='#type-DownloadItem'>DownloadItem</a>, set only the <code>id</code>
+    // field.
+    static void search(DownloadQuery query, SearchCallback callback);
+
+    // Pause the download. If the request was successful the download is in a
+    // paused state. Otherwise <a
+    // href='extension.html#property-lastError'>chrome.extension.lastError</a>
+    // contains an error message. The request will fail if the download is not
+    // active.
+    // |downloadId|: The id of the download to pause.
+    // |callback|: Called when the pause request is completed. 
+    static void pause(long downloadId, optional NullCallback callback);
+
+    // Resume a paused download. If the request was successful the download is
+    // in progress and unpaused. Otherwise <a
+    // href='extension.html#property-lastError'>chrome.extension.lastError</a>
+    // contains an error message. The request will fail if the download is not
+    // active.
+    // |downloadId|: The id of the download to resume.
+    // |callback|: Called when the resume request is completed.
+    static void resume(long downloadId, optional NullCallback callback);
+
+    // Cancel a download. When <code>callback</code> is run, the download is
+    // cancelled, completed, interrupted or doesn't exist anymore.
+    // |downloadId|: The id of the download to cancel.
+    // |callback|: Called when the cancel request is completed.
+    static void cancel(long downloadId, optional NullCallback callback);
+
+    // Retrieve an icon for the specified download. For new downloads, file
+    // icons are available after the <a href='#event-onCreated'>onCreated</a>
+    // event has been received. The image returned by this function while a
+    // download is in progress may be different from the image returned after
+    // the download is complete. Icon retrieval is done by querying the
+    // underlying operating system or toolkit depending on the platform. The
+    // icon that is returned will therefore depend on a number of factors
+    // including state of the download, platform, registered file types and
+    // visual theme. If a file icon cannot be determined, <a
+    // href='extension.html#property-lastError'>chrome.extension.lastError</a>
+    // will contain an error message.
+    // |downloadId|: The identifier for the download.
+    // |callback|: A URL to an image that represents the download.
+    static void getFileIcon(long downloadId,
+                            optional GetFileIconOptions options,
+                            GetFileIconCallback callback);
+
+    // Erase matching <a href='#type-DownloadItem'>DownloadItems</a> from
+    // history
+    [nodoc] static void erase(DownloadQuery query,
+                              optional EraseCallback callback);
+
+    // TODO(benjhayden) Comment.
+    [nodoc] static void setDestination(long downloadId, DOMString relativePath);
+
+    // Prompt the user to either accept or cancel a dangerous download.
+    // <code>acceptDanger()</code> does not automatically accept dangerous
+    // downloads.
+    [nodoc] static void acceptDanger(long downloadId);
+
+    // Show the downloaded file in its folder in a file manager.
+    [nodoc] static void show(long downloadId);
+
+    // Open the downloaded file.
+    [nodoc] static void open(long downloadId);
+
+    // Initiate dragging the file to another application.
+    [nodoc] static void drag(long downloadId);
+  };
+
+  interface Events {
+    // This event fires with the <a href='#type-DownloadItem'>DownloadItem</a>
+    // object when a download begins.
+    static void onCreated(DownloadItem downloadItem);
+
+    // Fires with the <code>downloadId</code> when a download is erased from
+    // history.
+    // |downloadId|: The <code>id</code> of the <a href='#type-DownloadItem'>DownloadItem</a> that was erased.
+    static void onErased(long downloadId);
+
+    // When any of a <a href='#type-DownloadItem'>DownloadItem</a>'s properties
+    // except <code>bytesReceived</code> changes, this event fires with the
+    // <code>downloadId</code> and an object containing the properties that changed.
+    static void onChanged(DownloadDelta downloadDelta);
+  };
+};
diff --git a/chrome/common/extensions/api/echo_private.json b/chrome/common/extensions/api/echo_private.json
new file mode 100644
index 0000000..905c1fa
--- /dev/null
+++ b/chrome/common/extensions/api/echo_private.json
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "echoPrivate",
+    "nodoc": true,
+    "platforms": ["chromeos"],
+    "types": [],
+    "functions": [
+      {
+        "name": "getRegistrationCode",
+        "description": "Get the group or coupon code from underlying storage.",
+        "type": "function",
+        "parameters": [
+          {
+            "name": "type",
+            "type": "string",
+            "description": "Type of coupon code requested to be read (coupon or group)."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "string",
+                "description" : "The coupon code."
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/events.json b/chrome/common/extensions/api/events.json
new file mode 100644
index 0000000..a5f824c
--- /dev/null
+++ b/chrome/common/extensions/api/events.json
@@ -0,0 +1,304 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "events",
+    "internal": true,
+    "unprivileged": true,
+    "types": [
+      {
+        "id": "Event",
+        "type": "object",
+        "description": "An object which allows the addition and removal of listeners for a Chrome event.",
+        "additionalProperties": { "type": "any"},
+        "functions": [
+          {
+            "name": "addListener",
+            "nocompile": true,
+            "type": "function",
+            "description": "Registers an event listener <em>callback</em> to an event.",
+            "parameters": [
+              {
+                "name": "callback",
+                "type": "function",
+                "description": "Called when an event occurs. The parameters of this function depend on the type of event."
+              }
+            ]
+          },
+          {
+            "name": "removeListener",
+            "nocompile": true,
+            "type": "function",
+            "description": "Deregisters an event listener <em>callback</em> from an event.",
+            "parameters": [
+              {
+                "name": "callback",
+                "type": "function",
+                "description": "Listener that shall be unregistered."
+              }
+            ]
+          },
+          {
+            "name": "hasListener",
+            "nocompile": true,
+            "type": "function",
+            "parameters": [
+              {
+                "name": "callback",
+                "type": "function",
+                "description": "Listener whose registration status shall be tested."
+              }
+            ],
+            "returns": {
+              "type": "boolean",
+              "description": "True if <em>callback</em> is registered to the event."
+            }
+          },
+          {
+            "name": "hasListeners",
+            "nocompile": true,
+            "type": "function",
+            "parameters": [],
+            "returns": {
+              "type": "boolean",
+              "description": "True if any event listeners are registered to the event."
+            }
+          },
+          {
+            "name": "addRules",
+            "type": "function",
+            "description": "Registers rules to handle events.",
+            "parameters": [
+              {
+                "nodoc": "true",
+                "name": "eventName",
+                "type": "string",
+                "description": "Name of the event this function affects."
+              },
+              {
+                "name": "rules",
+                "type": "array",
+                "items": {"$ref": "Rule"},
+                "description": "Rules to be registered. These do not replace previously registered rules."
+              },
+              {
+                "name": "callback",
+                "optional": true,
+                "type": "function",
+                "parameters": [
+                  {
+                    "name": "rules",
+                    "type": "array",
+                    "items": {"$ref": "Rule"},
+                    "description": "Rules that were registered, the optional parameters are filled with values."
+                  }
+                ],
+                "description": "Called with registered rules."
+              }
+            ]
+          },
+          {
+            "name": "getRules",
+            "type": "function",
+            "description": "Returns currently registered rules.",
+            "parameters": [
+              {
+                "nodoc": "true",
+                "name": "eventName",
+                "type": "string",
+                "description": "Name of the event this function affects."
+              },
+              {
+                "name": "ruleIdentifiers",
+                "optional": "true",
+                "type": "array",
+                "items": {"type": "string"},
+                "description": "If an array is passed, only rules with identifiers contained in this array are returned."
+              },
+              {
+                "name": "callback",
+                "type": "function",
+                "parameters": [
+                  {
+                    "name": "rules",
+                    "type": "array",
+                    "items": {"$ref": "Rule"},
+                    "description": "Rules that were registered, the optional parameters are filled with values."
+                  }
+                ],
+                "description": "Called with registered rules."
+              }
+            ]
+          },
+          {
+            "name": "removeRules",
+            "type": "function",
+            "description": "Unregisters currently registered rules.",
+            "parameters": [
+              {
+                "nodoc": "true",
+                "name": "eventName",
+                "type": "string",
+                "description": "Name of the event this function affects."
+              },
+              {
+                "name": "ruleIdentifiers",
+                "optional": "true",
+                "type": "array",
+                "items": {"type": "string"},
+                "description": "If an array is passed, only rules with identifiers contained in this array are unregistered."
+              },
+              {
+                "name": "callback",
+                "optional": true,
+                "type": "function",
+                "parameters": [],
+                "description": "Called when rules were unregistered."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "id": "Rule",
+        "type": "object",
+        "description": "Description of a declarative rule for handling events.",
+        "properties": {
+          "id": {
+            "type": "string",
+            "optional": true,
+            "description": "Optional identifier that allows referencing this rule."
+          },
+          "conditions": {
+            "type": "array",
+            "items": {"type": "any"},
+            "description": "List of conditions that can trigger the actions."
+          },
+          "actions": {
+            "type": "array",
+            "items": {"type": "any"},
+            "description": "List of actions that are triggered if one of the condtions is fulfilled."
+          },
+          "priority": {
+            "type": "integer",
+            "optional": true,
+            "description": "Optional priority of this rule. Defaults to 100."
+          }
+        }
+      },
+      {
+        "id": "UrlFilter",
+        "type": "object",
+        "description": "Filters URLs for various criteria. See <a href='#filtered'>event filtering</a>.",
+        "nocompile": true,
+        "properties": {
+          "hostContains": {
+            "type": "string",
+            "description": "Matches if the host name of the URL contains a specified string. To test whether a host name component has a prefix 'foo', use hostContains: '.foo'. This matches 'www.foobar.com' and 'foo.com', because an implicit dot is added at the beginning of the host name. Similarly, hostContains can be used to match against component suffix ('foo.') and to exactly match against components ('.foo.'). Suffix- and exact-matching for the last components need to be done separately using hostSuffix, because no implicit dot is added at the end of the host name.",
+            "optional": true
+          },
+          "hostEquals": {
+            "type": "string",
+            "description": "Matches if the host name of the URL is equal to a specified string.",
+            "optional": true
+          },
+          "hostPrefix": {
+            "type": "string",
+            "description": "Matches if the host name of the URL starts with a specified string.",
+            "optional": true
+          },
+          "hostSuffix": {
+            "type": "string",
+            "description": "Matches if the host name of the URL ends with a specified string.",
+            "optional": true
+          },
+          "pathContains": {
+            "type": "string",
+            "description": "Matches if the path segment of the URL contains a specified string.",
+            "optional": true
+          },
+          "pathEquals": {
+            "type": "string",
+            "description": "Matches if the path segment of the URL is equal to a specified string.",
+            "optional": true
+          },
+          "pathPrefix": {
+            "type": "string",
+            "description": "Matches if the path segment of the URL starts with a specified string.",
+            "optional": true
+          },
+          "pathSuffix": {
+            "type": "string",
+            "description": "Matches if the path segment of the URL ends with a specified string.",
+            "optional": true
+          },
+          "queryContains": {
+            "type": "string",
+            "description": "Matches if the query segment of the URL contains a specified string.",
+            "optional": true
+          },
+          "queryEquals": {
+            "type": "string",
+            "description": "Matches if the query segment of the URL is equal to a specified string.",
+            "optional": true
+          },
+          "queryPrefix": {
+            "type": "string",
+            "description": "Matches if the query segment of the URL starts with a specified string.",
+            "optional": true
+          },
+          "querySuffix": {
+            "type": "string",
+            "description": "Matches if the query segment of the URL ends with a specified string.",
+            "optional": true
+          },
+          "urlContains": {
+            "type": "string",
+            "description": "Matches if the URL (without fragment identifier) contains a specified string. Port numbers are stripped from the URL if they match the default port number.",
+            "optional": true
+          },
+          "urlEquals": {
+            "type": "string",
+            "description": "Matches if the URL (without fragment identifier) is equal to a specified string. Port numbers are stripped from the URL if they match the default port number.",
+            "optional": true
+          },
+          "urlMatches": {
+            "type": "string",
+            "description": "Matches if the URL (without fragment identifier) matches a specified regular expression. Port numbers are stripped from the URL if they match the default port number. The regular expressions use the <a href=\"http://code.google.com/p/re2/wiki/Syntax\">RE2 syntax</a>.",
+            "optional": true
+          },
+          "urlPrefix": {
+            "type": "string",
+            "description": "Matches if the URL (without fragment identifier) starts with a specified string. Port numbers are stripped from the URL if they match the default port number.",
+            "optional": true
+          },
+          "urlSuffix": {
+            "type": "string",
+            "description": "Matches if the URL (without fragment identifier) ends with a specified string. Port numbers are stripped from the URL if they match the default port number.",
+            "optional": true
+          },
+          "schemes": {
+            "type": "array",
+            "description": "Matches if the scheme of the URL is equal to any of the schemes specified in the array.",
+            "optional": true,
+            "items": { "type": "string" }
+          },
+          "ports": {
+            "type": "array",
+            "description": "Matches if the port of the URL is contained in any of the specified port lists. For example <code>[80, 443, [1000, 1200]]</code> matches all requests on port 80, 443 and in the range 1000-1200.",
+            "optional": true,
+            "items": {
+              "choices": [
+                {"type": "integer", "description": "A specific port."},
+                {"type": "array", "items": {"type": "integer"}, "description": "A pair of integers identiying the start and end (both inclusive) of a port range."}
+              ]
+            }
+          }
+        }
+      }
+    ]
+  }
+]
+
diff --git a/chrome/common/extensions/api/experimental_accessibility.json b/chrome/common/extensions/api/experimental_accessibility.json
new file mode 100644
index 0000000..901e6bc
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_accessibility.json
@@ -0,0 +1,284 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "experimental.accessibility",
+    "nodoc": true,
+    "types": [
+      {
+        "id": "AccessibilityObject",
+        "type": "object",
+        "description": "Parent class for accessibility information about an object.",
+        "properties": {
+          "type": {
+            "type": "string",
+            "description": "The type of this object, which determines the contents of 'details'.",
+            "enum": ["button", "checkbox", "combobox", "link", "menu", "menuitem", "radiobutton", "slider", "tab", "textbox", "window"]
+          },
+          "name": {
+            "type": "string",
+            "description": "The localized name of the object, like OK or Password. Do not rely on an exact string match because the text will be in the user's language and may change in the future."
+          },
+          "context": {
+            "type": "string",
+            "description": "The localized name of the context for the object, like the name of the surrounding toolbar or group of controls.",
+            "optional": true
+          },
+          "details": {
+            "description": "Other details like the state, depending on the type of object.",
+            "optional": true,
+            "choices": [
+              { "$ref": "CheckboxDetails" },
+              { "$ref": "ComboBoxDetails" },
+              { "$ref": "MenuDetails" },
+              { "$ref": "MenuItemDetails" },
+              { "$ref": "RadioButtonDetails" },
+              { "$ref": "SliderDetails" },
+              { "$ref": "TabDetails" },
+              { "$ref": "TextBoxDetails" }
+            ]
+          }
+        }
+      },
+      {
+        "id": "CheckboxDetails",
+        "type": "object",
+        "description": "Information about the state of a checkbox.",
+        "properties": {
+          "isChecked": {"type": "boolean", "description": "True if this checkbox is checked."}
+        }
+      },
+      {
+        "id": "ComboBoxDetails",
+        "type": "object",
+        "description": "Information about the state of a combo box.",
+        "properties": {
+          "value": {"type": "string", "description": "The value of the combo box."},
+          "itemCount": {"type": "integer", "description": "The number of items in the combo box's list."},
+          "itemIndex": {"type": "integer", "description": "The 0-based index of the current value, or -1 if the user entered a value not from the list."}
+        }
+      },
+      {
+        "id": "ListBoxDetails",
+        "type": "object",
+        "description": "Information about the state of a list box.",
+        "properties": {
+          "value": {"type": "string", "description": "The value of the list box."},
+          "itemCount": {"type": "integer", "description": "The number of items in the list."},
+          "itemIndex": {"type": "integer", "description": "The 0-based index of the selected value, or -1 if no items are selected."}
+        }
+      },
+      {
+        "id": "MenuDetails",
+        "type": "object",
+        "description": "Information about the state of a drop-down menu.",
+        "properties": {
+        }
+      },
+      {
+        "id": "MenuItemDetails",
+        "type": "object",
+        "description": "Information about a menu item.",
+        "properties": {
+          "hasSubmenu": {"type": "boolean", "description": "True if this item opens a submenu."},
+          "itemCount": {"type": "integer", "description": "The number of items in the menu."},
+          "itemIndex": {"type": "integer", "description": "The 0-based index of this menu item."}
+        }
+      },
+      {
+        "id": "RadioButtonDetails",
+        "type": "object",
+        "description": "Information about the state of a radio button.",
+        "properties": {
+          "isChecked": {"type": "boolean", "description": "True if this radio button is checked."},
+          "itemCount": {"type": "integer", "description": "The number of radio buttons in this group."},
+          "itemIndex": {"type": "integer", "description": "The 0-based index of this radio button in this group."}
+        }
+      },
+      {
+        "id": "SliderDetails",
+        "type": "object",
+        "description": "Information about the state of a slider.",
+        "properties": {
+          "stringValue": {"type": "string", "description": "The value of the slider as a string."}
+        }
+      },
+      {
+        "id": "TabDetails",
+        "type": "object",
+        "description": "Additional accessibility information about a tab.",
+        "properties": {
+          "itemCount": {"type": "integer", "description": "The number of tabs in this group."},
+          "itemIndex": {"type": "integer", "description": "The 0-based index of this tab in this group."}
+        }
+      },
+      {
+        "id": "TextBoxDetails",
+        "type": "object",
+        "description": "Information about the state of a text box.",
+        "properties": {
+          "value": {"type": "string", "description": "The value of the text box - the entered text."},
+          "isPassword": {"type": "boolean", "description": "True if this control contains password text whose contents should be obscured."},
+          "selectionStart": {"type": "integer", "description": "The index of the character where the selection starts, if this control contains editable text."},
+          "selectionEnd": {"type": "integer", "description": "The index of the character where the selection ends, if this control contains editable text."}
+        }
+      },
+      {
+        "id": "AlertInfo",
+        "type": "object",
+        "description": "Information about an alert",
+        "properties": {
+          "message": {
+            "type": "string",
+            "description": "The message the alert is showing."
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "setAccessibilityEnabled",
+        "type": "function",
+        "description": "Enables or disables the accessibility extension api. This must be set to true before event listeners or getFocusedControl will work.",
+        "parameters": [
+          {
+            "type": "boolean",
+            "name": "enabled",
+            "description": "True if accessibility support should be enabled."
+          }
+        ]
+      },
+      {
+        "name": "getFocusedControl",
+        "type": "function",
+        "description": "Gets information about the currently focused control.",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "control",
+                "description": "Details of the currently focused control, or null if nothing is focused.",
+                "$ref": "AccessibilityObject",
+                "optional": true
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getAlertsForTab",
+        "type": "function",
+        "description": "Gets alerts being shown on the given tab.",
+        "parameters": [
+          {
+            "name": "tabId",
+            "type": "integer",
+            "minimum": 0
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "alerts",
+                "type": "array",
+                "items": { "$ref": "AlertInfo" },
+                "description": "Alerts being shown on the given tab."
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onWindowOpened",
+        "type": "function",
+        "description": "Fired when a window is opened.",
+        "parameters": [
+          {
+            "$ref": "AccessibilityObject",
+            "name": "window",
+            "description": "Information about the window that was opened."
+          }
+        ]
+      },
+      {
+        "name": "onWindowClosed",
+        "type": "function",
+        "description": "Fired when a window is closed.",
+        "parameters": [
+          {
+            "$ref": "AccessibilityObject",
+            "name": "window",
+            "description": "Information about the window that was closed."
+          }
+        ]
+      },
+      {
+        "name": "onControlFocused",
+        "type": "function",
+        "description": "Fired when a control is focused.",
+        "parameters": [
+          {
+            "$ref": "AccessibilityObject",
+            "name": "control",
+            "description": "Details of the control that was focused."
+          }
+        ]
+      },
+      {
+        "name": "onControlAction",
+        "type": "function",
+        "description": "Fired when a control's action is taken, like pressing a button or toggling a checkbox.",
+        "parameters": [
+          {
+            "$ref": "AccessibilityObject",
+            "name": "control",
+            "description": "Details of the control whose action was taken."
+          }
+        ]
+      },
+      {
+        "name": "onTextChanged",
+        "type": "function",
+        "description": "Fired when text changes in an editable text control.",
+        "parameters": [
+          {
+            "$ref": "AccessibilityObject",
+            "name": "control",
+            "description": "Details of the control where the text changed."
+          }
+        ]
+      },
+      {
+        "name": "onMenuOpened",
+        "type": "function",
+        "description": "Fired when a menu is opened.",
+        "parameters": [
+          {
+            "$ref": "AccessibilityObject",
+            "name": "menu",
+            "description": "Information about the menu that was opened."
+          }
+        ]
+      },
+      {
+        "name": "onMenuClosed",
+        "type": "function",
+        "description": "Fired when a menu is closed.",
+        "parameters": [
+          {
+            "$ref": "AccessibilityObject",
+            "name": "menu",
+            "description": "Information about the menu that was closed."
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/experimental_app.json b/chrome/common/extensions/api/experimental_app.json
new file mode 100644
index 0000000..fbcc9dd
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_app.json
@@ -0,0 +1,84 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace":"experimental.app",
+    "functions": [
+      {
+        "name": "notify",
+        "type": "function",
+        "description": "Creates a notification from this app.",
+        "nodoc": true,
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "extensionId": {
+                "type": "string",
+                "optional": true,
+                "description": "An optional id to do notifications for an app other than the calling app. This is just to allow prototyping with an extension that sends notifications on behalf of apps that don't support notifications yet; this will be removed before the API becomes stable."
+              },
+              "title": {
+                "type": "string",
+                "optional": true,
+                "description": "The title of the notification."
+              },
+              "bodyText": {
+                "type": "string",
+                "optional": true,
+                "description": "The text content of the notification."
+              },
+              "linkUrl": {
+                "type": "string",
+                "optional": true,
+                "description": "The URL for an optional link to show along with the notification. If you specify a linkUrl, you must also specify a value for linkText."
+              },
+              "linkText": {
+                "type": "string",
+                "optional": true,
+                "description": "If a linkUrl is provided, this is required and will be used as the linkified text. It should be relatively short."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [],
+            "description": "A callback when the function is complete. Any errors will be reported in <a href='extension.html#property-lastError'>chrome.extension.lastError</a>."
+          }
+        ]
+      },
+      {
+        "name": "clearAllNotifications",
+        "type": "function",
+        "description": "Clears all previously sent notifications.",
+        "nodoc": true,
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "optional": true,
+            "properties": {
+              "extensionId": {
+                "type": "string",
+                "optional": true,
+                "description": "An optional id to do notifications for an app other than the calling app. This is just to allow prototyping with an extension that sends notifications on behalf of apps that don't support notifications yet; this will be removed before the API becomes stable."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "description": "A callback when the function is complete. Any errors will be reported in <a href='extension.html#property-lastError'>chrome.extension.lastError</a>.",
+            "parameters": []
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/experimental_discovery.idl b/chrome/common/extensions/api/experimental_discovery.idl
new file mode 100644
index 0000000..d8b4e5e
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_discovery.idl
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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.
+
+namespace experimental.discovery {
+
+  dictionary SuggestDetails {
+    // The URL to suggest and that will be displayed in the new tab page under
+    // the recommended pane.
+    DOMString linkUrl;
+
+    // The linkified text. It should be relatively short.
+    DOMString linkText;
+
+    // The url of the image to use as a tile.
+    DOMString? urlImage;
+
+    // A score indicating how interesting that suggestion is. The value must be
+    // between 0 and 1. A suggestion with score 1 is twice as likely to be
+    // displayed than one with a score of 0.5. Defaults to 1.
+    // TODO: need minimum=0 and maximum=1.
+    double? score;
+  };
+
+  interface Functions {
+    // Suggests a URL for discovery.
+    // |details|: Detailed information on the URL to suggest.
+    static void suggest(SuggestDetails details);
+
+    // Removes a URL that was previously suggested for discovery by this
+    // extension.
+    // |linkUrl|: The URl to remove from discovery. Must be exactly the same as
+    // a linkUrl previously used on a call to suggest.
+    static void removeSuggestion(DOMString linkUrl);
+
+    // Clear all the URLs that were previously suggested for discovery by this
+    // extension.
+    static void clearAllSuggestions();
+  };
+};
diff --git a/chrome/common/extensions/api/experimental_dns.idl b/chrome/common/extensions/api/experimental_dns.idl
new file mode 100644
index 0000000..23c5c4b
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_dns.idl
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 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.
+
+[nodoc] namespace experimental.dns {
+
+  dictionary ResolveCallbackResolveInfo {
+    // The result code. Zero indicates success.
+    long resultCode;
+
+    // A string representing the IP address literal. Supplied only if resultCode
+    // indicates success. Note that we presently return only IPv4 addresses.
+    DOMString? address;
+  };
+
+  callback ResolveCallback = void (ResolveCallbackResolveInfo resolveInfo);
+
+  interface Functions {
+    // Resolves the given hostname or IP address literal.
+    // |hostname| : The hostname to resolve.
+    // |callback| : Called when the resolution operation completes.
+    static void resolve(DOMString hostname,
+                        ResolveCallback callback);
+  };
+
+};
diff --git a/chrome/common/extensions/api/experimental_history.json b/chrome/common/extensions/api/experimental_history.json
new file mode 100644
index 0000000..09a6f8e
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_history.json
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "experimental.history",
+    "types": [
+      {
+        "id": "MostVisitedItem",
+        "type": "object",
+        "properties": {
+           "url": {"type": "string", "description": "The URL navigated to by a user."},
+           "title": {"type": "string", "description": "The title of the page when it was last loaded."}
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "getMostVisited",
+        "type": "function",
+        "description": "Retrieves most visited URLs on the time specified.",
+        "parameters": [
+          { "name": "details",
+            "type": "object",
+            "properties": {
+              "filterTime": {"type": "number", "optional": true, "description": "Sets the time to be used as a basis for the query, represented in milliseconds since the epoch. Defaults to the current time."},
+              "filterWidth": {"type": "number", "optional": true, "description": "Limit results to those visited at filterTime +/- this on each day, in milliseconds."},
+              "dayOfTheWeek": {"type": "integer", "optional": true, "minimum": 0, "description": "Limit results to those visited on this day of the week (0 - Sunday, 1 - Monday, etc.) starting with this week."},
+              "maxResults": {"type": "integer", "optional": true, "minimum": 0, "description": "The maximum number of results to retrieve. Defaults to 100."}
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              { "name": "results", "type": "array", "items":  { "$ref": "MostVisitedItem"} }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/experimental_identity.idl b/chrome/common/extensions/api/experimental_identity.idl
new file mode 100644
index 0000000..625b0cd
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_identity.idl
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 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.
+
+namespace experimental.identity {
+
+  [inline_doc] dictionary TokenDetails {
+    // Whether to prompt the user to log in or grant scope permissions (if they
+    // have not already done so). Default is false.
+    boolean? interactive;
+  };
+
+  [inline_doc] dictionary WebAuthFlowDetails {
+    // The URL that initiates the auth flow.
+    DOMString url;
+
+    // Whether to launch auth flow in interactive mode. Default is false.
+    boolean? interactive;
+
+    // Width of the window, if one is shown in interactive mode.
+    long? width;
+
+    // Height of the window, if one is shown in interactive mode.
+    long? height;
+
+    // X coordinate of the window, if one is shown in interactive mode.
+    long? left;
+
+    // Y coordinate of the window, if one is shown in interactive mode.
+    long? top;
+  };
+
+  callback GetAuthTokenCallback = void (optional DOMString token);
+  callback LaunchWebAuthFlowCallback = void (optional DOMString responseUrl);
+
+  interface Functions {
+    // Gets an OAuth2 access token as specified by the manifest.
+    //
+    // |details| : Token options.
+    // |callback| : Called with an OAuth2 access token as specified by the
+    // manifest, or undefined if there was an error.
+    static void getAuthToken(optional TokenDetails details,
+                             GetAuthTokenCallback callback);
+
+    // Starts an auth flow at the specified URL.
+    //
+    // |details| : WebAuth flow options.
+    // |callback| : Called with the URL redirected back to your application.
+    static void launchWebAuthFlow(WebAuthFlowDetails details,
+                                  LaunchWebAuthFlowCallback callback);
+  };
+};
diff --git a/chrome/common/extensions/api/experimental_idltest.idl b/chrome/common/extensions/api/experimental_idltest.idl
new file mode 100644
index 0000000..4ee9ff7
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_idltest.idl
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 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.
+
+// An API to test IDL schema specifications.
+
+[nodoc] namespace experimental.idltest {
+
+  callback LongArrayCallback = void(long[] array);
+  callback ArrayBufferCallback = void(ArrayBuffer buffer);
+
+  interface Functions {
+    // Functions for testing binary data request/response parameters. The first
+    // two just return back the bytes they were passed in an array.
+    static void sendArrayBuffer(ArrayBuffer input, LongArrayCallback cb);
+
+    // TODO(asargent) - we currently can't have [instanceOf=ArrayBufferView],
+    // I think because ArrayBufferView isn't an instantiable type. The best
+    // we might be able to do is have a 'choices' list including all the
+    // typed array subclasses like Uint8Array, Uint16Array, Float32Array, etc.
+    static void sendArrayBufferView([instanceOf=Uint8Array] object input,
+                                    LongArrayCallback cb);
+    static void getArrayBuffer(ArrayBufferCallback cb);
+
+    // This function should not have C++ code autogenerated (the variable name
+    // |switch| should cause compile errors if it does). But the name should
+    // get defined and made visible from within extensions/apps code.
+    [nocompile] static void nocompileFunc(long switch);
+  };
+
+};
diff --git a/chrome/common/extensions/api/experimental_infobars.json b/chrome/common/extensions/api/experimental_infobars.json
new file mode 100644
index 0000000..c8e161e
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_infobars.json
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "experimental.infobars",
+    "dependencies": [ "windows" ],
+    "types": [],
+    "functions": [
+      {
+        "name": "show",
+        "type": "function",
+        "description": "Shows an infobar in the specified tab. The infobar will be closed automatically when the tab navigates. Use window.close() to close the infobar before then.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {
+                "type": "integer",
+                "description": "The tab id for the tab to display the infobar in."
+              },
+              "path": {
+                "type": "string",
+                "description": "The html file that contains the infobar."
+              },
+              "height": {
+                "type": "integer",
+                "description": "The height (in pixels) of the infobar to show. If omitted, the default infobar height will be used.",
+                "optional": true,
+                "minimum": 0,
+                "maximum": 72
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "window", "$ref": "windows.Window", "description": "Contains details about the window in which the infobar was created."
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/experimental_input_virtual_keyboard.json b/chrome/common/extensions/api/experimental_input_virtual_keyboard.json
new file mode 100644
index 0000000..2aea6ca
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_input_virtual_keyboard.json
@@ -0,0 +1,60 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "experimental.input.virtualKeyboard",
+    "nodoc": true,
+    "types": [],
+    "functions": [
+      {
+        "name": "sendKeyboardEvent",
+        "type": "function",
+        "description": "Sends a keyboard event to Chrome.",
+        "parameters": [
+          { "type": "object",
+            "name": "event",
+            "properties": {
+              "type": {
+                "type": "string",
+                "description": "One of 'keyup' or 'keydown'."
+              },
+              "keyIdentifier": {
+                "type": "string",
+                "description": "See http://www.w3.org/TR/2003/NOTE-DOM-Level-3-Events-20031107/keyset.html#KeySet-Set"
+              },
+              "altKey": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether or not the ALT key is pressed."
+              },
+              "ctrlKey": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether or not the CTRL key is pressed."
+              },
+              "metaKey": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether or not the META key is pressed."
+              },
+              "shiftKey": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether or not the SHIFT key is pressed."
+              }
+            },
+            "description": "The keyboard event to be sent."
+          },
+          { "type": "function",
+            "name": "callback",
+            "optional": true,
+            "description": "This function is called when the event processing is completed.",
+            "parameters": []
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/experimental_media_galleries.idl b/chrome/common/extensions/api/experimental_media_galleries.idl
new file mode 100644
index 0000000..0ada32e
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_media_galleries.idl
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 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.
+
+namespace experimental.mediaGalleries {
+
+  callback AssembleMediaFileCallback =
+      void ([instanceOf=Blob] optional object mediaFile);
+
+  interface Functions {
+    // Create a new MediaFile setting the metadata in the Blob to the supplied
+    // values, overriding any existing metadata in the media file. If user agent
+    // does not recognize the Blob as a supported file format, it will fail.
+    // |mediaFileContents| : the media bytes.
+    // |metadata| : the metadata. TODO(estade): this should be
+    // [instanceOf=Metafile].
+    static void assembleMediaFile(
+        [instanceOf=Blob] object mediaFileContents,
+        object metadata,
+        AssembleMediaFileCallback callback);
+
+    // Get any thumbnails contained in the passed media file. The resulting
+    // directory reader refers to a virtual directory that can not be navigated
+    // to. If there are no thumbnails in the passed file entry, the virtual
+    // directory will have no entries.
+    // TODO(estade): The return type should be Directory. The argument type
+    // should be [instanceOf=FileEntry].
+    [nocompile] static object extractEmbeddedThumbnails(object mediaFile);
+  };
+
+};
diff --git a/chrome/common/extensions/api/experimental_notification.idl b/chrome/common/extensions/api/experimental_notification.idl
new file mode 100644
index 0000000..8164b91
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_notification.idl
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 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.
+
+[nodoc] namespace experimental.notification {
+  dictionary ShowOptions {
+    DOMString iconUrl;
+    DOMString title;
+    DOMString message;
+    DOMString? extraField;
+    DOMString? secondExtraField;
+    DOMString replaceId;
+
+    // Used for internal event routing. Do not set or rely on its value.
+    [nodoc] long? srcId;
+  };
+
+  dictionary ShowInfo {
+    boolean result;
+  };
+
+  callback ShowCallback = void (ShowInfo showInfo);
+
+  interface Functions {
+    static void show(ShowOptions options, ShowCallback callback);
+  };
+
+};
diff --git a/chrome/common/extensions/api/experimental_offscreen_tabs.json b/chrome/common/extensions/api/experimental_offscreen_tabs.json
new file mode 100644
index 0000000..1058a67
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_offscreen_tabs.json
@@ -0,0 +1,354 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "experimental.offscreenTabs",
+    "types": [
+      {
+        "id": "OffscreenTab",
+        "type": "object",
+        "properties": {
+          "id": {
+            "type": "integer", 
+            "minimum": 0, 
+            "description": "The ID of the offscreen tab. Tab IDs are unique within a browser session."
+          },
+          "url": {
+            "type": 
+            "string", 
+            "description": "URL of the offscreen tab."
+          },
+          "width": {
+            "type": "integer", 
+            "minimum": 0, 
+            "description": "Width of the window."
+          },
+          "height": {
+            "type": "integer", 
+            "minimum": 0, 
+            "description": "Height of the window."
+          }
+        }
+      },
+      {
+        "id": "MouseEvent",
+        "type": "object",
+        "properties": {
+          "type": {
+            "type": "string",
+            "enum": [ "mousedown", "mouseup", "click", "mousemove", "mousewheel" ]
+          },
+          "button": { "type": "integer", "minimum": 0, "optional": true },
+          "wheelDeltaX": { "type": "integer", "optional": true },
+          "wheelDeltaY": { "type": "integer", "optional": true },
+          "altKey": { "type": "boolean" },
+          "ctrlKey": { "type": "boolean" },
+          "shiftKey": { "type": "boolean" },
+          "metaKey": { "type": "boolean", "optional": true }
+        }
+      },
+      {
+        "id": "KeyboardEvent",
+        "type": "object",
+        "properties": {
+          "type": {
+            "type": "string",
+            "enum": [ "keypress", "keydown", "keyup" ]
+          },
+          "charCode": { "type": "integer" },
+          "keyCode": { "type": "integer" },
+          "altKey": { "type": "boolean" },
+          "ctrlKey": { "type": "boolean" },
+          "shiftKey": { "type": "boolean" },
+          "metaKey": { "type": "boolean", "optional": true }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "create",
+        "type": "function",
+        "description": "Creates a new offscreen tab.",
+        "parameters": [
+          {
+            "name": "createProperties",
+            "type": "object",
+            "properties": {
+              "url": {
+                "type": "string",
+                "description": "The URL to navigate the offscreen tab to initially. Fully-qualified URLs must include a scheme (i.e. 'http://www.google.com', not 'www.google.com'). Relative URLs will be relative to the current page within the extension. Note that you can't create offscreen tabs from background pages. The lifetime of the offscreen tab is tied to the context of its creator (tab, browser action, etc.)."
+              },
+              "width": {
+                "type": "integer",
+                "optional": true,
+                "minimum": 0,
+                "description": "Width of the offscreen tab. Defaults to width of the current tab."
+              },
+              "height": {
+                "type": "integer",
+                "optional": true,
+                "minimum": 0,
+                "description": "Height of the offscreen tab. Defaults to height of the current tab."
+              }
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,      
+            "parameters": [
+              {
+                "name": "offscreenTab",
+                "$ref": "OffscreenTab",
+                "optional": true,
+                "description": "Details of the offscreen tab."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "get",
+        "type": "function",
+        "description": "Gets details about the specified offscreen tab.",
+        "parameters": [
+          {
+            "name": "offscreenTabId",
+            "type": "integer",
+            "minimum": 0,
+            "description": "ID of the offscreen tab."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name": "offscreenTab",
+                "$ref": "OffscreenTab",
+                "description": "Details of the offscreen tab."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getAll",
+        "type": "function",
+        "description": "Gets an array of all offscreen tabs.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name": "offscreenTabs",
+                "type": "array", 
+                "items": { 
+                  "$ref": "OffscreenTab" 
+                } 
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "remove",
+        "type": "function",
+        "description": "Removes an offscreen tab.",
+        "parameters": [
+          {
+            "name": "offscreenTabId",
+            "type": "integer",
+            "minimum": 0,
+            "description": "ID of the offscreen tab."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "sendMouseEvent",
+        "type": "function",
+        "description": "Dispatches a mouse event in the offscreen tab.",
+        "parameters": [
+          {
+            "name": "offscreenTabId",
+            "type": "integer",
+            "minimum": 0,
+            "description": "ID of the offscreen tab."
+          },
+          {
+            "name": "mouseEvent",
+            "$ref": "MouseEvent",
+            "description": "A JavaScript MouseEvent object. Supported event types: <i>mousedown</i>, <i>mouseup</i>, <i>click</i>, <i>mousemove</i>, <i>mousewheel</i>."
+          },
+          {
+            "name": "position",
+            "type": "object",
+            "optional": true,
+            "description": "The position where the mouse event should be dispatched within the offscreen page. Not required for mousewheel events.",
+            "properties": {
+              "x": { "type": "integer", "minimum": 0 },
+              "y": { "type": "integer", "minimum": 0 }
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "sendKeyboardEvent",
+        "type": "function",
+        "description": "Dispatches a keyboard event in the offscreen tab.",
+        "parameters": [
+          {
+            "name": "offscreenTabId",
+            "type": "integer",
+            "minimum": 0,
+            "description": "ID of the offscreen tab."
+          },
+          {
+            "name": "keyboardEvent",
+            "$ref": "KeyboardEvent",
+            "description": "A JavaScript KeyboardEvent object. Supported event types: <i>keydown</i>, <i>keyup</i>, <i>keypress</i>. Note, only <i>keypress</i> produces a visible result on screen."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,  
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "toDataUrl",
+        "type": "function",
+        "description": "Captures the visible area of an offscreen tab. ",
+        "parameters": [
+          {
+            "name": "offscreenTabId",
+            "type": "integer",
+            "minimum": 0,
+            "description": "The ID of the offscreen tab."
+          },
+          {
+            "name": "options",
+            "type": "object",
+            "optional": true,
+            "description": "Set parameters of image capture, such as the format of the resulting image.",
+            "properties": {
+              "format": {
+                "type": "string",
+                "optional": true,
+                "enum": ["jpeg", "png"],
+                "description": "The format of the resulting image. Default is jpeg."
+              },
+              "quality": {
+                "type": "integer",
+                "optional": true,
+                "minimum": 0,
+                "maximum": 100,
+                "description": "When format is 'jpeg', controls the quality of the resulting image. This value is ignored for PNG images. As quality is decreased, the resulting image will have more visual artifacts, and the number of bytes needed to store it will decrease."
+              }
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name": "dataUrl",
+                "type": "string",
+                "description": "A data URL which encodes an image of the visible area of the captured offscreen tab. May be assigned to the 'src' property of an HTML Image element or WebGL texture source for display."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "update",
+        "type": "function",
+        "description": "Modifies the properties of an offscreen tab. Properties that are not specified in updateProperties are not modified.",
+        "parameters": [
+          {
+            "name": "offscreenTabId",
+            "type": "integer", 
+            "minimum": 0,
+            "description": "The ID of the offscreen tab."
+          },
+          {
+            "name": "updateProperties",
+            "type": "object",
+            "properties": {
+              "url": {
+                "type": "string",
+                "optional": true,
+                "description": "The URL the offscreen tab is displaying."
+              },
+              "width": {
+                "type": "integer",
+                "optional": true,
+                "minimum": 0,
+                "description": "Width of the window."
+              },
+              "height": {
+                "type": "integer",
+                "optional": true,
+                "minimum": 0,
+                "description": "Height of the window."
+              }
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onUpdated",
+        "type": "function",
+        "description": "Fires when an offscreen tab is updated. ",
+        "parameters": [
+          {
+            "name": "offscreenTabId",
+            "type": "integer",
+            "minimum": 0,
+            "description": "ID of the updated offscreen tab"
+          },
+          {
+            "name": "changeInfo",
+            "type": "object",
+            "description": "Lists the changes to the state of the offscreen tab that was updated.",
+            "properties": {
+              "url": {
+                "type": "string",
+                "optional": true,
+                "description": "The offscreen tab's URL if it has changed."
+              }
+            }
+          },
+          {
+            "name": "offscreenTab",
+            "$ref": "OffscreenTab",
+            "description": "Details of the offscreen tab."
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/experimental_power.json b/chrome/common/extensions/api/experimental_power.json
new file mode 100644
index 0000000..8f3ac0b
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_power.json
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 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.
+[
+  {
+    "namespace": "experimental.power",
+    "types": [],
+    "functions": [
+      {
+        "name": "requestKeepAwake",
+        "type": "function",
+        "description": "Requests that the machine be kept awake. Requests can be canceled manually with releaseKeepAwake, and are automatically canceled when the machine is restarted, or when the extension is disabled or uninstalled. Calling this multiple times has the same effect as calling it once.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "success",
+                "type": "boolean",
+                "description": "True if the request was successful, false otherwise."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "releaseKeepAwake",
+        "type": "function",
+        "description": "Releases a keep awake request. Once there are no keep awake requests active on the system, normal power management will resume.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "success",
+                "type": "boolean",
+                "description": "True if the release was successful, false otherwise."
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/experimental_processes.json b/chrome/common/extensions/api/experimental_processes.json
new file mode 100644
index 0000000..5fdeebc
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_processes.json
@@ -0,0 +1,262 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "experimental.processes",
+    "types": [
+      {
+        "id": "Process",
+        "type": "object",
+        "description": "An object containing information about one of the browser's processes.",
+        "properties": {
+          "id": {
+            "type": "integer",
+            "description": "Unique ID of the process provided by the browser." 
+          },
+          "osProcessId": {
+            "type": "integer",
+            "description": "The ID of the process, as provided by the OS."
+          },
+          "type": {
+            "type": "string",
+            "enum": ["browser", "renderer", "extension", "notification", "plugin", "worker", "nacl", "utility", "gpu", "other"],
+            "description": "The type of process."
+          },
+          "profile": {
+            "type": "string",
+            "description": "The profile which the process is associated with."
+          },
+          "tabs": {
+            "type": "array",  "items": {"type": "integer", "minimum": 0},
+            "description": "Array of Tab IDs that have a page rendered by this process. The list will be non-empty for renderer processes only."
+          },
+          "cpu": {
+            "type": "number",
+            "optional": true,
+            "description": "The most recent measurement of the process CPU usage, between 0 and 100%. Only available when receiving the object as part of a callback from onUpdated or onUpdatedWithMemory."
+          },
+          "network": {
+            "type": "number",
+            "optional": true,
+            "description": "The most recent measurement of the process network usage, in bytes per second. Only available when receiving the object as part of a callback from onUpdated or onUpdatedWithMemory."
+          },
+          "privateMemory": {
+            "type": "number",
+            "optional": true,
+            "description": "The most recent measurement of the process private memory usage, in bytes. Only available when receiving the object as part of a callback from onUpdatedWithMemory or getProcessInfo with the includeMemory flag."
+          },
+          "jsMemoryAllocated": {
+            "type": "number",
+            "optional": true,
+            "description": "The most recent measurement of the process JavaScript allocated memory, in bytes. Only available when receiving the object as part of a callback from onUpdated or onUpdatedWithMemory."
+          },
+          "jsMemoryUsed": {
+            "type": "number",
+            "optional": true,
+            "description": "The most recent measurement of the process JavaScript memory used, in bytes. Only available when receiving the object as part of a callback from onUpdated or onUpdatedWithMemory."
+          },
+          "sqliteMemory": {
+            "type": "number",
+            "optional": true,
+            "description": "The most recent measurement of the process’s SQLite memory usage, in bytes. Only available when receiving the object as part of a callback from onUpdated or onUpdatedWithMemory."
+          },
+          "fps": {
+            "type": "number",
+            "optional": true,
+            "description": "The most recent measurement of the process frames per second. Only available when receiving the object as part of a callback from onUpdated or onUpdatedWithMemory."
+          },
+          "imageCache": {
+            "$ref": "Cache",
+            "optional": true,
+            "description": "The most recent information about the image cache for the process. Only available when receiving the object as part of a callback from onUpdated or onUpdatedWithMemory."
+          },
+          "scriptCache": {
+            "$ref": "Cache",
+            "optional": true,
+            "description": "The most recent information about the script cache for the process. Only available when receiving the object as part of a callback from onUpdated or onUpdatedWithMemory."
+          },
+          "cssCache": {
+            "$ref": "Cache",
+            "optional": true,
+            "description": "The most recent information about the CSS cache for the process. Only available when receiving the object as part of a callback from onUpdated or onUpdatedWithMemory."
+          }
+        }
+      },
+      {
+        "id": "Cache",
+        "type": "object",
+        "description": "The Cache object contains information about the size and utilization of a cache used by the browser.",
+        "properties": {
+          "size": {
+            "type": "number",
+            "description": "The size of the cache, in bytes."
+          },
+          "liveSize": {
+            "type": "number",
+            "description": "The part of the cache that is utilized, in bytes."
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "terminate",
+        "type": "function",
+        "description": "Terminates the specified renderer process. Equivalent to visiting about:crash, but without changing the tab's URL.",
+        "parameters": [
+          {
+            "name": "processId",
+            "type": "integer",
+            "minimum": 0,
+            "description": "The ID of the process to be terminated."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "didTerminate",
+                "type": "boolean",
+                "description": "True if terminating the process was successful, otherwise false."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getProcessIdForTab",
+        "type": "function",
+        "description": "Returns the ID of the renderer process for the specified tab.",
+        "parameters": [
+          {
+            "name": "tabId",
+            "type": "integer",
+            "minimum": 0,
+            "description": "The ID of the tab for which the renderer process ID is to be returned."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name": "processId",
+                "type": "integer",
+                "description": "Process ID of the tab's renderer process."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getProcessInfo",
+        "type": "function",
+        "description": "Retrieves the process information for each process ID specified.",
+        "parameters": [
+          {
+            "name": "processIds",
+            "choices": [
+              {"type": "integer", "minimum": 0},
+              {"type": "array", "items": {"type": "integer", "minimum": 0}}
+            ],
+            "description": "The list of process IDs or single process ID for which to return the process information. An empty list indicates all processes are requested."
+          },
+          {
+            "name": "includeMemory",
+            "type": "boolean",
+            "description": "True if detailed memory usage is required. Note, collecting memory usage information incurs extra CPU usage and should only be queried for when needed."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the processes information is collected.",
+            "parameters": [
+              {
+                "name": "processes",
+                "description": "A dictionary of Process objects for each requested process that is a live child process of the current browser process, indexed by process ID. Metrics requiring aggregation over time will not be populated in each Process object.",
+                "type": "object",
+                "additionalProperties": { "$ref": "Process" }
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onUpdated",
+        "type": "function",
+        "description": "Fired each time the Task Manager updates its process statistics, providing the dictionary of updated Process objects, indexed by process ID.",
+        "parameters": [
+          {
+            "name": "processes",
+            "type": "object",
+            "description": "A dictionary of updated Process objects for each live process in the browser, indexed by process ID.  Metrics requiring aggregation over time will be populated in each Process object.",
+            "additionalProperties": { "$ref": "Process" }
+          }
+        ]
+      },
+      {
+        "name": "onUpdatedWithMemory",
+        "type": "function",
+        "description": "Fired each time the Task Manager updates its process statistics, providing the dictionary of updated Process objects, indexed by process ID. Identical to onUpdate, with the addition of memory usage details included in each Process object. Note, collecting memory usage information incurs extra CPU usage and should only be listened for when needed.",
+        "parameters": [
+          {
+            "name": "processes",
+            "type": "object",
+            "description": "A dictionary of updated Process objects for each live process in the browser, indexed by process ID.  Memory usage details will be included in each Process object.",
+            "additionalProperties": { "$ref": "Process" }
+          }
+        ]
+      },
+      {
+        "name": "onCreated",
+        "type": "function",
+        "description": "Fired each time a process is created, providing the corrseponding Process object.",
+        "parameters": [
+          {
+            "name": "process",
+            "description": "Details of the process that was created. Metrics requiring aggregation over time will not be populated in the object.",
+            "$ref": "Process"
+          }
+        ]
+      },
+      {
+        "name": "onUnresponsive",
+        "type": "function",
+        "description": "Fired each time a process becomes unresponsive, providing the corrseponding Process object.",
+        "parameters": [
+          {
+            "name": "process",
+            "description": "Details of the unresponsive process. Metrics requiring aggregation over time will not be populated in the object. Only available for renderer processes.",
+            "$ref": "Process"
+          }
+        ]
+      },
+      {
+        "name": "onExited",
+        "type": "function",
+        "description": "Fired each time a process is terminated, providing the type of exit.",
+        "parameters": [
+          {
+            "name": "processId",
+            "description": "The ID of the process that exited.",
+            "type": "integer"
+          },
+          {
+            "name": "exitType",
+            "description": "The type of exit that occurred for the process - normal, abnormal, killed, crashed. Only available for renderer processes.",
+            "type": "integer"
+          },
+          {
+            "name": "exitCode",
+            "description": "The exit code if the process exited abnormally. Only available for renderer processes.",
+            "type": "integer"
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/experimental_record.json b/chrome/common/extensions/api/experimental_record.json
new file mode 100644
index 0000000..ce55501
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_record.json
@@ -0,0 +1,109 @@
+[
+  {
+    "namespace": "experimental.record",
+    "types": [
+      {
+        "id": "SessionDetails",
+        "type": "object",
+        "description": "",
+        "properties": {
+          "extensionPath": {
+            "type": "string",
+            "optional": true,
+            "description": 
+                "Absolute path to an unpacked extension to run in the subbrowser session."
+          }
+        }
+      },
+      {
+        "id": "ReplayURLsResult",
+        "type": "object",
+        "description": "Return value for Replay callback",
+        "properties": {
+          "runTime": {
+            "type": "number",
+            "description": "Time in milliseconds to complete all runs."
+           },
+           "stats": {
+             "type": "string",
+             "description": "Full multiline dump of output stats, showing one statistic per line, comprising an abbreviated statistic name and its value (e.g. vmsize_f_b= 696164352 bytes for final vm size).  This is ugly, and will be changed shortly."
+          },
+          "errors": {
+            "type": "array",
+            "items": {"type": "string"},
+            "description": "List of errors during replay.  Presently, this should only be abnormal browser termination for unexpected reasons."
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "captureURLs",
+        "description": "",
+        "type": "function",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "Unique name of the capture.",
+            "name": "captureName"
+          },
+          {
+            "type": "array",
+            "items": {"type": "string"},
+            "description": "URL list to visit during capture.",
+            "name": "urls"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when capture has completed.",
+            "optional": true,
+            "parameters": [
+              {
+                "type": "array",
+                "items": {"type": "string"},
+                "name": "errors",
+                "description": "List of any URLs that failed to load, one error per textline, along with failure reason (e.g. unknown domain).  Also may include general abnormal-exit message if the subbrowser run failed for other reasons."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "replayURLs",
+        "description": "",
+        "type": "function",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "captureName",
+            "description": "Unique name of capture.  Use to determine cache."
+          },
+          {
+            "type": "integer", 
+            "name": "repeatCount",
+            "minimum": 0,
+            "maximum": 100
+          },
+          {
+            "$ref": "SessionDetails",
+            "name": "details",
+            "optional": true
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "description": "Called when playback has completed.",
+            "parameters": [
+              {
+                "$ref": "ReplayURLsResult",
+                "name": "result"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/experimental_rlz.json b/chrome/common/extensions/api/experimental_rlz.json
new file mode 100644
index 0000000..b954a4e
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_rlz.json
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "experimental.rlz",
+    "nodoc": true,
+    "types": [],
+    "functions": [
+      {
+        "name": "recordProductEvent",
+        "type": "function",
+        "description": "Records an RLZ event for a given product's access point.",
+        "parameters": [
+          {"name": "product", "type": "string", "minLength": 1, "maxLength": 1},
+          {"name": "accessPoint", "type": "string", "minLength": 1, "maxLength": 2},
+          {"name": "event", "type": "string", "enum": ["install", "set-to-google", "first-search", "activate"]}
+        ]
+      },
+      {
+        "name": "getAccessPointRlz",
+        "type": "function",
+        "description": "Gets the RLZ string to be used when accessing a Google property through the given access point.",
+        "parameters": [
+          {"name": "accessPoint", "type": "string", "minLength": 1, "maxLength": 2},
+          {"name": "callback", "type": "function", "parameters": [{"name": "rlz", "type": "string"}]}
+        ]
+      },
+      {
+        "name": "sendFinancialPing",
+        "type": "function",
+        "description": "Sends Google promotional information about this extension.",
+        "parameters": [
+          {"name": "product", "type": "string", "minLength": 1, "maxLength": 1},
+          {"name": "accessPoints", "type": "array", "items": {"type": "string", "minLength": 1, "maxLength": 2}, "minItems": 1},
+          {"name": "signature", "type": "string"},
+          {"name": "brand", "type": "string"},
+          {"name": "id", "type": "string"},
+          {"name": "lang", "type": "string"},
+          {"name": "exclude_machine_id", "type": "boolean"},
+          {"name": "callback", "type": "function", "optional": true, "parameters": [{"name": "sent", "type": "boolean"}]}
+        ]
+      },
+      {
+        "name": "clearProductState",
+        "type": "function",
+        "description": "Clears all product-specific RLZ state from the machine, as well as clearing all events for the specified access points.",
+        "parameters": [
+          {"name": "product", "type": "string", "minLength": 1, "maxLength": 1},
+          {"name": "accessPoints", "type": "array", "items": {"type": "string", "minLength": 1, "maxLength": 2}, "minItems": 1}
+        ]
+      }
+    ],
+    "events": []
+  }
+]
diff --git a/chrome/common/extensions/api/experimental_speech_input.json b/chrome/common/extensions/api/experimental_speech_input.json
new file mode 100644
index 0000000..280eb9d
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_speech_input.json
@@ -0,0 +1,166 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "experimental.speechInput",
+    "types": [
+      {
+        "id": "SpeechInputStartOptions",
+        "type": "object",
+        "description": "Object describing the options used for speech recognition.",
+        "properties": {
+          "language": {
+            "type": "string",
+            "optional": true,
+            "description": "BCP-47 language code of the language to recognize. When set to 'auto' or not defined defaults to user's most preferred content language. Will use 'en-US' if not supported or invalid."
+          },
+          "grammar": {
+            "type": "string",
+            "description": "Name of the grammar to use. Defaults to 'builtin:dictation'.",
+            "optional": true
+          },
+          "filterProfanities": {
+            "type": "boolean",
+            "optional": true,
+            "description": "Enable or disable profanity filtering. Will use the default Chrome filtering settings if not set."
+          }
+        }
+      },
+      {
+        "id": "SpeechInputError",
+        "type": "object",
+        "description": "Object describing a speech input error.",
+        "properties": {
+          "code": {
+            "type": "string",
+            "enum": ["noSpeechHeard", "noResults", "captureError", "networkError"],
+            "description": "Code identifying the error case."
+          }
+        }
+      },
+      {
+        "id": "SpeechInputResultHypothesis",
+        "type": "object",
+        "description": "Object describing a speech recognition hypothesis.",
+        "properties": {
+          "utterance": {
+            "type": "string",
+            "description": "Text of this hypothesis."
+          },
+          "confidence": {
+            "type": "number",
+            "description": "Confidence of the hypothesis. Rated from 0 (lowest) to 1 (highest)."
+          }
+        }
+      },
+      {
+        "id": "SpeechInputResultEvent",
+        "type": "object",
+        "description": "Object containing the recognition results.",
+        "properties": {
+          "hypotheses": {
+            "type": "array",
+            "optional": true,
+            "description": "Array of zero or more objects describing the stable candidate hypotheses sorted by decreasing likelihood.",
+            "items": { "$ref": "SpeechInputResultHypothesis" }
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "start",
+        "type": "function",
+        "description": "Request to start recording as a new speech recognition session.",
+        "parameters": [
+          {
+            "name": "options",
+            "$ref": "SpeechInputStartOptions",
+            "optional": true,
+            "description": "Options used for this speech recognition session."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "description": "Called when the speech recognition session has successfully started recording.",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "stop",
+        "type": "function",
+        "description": "Request to stop an ongoing speech recognition session.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "description": "Called when the audio recording has stopped and any pending recognition results have been completed.",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "isRecording",
+        "type": "function",
+        "description": "Determine if audio recording is currently in process in Chrome, not limited to this API.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "result",
+                "type": "boolean",
+                "description": "Flag set to true if recording is in process in Chrome, false otherwise."
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onError",
+        "type": "function",
+        "description": "Called in case of an error in speech recognition.",
+        "parameters": [
+          {
+            "name": "error",
+            "$ref": "SpeechInputError",
+            "description": "Error being reported."
+          }
+        ]
+      },
+      {
+        "name": "onSoundStart",
+        "type": "function",
+        "description": "Called when the system starts detecting sound in the input data.",
+        "parameters": []
+      },
+      {
+        "name": "onSoundEnd",
+        "type": "function",
+        "description": "Called when the system detects enough silence to consider the ongoing speech has ended.",
+        "parameters": []
+      },
+      {
+        "name": "onResult",
+        "type": "function",
+        "description": "Called when speech recognition results are available.",
+        "parameters": [
+          {
+            "name": "event",
+            "$ref": "SpeechInputResultEvent",
+            "description": "Object containing the speech recognition results."
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/experimental_system_info_cpu.idl b/chrome/common/extensions/api/experimental_system_info_cpu.idl
new file mode 100644
index 0000000..5e2308b
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_system_info_cpu.idl
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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.
+
+// File-level comment to appease parser. Eventually this will not be necessary.
+namespace experimental.systemInfo.cpu {
+
+  dictionary CpuInfo {
+    // The number of logical processors.
+    long numOfProcessors;
+    // The architecture name of the processors.
+    DOMString archName;
+    // The model name of the processors.
+    DOMString modelName;
+  };
+
+  dictionary CpuUpdateInfo {
+    // The average usage percent of all processors, as a number 
+    // between 0 and 100.
+    double averageUsage;
+    // The CPU usage array for each logic processor.
+    double[] usagePerProcessor;
+  };
+
+  callback CpuInfoCallback = void (CpuInfo info);
+  
+  interface Functions {
+    // Get CPU information.
+    static void get(CpuInfoCallback callback);
+  };
+
+  interface Events {
+    // Fired periodically to report CPU history usage information. The default
+    // period interval is 1 seconds.
+    static void onUpdated(CpuUpdateInfo info);
+  };
+};
diff --git a/chrome/common/extensions/api/experimental_system_info_memory.idl b/chrome/common/extensions/api/experimental_system_info_memory.idl
new file mode 100644
index 0000000..09f226f
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_system_info_memory.idl
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 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.
+
+// File-level comment to appease parser. Eventually this will not be necessary.
+namespace experimental.systemInfo.memory {
+
+  dictionary MemoryInfo {
+    // The total amount of physical memory capacity, in bytes.
+    double capacity;
+    // The amount of available capacity, in bytes.
+    double availableCapacity;
+  };
+
+  callback MemoryInfoCallback = void (MemoryInfo info);
+  
+  interface Functions {
+    // Get physical memory information.
+    static void get(MemoryInfoCallback callback);
+  };
+};
diff --git a/chrome/common/extensions/api/experimental_system_info_storage.idl b/chrome/common/extensions/api/experimental_system_info_storage.idl
new file mode 100644
index 0000000..13ed295
--- /dev/null
+++ b/chrome/common/extensions/api/experimental_system_info_storage.idl
@@ -0,0 +1,50 @@
+// Copyright (c) 2012 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.
+
+namespace experimental.systemInfo.storage {
+
+  enum StorageUnitType {
+    // Unknow storage type.
+    unknown,
+    // Hard disk.
+    harddisk,
+    // Removable storage, e.g.USB device.
+    removable
+  };
+
+  dictionary StorageUnitInfo {
+    // The unique id of the storage unit.
+    DOMString id;
+    // The type of storage device. The value is one of the constants defined
+    // for this type.
+    StorageUnitType type;
+    // The total amount of the storage space, in bytes.
+    double capacity;
+    // The available amount of the storage space, in bytes.
+    double availableCapacity;
+  };
+
+  dictionary StorageChangeInfo {
+    // The uniue id of the storage unit already changed.
+    DOMString id;
+    // The new value of the available capacity.
+    double availableCapacity;
+  };
+
+  callback StorageInfoCallback = void (StorageUnitInfo[] info);
+
+  interface Functions {
+    // Get the storage information from the system. The argument passed to the
+    // callback is an array of StorageUnitInfo objects.
+    static void get(StorageInfoCallback callback);
+  };
+
+  interface Events {
+    // Fired when the storage information is updated.
+    // |info|   : The changed information for the specified storage unit
+    static void onAvailableCapacityChanged(StorageChangeInfo info);
+  };
+ 
+};
+
diff --git a/chrome/common/extensions/api/extension.json b/chrome/common/extensions/api/extension.json
new file mode 100644
index 0000000..f10a52b
--- /dev/null
+++ b/chrome/common/extensions/api/extension.json
@@ -0,0 +1,384 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "extension",
+    "nocompile": true,
+    "dependencies": [ "events", "tabs" ],
+    "types": [
+      {
+        "id": "MessageSender",
+        "type": "object",
+        "description": "An object containing information about the script context that sent a message or request.",
+        "properties": {
+          "tab": {"$ref": "tabs.Tab", "optional": true, "description":"This property will <b>only</b> be present when the connection was opened from a tab or content script."},
+          "id": {"type": "string", "description": "The extension ID of the extension that opened the connection."}
+        }
+      },
+      {
+        "id": "Port",
+        "type": "object",
+        "description": "An object which allows two way communication with other pages.",
+        "properties": {
+          "name": {"type": "string"},
+          "onDisconnect": { "$ref": "events.Event" },
+          "onMessage": { "$ref": "events.Event" },
+          "postMessage": {"type": "function"},
+          "sender": {
+            "$ref": "MessageSender",
+            "optional": true,
+            "description": "This property will <b>only</b> be present on ports passed to onConnect/onConnectExternal listeners."
+          }
+        },
+        "additionalProperties": { "type": "any"}
+      }
+    ],
+    "properties": {
+      "lastError": {
+        "type": "object",
+        "optional": true,
+        "unprivileged": true,
+        "description": "Set for the lifetime of a callback if an ansychronous extension api has resulted in an error. If no error has occured lastError will be <var>undefined</var>.",
+        "properties": {
+          "message": { "type": "string", "description": "Description of the error that has taken place." }
+        }
+      },
+      "inIncognitoContext": {
+        "type": "boolean",
+        "optional": true,
+        "unprivileged": true,
+        "description": "True for content scripts running inside incognito tabs, and for extension pages running inside an incognito process. The latter only applies to extensions with 'split' incognito_behavior."
+      }
+    },
+    "functions": [
+      {
+        "name": "connect",
+        "type": "function",
+        "unprivileged": true,
+        "description": "Attempts to connect to other listeners within the extension (such as the extension's background page). This is primarily useful for content scripts connecting to their extension processes. Note that this does not connect to any listeners in a content script. Extensions may connect to content scripts embedded in tabs via <a href='tabs.html#method-connect'><code>chrome.tabs.connect()</code></a>.",
+        "parameters": [
+          {"type": "string", "name": "extensionId", "optional": true, "description": "The extension ID of the extension you want to connect to. If omitted, default is your own extension."},
+          {
+            "type": "object",
+            "name": "connectInfo",
+            "properties": {
+              "name": { "type": "string", "optional": true, "description": "Will be passed into onConnect for extension processes that are listening for the connection event." }
+            },
+            "optional": true
+          }
+        ],
+        "returns": {
+          "$ref": "Port",
+          "description": "Port through which messages can be sent and received with the extension. The port's <a href='extension.html#type-Port'>onDisconnect</a> event is fired if extension does not exist. "
+        }
+      },
+      {
+        "name": "connectNative",
+        "nodoc": true,
+        "type": "function",
+        "description": "Attempts to connect a native application in the host machine. The native application must have already registered itself in the proper directory.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "appName",
+            "description": "The name of the registered app to connect to."
+          },
+          {
+            "name": "connectionMessage",
+            "description": "The object that will be passed to the registered native app on connection.",
+            "type": "object",
+            "additionalProperties": {
+              "type": "any"
+            }
+          }
+        ],
+        "returns": {
+          "$ref": "Port",
+          "description": "Port through which messages can be sent and received with the application"
+        }
+      },
+      {
+        "name": "sendRequest",
+        "nodoc": true,
+        "type": "function",
+        "allowAmbiguousOptionalArguments": true,
+        "unprivileged": true,
+        "description": "Deprecated: Please use sendMessage.",
+        "parameters": [
+          {"type": "string", "name": "extensionId", "optional": true, "description": "The extension ID of the extension you want to connect to. If omitted, default is your own extension."},
+          { "type": "any", "name": "request" },
+          {
+            "type": "function",
+            "name": "responseCallback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "response",
+                "type": "any",
+                "description": "The JSON response object sent by the handler of the request. If an error occurs while connecting to the extension, the callback will be called with no arguments and <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will be set to the error message."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "sendMessage",
+        "type": "function",
+        "allowAmbiguousOptionalArguments": true,
+        "unprivileged": true,
+        "description": "Sends a single message to other listeners within the extension. Similar to chrome.extension.connect, but only sends a single message with an optional response. The <a href='extension.html#event-onMessage'>chrome.extension.onMessage</a> event is fired in each extension page of the extension. Note that extensions cannot send messages to content scripts using this method. To send messages to content scripts, use <a href='tabs.html#method-sendMessage'><code>chrome.tabs.sendMessage()</code></a>.",
+        "parameters": [
+          {"type": "string", "name": "extensionId", "optional": true, "description": "The extension ID of the extension you want to connect to. If omitted, default is your own extension."},
+          { "type": "any", "name": "message" },
+          {
+            "type": "function",
+            "name": "responseCallback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "response",
+                "type": "any",
+                "description": "The JSON response object sent by the handler of the message. If an error occurs while connecting to the extension, the callback will be called with no arguments and <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will be set to the error message."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "sendNativeMessage",
+        "nodoc": true,
+        "type": "function",
+        "description": "Send a single message to a registered native application.",
+        "parameters": [
+          {
+            "name": "registeredNativeApp",
+            "description": "The name of the registered native application.",
+            "type": "string"
+          },
+          {
+            "name": "message",
+            "description": "The message that will be passed to the registered native application.",
+            "type": "object",
+            "additionalProperties": {
+              "type": "any"
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "description": "Called with the response from the native application.",
+            "parameters": [
+              {
+                "name": "nativeResponse",
+                "type": "object",
+                "description": "Whatever the native application responds with.",
+                "additionalProperties": {
+                  "type": "any"
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getURL",
+        "type": "function",
+        "unprivileged": true,
+        "description": "Converts a relative path within an extension install directory to a fully-qualified URL.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "path",
+            "description": "A path to a resource within an extension expressed relative to its install directory."
+          }
+        ],
+        "returns": {
+          "type": "string",
+          "description": "The fully-qualified URL to the resource."
+        }
+      },
+      {
+        "name": "getViews",
+        "type": "function",
+        "description": "Returns an array of the JavaScript 'window' objects for each of the pages running inside the current extension.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "fetchProperties",
+            "optional": true,
+            "properties": {
+              "type": {
+                "type": "string",
+                "enum": ["tab", "infobar", "notification", "popup"],
+                "optional": true,
+                "description": "The type of view to get. If omitted, returns all views (including background pages and tabs). Valid values: 'tab', 'infobar', 'notification', 'popup'."
+              },
+              "windowId": {
+                "type": "integer",
+                "optional": true,
+                "description": "The window to restrict the search to. If omitted, returns all views."
+              }
+            }
+          }
+        ],
+        "returns": {
+          "type": "array",
+          "description": "Array of global objects",
+          "items": { "type": "object", "isInstanceOf": "global", "additionalProperties": { "type": "any" } }
+        }
+      },
+      {
+        "name": "getBackgroundPage",
+        "type": "function",
+        "description": "Returns the JavaScript 'window' object for the background page running inside the current extension. Returns null if the extension has no background page.",
+        "parameters": [],
+        "returns": {
+          "type": "object", "isInstanceOf": "global", "additionalProperties": { "type": "any" }
+         }
+      },
+      {
+        "name": "getExtensionTabs",
+        "nodoc": true,
+        "type": "function",
+        "maximumManifestVersion": 1,
+        "description": "Deprecated. Please use getViews({type: 'TAB'}). Returns an array of the JavaScript 'window' objects for each of the tabs running inside the current extension. If windowId is specified, returns only the 'window' objects of tabs attached to the specified window.",
+        "parameters": [
+          {"type": "integer", "name": "windowId", "optional": true}
+        ],
+        "returns": {
+          "type": "array",
+          "description": "Array of global window objects",
+          "items": { "type": "object", "isInstanceOf": "global", "additionalProperties": { "type": "any" } }
+        }
+      },
+      {
+        "name": "isAllowedIncognitoAccess",
+        "type": "function",
+        "description": "Retrieves the state of the extension's access to Incognito-mode (as determined by the user-controlled 'Allowed in Incognito' checkbox.",
+        "min_version": "12.0.706.0",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "isAllowedAccess",
+                "type": "boolean",
+                "description": "True if the extension has access to Incognito mode, false otherwise."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "isAllowedFileSchemeAccess",
+        "type": "function",
+        "description": "Retrieves the state of the extension's access to the 'file://' scheme (as determined by the user-controlled 'Allow access to File URLs' checkbox.",
+        "min_version": "12.0.706.0",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "isAllowedAccess",
+                "type": "boolean",
+                "description": "True if the extension can access the 'file://' scheme, false otherwise."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setUpdateUrlData",
+        "type": "function",
+        "description": "Sets the value of the ap CGI parameter used in the extension's update URL.  This value is ignored for extensions that are hosted in the Chrome Extension Gallery.",
+        "parameters": [
+          {"type": "string", "name": "data", "maxLength": 1024}
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onConnect",
+        "type": "function",
+        "unprivileged": true,
+        "anonymous": true,
+        "description": "Fired when a connection is made from either an extension process or a content script.",
+        "parameters": [
+          {"$ref": "Port", "name": "port"}
+        ]
+      },
+      {
+        "name": "onConnectExternal",
+        "type": "function",
+        "anonymous": true,
+        "description": "Fired when a connection is made from another extension.",
+        "parameters": [
+          {"$ref": "Port", "name": "port"}
+        ]
+      },
+      {
+        "name": "onRequest",
+        "nodoc": true,
+        "type": "function",
+        "anonymous": true,
+        "unprivileged": true,
+        "description": "Deprecated: please use onMessage.",
+        "parameters": [
+          {"name": "request", "type": "any", "description": "The request sent by the calling script."},
+          {"name": "sender", "$ref": "MessageSender" },
+          {"name": "sendResponse", "type": "function", "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object, or undefined if there is no response. If you have more than one <code>onRequest</code> listener in the same document, then only one may send a response." }
+        ]
+      },
+      {
+        "name": "onRequestExternal",
+        "nodoc": true,
+        "type": "function",
+        "anonymous": true,
+        "description": "Deprecated: please use onMessageExternal.",
+        "parameters": [
+          {"name": "request", "type": "any", "description": "The request sent by the calling script."},
+          {"name": "sender", "$ref": "MessageSender" },
+          {"name": "sendResponse", "type": "function", "description": "Function to call when you have a response. The argument should be any JSON-ifiable object, or undefined if there is no response." }
+        ]
+      },
+      {
+        "name": "onMessage",
+        "type": "function",
+        "anonymous": true,
+        "unprivileged": true,
+        "description": "Fired when a message is sent from either an extension process or a content script.",
+        "parameters": [
+          {"name": "message", "type": "any", "description": "The message sent by the calling script."},
+          {"name": "sender", "$ref": "MessageSender" },
+          {"name": "sendResponse", "type": "function", "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one <code>onMessage</code> listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until <code>sendResponse</code> is called)." }
+        ],
+        "returns": {
+          "type": "boolean",
+          "optional": "true",
+          "description": "Return true from the event listener if you wish to call <code>sendResponse</code> after the event listener returns."
+        }
+      },
+      {
+        "name": "onMessageExternal",
+        "type": "function",
+        "anonymous": true,
+        "description": "Fired when a message is sent from another extension. Cannot be used in a content script.",
+        "parameters": [
+          {"name": "message", "type": "any", "description": "The message sent by the calling script."},
+          {"name": "sender", "$ref": "MessageSender" },
+          {"name": "sendResponse", "type": "function", "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one <code>onMessage</code> listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until <code>sendResponse</code> is called)." }
+        ],
+        "returns": {
+          "type": "boolean",
+          "optional": "true",
+          "description": "Return true from the event listener if you wish to call <code>sendResponse</code> after the event listener returns."
+        }
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/extension_api.cc b/chrome/common/extensions/api/extension_api.cc
new file mode 100644
index 0000000..eaf64a6
--- /dev/null
+++ b/chrome/common/extensions/api/extension_api.cc
@@ -0,0 +1,867 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/api/extension_api.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "base/values.h"
+#include "chrome/common/extensions/api/generated_schemas.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/features/simple_feature_provider.h"
+#include "chrome/common/extensions/permissions/permission_set.h"
+#include "googleurl/src/gurl.h"
+#include "grit/common_resources.h"
+#include "grit/extensions_api_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+
+using base::DictionaryValue;
+using base::ListValue;
+using base::Value;
+
+namespace extensions {
+
+using api::GeneratedSchemas;
+
+namespace {
+
+const char* kChildKinds[] = {
+  "functions",
+  "events"
+};
+
+// Returns true if |dict| has an unprivileged "true" property.
+bool IsUnprivileged(const DictionaryValue* dict) {
+  bool unprivileged = false;
+  return dict->GetBoolean("unprivileged", &unprivileged) && unprivileged;
+}
+
+// Returns whether the list at |name_space_node|.|child_kind| contains any
+// children with an { "unprivileged": true } property.
+bool HasUnprivilegedChild(const DictionaryValue* name_space_node,
+                          const std::string& child_kind) {
+  const ListValue* child_list = NULL;
+  const DictionaryValue* child_dict = NULL;
+
+  if (name_space_node->GetList(child_kind, &child_list)) {
+    for (size_t i = 0; i < child_list->GetSize(); ++i) {
+      const DictionaryValue* item = NULL;
+      CHECK(child_list->GetDictionary(i, &item));
+      if (IsUnprivileged(item))
+        return true;
+    }
+  } else if (name_space_node->GetDictionary(child_kind, &child_dict)) {
+    for (DictionaryValue::Iterator it(*child_dict); it.HasNext();
+         it.Advance()) {
+      const DictionaryValue* item = NULL;
+      CHECK(it.value().GetAsDictionary(&item));
+      if (IsUnprivileged(item))
+        return true;
+    }
+  }
+
+  return false;
+}
+
+base::StringPiece ReadFromResource(int resource_id) {
+  return ResourceBundle::GetSharedInstance().GetRawDataResource(
+      resource_id);
+}
+
+scoped_ptr<ListValue> LoadSchemaList(const std::string& name,
+                                     const base::StringPiece& schema) {
+  std::string error_message;
+  scoped_ptr<Value> result(
+      base::JSONReader::ReadAndReturnError(
+          schema,
+          base::JSON_PARSE_RFC | base::JSON_DETACHABLE_CHILDREN,  // options
+          NULL,  // error code
+          &error_message));
+
+  // Tracking down http://crbug.com/121424
+  char buf[128];
+  base::snprintf(buf, arraysize(buf), "%s: (%d) '%s'",
+      name.c_str(),
+      result.get() ? result->GetType() : -1,
+      error_message.c_str());
+
+  CHECK(result.get()) << error_message << " for schema " << schema;
+  CHECK(result->IsType(Value::TYPE_LIST)) << " for schema " << schema;
+  return scoped_ptr<ListValue>(static_cast<ListValue*>(result.release()));
+}
+
+const DictionaryValue* FindListItem(const ListValue* list,
+                                    const std::string& property_name,
+                                    const std::string& property_value) {
+  for (size_t i = 0; i < list->GetSize(); ++i) {
+    const DictionaryValue* item = NULL;
+    CHECK(list->GetDictionary(i, &item))
+        << property_value << "/" << property_name;
+    std::string value;
+    if (item->GetString(property_name, &value) && value == property_value)
+      return item;
+  }
+
+  return NULL;
+}
+
+const DictionaryValue* GetSchemaChild(const DictionaryValue* schema_node,
+                                      const std::string& child_name) {
+  const DictionaryValue* child_node = NULL;
+  for (size_t i = 0; i < arraysize(kChildKinds); ++i) {
+    const ListValue* list_node = NULL;
+    if (!schema_node->GetList(kChildKinds[i], &list_node))
+      continue;
+    child_node = FindListItem(list_node, "name", child_name);
+    if (child_node)
+      return child_node;
+  }
+
+  return NULL;
+}
+
+struct Static {
+  Static()
+      : api(ExtensionAPI::CreateWithDefaultConfiguration()) {
+  }
+  scoped_ptr<ExtensionAPI> api;
+};
+
+base::LazyInstance<Static> g_lazy_instance = LAZY_INSTANCE_INITIALIZER;
+
+// If it exists and does not already specify a namespace, then the value stored
+// with key |key| in |schema| will be updated to |schema_namespace| + "." +
+// |schema[key]|.
+void MaybePrefixFieldWithNamespace(const std::string& schema_namespace,
+                                   DictionaryValue* schema,
+                                   const std::string& key) {
+  if (!schema->HasKey(key))
+    return;
+
+  std::string old_id;
+  CHECK(schema->GetString(key, &old_id));
+  if (old_id.find(".") == std::string::npos)
+    schema->SetString(key, schema_namespace + "." + old_id);
+}
+
+// Modify all "$ref" keys anywhere in |schema| to be prefxied by
+// |schema_namespace| if they do not already specify a namespace.
+void PrefixRefsWithNamespace(const std::string& schema_namespace,
+                             Value* value) {
+  if (value->IsType(Value::TYPE_LIST)) {
+    ListValue* list;
+    CHECK(value->GetAsList(&list));
+    for (ListValue::iterator i = list->begin(); i != list->end(); ++i) {
+      PrefixRefsWithNamespace(schema_namespace, *i);
+    }
+  } else if (value->IsType(Value::TYPE_DICTIONARY)) {
+    DictionaryValue* dict;
+    CHECK(value->GetAsDictionary(&dict));
+    MaybePrefixFieldWithNamespace(schema_namespace, dict, "$ref");
+    for (DictionaryValue::key_iterator i = dict->begin_keys();
+        i != dict->end_keys(); ++i) {
+      Value* next_value;
+      CHECK(dict->GetWithoutPathExpansion(*i, &next_value));
+      PrefixRefsWithNamespace(schema_namespace, next_value);
+    }
+  }
+}
+
+// Modify all objects in the "types" section of the schema to be prefixed by
+// |schema_namespace| if they do not already specify a namespace.
+void PrefixTypesWithNamespace(const std::string& schema_namespace,
+                              DictionaryValue* schema) {
+  if (!schema->HasKey("types"))
+    return;
+
+  // Add the namespace to all of the types defined in this schema
+  ListValue *types;
+  CHECK(schema->GetList("types", &types));
+  for (size_t i = 0; i < types->GetSize(); ++i) {
+    DictionaryValue *type;
+    CHECK(types->GetDictionary(i, &type));
+    MaybePrefixFieldWithNamespace(schema_namespace, type, "id");
+    MaybePrefixFieldWithNamespace(schema_namespace, type, "customBindings");
+  }
+}
+
+// Modify the schema so that all types are fully qualified.
+void PrefixWithNamespace(const std::string& schema_namespace,
+                         DictionaryValue* schema) {
+  PrefixTypesWithNamespace(schema_namespace, schema);
+  PrefixRefsWithNamespace(schema_namespace, schema);
+}
+
+}  // namespace
+
+// static
+ExtensionAPI* ExtensionAPI::GetSharedInstance() {
+  return g_lazy_instance.Get().api.get();
+}
+
+// static
+ExtensionAPI* ExtensionAPI::CreateWithDefaultConfiguration() {
+  ExtensionAPI* api = new ExtensionAPI();
+  api->InitDefaultConfiguration();
+  return api;
+}
+
+// static
+void ExtensionAPI::SplitDependencyName(const std::string& full_name,
+                                       std::string* feature_type,
+                                       std::string* feature_name) {
+  size_t colon_index = full_name.find(':');
+  if (colon_index == std::string::npos) {
+    // TODO(aa): Remove this code when all API descriptions have been updated.
+    *feature_type = "api";
+    *feature_name = full_name;
+    return;
+  }
+
+  *feature_type = full_name.substr(0, colon_index);
+  *feature_name = full_name.substr(colon_index + 1);
+}
+
+void ExtensionAPI::LoadSchema(const std::string& name,
+                              const base::StringPiece& schema) {
+  scoped_ptr<ListValue> schema_list(LoadSchemaList(name, schema));
+  std::string schema_namespace;
+
+  while (!schema_list->empty()) {
+    DictionaryValue* schema = NULL;
+    {
+      Value* value = NULL;
+      schema_list->Remove(schema_list->GetSize() - 1, &value);
+      CHECK(value->IsType(Value::TYPE_DICTIONARY));
+      schema = static_cast<DictionaryValue*>(value);
+    }
+
+    CHECK(schema->GetString("namespace", &schema_namespace));
+    PrefixWithNamespace(schema_namespace, schema);
+    schemas_[schema_namespace] = make_linked_ptr(schema);
+    CHECK_EQ(1u, unloaded_schemas_.erase(schema_namespace));
+
+    // Populate |{completely,partially}_unprivileged_apis_|.
+    //
+    // For "partially", only need to look at functions/events; even though
+    // there are unprivileged properties (e.g. in extensions), access to those
+    // never reaches C++ land.
+    bool unprivileged = false;
+    if (schema->GetBoolean("unprivileged", &unprivileged) && unprivileged) {
+      completely_unprivileged_apis_.insert(schema_namespace);
+    } else if (HasUnprivilegedChild(schema, "functions") ||
+               HasUnprivilegedChild(schema, "events") ||
+               HasUnprivilegedChild(schema, "properties")) {
+      partially_unprivileged_apis_.insert(schema_namespace);
+    }
+
+    // Populate |url_matching_apis_|.
+    ListValue* matches = NULL;
+    if (schema->GetList("matches", &matches)) {
+      URLPatternSet pattern_set;
+      for (size_t i = 0; i < matches->GetSize(); ++i) {
+        std::string pattern;
+        CHECK(matches->GetString(i, &pattern));
+        pattern_set.AddPattern(
+            URLPattern(UserScript::kValidUserScriptSchemes, pattern));
+      }
+      url_matching_apis_[schema_namespace] = pattern_set;
+    }
+
+    // Populate feature maps.
+    // TODO(aa): Consider not storing features that can never run on the current
+    // machine (e.g., because of platform restrictions).
+    bool uses_feature_system = false;
+    schema->GetBoolean("uses_feature_system", &uses_feature_system);
+    if (!uses_feature_system)
+      continue;
+
+    Feature* feature = new Feature();
+    feature->set_name(schema_namespace);
+    feature->Parse(schema);
+
+    FeatureMap* schema_features = new FeatureMap();
+    CHECK(features_.insert(
+        std::make_pair(schema_namespace,
+                       make_linked_ptr(schema_features))).second);
+    CHECK(schema_features->insert(
+        std::make_pair("", make_linked_ptr(feature))).second);
+
+    for (size_t i = 0; i < arraysize(kChildKinds); ++i) {
+      ListValue* child_list = NULL;
+      schema->GetList(kChildKinds[i], &child_list);
+      if (!child_list)
+        continue;
+
+      for (size_t j = 0; j < child_list->GetSize(); ++j) {
+        DictionaryValue* child = NULL;
+        CHECK(child_list->GetDictionary(j, &child));
+
+        scoped_ptr<Feature> child_feature(new Feature(*feature));
+        child_feature->Parse(child);
+        if (child_feature->Equals(*feature))
+          continue;  // no need to store no-op features
+
+        std::string child_name;
+        CHECK(child->GetString("name", &child_name));
+        child_feature->set_name(schema_namespace + "." + child_name);
+        CHECK(schema_features->insert(
+            std::make_pair(child_name,
+                           make_linked_ptr(child_feature.release()))).second);
+      }
+    }
+  }
+}
+
+ExtensionAPI::ExtensionAPI() {
+  RegisterDependencyProvider("api", this);
+
+  // TODO(aa): Can remove this when all JSON files are converted.
+  RegisterDependencyProvider("", this);
+}
+
+ExtensionAPI::~ExtensionAPI() {
+}
+
+void ExtensionAPI::InitDefaultConfiguration() {
+  RegisterDependencyProvider(
+      "manifest", SimpleFeatureProvider::GetManifestFeatures());
+  RegisterDependencyProvider(
+      "permission", SimpleFeatureProvider::GetPermissionFeatures());
+
+  // Schemas to be loaded from resources.
+  CHECK(unloaded_schemas_.empty());
+  RegisterSchema("app", ReadFromResource(
+      IDR_EXTENSION_API_JSON_APP));
+  RegisterSchema("bookmarks", ReadFromResource(
+      IDR_EXTENSION_API_JSON_BOOKMARKS));
+  RegisterSchema("bookmarkManagerPrivate", ReadFromResource(
+      IDR_EXTENSION_API_JSON_BOOKMARKMANAGERPRIVATE));
+  RegisterSchema("browserAction", ReadFromResource(
+      IDR_EXTENSION_API_JSON_BROWSERACTION));
+  RegisterSchema("browsingData", ReadFromResource(
+      IDR_EXTENSION_API_JSON_BROWSINGDATA));
+  RegisterSchema("chromeosInfoPrivate", ReadFromResource(
+      IDR_EXTENSION_API_JSON_CHROMEOSINFOPRIVATE));
+  RegisterSchema("cloudPrintPrivate", ReadFromResource(
+      IDR_EXTENSION_API_JSON_CLOUDPRINTPRIVATE));
+  RegisterSchema("commands", ReadFromResource(
+      IDR_EXTENSION_API_JSON_COMMANDS));
+  RegisterSchema("contentSettings", ReadFromResource(
+      IDR_EXTENSION_API_JSON_CONTENTSETTINGS));
+  RegisterSchema("contextMenus", ReadFromResource(
+      IDR_EXTENSION_API_JSON_CONTEXTMENUS));
+  RegisterSchema("cookies", ReadFromResource(
+      IDR_EXTENSION_API_JSON_COOKIES));
+  RegisterSchema("debugger", ReadFromResource(
+      IDR_EXTENSION_API_JSON_DEBUGGER));
+  RegisterSchema("declarativeWebRequest", ReadFromResource(
+      IDR_EXTENSION_API_JSON_DECLARATIVE_WEBREQUEST));
+  RegisterSchema("devtools", ReadFromResource(
+      IDR_EXTENSION_API_JSON_DEVTOOLS));
+  RegisterSchema("events", ReadFromResource(
+      IDR_EXTENSION_API_JSON_EVENTS));
+  RegisterSchema("experimental.accessibility", ReadFromResource(
+      IDR_EXTENSION_API_JSON_EXPERIMENTAL_ACCESSIBILITY));
+  RegisterSchema("experimental.app", ReadFromResource(
+      IDR_EXTENSION_API_JSON_EXPERIMENTAL_APP));
+  RegisterSchema("experimental.history", ReadFromResource(
+      IDR_EXTENSION_API_JSON_EXPERIMENTAL_HISTORY));
+  RegisterSchema("experimental.infobars", ReadFromResource(
+      IDR_EXTENSION_API_JSON_EXPERIMENTAL_INFOBARS));
+  RegisterSchema("experimental.input.virtualKeyboard", ReadFromResource(
+      IDR_EXTENSION_API_JSON_EXPERIMENTAL_INPUT_VIRTUALKEYBOARD));
+  RegisterSchema("experimental.offscreenTabs", ReadFromResource(
+      IDR_EXTENSION_API_JSON_EXPERIMENTAL_OFFSCREENTABS));
+  RegisterSchema("experimental.power", ReadFromResource(
+      IDR_EXTENSION_API_JSON_EXPERIMENTAL_POWER));
+  RegisterSchema("experimental.processes", ReadFromResource(
+      IDR_EXTENSION_API_JSON_EXPERIMENTAL_PROCESSES));
+  RegisterSchema("experimental.record", ReadFromResource(
+      IDR_EXTENSION_API_JSON_EXPERIMENTAL_RECORD));
+  RegisterSchema("experimental.rlz", ReadFromResource(
+      IDR_EXTENSION_API_JSON_EXPERIMENTAL_RLZ));
+  RegisterSchema("runtime", ReadFromResource(
+      IDR_EXTENSION_API_JSON_RUNTIME));
+  RegisterSchema("experimental.speechInput", ReadFromResource(
+      IDR_EXTENSION_API_JSON_EXPERIMENTAL_SPEECHINPUT));
+  RegisterSchema("extension", ReadFromResource(
+      IDR_EXTENSION_API_JSON_EXTENSION));
+  RegisterSchema("fileBrowserHandler", ReadFromResource(
+      IDR_EXTENSION_API_JSON_FILEBROWSERHANDLER));
+  RegisterSchema("fileBrowserHandlerInternal", ReadFromResource(
+      IDR_EXTENSION_API_JSON_FILEBROWSERHANDLERINTERNAL));
+  RegisterSchema("fileBrowserPrivate", ReadFromResource(
+      IDR_EXTENSION_API_JSON_FILEBROWSERPRIVATE));
+  RegisterSchema("fontSettings", ReadFromResource(
+      IDR_EXTENSION_API_JSON_FONTSSETTINGS));
+  RegisterSchema("history", ReadFromResource(
+      IDR_EXTENSION_API_JSON_HISTORY));
+  RegisterSchema("i18n", ReadFromResource(
+      IDR_EXTENSION_API_JSON_I18N));
+  RegisterSchema("idle", ReadFromResource(
+      IDR_EXTENSION_API_JSON_IDLE));
+  RegisterSchema("input.ime", ReadFromResource(
+      IDR_EXTENSION_API_JSON_INPUT_IME));
+  RegisterSchema("inputMethodPrivate", ReadFromResource(
+      IDR_EXTENSION_API_JSON_INPUTMETHODPRIVATE));
+  RegisterSchema("managedModePrivate", ReadFromResource(
+      IDR_EXTENSION_API_JSON_MANAGEDMODEPRIVATE));
+  RegisterSchema("management", ReadFromResource(
+      IDR_EXTENSION_API_JSON_MANAGEMENT));
+  RegisterSchema("mediaPlayerPrivate", ReadFromResource(
+      IDR_EXTENSION_API_JSON_MEDIAPLAYERPRIVATE));
+  RegisterSchema("metricsPrivate", ReadFromResource(
+      IDR_EXTENSION_API_JSON_METRICSPRIVATE));
+  RegisterSchema("echoPrivate", ReadFromResource(
+      IDR_EXTENSION_API_JSON_ECHOPRIVATE));
+  RegisterSchema("omnibox", ReadFromResource(
+      IDR_EXTENSION_API_JSON_OMNIBOX));
+  RegisterSchema("pageAction", ReadFromResource(
+      IDR_EXTENSION_API_JSON_PAGEACTION));
+  RegisterSchema("pageActions", ReadFromResource(
+      IDR_EXTENSION_API_JSON_PAGEACTIONS));
+  RegisterSchema("pageCapture", ReadFromResource(
+      IDR_EXTENSION_API_JSON_PAGECAPTURE));
+  RegisterSchema("permissions", ReadFromResource(
+      IDR_EXTENSION_API_JSON_PERMISSIONS));
+  RegisterSchema("privacy", ReadFromResource(
+      IDR_EXTENSION_API_JSON_PRIVACY));
+  RegisterSchema("proxy", ReadFromResource(
+      IDR_EXTENSION_API_JSON_PROXY));
+  RegisterSchema("scriptBadge", ReadFromResource(
+      IDR_EXTENSION_API_JSON_SCRIPTBADGE));
+  RegisterSchema("storage", ReadFromResource(
+      IDR_EXTENSION_API_JSON_STORAGE));
+  RegisterSchema("systemPrivate", ReadFromResource(
+      IDR_EXTENSION_API_JSON_SYSTEMPRIVATE));
+  RegisterSchema("tabs", ReadFromResource(
+      IDR_EXTENSION_API_JSON_TABS));
+  RegisterSchema("terminalPrivate", ReadFromResource(
+      IDR_EXTENSION_API_JSON_TERMINALPRIVATE));
+  RegisterSchema("test", ReadFromResource(
+      IDR_EXTENSION_API_JSON_TEST));
+  RegisterSchema("topSites", ReadFromResource(
+      IDR_EXTENSION_API_JSON_TOPSITES));
+  RegisterSchema("ttsEngine", ReadFromResource(
+      IDR_EXTENSION_API_JSON_TTSENGINE));
+  RegisterSchema("tts", ReadFromResource(
+      IDR_EXTENSION_API_JSON_TTS));
+  RegisterSchema("types", ReadFromResource(
+      IDR_EXTENSION_API_JSON_TYPES));
+  RegisterSchema("wallpaperPrivate", ReadFromResource(
+      IDR_EXTENSION_API_JSON_WALLPAPERPRIVATE));
+  RegisterSchema("webNavigation", ReadFromResource(
+      IDR_EXTENSION_API_JSON_WEBNAVIGATION));
+  RegisterSchema("webRequest", ReadFromResource(
+      IDR_EXTENSION_API_JSON_WEBREQUEST));
+  RegisterSchema("webRequestInternal", ReadFromResource(
+      IDR_EXTENSION_API_JSON_WEBREQUESTINTERNAL));
+  RegisterSchema("webSocketProxyPrivate", ReadFromResource(
+      IDR_EXTENSION_API_JSON_WEBSOCKETPROXYPRIVATE));
+  RegisterSchema("webstore", ReadFromResource(
+      IDR_EXTENSION_API_JSON_WEBSTORE));
+  RegisterSchema("webstorePrivate", ReadFromResource(
+      IDR_EXTENSION_API_JSON_WEBSTOREPRIVATE));
+  RegisterSchema("windows", ReadFromResource(
+      IDR_EXTENSION_API_JSON_WINDOWS));
+
+  // Schemas to be loaded via JSON generated from IDL files.
+  GeneratedSchemas::Get(&unloaded_schemas_);
+}
+
+void ExtensionAPI::RegisterSchema(const std::string& name,
+                                  const base::StringPiece& source) {
+  unloaded_schemas_[name] = source;
+}
+
+void ExtensionAPI::RegisterDependencyProvider(const std::string& name,
+                                              FeatureProvider* provider) {
+  dependency_providers_[name] = provider;
+}
+
+bool ExtensionAPI::IsAvailable(const std::string& full_name,
+                               const Extension* extension,
+                               Feature::Context context) {
+  std::set<std::string> dependency_names;
+  dependency_names.insert(full_name);
+  ResolveDependencies(&dependency_names);
+
+  for (std::set<std::string>::iterator iter = dependency_names.begin();
+       iter != dependency_names.end(); ++iter) {
+    Feature* feature = GetFeatureDependency(full_name);
+    CHECK(feature) << *iter;
+
+    Feature::Availability availability =
+        feature->IsAvailableToContext(extension, context);
+    if (!availability.is_available())
+      return false;
+  }
+
+  return true;
+}
+
+bool ExtensionAPI::IsPrivileged(const std::string& full_name) {
+  std::string child_name;
+  std::string api_name = GetAPINameFromFullName(full_name, &child_name);
+
+  // First try to use the feature system.
+  Feature* feature(GetFeature(full_name));
+  if (feature) {
+    // An API is 'privileged' if it or any of its dependencies can only be run
+    // in a blessed context.
+    std::set<std::string> resolved_dependencies;
+    resolved_dependencies.insert(full_name);
+    ResolveDependencies(&resolved_dependencies);
+    for (std::set<std::string>::iterator iter = resolved_dependencies.begin();
+         iter != resolved_dependencies.end(); ++iter) {
+      Feature* dependency = GetFeatureDependency(*iter);
+      for (std::set<Feature::Context>::iterator context =
+               dependency->contexts()->begin();
+           context != dependency->contexts()->end(); ++context) {
+        if (*context != Feature::BLESSED_EXTENSION_CONTEXT)
+          return false;
+      }
+    }
+    return true;
+  }
+
+  // If this API hasn't been converted yet, fall back to the old system.
+  if (completely_unprivileged_apis_.count(api_name))
+    return false;
+
+  const DictionaryValue* schema = GetSchema(api_name);
+  if (partially_unprivileged_apis_.count(api_name))
+    return IsChildNamePrivileged(schema, child_name);
+
+  return true;
+}
+
+bool ExtensionAPI::IsChildNamePrivileged(const DictionaryValue* name_space_node,
+                                         const std::string& child_name) {
+  bool unprivileged = false;
+  const DictionaryValue* child = GetSchemaChild(name_space_node, child_name);
+  if (!child || !child->GetBoolean("unprivileged", &unprivileged))
+    return true;
+
+  return !unprivileged;
+}
+
+const DictionaryValue* ExtensionAPI::GetSchema(const std::string& full_name) {
+  std::string child_name;
+  std::string api_name = GetAPINameFromFullName(full_name, &child_name);
+
+  const DictionaryValue* result = NULL;
+  SchemaMap::iterator maybe_schema = schemas_.find(api_name);
+  if (maybe_schema != schemas_.end()) {
+    result = maybe_schema->second.get();
+  } else {
+    // Might not have loaded yet; or might just not exist.
+    std::map<std::string, base::StringPiece>::iterator maybe_schema_resource =
+        unloaded_schemas_.find(api_name);
+    if (maybe_schema_resource == unloaded_schemas_.end())
+      return NULL;
+
+    LoadSchema(maybe_schema_resource->first, maybe_schema_resource->second);
+    maybe_schema = schemas_.find(api_name);
+    CHECK(schemas_.end() != maybe_schema);
+    result = maybe_schema->second.get();
+  }
+
+  if (!child_name.empty())
+    result = GetSchemaChild(result, child_name);
+
+  return result;
+}
+
+namespace {
+
+const char* kDisallowedPlatformAppFeatures[] = {
+  // "app" refers to the the legacy app namespace for hosted apps.
+  "app",
+  "extension",
+  "tabs",
+  "windows"
+};
+
+bool IsFeatureAllowedForExtension(const std::string& feature,
+                                  const extensions::Extension& extension) {
+  if (extension.is_platform_app()) {
+    for (size_t i = 0; i < arraysize(kDisallowedPlatformAppFeatures); ++i) {
+      if (feature == kDisallowedPlatformAppFeatures[i])
+        return false;
+    }
+  }
+
+  return true;
+}
+
+// Removes APIs from |apis| that should not be allowed for |extension|.
+// TODO(kalman/asargent) - Make it possible to specify these rules
+// declaratively.
+void RemoveDisallowedAPIs(const Extension& extension,
+                          std::set<std::string>* apis) {
+  CHECK(apis);
+  std::set<std::string>::iterator i = apis->begin();
+  while (i != apis->end()) {
+    if (!IsFeatureAllowedForExtension(*i, extension)) {
+      apis->erase(i++);
+    } else {
+      ++i;
+    }
+  }
+}
+
+}  // namespace
+
+scoped_ptr<std::set<std::string> > ExtensionAPI::GetAPIsForContext(
+    Feature::Context context, const Extension* extension, const GURL& url) {
+  // We're forced to load all schemas now because we need to know the metadata
+  // about every API -- and the metadata is stored in the schemas themselves.
+  // This is a shame.
+  // TODO(aa/kalman): store metadata in a separate file and don't load all
+  // schemas.
+  LoadAllSchemas();
+
+  std::set<std::string> temp_result;
+
+  // First handle all the APIs that have been converted to the feature system.
+  if (extension) {
+    for (APIFeatureMap::iterator iter = features_.begin();
+         iter != features_.end(); ++iter) {
+      if (IsAvailable(iter->first, extension, context))
+        temp_result.insert(iter->first);
+    }
+  }
+
+  // Second, fall back to the old way.
+  // TODO(aa): Remove this when all APIs have been converted.
+  switch (context) {
+    case Feature::UNSPECIFIED_CONTEXT:
+      break;
+
+    case Feature::BLESSED_EXTENSION_CONTEXT:
+      if (extension) {
+        // Availability is determined by the permissions of the extension.
+        GetAllowedAPIs(extension, &temp_result);
+        ResolveDependencies(&temp_result);
+        RemoveDisallowedAPIs(*extension, &temp_result);
+      }
+      break;
+
+    case Feature::UNBLESSED_EXTENSION_CONTEXT:
+    case Feature::CONTENT_SCRIPT_CONTEXT:
+      if (extension) {
+        // Same as BLESSED_EXTENSION_CONTEXT, but only those APIs that are
+        // unprivileged.
+        GetAllowedAPIs(extension, &temp_result);
+        // Resolving dependencies before removing unprivileged APIs means that
+        // some unprivileged APIs may have unrealised dependencies. Too bad!
+        ResolveDependencies(&temp_result);
+        RemovePrivilegedAPIs(&temp_result);
+      }
+      break;
+
+    case Feature::WEB_PAGE_CONTEXT:
+      if (url.is_valid()) {
+        // Availablility is determined by the url.
+        GetAPIsMatchingURL(url, &temp_result);
+      }
+      break;
+  }
+
+  // Filter out all non-API features and remove the feature type part of the
+  // name.
+  scoped_ptr<std::set<std::string> > result(new std::set<std::string>());
+  for (std::set<std::string>::iterator iter = temp_result.begin();
+       iter != temp_result.end(); ++iter) {
+    std::string feature_type;
+    std::string feature_name;
+    SplitDependencyName(*iter, &feature_type, &feature_name);
+    if (feature_type == "api")
+      result->insert(feature_name);
+  }
+
+  return result.Pass();
+}
+
+Feature* ExtensionAPI::GetFeature(const std::string& full_name) {
+  // Ensure it's loaded.
+  GetSchema(full_name);
+
+  std::string child_name;
+  std::string api_namespace = GetAPINameFromFullName(full_name, &child_name);
+
+  APIFeatureMap::iterator feature_map = features_.find(api_namespace);
+  if (feature_map == features_.end())
+    return NULL;
+
+  Feature* result = NULL;
+  FeatureMap::iterator child_feature = feature_map->second->find(child_name);
+  if (child_feature != feature_map->second->end()) {
+    result = child_feature->second.get();
+  } else {
+    FeatureMap::iterator parent_feature = feature_map->second->find("");
+    CHECK(parent_feature != feature_map->second->end());
+    result = parent_feature->second.get();
+  }
+
+  if (result->contexts()->empty()) {
+    LOG(ERROR) << "API feature '" << full_name
+               << "' must specify at least one context.";
+    return NULL;
+  }
+
+  return result;
+}
+
+Feature* ExtensionAPI::GetFeatureDependency(const std::string& full_name) {
+  std::string feature_type;
+  std::string feature_name;
+  SplitDependencyName(full_name, &feature_type, &feature_name);
+
+  FeatureProviderMap::iterator provider =
+      dependency_providers_.find(feature_type);
+  CHECK(provider != dependency_providers_.end()) << full_name;
+
+  Feature* feature = provider->second->GetFeature(feature_name);
+  CHECK(feature) << full_name;
+
+  return feature;
+}
+
+std::string ExtensionAPI::GetAPINameFromFullName(const std::string& full_name,
+                                                 std::string* child_name) {
+  std::string api_name_candidate = full_name;
+  while (true) {
+    if (features_.find(api_name_candidate) != features_.end() ||
+        schemas_.find(api_name_candidate) != schemas_.end() ||
+        unloaded_schemas_.find(api_name_candidate) != unloaded_schemas_.end()) {
+      std::string result = api_name_candidate;
+
+      if (child_name) {
+        if (result.length() < full_name.length())
+          *child_name = full_name.substr(result.length() + 1);
+        else
+          *child_name = "";
+      }
+
+      return result;
+    }
+
+    size_t last_dot_index = api_name_candidate.rfind('.');
+    if (last_dot_index == std::string::npos)
+      break;
+
+    api_name_candidate = api_name_candidate.substr(0, last_dot_index);
+  }
+
+  *child_name = "";
+  return "";
+}
+
+void ExtensionAPI::GetAllowedAPIs(
+    const Extension* extension, std::set<std::string>* out) {
+  for (SchemaMap::const_iterator i = schemas_.begin(); i != schemas_.end();
+      ++i) {
+    if (features_.find(i->first) != features_.end()) {
+      // This API is controlled by the feature system. Nothing to do here.
+      continue;
+    }
+
+    if (extension->required_permission_set()->HasAnyAccessToAPI(i->first) ||
+        extension->optional_permission_set()->HasAnyAccessToAPI(i->first)) {
+      out->insert(i->first);
+    }
+  }
+}
+
+void ExtensionAPI::ResolveDependencies(std::set<std::string>* out) {
+  std::set<std::string> missing_dependencies;
+  for (std::set<std::string>::iterator i = out->begin(); i != out->end(); ++i)
+    GetMissingDependencies(*i, *out, &missing_dependencies);
+
+  while (missing_dependencies.size()) {
+    std::string next = *missing_dependencies.begin();
+    missing_dependencies.erase(next);
+    out->insert(next);
+    GetMissingDependencies(next, *out, &missing_dependencies);
+  }
+}
+
+void ExtensionAPI::GetMissingDependencies(
+    const std::string& api_name,
+    const std::set<std::string>& excluding,
+    std::set<std::string>* out) {
+  std::string feature_type;
+  std::string feature_name;
+  SplitDependencyName(api_name, &feature_type, &feature_name);
+
+  // Only API features can have dependencies for now.
+  if (feature_type != "api")
+    return;
+
+  const DictionaryValue* schema = GetSchema(feature_name);
+  CHECK(schema) << "Schema for " << feature_name << " not found";
+
+  const ListValue* dependencies = NULL;
+  if (!schema->GetList("dependencies", &dependencies))
+    return;
+
+  for (size_t i = 0; i < dependencies->GetSize(); ++i) {
+    std::string dependency_name;
+    if (dependencies->GetString(i, &dependency_name) &&
+        !excluding.count(dependency_name)) {
+      out->insert(dependency_name);
+    }
+  }
+}
+
+void ExtensionAPI::RemovePrivilegedAPIs(std::set<std::string>* apis) {
+  std::set<std::string> privileged_apis;
+  for (std::set<std::string>::iterator i = apis->begin(); i != apis->end();
+      ++i) {
+    if (!completely_unprivileged_apis_.count(*i) &&
+        !partially_unprivileged_apis_.count(*i)) {
+      privileged_apis.insert(*i);
+    }
+  }
+  for (std::set<std::string>::iterator i = privileged_apis.begin();
+      i != privileged_apis.end(); ++i) {
+    apis->erase(*i);
+  }
+}
+
+void ExtensionAPI::GetAPIsMatchingURL(const GURL& url,
+                                      std::set<std::string>* out) {
+  for (std::map<std::string, URLPatternSet>::const_iterator i =
+      url_matching_apis_.begin(); i != url_matching_apis_.end(); ++i) {
+    if (features_.find(i->first) != features_.end()) {
+      // This API is controlled by the feature system. Nothing to do.
+      continue;
+    }
+
+    if (i->second.MatchesURL(url))
+      out->insert(i->first);
+  }
+}
+
+void ExtensionAPI::LoadAllSchemas() {
+  while (unloaded_schemas_.size()) {
+    std::map<std::string, base::StringPiece>::iterator it =
+        unloaded_schemas_.begin();
+    LoadSchema(it->first, it->second);
+  }
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/api/extension_api.h b/chrome/common/extensions/api/extension_api.h
new file mode 100644
index 0000000..218f97f
--- /dev/null
+++ b/chrome/common/extensions/api/extension_api.h
@@ -0,0 +1,189 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_API_EXTENSION_API_H_
+#define CHROME_COMMON_EXTENSIONS_API_EXTENSION_API_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "base/string_piece.h"
+#include "base/values.h"
+#include "chrome/common/extensions/features/feature.h"
+#include "chrome/common/extensions/features/feature_provider.h"
+#include "chrome/common/extensions/url_pattern_set.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class Value;
+}
+
+class GURL;
+
+namespace extensions {
+
+class Extension;
+class Feature;
+
+// C++ Wrapper for the JSON API definitions in chrome/common/extensions/api/.
+//
+// WARNING: This class is accessed on multiple threads in the browser process
+// (see ExtensionFunctionDispatcher). No state should be modified after
+// construction.
+class ExtensionAPI : public FeatureProvider {
+ public:
+  // Returns a single shared instance of this class. This is the typical use
+  // case in Chrome.
+  //
+  // TODO(aa): Make this const to enforce thread-safe usage.
+  static ExtensionAPI* GetSharedInstance();
+
+  // Creates a new instance configured the way ExtensionAPI typically is in
+  // Chrome. Use the default constructor to get a clean instance.
+  static ExtensionAPI* CreateWithDefaultConfiguration();
+
+  // Splits a name like "permission:bookmark" into ("permission", "bookmark").
+  // The first part refers to a type of feature, for example "manifest",
+  // "permission", or "api". The second part is the full name of the feature.
+  static void SplitDependencyName(const std::string& full_name,
+                                  std::string* feature_type,
+                                  std::string* feature_name);
+
+  // Creates a completely clean instance. Configure using RegisterSchema() and
+  // RegisterDependencyProvider before use.
+  ExtensionAPI();
+  virtual ~ExtensionAPI();
+
+  void RegisterSchema(const std::string& api_name,
+                      const base::StringPiece& source);
+
+  void RegisterDependencyProvider(const std::string& name,
+                                  FeatureProvider* provider);
+
+  // Returns true if the specified API is available. |api_full_name| can be
+  // either a namespace name (like "bookmarks") or a member name (like
+  // "bookmarks.create"). Returns true if the feature and all of its
+  // dependencies are available to the specified context.
+  bool IsAvailable(const std::string& api_full_name,
+                   const Extension* extension,
+                   Feature::Context context);
+
+  // Returns true if |name| is a privileged API path. Privileged paths can only
+  // be called from extension code which is running in its own designated
+  // extension process. They cannot be called from extension code running in
+  // content scripts, or other low-privileged contexts.
+  bool IsPrivileged(const std::string& name);
+
+  // Gets the schema for the extension API with namespace |full_name|.
+  // Ownership remains with this object.
+  const base::DictionaryValue* GetSchema(const std::string& full_name);
+
+  // Gets the APIs available to |context| given an |extension| and |url|. The
+  // extension or URL may not be relevant to all contexts, and may be left
+  // NULL/empty.
+  scoped_ptr<std::set<std::string> > GetAPIsForContext(
+      Feature::Context context, const Extension* extension, const GURL& url);
+
+  // Gets a Feature object describing the API with the specified |full_name|.
+  // This can be either an API namespace (like history, or
+  // experimental.bookmarks), or it can be an individual function or event.
+  virtual Feature* GetFeature(const std::string& full_name) OVERRIDE;
+
+  // Splits a full name from the extension API into its API and child name
+  // parts. Some examples:
+  //
+  // "bookmarks.create" -> ("bookmarks", "create")
+  // "experimental.input.ui.cursorUp" -> ("experimental.input.ui", "cursorUp")
+  // "storage.sync.set" -> ("storage", "sync.get")
+  // "<unknown-api>.monkey" -> ("", "")
+  //
+  // The |child_name| parameter can be be NULL if you don't need that part.
+  std::string GetAPINameFromFullName(const std::string& full_name,
+                                     std::string* child_name);
+
+  void InitDefaultConfiguration();
+
+  // Loads the schemas registered with RegisterSchema().
+  void LoadAllSchemas();
+
+ private:
+  friend struct DefaultSingletonTraits<ExtensionAPI>;
+
+  // Loads a schema.
+  void LoadSchema(const std::string& name, const base::StringPiece& schema);
+
+  // Returns true if the function or event under |namespace_node| with
+  // the specified |child_name| is privileged, or false otherwise. If the name
+  // is not found, defaults to privileged.
+  bool IsChildNamePrivileged(const base::DictionaryValue* namespace_node,
+                             const std::string& child_name);
+
+  // Adds all APIs to |out| that |extension| has any permission (required or
+  // optional) to use.
+  // NOTE: This only works for non-feature-controlled APIs.
+  // TODO(aa): Remove this when all APIs are converted to the feature system.
+  void GetAllowedAPIs(const Extension* extension, std::set<std::string>* out);
+
+  // Gets a feature from any dependency provider.
+  Feature* GetFeatureDependency(const std::string& dependency_name);
+
+  // Adds dependent schemas to |out| as determined by the "dependencies"
+  // property.
+  // TODO(aa): Consider making public and adding tests.
+  void ResolveDependencies(std::set<std::string>* out);
+
+  // Adds any APIs listed in "dependencies" found in the schema for |api_name|
+  // but not in |excluding| to |out|.
+  void GetMissingDependencies(
+      const std::string& api_name,
+      const std::set<std::string>& excluding,
+      std::set<std::string>* out);
+
+  // Removes all APIs from |apis| which are *entirely* privileged. This won't
+  // include APIs such as "storage" which is entirely unprivileged, nor
+  // "extension" which has unprivileged components.
+  void RemovePrivilegedAPIs(std::set<std::string>* apis);
+
+  // Adds an APIs that match |url| to |out|.
+  // NOTE: This only works for non-feature-controlled APIs.
+  // TODO(aa): Remove this when all APIs are converted to the feature system.
+  void GetAPIsMatchingURL(const GURL& url, std::set<std::string>* out);
+
+  // Map from each API that hasn't been loaded yet to the schema which defines
+  // it. Note that there may be multiple APIs per schema.
+  std::map<std::string, base::StringPiece> unloaded_schemas_;
+
+  // Schemas for each namespace.
+  typedef std::map<std::string, linked_ptr<const DictionaryValue> > SchemaMap;
+  SchemaMap schemas_;
+
+  // APIs that are entirely unprivileged.
+  std::set<std::string> completely_unprivileged_apis_;
+
+  // APIs that are not entirely unprivileged, but have unprivileged components.
+  std::set<std::string> partially_unprivileged_apis_;
+
+  // APIs that have URL matching permissions.
+  std::map<std::string, URLPatternSet> url_matching_apis_;
+
+  typedef std::map<std::string, linked_ptr<Feature> > FeatureMap;
+  typedef std::map<std::string, linked_ptr<FeatureMap> > APIFeatureMap;
+  APIFeatureMap features_;
+
+  // FeatureProviders used for resolving dependencies.
+  typedef std::map<std::string, FeatureProvider*> FeatureProviderMap;
+  FeatureProviderMap dependency_providers_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExtensionAPI);
+};
+
+}  // extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_API_EXTENSION_API_H_
diff --git a/chrome/common/extensions/api/extension_api_unittest.cc b/chrome/common/extensions/api/extension_api_unittest.cc
new file mode 100644
index 0000000..7d14560
--- /dev/null
+++ b/chrome/common/extensions/api/extension_api_unittest.cc
@@ -0,0 +1,488 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/api/extension_api.h"
+
+#include <string>
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/json/json_writer.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/values.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/extension.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+namespace {
+
+class TestFeatureProvider : public FeatureProvider {
+ public:
+  explicit TestFeatureProvider(Feature::Context context)
+      : context_(context) {
+  }
+
+  virtual Feature* GetFeature(const std::string& name) OVERRIDE {
+    Feature* result = new Feature();
+    result->set_name(name);
+    result->extension_types()->insert(Extension::TYPE_EXTENSION);
+    result->contexts()->insert(context_);
+    to_destroy_.push_back(make_linked_ptr(result));
+    return result;
+  }
+
+ private:
+  std::vector<linked_ptr<Feature> > to_destroy_;
+  Feature::Context context_;
+};
+
+TEST(ExtensionAPI, Creation) {
+  ExtensionAPI* shared_instance = ExtensionAPI::GetSharedInstance();
+  EXPECT_EQ(shared_instance, ExtensionAPI::GetSharedInstance());
+
+  scoped_ptr<ExtensionAPI> new_instance(
+      ExtensionAPI::CreateWithDefaultConfiguration());
+  EXPECT_NE(new_instance.get(),
+            scoped_ptr<ExtensionAPI>(
+                ExtensionAPI::CreateWithDefaultConfiguration()).get());
+
+  ExtensionAPI empty_instance;
+
+  struct {
+    ExtensionAPI* api;
+    bool expect_populated;
+  } test_data[] = {
+    { shared_instance, true },
+    { new_instance.get(), true },
+    { &empty_instance, false }
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+    EXPECT_EQ(test_data[i].expect_populated,
+              test_data[i].api->GetSchema("bookmarks.create") != NULL);
+  }
+}
+
+TEST(ExtensionAPI, SplitDependencyName) {
+  struct {
+    std::string input;
+    std::string expected_feature_type;
+    std::string expected_feature_name;
+  } test_data[] = {
+    { "", "api", "" },  // assumes "api" when no type is present
+    { "foo", "api", "foo" },
+    { "foo:", "foo", "" },
+    { ":foo", "", "foo" },
+    { "foo:bar", "foo", "bar" },
+    { "foo:bar.baz", "foo", "bar.baz" }
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+    std::string feature_type;
+    std::string feature_name;
+    ExtensionAPI::SplitDependencyName(test_data[i].input, &feature_type,
+                                      &feature_name);
+    EXPECT_EQ(test_data[i].expected_feature_type, feature_type) << i;
+    EXPECT_EQ(test_data[i].expected_feature_name, feature_name) << i;
+  }
+}
+
+TEST(ExtensionAPI, IsPrivileged) {
+  scoped_ptr<ExtensionAPI> extension_api(
+      ExtensionAPI::CreateWithDefaultConfiguration());
+
+  EXPECT_FALSE(extension_api->IsPrivileged("extension.connect"));
+  EXPECT_FALSE(extension_api->IsPrivileged("extension.onConnect"));
+
+  // Properties are not supported yet.
+  EXPECT_TRUE(extension_api->IsPrivileged("extension.lastError"));
+
+  // Default unknown names to privileged for paranoia's sake.
+  EXPECT_TRUE(extension_api->IsPrivileged(""));
+  EXPECT_TRUE(extension_api->IsPrivileged("<unknown-namespace>"));
+  EXPECT_TRUE(extension_api->IsPrivileged("extension.<unknown-member>"));
+
+  // Exists, but privileged.
+  EXPECT_TRUE(extension_api->IsPrivileged("extension.getViews"));
+  EXPECT_TRUE(extension_api->IsPrivileged("history.search"));
+
+  // Whole APIs that are unprivileged.
+  EXPECT_FALSE(extension_api->IsPrivileged("app.getDetails"));
+  EXPECT_FALSE(extension_api->IsPrivileged("app.isInstalled"));
+  EXPECT_FALSE(extension_api->IsPrivileged("storage.local"));
+  EXPECT_FALSE(extension_api->IsPrivileged("storage.local.onChanged"));
+  EXPECT_FALSE(extension_api->IsPrivileged("storage.local.set"));
+  EXPECT_FALSE(extension_api->IsPrivileged("storage.local.MAX_ITEMS"));
+  EXPECT_FALSE(extension_api->IsPrivileged("storage.set"));
+}
+
+TEST(ExtensionAPI, IsPrivilegedFeatures) {
+  struct {
+    std::string filename;
+    std::string api_full_name;
+    bool expect_is_privilged;
+    Feature::Context test2_contexts;
+  } test_data[] = {
+    { "is_privileged_features_1.json", "test", false,
+      Feature::UNSPECIFIED_CONTEXT },
+    { "is_privileged_features_2.json", "test", true,
+      Feature::UNSPECIFIED_CONTEXT },
+    { "is_privileged_features_3.json", "test", false,
+      Feature::UNSPECIFIED_CONTEXT },
+    { "is_privileged_features_4.json", "test.bar", false,
+      Feature::UNSPECIFIED_CONTEXT },
+    { "is_privileged_features_5.json", "test.bar", true,
+      Feature::BLESSED_EXTENSION_CONTEXT },
+    { "is_privileged_features_5.json", "test.bar", false,
+      Feature::UNBLESSED_EXTENSION_CONTEXT }
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+    FilePath manifest_path;
+    PathService::Get(chrome::DIR_TEST_DATA, &manifest_path);
+    manifest_path = manifest_path.AppendASCII("extensions")
+        .AppendASCII("extension_api_unittest")
+        .AppendASCII(test_data[i].filename);
+
+    std::string manifest_str;
+    ASSERT_TRUE(file_util::ReadFileToString(manifest_path, &manifest_str))
+        << test_data[i].filename;
+
+    ExtensionAPI api;
+    api.RegisterSchema("test", manifest_str);
+
+    TestFeatureProvider test2_provider(test_data[i].test2_contexts);
+    if (test_data[i].test2_contexts != Feature::UNSPECIFIED_CONTEXT) {
+      api.RegisterDependencyProvider("test2", &test2_provider);
+    }
+
+    api.LoadAllSchemas();
+    EXPECT_EQ(test_data[i].expect_is_privilged,
+              api.IsPrivileged(test_data[i].api_full_name)) << i;
+  }
+}
+
+TEST(ExtensionAPI, LazyGetSchema) {
+  scoped_ptr<ExtensionAPI> apis(ExtensionAPI::CreateWithDefaultConfiguration());
+
+  EXPECT_EQ(NULL, apis->GetSchema(""));
+  EXPECT_EQ(NULL, apis->GetSchema(""));
+  EXPECT_EQ(NULL, apis->GetSchema("experimental"));
+  EXPECT_EQ(NULL, apis->GetSchema("experimental"));
+  EXPECT_EQ(NULL, apis->GetSchema("foo"));
+  EXPECT_EQ(NULL, apis->GetSchema("foo"));
+
+  EXPECT_TRUE(apis->GetSchema("experimental.dns"));
+  EXPECT_TRUE(apis->GetSchema("experimental.dns"));
+  EXPECT_TRUE(apis->GetSchema("experimental.infobars"));
+  EXPECT_TRUE(apis->GetSchema("experimental.infobars"));
+  EXPECT_TRUE(apis->GetSchema("extension"));
+  EXPECT_TRUE(apis->GetSchema("extension"));
+  EXPECT_TRUE(apis->GetSchema("omnibox"));
+  EXPECT_TRUE(apis->GetSchema("omnibox"));
+  EXPECT_TRUE(apis->GetSchema("storage"));
+  EXPECT_TRUE(apis->GetSchema("storage"));
+}
+
+scoped_refptr<Extension> CreateExtensionWithPermissions(
+    const std::set<std::string>& permissions) {
+  DictionaryValue manifest;
+  manifest.SetString("name", "extension");
+  manifest.SetString("version", "1.0");
+  manifest.SetInteger("manifest_version", 2);
+  {
+    scoped_ptr<ListValue> permissions_list(new ListValue());
+    for (std::set<std::string>::const_iterator i = permissions.begin();
+        i != permissions.end(); ++i) {
+      permissions_list->Append(Value::CreateStringValue(*i));
+    }
+    manifest.Set("permissions", permissions_list.release());
+  }
+
+  std::string error;
+  scoped_refptr<Extension> extension(Extension::Create(
+      FilePath(), Extension::LOAD, manifest, Extension::NO_FLAGS, &error));
+  CHECK(extension.get());
+  CHECK(error.empty());
+
+  return extension;
+}
+
+scoped_refptr<Extension> CreateExtensionWithPermission(
+    const std::string& permission) {
+  std::set<std::string> permissions;
+  permissions.insert(permission);
+  return CreateExtensionWithPermissions(permissions);
+}
+
+TEST(ExtensionAPI, ExtensionWithUnprivilegedAPIs) {
+  scoped_refptr<Extension> extension;
+  {
+    std::set<std::string> permissions;
+    permissions.insert("storage");
+    permissions.insert("history");
+    extension = CreateExtensionWithPermissions(permissions);
+  }
+
+  scoped_ptr<ExtensionAPI> extension_api(
+      ExtensionAPI::CreateWithDefaultConfiguration());
+
+  scoped_ptr<std::set<std::string> > privileged_apis =
+      extension_api->GetAPIsForContext(
+          Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL());
+
+  scoped_ptr<std::set<std::string> > unprivileged_apis =
+      extension_api->GetAPIsForContext(
+          Feature::UNBLESSED_EXTENSION_CONTEXT, extension.get(), GURL());
+
+  scoped_ptr<std::set<std::string> > content_script_apis =
+      extension_api->GetAPIsForContext(
+          Feature::CONTENT_SCRIPT_CONTEXT, extension.get(), GURL());
+
+  // "storage" is completely unprivileged.
+  EXPECT_EQ(1u, privileged_apis->count("storage"));
+  EXPECT_EQ(1u, unprivileged_apis->count("storage"));
+  EXPECT_EQ(1u, content_script_apis->count("storage"));
+
+  // "extension" is partially unprivileged.
+  EXPECT_EQ(1u, privileged_apis->count("extension"));
+  EXPECT_EQ(1u, unprivileged_apis->count("extension"));
+  EXPECT_EQ(1u, content_script_apis->count("extension"));
+
+  // "history" is entirely privileged.
+  EXPECT_EQ(1u, privileged_apis->count("history"));
+  EXPECT_EQ(0u, unprivileged_apis->count("history"));
+  EXPECT_EQ(0u, content_script_apis->count("history"));
+}
+
+TEST(ExtensionAPI, ExtensionWithDependencies) {
+  // Extension with the "ttsEngine" permission but not the "tts" permission; it
+  // must load TTS.
+  {
+    scoped_refptr<Extension> extension =
+        CreateExtensionWithPermission("ttsEngine");
+    scoped_ptr<ExtensionAPI> api(
+        ExtensionAPI::CreateWithDefaultConfiguration());
+    scoped_ptr<std::set<std::string> > apis = api->GetAPIsForContext(
+        Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL());
+    EXPECT_EQ(1u, apis->count("ttsEngine"));
+    EXPECT_EQ(1u, apis->count("tts"));
+  }
+
+  // Conversely, extension with the "tts" permission but not the "ttsEngine"
+  // permission shouldn't get the "ttsEngine" permission.
+  {
+    scoped_refptr<Extension> extension =
+        CreateExtensionWithPermission("tts");
+    scoped_ptr<ExtensionAPI> api(
+        ExtensionAPI::CreateWithDefaultConfiguration());
+    scoped_ptr<std::set<std::string> > apis = api->GetAPIsForContext(
+        Feature::BLESSED_EXTENSION_CONTEXT, extension.get(), GURL());
+    EXPECT_EQ(0u, apis->count("ttsEngine"));
+    EXPECT_EQ(1u, apis->count("tts"));
+  }
+}
+
+bool MatchesURL(
+    ExtensionAPI* api, const std::string& api_name, const std::string& url) {
+  scoped_ptr<std::set<std::string> > apis =
+      api->GetAPIsForContext(Feature::WEB_PAGE_CONTEXT, NULL, GURL(url));
+  return apis->count(api_name);
+}
+
+TEST(ExtensionAPI, URLMatching) {
+  scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration());
+
+  // "app" API is available to all URLs that content scripts can be injected.
+  EXPECT_TRUE(MatchesURL(api.get(), "app", "http://example.com/example.html"));
+  EXPECT_TRUE(MatchesURL(api.get(), "app", "https://blah.net"));
+  EXPECT_TRUE(MatchesURL(api.get(), "app", "file://somefile.html"));
+
+  // But not internal URLs (for chrome-extension:// the app API is injected by
+  // GetSchemasForExtension).
+  EXPECT_FALSE(MatchesURL(api.get(), "app", "about:flags"));
+  EXPECT_FALSE(MatchesURL(api.get(), "app", "chrome://flags"));
+  EXPECT_FALSE(MatchesURL(api.get(), "app",
+                          "chrome-extension://fakeextension"));
+
+  // "storage" API (for example) isn't available to any URLs.
+  EXPECT_FALSE(MatchesURL(api.get(), "storage",
+                          "http://example.com/example.html"));
+  EXPECT_FALSE(MatchesURL(api.get(), "storage", "https://blah.net"));
+  EXPECT_FALSE(MatchesURL(api.get(), "storage", "file://somefile.html"));
+  EXPECT_FALSE(MatchesURL(api.get(), "storage", "about:flags"));
+  EXPECT_FALSE(MatchesURL(api.get(), "storage", "chrome://flags"));
+  EXPECT_FALSE(MatchesURL(api.get(), "storage",
+                          "chrome-extension://fakeextension"));
+}
+
+TEST(ExtensionAPI, GetAPINameFromFullName) {
+  struct {
+    std::string input;
+    std::string api_name;
+    std::string child_name;
+  } test_data[] = {
+    { "", "", "" },
+    { "unknown", "", "" },
+    { "bookmarks", "bookmarks", "" },
+    { "bookmarks.", "bookmarks", "" },
+    { ".bookmarks", "", "" },
+    { "bookmarks.create", "bookmarks", "create" },
+    { "bookmarks.create.", "bookmarks", "create." },
+    { "bookmarks.create.monkey", "bookmarks", "create.monkey" },
+    { "bookmarkManagerPrivate", "bookmarkManagerPrivate", "" },
+    { "bookmarkManagerPrivate.copy", "bookmarkManagerPrivate", "copy" }
+  };
+
+  scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration());
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+    std::string child_name;
+    std::string api_name = api->GetAPINameFromFullName(test_data[i].input,
+                                                       &child_name);
+    EXPECT_EQ(test_data[i].api_name, api_name) << test_data[i].input;
+    EXPECT_EQ(test_data[i].child_name, child_name) << test_data[i].input;
+  }
+}
+
+TEST(ExtensionAPI, DefaultConfigurationFeatures) {
+  scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration());
+
+  Feature* bookmarks = api->GetFeature("bookmarks");
+  Feature* bookmarks_create = api->GetFeature("bookmarks.create");
+
+  struct {
+    Feature* feature;
+    // TODO(aa): More stuff to test over time.
+  } test_data[] = {
+    { bookmarks },
+    { bookmarks_create }
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+    Feature* feature = test_data[i].feature;
+    ASSERT_TRUE(feature) << i;
+
+    EXPECT_TRUE(feature->whitelist()->empty());
+    EXPECT_TRUE(feature->extension_types()->empty());
+
+    EXPECT_EQ(1u, feature->contexts()->size());
+    EXPECT_TRUE(feature->contexts()->count(
+        Feature::BLESSED_EXTENSION_CONTEXT));
+
+    EXPECT_EQ(Feature::UNSPECIFIED_LOCATION, feature->location());
+    EXPECT_EQ(Feature::UNSPECIFIED_PLATFORM, feature->platform());
+    EXPECT_EQ(0, feature->min_manifest_version());
+    EXPECT_EQ(0, feature->max_manifest_version());
+  }
+}
+
+TEST(ExtensionAPI, FeaturesRequireContexts) {
+  scoped_ptr<ListValue> schema1(new ListValue());
+  DictionaryValue* feature_definition = new DictionaryValue();
+  schema1->Append(feature_definition);
+  feature_definition->SetString("namespace", "test");
+  feature_definition->SetBoolean("uses_feature_system", true);
+
+  scoped_ptr<ListValue> schema2(schema1->DeepCopy());
+
+  ListValue* contexts = new ListValue();
+  contexts->Append(Value::CreateStringValue("content_script"));
+  feature_definition->Set("contexts", contexts);
+
+  struct {
+    ListValue* schema;
+    bool expect_success;
+  } test_data[] = {
+    { schema1.get(), true },
+    { schema2.get(), false }
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+    std::string schema_source;
+    base::JSONWriter::Write(test_data[i].schema, &schema_source);
+
+    ExtensionAPI api;
+    api.RegisterSchema("test", base::StringPiece(schema_source));
+    api.LoadAllSchemas();
+
+    Feature* feature = api.GetFeature("test");
+    EXPECT_EQ(test_data[i].expect_success, feature != NULL) << i;
+  }
+}
+
+static void GetDictionaryFromList(const DictionaryValue* schema,
+                                  const std::string& list_name,
+                                  const int list_index,
+                                  const DictionaryValue** out) {
+  const ListValue* list;
+  EXPECT_TRUE(schema->GetList(list_name, &list));
+  EXPECT_TRUE(list->GetDictionary(list_index, out));
+}
+
+TEST(ExtensionAPI, TypesHaveNamespace) {
+  FilePath manifest_path;
+  PathService::Get(chrome::DIR_TEST_DATA, &manifest_path);
+  manifest_path = manifest_path.AppendASCII("extensions")
+      .AppendASCII("extension_api_unittest")
+      .AppendASCII("types_have_namespace.json");
+
+  std::string manifest_str;
+  ASSERT_TRUE(file_util::ReadFileToString(manifest_path, &manifest_str))
+      << "Failed to load: " << manifest_path.value();
+
+  ExtensionAPI api;
+  api.RegisterSchema("test.foo", manifest_str);
+  api.LoadAllSchemas();
+
+  const DictionaryValue* schema = api.GetSchema("test.foo");
+
+  const DictionaryValue* dict;
+  const DictionaryValue* sub_dict;
+  std::string type;
+
+  GetDictionaryFromList(schema, "types", 0, &dict);
+  EXPECT_TRUE(dict->GetString("id", &type));
+  EXPECT_EQ("test.foo.TestType", type);
+  EXPECT_TRUE(dict->GetString("customBindings", &type));
+  EXPECT_EQ("test.foo.TestType", type);
+  EXPECT_TRUE(dict->GetDictionary("properties", &sub_dict));
+  const DictionaryValue* property;
+  EXPECT_TRUE(sub_dict->GetDictionary("foo", &property));
+  EXPECT_TRUE(property->GetString("$ref", &type));
+  EXPECT_EQ("test.foo.OtherType", type);
+  EXPECT_TRUE(sub_dict->GetDictionary("bar", &property));
+  EXPECT_TRUE(property->GetString("$ref", &type));
+  EXPECT_EQ("fully.qualified.Type", type);
+
+  GetDictionaryFromList(schema, "functions", 0, &dict);
+  GetDictionaryFromList(dict, "parameters", 0, &sub_dict);
+  EXPECT_TRUE(sub_dict->GetString("$ref", &type));
+  EXPECT_EQ("test.foo.TestType", type);
+  EXPECT_TRUE(dict->GetDictionary("returns", &sub_dict));
+  EXPECT_TRUE(sub_dict->GetString("$ref", &type));
+  EXPECT_EQ("fully.qualified.Type", type);
+
+  GetDictionaryFromList(schema, "functions", 1, &dict);
+  GetDictionaryFromList(dict, "parameters", 0, &sub_dict);
+  EXPECT_TRUE(sub_dict->GetString("$ref", &type));
+  EXPECT_EQ("fully.qualified.Type", type);
+  EXPECT_TRUE(dict->GetDictionary("returns", &sub_dict));
+  EXPECT_TRUE(sub_dict->GetString("$ref", &type));
+  EXPECT_EQ("test.foo.TestType", type);
+
+  GetDictionaryFromList(schema, "events", 0, &dict);
+  GetDictionaryFromList(dict, "parameters", 0, &sub_dict);
+  EXPECT_TRUE(sub_dict->GetString("$ref", &type));
+  EXPECT_EQ("test.foo.TestType", type);
+  GetDictionaryFromList(dict, "parameters", 1, &sub_dict);
+  EXPECT_TRUE(sub_dict->GetString("$ref", &type));
+  EXPECT_EQ("fully.qualified.Type", type);
+}
+
+}  // namespace
+}  // namespace extensions
diff --git a/chrome/common/extensions/api/file_browser_handler.json b/chrome/common/extensions/api/file_browser_handler.json
new file mode 100644
index 0000000..7f566a8
--- /dev/null
+++ b/chrome/common/extensions/api/file_browser_handler.json
@@ -0,0 +1,101 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace":"fileBrowserHandler",
+    "platforms": ["chromeos"],
+    "types": [
+      {
+        "id": "FileHandlerExecuteEventDetails",
+        "type": "object",
+        "description": "Event details payload for fileBrowserHandler.onExecute event.",
+        "properties": {
+          "entries": {
+            "type": "array",
+            "items": { "type": "any" },
+            "description": "Array of Entry instances representing files that are targets of this action (selected in ChromeOS file browser)."
+          },
+          "tab_id" : {
+            "type": "integer",
+            "optional": true,
+            "description": "The ID of the tab that raised this event. Tab IDs are unique within a browser session."
+          }
+        }
+      }
+    ],
+
+    "events": [
+      {
+        "name": "onExecute",
+        "type": "function",
+        "description": "Fired when file system action is executed from ChromeOS file browser.",
+        "parameters": [
+          {
+            "name": "id",
+            "type": "string",
+            "description": "File browser action id as specified in the listener component's manifest."
+          },
+          {
+            "name": "details",
+            "$ref": "FileHandlerExecuteEventDetails",
+            "description": "File handler execute event details."
+          }
+        ]
+      }
+    ],
+
+    "functions": [
+      {
+        "name": "selectFile",
+        "type": "function",
+        "description": "Prompts user to select file path under which file should be saved. When the file is selected, file access permission required to use the file (read, write and create) are granted to the caller. The file will not actually get created during the functin call, so function caller must ensure its existance before using it. The function has to be invoked with a user gesture.",
+        "parameters": [
+          {
+            "name": "selectionParams",
+            "type": "object",
+            "description": "Parameters that will be used while selecting the file.",
+            "properties": {
+              "suggestedName": {
+                  "type": "string",
+                  "description": "Suggested name for the file."
+              },
+              "allowedFileExtensions": {
+                  "type": "array",
+                  "items": { "type": "string" },
+                  "optional": true,
+                  "description": "List of file extensions that the selected file can have. The list is also used to specify what files to be shown in the select file dialog. Files with the listed extensions are only shown in the dialog. Extensions should not include the leading '.'. Example: ['jpg', 'png']"
+              }
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Function called upon completion.",
+            "parameters": [
+              {
+                "name": "result",
+                "description": "Result of the method.",
+                "type": "object",
+                "properties": {
+                  "success": {
+                    "type": "boolean",
+                    "description": "Whether the file has been selected."
+                  },
+                  "entry": {
+                    "type": "object",
+                    "constructor": "Entry",
+                    "additionalProperties": { "type": "any" },
+                    "optional": true,
+                    "description": "Selected file entry. It will be null if a file hasn't been selected."
+                  }
+                }
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/file_browser_handler_internal.json b/chrome/common/extensions/api/file_browser_handler_internal.json
new file mode 100644
index 0000000..38fa8d3
--- /dev/null
+++ b/chrome/common/extensions/api/file_browser_handler_internal.json
@@ -0,0 +1,83 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "fileBrowserHandlerInternal",
+    "nodoc": true,
+    "internal": true,
+    "platforms": ["chromeos"],
+    "types": [
+      {
+        "id": "FileEntryInfo",
+        "type": "object",
+        "description": "Information needed to build a file entry that will be returned through the API.",
+        "properties": {
+          "fileSystemName": {
+            "type": "string"
+          },
+          "fileSystemRoot": {
+            "type": "string"
+          },
+          "fileFullPath": {
+            "type": "string"
+          },
+          "fileIsDirectory": {
+            "type": "boolean"
+          }
+        }
+      }
+    ],
+
+    "functions": [
+      {
+        "name": "selectFile",
+        "type": "function",
+        "description": "Prompts user to select file path under which a new file will be created. If the user selects file, the file gets created or, if it already exists, truncated. The function has to be called with user gesture.",
+        "parameters": [
+          {
+            "name": "selectionParams",
+            "type": "object",
+            "description": "Parameters that will be used to create new file.",
+            "properties": {
+              "suggestedName": {
+                  "type": "string",
+                  "description": "Suggested name for the new file."
+              },
+              "allowedFileExtensions": {
+                  "type": "array",
+                  "items": { "type": "string" },
+                  "optional": true,
+                  "description": "List of file extensions that the selected file can have. The list is also used to specify what files to be shown in the select file dialog. Files with the listed extensions are only shown in the dialog. Extensions should not include the leading '.'. Example: ['jpg', 'png']"
+              }
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Function called upon completion.",
+            "parameters": [
+              {
+                "name": "result",
+                "description": "Result of the method.",
+                "type": "object",
+                "properties": {
+                  "success": {
+                    "type": "boolean",
+                    "description": "Has the file been selected."
+                  },
+                  "entry": {
+                    "$ref": "FileEntryInfo",
+                    "optional": true,
+                    "description": "Selected file entry."
+                  }
+                }
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/file_browser_private.json b/chrome/common/extensions/api/file_browser_private.json
new file mode 100644
index 0000000..95c9789
--- /dev/null
+++ b/chrome/common/extensions/api/file_browser_private.json
@@ -0,0 +1,1139 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace":"fileBrowserPrivate",
+    "nodoc": "true",
+    "types": [
+      {
+        "id": "FileBrowserTask",
+        "type": "object",
+        "description": "Represents information about available browser tasks. A task is an abstraction of an operation that the file browser can perform over a selected file set.",
+        "properties": {
+          "taskId": {"type": "string", "description": "The unique identifier of the task."},
+          "title": {"type": "string", "description": "Task title."},
+          "iconUrl": {"type": "string", "description": "Task icon url (from chrome://extension-icon/...)"},
+          "driveApp": {"type": "boolean", "description": "True if this is Drive App."},
+          "isDefault": {"type": "boolean", "description": "True if this task is a default task for the selected files."}
+        }
+      },
+      {
+        "id": "VolumeInfo",
+        "type": "object",
+        "description": "Mounted disk volume information.",
+        "properties": {
+          "mountPath": {
+            "type": "string",
+            "description": "Disk volume mount point path. The value corresponds to its Entry.fullPath in File API."
+          },
+          "devicePath": {
+            "type": "string",
+            "description": "Disk volume device path."
+          },
+          "label": {
+            "type": "string",
+            "description": "Volume label."
+          },
+          "deviceType": {
+            "type": "string",
+            "enum": ["sd", "usb", "optical", "mobile", "unknown"],
+            "description": "Device type."
+          },
+          "readOnly": {
+            "type": "boolean",
+            "description": "Flag that specifies if volume is mounted in read-only mode."
+          },
+          "totalSizeKB": {
+            "type": "integer",
+            "description": "Total disk volume size in KBs"
+          }
+        }
+      },
+      {
+        "id": "DriveWebApp",
+        "type": "object",
+        "description": "GData WebApp properties.",
+        "properties": {
+          "appId": {
+            "type": "string",
+            "description": "WebApp ID."
+          },
+          "appName": {
+            "type": "string",
+            "description": "WebApp name."
+          },
+          "appIcon": {
+            "type": "string",
+            "optional": true,
+            "description": "URL to the GData application icon for this application."
+          },
+          "docIcon": {
+            "type": "string",
+            "optional": true,
+            "description": "URL to the GData document icon for documents associated with this application."
+          },
+          "objectType": {
+            "type": "string",
+            "description": "Object (file) type description."
+          },
+          "isPrimary": {
+            "type": "boolean",
+            "description": "True if this WebApp is the primary (default) open action for this file."
+          }
+        }
+      },
+      {
+        "id": "GDataFileProperties",
+        "type": "object",
+        "description": "GData file properties.",
+        "properties": {
+          "fileUrl": {
+            "type": "string",
+            "optional": true,
+            "description": "the URL given for this file."
+          },
+          "thumbnailUrl": {
+            "type": "string",
+            "optional": true,
+            "description": "URL to the GData thumbnail image for this file."
+          },
+          "contentUrl": {
+            "type": "string",
+            "optional": true,
+            "description": "GData URL to the content for this file."
+          },
+          "editUrl": {
+            "type": "string",
+            "optional": true,
+            "description": "GData edit URL for this file."
+          },
+          "shareUrl": {
+            "type": "string",
+            "optional": true,
+            "description": "GData share URL for this file."
+          },
+          "isPinned": {
+            "type": "boolean",
+            "optional": true,
+            "description": "True if the file is pinned in GData cache."
+          },
+          "isPresent": {
+            "type": "boolean",
+            "optional": true,
+            "description": "True if the file is present in GData cache."
+          },
+          "isDirty": {
+            "type": "boolean",
+            "optional": true,
+            "description": "True if the file is awaiting upload in GData cache."
+          },
+          "isHosted": {
+            "type": "boolean",
+            "optional": true,
+            "description": "True if the file is hosted on a GData server instead of local."
+          },
+          "errorCode": {
+            "type": "integer",
+            "optional": true,
+            "description": "The error code (from base::PlatformFileError) if fetching the properties for this file had an error."
+          },
+          "driveApps" : {
+            "type": "array",
+            "optional": true,
+            "items": {"$ref": "DriveWebApp"},
+            "description": "An array of WebApps capable of opening this file."
+          },
+          "contentMimeType": {
+            "type": "string",
+            "optional": true,
+            "description": "GData MIME type for this file."
+          }
+        }
+      },
+      {
+        "id": "MountPointInfo",
+        "type": "object",
+        "description": "Mounted point information.",
+        "properties": {
+          "mountPath": {
+            "type": "string",
+            "optional": true,
+            "description": "Disk volume mount point path. The value corresponds to its Entry.fullPath in File API."
+          },
+          "sourcePath": {
+            "type": "string",
+            "description": "The path to the mounted device, archive file or network resource."
+          },
+          "mountType": {
+            "type": "string",
+            "enum": ["device", "file", "network"],
+            "description": "Type of the mount."
+          },
+          "mountCondition": {
+            "type": "string",
+            "description": "Additional data about mount, for example, that the filesystem is not supported."
+          }
+        }
+      },
+      {
+        "id": "MountPointSizeStats",
+        "type": "object",
+        "description": "Information about total and remaining size on the mount point.",
+        "properties": {
+          "totalSizeKB": {
+            "type": "integer",
+            "description": "Total available size on the mount point."
+          },
+          "remainingSizeKB": {
+            "type": "integer",
+            "description": "Remaining available size on the mount point."
+          }
+        }
+      },
+      {
+        "id": "VolumeMetadata",
+        "type": "object",
+        "description": "Mounted disk volume metadata.",
+        "properties": {
+          "mountPath": {
+            "type": "string",
+            "description": "Disk volume mount point path."
+          },
+          "devicePath": {
+            "type": "string",
+            "description": "Disk volume device path."
+          },
+          "systemPath": {
+            "type": "string",
+            "description": "Disk volume system path."
+          },
+          "filePath": {
+            "type": "string",
+            "description": "Disk volume file path."
+          },
+          "deviceLabel": {
+            "type": "string",
+            "description": "Volume label."
+          },
+          "driveLabel": {
+            "type": "string",
+            "description": "Volume's disk label."
+          },
+          "deviceType": {
+            "type": "string",
+            "enum": ["usb", "sd", "optical", "mobile", "unknown"],
+            "description": "Device type."
+          },
+          "isParent": {
+            "type": "boolean",
+            "description": "Flag that specifies if volume is a parent device."
+          },
+          "isReadOnly": {
+            "type": "boolean",
+            "description": "Flag that specifies if volume is mounted in read-only mode."
+          },
+          "hasMedia": {
+            "type": "boolean",
+            "description": "Flag that specifies if volume has any media."
+          },
+          "isOnBootDevice": {
+            "type": "boolean",
+            "description": "Flag that specifies if volume is on boot device."
+          },
+          "totalSize": {
+            "type": "integer",
+            "description": "Total disk volume size."
+          }
+        }
+      },
+      {
+        "id": "MountEvent",
+        "type": "object",
+        "description": "Payload data for disk mount / unmount event.",
+        "properties": {
+          "eventType": {
+            "type": "string",
+            "enum": ["added", "removed"],
+            "description": "Event type that tells listeners which disk volume even was raised."
+          },
+          "volumeInfo": {
+            "$ref": "VolumeInfo",
+            "description":"Volume information that this mount event applies to."
+          }
+        }
+      },
+      {
+        "id": "MountCompletedEvent",
+        "type": "object",
+        "description": "Payload data for mount event.",
+        "properties": {
+          "eventType": {
+            "type": "string",
+            "enum": ["mount", "unmount"],
+            "description": "Is the event raised for mounting or unmounting."
+          },
+          "status": {
+            "type": "string",
+            "enum": ["success", "error_unknown", "error_internal",
+                     "error_unknown_filesystem", "error_unsuported_filesystem",
+                     "error_invalid_archive", "error_authentication",
+                     "error_network", "error_path_unmounted"],
+            "description": "Event type that tells listeners if mount was successful or an error occurred. It also specifies the error."
+          },
+          "sourcePath": {
+            "type": "string",
+            "description": "Path that has been mounted."
+          },
+          "mountPath": {
+            "type": "string",
+            "optional": true,
+            "description": "Path that sourcePath was mounted to."
+          },
+          "mountType": {
+            "type": "string",
+            "enum": ["device", "file", "network", "gdata"],
+            "description": "Type of the mount."
+          }
+        }
+      },
+      {
+        "id": "FileTransferStatus",
+        "type": "object",
+        "description": "Payload data for file transfer status updates.",
+        "properties": {
+          "fileUrl": {
+            "type": "string",
+            "description": "URL of file that is being transfered."
+          },
+          "transferState": {
+            "type": "string",
+            "enum": ["started", "in_progress", "completed", "failed"],
+            "description": "File transfer progress state."
+          },
+          "transferType": {
+            "type": "string",
+            "enum": ["upload", "download"],
+            "description": "Defines file transfer direction."
+          },
+          "processed": {
+            "type": "integer",
+            "optional": true,
+            "description": "Completed portion of the transfer operation."
+          },
+          "total": {
+            "type": "integer",
+            "optional": true,
+            "description": "Total size (cost) of transfer operation."
+          }
+        }
+      },
+      {
+        "id": "FileTransferCancelStatus",
+        "type": "object",
+        "description": "Payload data for file transfer cancel response.",
+        "properties": {
+          "fileUrl": {
+            "type": "string",
+            "description": "URL of file that is being transfered."
+          },
+          "canceled": {
+            "type": "boolean",
+            "description": "True if ongoing transfer operation was found and canceled."
+          }
+        }
+      },
+      {
+        "id": "FileWatchEvent",
+        "type": "object",
+        "description": "Directory change notification details.",
+        "properties": {
+          "eventType": {
+            "type": "string",
+            "enum": ["changed", "error"],
+            "description": "Specifies type of event that is raised."
+          },
+          "directoryUrl": {
+            "type": "string",
+            "description": "URL of watched directory."
+          },
+          "changedEntries": {
+            "type": "array",
+            "items": {"$ref": "FileWatchChangedEntry"}
+          }
+        }
+      },
+      {
+        "id": "FileWatchChangedEntry",
+        "type": "object",
+        "description": "Information about changed file or directory",
+        "properties": {
+          "type": "string",
+          "enum": ["added", "deleted", "updated"],
+          "description": "Specifies type of the change."
+        },
+        "fileUrl": {
+          "type": "string",
+          "description": "URL of the changed file."
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "cancelDialog",
+        "type": "function",
+        "description": "Cancels file selection.",
+        "parameters": []
+      },
+      {
+        "name": "executeTask",
+        "description": "Executes file browser task over selected files",
+        "parameters": [
+          {
+            "name": "taskId",
+            "type": "string",
+            "description": "The unique identifier of task to execute."
+          },
+          {
+            "name": "fileURLs",
+            "type": "array",
+            "description": "Array of file URLs",
+            "items": { "type": "string" }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "success",
+                "type": "boolean",
+                "optional": true,
+                "description": "True of task execution was successfully initiated."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setDefaultTask",
+        "description": "Sets the default task for the supplied MIME types and suffixes of the supplied file URLs. Lists of MIME types and URLs may contain duplicates.",
+        "parameters": [
+          {
+            "name": "taskId",
+            "type": "string",
+            "description": "The unique identifier of task to mark as default."
+          },
+          {
+            "name": "fileURLs",
+            "type": "array",
+            "description": "Array of selected file URLs to extract suffixes from.",
+            "items": { "type": "string" }
+          },
+          {
+            "name": "mimeTypes",
+            "type": "array",
+            "optional": true,
+            "description": "Array of selected file MIME types.",
+            "items": { "type": "string" }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": []
+          }
+        ]        
+      },
+      {
+        "name": "getFileTasks",
+        "description": "Gets the list of tasks that can be performed over selected files.",
+        "parameters": [
+          {
+            "name": "fileURLs",
+            "type": "array",
+            "description": "Array of selected file URLs",
+            "items": { "type": "string" }
+          },
+          {
+            "name": "mimeTypes",
+            "type": "array",
+            "description": "Array of selected file MIME types",
+            "items": { "type": "string" }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name": "tasks",
+                "type": "array",
+                "items": {"$ref": "FileBrowserTask"},
+                "description": "The list of matched file URL patterns for this task."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getStrings",
+        "type": "function",
+        "description": "Gets localized strings and initialization data.",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "object",
+                "additionalProperties": { "type": "any" }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "addFileWatch",
+        "description": "Adds file watch.",
+        "parameters": [
+          {
+            "name": "fileUrl",
+            "type": "string",
+            "description": "URL of file to watch"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name" : "success",
+                "type": "boolean",
+                "optional": true,
+                "description": "True when file watch is successfully added."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "removeFileWatch",
+        "description": "Removes file watch.",
+        "parameters": [
+          {
+            "name": "fileUrl",
+            "type": "string",
+            "description": "URL of watched file to remove"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name" : "success",
+                "type": "boolean",
+                "optional": true,
+                "description": "True when file watch is successfully removed."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "requestLocalFileSystem",
+        "description": "Requests access to local file system",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name" : "fileSystem",
+                "type": "object",
+                "optional": true,
+                "description": "A DOMFileSystem instance for local file system access. null if the caller has no appropriate permissions."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "selectFiles",
+        "type": "function",
+        "description": "Selects multiple files.",
+        "parameters": [
+          {
+            "name": "selectedPaths",
+            "type": "array",
+            "description": "Array of selected paths",
+            "items": {"type": "string"}
+          }
+        ]
+      },
+      {
+        "name": "selectFile",
+        "type": "function",
+        "description": "Selects a file.",
+        "parameters": [
+          {
+            "name": "selectedPath",
+            "type": "string",
+            "description": "A selected path"
+          },
+          {
+            "name": "index",
+            "type": "integer",
+            "description": "Index of Filter"
+          }
+        ]
+      },
+      {
+        "name": "viewFiles",
+        "type": "function",
+        "description": "Views multiple files.",
+        "parameters": [
+          {
+            "name": "fileUrls",
+            "type": "array",
+            "description": "Array of selected paths",
+            "items": {"type": "string"}
+          },
+          {
+            "name": "id",
+            "type": "string",
+            "description": "File browser handler id as for internal tasks."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name" : "success",
+                "type": "boolean",
+                "description": "True if the selected files can be viewed by the browser."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getGDataFileProperties",
+        "description": "Requests GData file properties for a list of files",
+        "parameters": [
+          {
+            "name": "fileUrls",
+            "type": "array",
+            "description": "Array of file URLs to fetch properties for."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name" : "fileProperties",
+                "type": "array",
+                "items": {"$ref": "GDataFileProperties"},
+                "description": "An array of the requested file properties, one entry for each file in fileUrls."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "pinGDataFile",
+        "description": "Pins/unpins a GData file in the cache",
+        "parameters": [
+          {
+            "name": "fileUrls",
+            "type": "array",
+            "description": "Array of file URLs to pin/unpin."
+          },
+          {
+            "name": "pin",
+            "type": "boolean",
+            "description": "Pass true to pin the files listed."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name" : "fileProperties",
+                "type": "array",
+                "items": {"$ref": "GDataFileProperties"},
+                "description": "An array of the pinned properties after pinning/unpinning the requested files, one entry for each file in fileUrls."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getFileLocations",
+        "description": "Get file locations",
+        "parameters": [
+          {
+            "name": "fileUrls",
+            "type": "array",
+            "description": "Array of file URLs to check.",
+            "items": { "type": "string" }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name" : "locations",
+                "type": "array",
+                "items": {"type": "string"},
+                "description": "An array of the file locations for the requested files, one entry for each file in fileUrls."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getGDataFiles",
+        "description": "Get GData files",
+        "parameters": [
+          {
+            "name": "fileUrls",
+            "type": "array",
+            "description": "Array of gdata file URLs to get.",
+            "items": { "type": "string" }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name" : "localFilePaths",
+                "type": "array",
+                "items": {"type": "string"},
+                "description": "An array of the local file paths for the requested files, one entry for each file in fileUrls."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getVolumeMetadata",
+        "description": "Requests volume's metadata",
+        "parameters": [
+          {
+            "name": "mountUrl",
+            "type": "string",
+            "description": "Mount url of the volume."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name" : "volumeMetadata",
+                "$ref": "VolumeMetadata",
+                "optional": true,
+                "description": "A requested metadata dictionary object. undefined if there is no volume with selected devicePath"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "addMount",
+        "description": "Mount a resource or a file.",
+        "parameters": [
+          {
+            "name": "source",
+            "type": "string",
+            "description": "Mount point source. For compressed files it is relative file path within external file system"
+          },
+          {
+            "name": "mountType",
+            "type": "string",
+            "enum": ["device", "file", "network", "gdata"],
+            "description": "Mount point type. 'file' for compressed files"
+          },
+          {
+            "name": "options",
+            "type": "object",
+            "description": "Name/value pairs for source specific options"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name": "sourcePath",
+                "type": "string",
+                "description": "Source path of the mount."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "removeMount",
+        "description": "Unmounts a mounted resource.",
+        "parameters": [
+          {
+            "name": "mountPath",
+            "type": "string",
+            "description": "A path of the mount."
+          }
+        ]
+      },
+      {
+        "name": "getMountPoints",
+        "description": "Get the list of mount points.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name" : "mountPoints",
+                "type": "array",
+                "items": {"$ref": "MountPointInfo"},
+                "description": "The list of MountPointInfo representing mounted devices."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getFileTransfers",
+        "description": "Get the list of ongoing file transfer operations.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name" : "fileTransfers",
+                "type": "array",
+                "items": {"$ref": "FileTransferStatus"},
+                "description": "The list of FileTransferStatus representing ongoing file transfers."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "transferFile",
+        "description": "Transfers file from local to remote file system.",
+        "parameters": [
+          {
+            "name": "sourceFileUrl",
+            "type": "string",
+            "description": "Source file from the local file system."
+          },
+          {
+            "name": "destinationFileUrl",
+            "type": "string",
+            "description": "Destination file on the remote file system."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "description": "Completion callback. chrome.extension.lastError will be set if there was an error.",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "cancelFileTransfers",
+        "description": "Cancels ongoing file transfers for selected files.",
+        "parameters": [
+          {
+            "name": "fileUrls",
+            "type": "array",
+            "description": "Array of files for which ongoing transfer should be canceled.",
+            "items": {"type": "string"}
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name" : "fileTransferCancelStatuses",
+                "type": "array",
+                "items": {"$ref": "FileTransferCancelStatus"},
+                "description": "The list of FileTransferCancelStatus."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setLastModified",
+        "description": "Updates last modified to specified time in seconds",
+        "parameters": [
+          {
+            "name": "fileUrl",
+            "type": "string",
+            "description": "File url from the local file system."
+          },
+          {
+            "name": "lastModified",
+            "type": "string",
+            "description": "Date to set as last modification date in ms. Should be passed to C++ as string, since 'long' type isn't supported"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "description": "Completion callback. chrome.extension.lastError will be set if there was an error.",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "getSizeStats",
+        "description": "Retrieves total and remaining size of a mount point.",
+        "parameters": [
+          {
+            "name": "mountPath",
+            "type": "string",
+            "description": "Mount point path."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name" : "sizeStats",
+                "optional": true,
+                "$ref": "MountPointSizeStats",
+                "description": "Name/value pairs of size stats. Will be undefined if stats could not be determined."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "formatDevice",
+        "description": "Formats a mounted device",
+        "parameters": [
+          {
+            "name": "mountPath",
+            "type": "string",
+            "description": "Device's mount path."
+          }
+        ]
+      },
+      {
+        "name": "toggleFullscreen",
+        "description": "Switches fullscreen mode on/off for the File Browser.",
+        "parameters": []
+      },
+      {
+        "name": "isFullscreen",
+        "description": "Checks if the browser is in fullscreen mode.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name" : "result",
+                "type": "boolean",
+                "description": "Whether the browser is in fullscreen mode."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getPreferences",
+        "description": "Retrieves file manager preferences .",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "object",
+                "properties": {
+                  "driveEnabled": {"type":"boolean"},
+                  "cellularDisabled": {"type":"boolean"},
+                  "hostedFilesDisabled": {"type":"boolean"},
+                  "use24hourClock": {"type":"boolean"}
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setPreferences",
+        "description": "Sets file manager preferences.",
+        "parameters": [
+          {
+            "name": "changeInfo",
+            "type": "object",
+            "properties": {
+              "cellularDisabled": {"type":"boolean", "optional":true},
+              "hostedFilesDisabled": {"type":"boolean", "optional":true}
+            }
+          }
+        ]
+      },
+      {
+        "name": "searchGData",
+        "type": "function",
+        "description": "Performs drive content search.",
+        "parameters": [
+          {
+            "name": "query",
+            "description": "Search query.",
+            "type": "string"
+          },
+          {
+            "name": "nextFeed",
+            "type": "string",
+            "description": "ID of the search feed that should be fetched next. Value passed here should be gotten from previous searchGData call. It can be empty for the initial search request."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name": "entries",
+                "type": "array",
+                "items": {
+                  "type": "object",
+                  "isInstanceOf": "Entry",
+                  "description": "Entry representing a search result."
+                }
+              },
+              {
+                "name": "nextFeed",
+                "type": "string",
+                "description": "ID of the feed that contains next chunk of the search result. Should be sent to the next searchGData request to perform incremental search."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "clearDriveCache",
+        "description": "Clear all GData local caches.",
+        "parameters": []
+      },
+      {
+        "name": "reloadDrive",
+        "description": "Reload the filesystem metadata from the server immediately.",
+        "parameters": []
+      },
+      {
+        "name": "getNetworkConnectionState",
+        "description": "Retrieves the state of the currently active network connection.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "object",
+                "properties": {
+                  "type": {"type": "string"},
+                  "online": {"type": "boolean"}
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "requestDirectoryRefresh",
+        "description": "Requests a refresh of a directory. Used to get the latest metadata of files in a particular directory. Upon completion, onDirectoryChanged event is raised against the target directory, so that the file browser can redraw contents in the directory. Note that the onFileChanged event can be raised even if there is no change in the directory as it's expensive to check if the new contents match the existing ones.",
+        "parameters": [
+          {
+            "name": "fileURL",
+            "type": "string",
+            "description": "URL of the target directory"
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onDiskChanged",
+        "type": "function",
+        "description": "Fired when disk mount/unmount event is detected.",
+        "parameters": [
+          {
+            "$ref": "MountEvent",
+            "name": "event",
+            "description": "Mount event information."
+          }
+        ]
+      },
+      {
+        "name": "onMountCompleted",
+        "type": "function",
+        "description": "Fired when mount event is detected.",
+        "parameters": [
+          {
+            "$ref": "MountCompletedEvent",
+            "name": "event",
+            "description": "MountCompleted event information."
+          }
+        ]
+      },
+      {
+        "name": "onFileTransfersUpdated",
+        "type": "function",
+        "description": "Fired when file transfers with remote file system are in progress.",
+        "parameters": [
+          {
+            "type": "array",
+            "items": {"$ref": "FileTransferStatus"},
+            "name": "event",
+            "description": "List of ongoing file statuses for ongoing transfer operations."
+          }
+        ]
+      },
+      {
+        "name": "onDirectoryChanged",
+        "type": "function",
+        "description": "Fired when watched file change event is detected in a watched directory.",
+        "parameters": [
+          {
+            "$ref": "FileWatchEvent",
+            "name": "event",
+            "description": "File watch event information."
+          }
+        ]
+      },
+      {
+        "name": "onDocumentFeedFetched",
+        "type": "function",
+        "description": "Fired when a document feed is fetched.",
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "entriesFetched",
+            "description": "Number of entries fetched so far."
+          }
+        ]
+      },
+      {
+        "name": "onPreferencesChanged",
+        "type": "function",
+        "description": "Fired when file manager preferences change. The preferences can be retrieved via 'getPreferences'.",
+        "parameters": []
+      },
+      {
+        "name": "onNetworkConnectionChanged",
+        "type": "function",
+        "description": "Fired when the active network connection state changes. The network connection state can be retrieved via 'getNetworkConnectionState'.",
+        "parameters": []
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/file_system.idl b/chrome/common/extensions/api/file_system.idl
new file mode 100644
index 0000000..f101430
--- /dev/null
+++ b/chrome/common/extensions/api/file_system.idl
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 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.
+
+namespace fileSystem {
+  dictionary AcceptOption {
+    // This is the optional text description for this option. If not present,
+    // a description will be automatically generated; typically containing an
+    // expanded list of valid extensions (e.g. "text/html" may expand to
+    // "*.html, *.htm").
+    DOMString? description;
+
+    // Mime-types to accept, e.g. "image/jpeg" or "audio/*". One of mimeTypes or
+    // extensions must contain at least one valid element.
+    DOMString[]? mimeTypes;
+
+    // Extensions to accept, e.g. "jpg", "gif", "crx".
+    DOMString[]? extensions;
+  };
+
+  dictionary ChooseEntryOptions {
+    // Type of the prompt to show. Valid types are 'openFile',
+    // 'openWritableFile' or 'saveFile'.
+    //
+    // Both 'openFile' and 'openWritableFile' will prompt the user to open an
+    // existing file, with 'openFile' returning a read-only FileEntry on
+    // success. 'saveFile' will prompt the user to choose an existing file or
+    // a new file, and will return a writable FileEntry.
+    // Calls to chooseFile with either 'openWritableFile' or 'saveFile' will
+    // fail unless the application has the 'write' permission under
+    // 'fileSystem'.
+    //
+    // The default is 'openFile'.
+    DOMString? type;
+
+    // The suggested file name that will be presented to the user as the
+    // default name to read or write. This is optional.
+    DOMString? suggestedName;
+
+    // The optional list of accept options for this file opener. Each option
+    // will be presented as a unique group to the end-user.
+    AcceptOption[]? accepts;
+
+    // Whether to accept all file types, in addition to the options specified
+    // in the accepts argument. The default is true. If the accepts field is
+    // unset or contains no valid entries, this will always be reset to true.
+    boolean? acceptsAllTypes;
+  };
+  callback GetDisplayPathCallback = void (DOMString displayPath);
+  callback FileEntryCallback = void ([instanceOf=FileEntry] object fileEntry);
+  callback IsWritableCallback = void (boolean isWritable);
+
+  interface Functions {
+    // Get the display path of a FileEntry object. The display path is based on
+    // the full path of the file on the local file system, but may be made more
+    // readable for display purposes.
+    static void getDisplayPath([instanceOf=FileEntry] object fileEntry,
+                               GetDisplayPathCallback callback);
+
+    // Get a writable FileEntry from another FileEntry. This call will fail if
+    // the application does not have the 'write' permission under 'fileSystem'.
+    static void getWritableEntry([instanceOf=FileEntry] object fileEntry,
+                                 FileEntryCallback callback);
+
+    // Gets whether this FileEntry is writable or not.
+    static void isWritableEntry([instanceOf=FileEntry] object fileEntry,
+                                IsWritableCallback callback);
+
+    // Ask the user to choose a file.
+    static void chooseEntry(optional ChooseEntryOptions options,
+                            FileEntryCallback callback);
+  };
+};
diff --git a/chrome/common/extensions/api/font_settings.json b/chrome/common/extensions/api/font_settings.json
new file mode 100644
index 0000000..28f87ae
--- /dev/null
+++ b/chrome/common/extensions/api/font_settings.json
@@ -0,0 +1,496 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "fontSettings",
+    "documentation_permissions_required": ["fontSettings"],
+    "types": [
+      {
+        "id": "FontName",
+        "type": "object",
+        "description": "Represents a font name.",
+        "properties": {
+          "fontId": {
+            "type": "string",
+            "description": "The font ID."
+          },
+          "displayName": {
+            "type": "string",
+            "description": "The display name of the font."
+          }
+        }
+      },
+      {
+        "id": "ScriptCode",
+        "type": "string",
+        "enum": [ "Afak", "Arab", "Armi", "Armn", "Avst", "Bali", "Bamu", "Bass", "Batk",
+                  "Beng", "Blis", "Bopo", "Brah", "Brai", "Bugi", "Buhd", "Cakm", "Cans",
+                  "Cari", "Cham", "Cher", "Cirt", "Copt", "Cprt", "Cyrl", "Cyrs", "Deva",
+                  "Dsrt", "Dupl", "Egyd", "Egyh", "Egyp", "Elba", "Ethi", "Geor", "Geok",
+                  "Glag", "Goth", "Gran", "Grek", "Gujr", "Guru", "Hang", "Hani", "Hano",
+                  "Hans", "Hant", "Hebr", "Hluw", "Hmng", "Hung", "Inds", "Ital", "Java",
+                  "Jpan", "Jurc", "Kali", "Khar", "Khmr", "Khoj", "Knda", "Kpel", "Kthi",
+                  "Lana", "Laoo", "Latf", "Latg", "Latn", "Lepc", "Limb", "Lina", "Linb",
+                  "Lisu", "Loma", "Lyci", "Lydi", "Mand", "Mani", "Maya", "Mend", "Merc",
+                  "Mero", "Mlym", "Moon", "Mong", "Mroo", "Mtei", "Mymr", "Narb", "Nbat",
+                  "Nkgb", "Nkoo", "Nshu", "Ogam", "Olck", "Orkh", "Orya", "Osma", "Palm",
+                  "Perm", "Phag", "Phli", "Phlp", "Phlv", "Phnx", "Plrd", "Prti", "Rjng",
+                  "Roro", "Runr", "Samr", "Sara", "Sarb", "Saur", "Sgnw", "Shaw", "Shrd",
+                  "Sind", "Sinh", "Sora", "Sund", "Sylo", "Syrc", "Syre", "Syrj", "Syrn",
+                  "Tagb", "Takr", "Tale", "Talu", "Taml", "Tang", "Tavt", "Telu", "Teng",
+                  "Tfng", "Tglg", "Thaa", "Thai", "Tibt", "Tirh", "Ugar", "Vaii", "Visp",
+                  "Wara", "Wole", "Xpeo", "Xsux", "Yiii", "Zmth", "Zsym", "Zyyy" ],
+        "description": "An ISO 15924 script code. The default, or global, script is represented by script code \"Zyyy\"."
+      },
+      {
+        "id": "GenericFamily",
+        "type": "string",
+        "enum": ["standard", "sansserif", "serif", "fixed", "cursive", "fantasy"],
+        "description": "A CSS generic font family."
+      },
+      {
+        "id": "LevelOfControl",
+        "description": "One of<br><var>not_controllable</var>: cannot be controlled by any extension<br><var>controlled_by_other_extensions</var>: controlled by extensions with higher precedence<br><var>controllable_by_this_extension</var>: can be controlled by this extension<br><var>controlled_by_this_extension</var>: controlled by this extension",
+        "type": "string",
+        "enum": ["not_controllable", "controlled_by_other_extensions", "controllable_by_this_extension", "controlled_by_this_extension"]
+      }
+    ],
+    "functions": [
+      {
+        "name": "clearFont",
+        "description": "Clears the font set by this extension, if any.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "script": {
+                "$ref": "ScriptCode",
+                "description": "The script for which the font should be cleared. If omitted, the global script font setting is cleared.",
+                "optional": true
+              },
+              "genericFamily": {
+                "$ref": "GenericFamily",
+                "description": "The generic font family for which the font should be cleared."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "getFont",
+        "description": "Gets the font for a given script and generic font family.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "script": {
+                "$ref": "ScriptCode",
+                "description": "The script for which the font should be retrieved. If omitted, the font setting for the global script (script code \"Zyyy\") is retrieved.",
+                "optional": true
+              },
+              "genericFamily": {
+                "$ref": "GenericFamily",
+                "description": "The generic font family for which the font should be retrieved."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "details",
+                "type": "object",
+                "properties": {
+                  "fontId": {
+                    "type": "string",
+                    "description": "The font ID. Rather than the literal font ID preference value, this may be the ID of the font that the system resolves the preference value to. So, <var>fontId</var> can differ from the font passed to <code>setFont</code>, if, for example, the font is not available on the system. The empty string signifies fallback to the global script font setting."
+                  },
+                  "levelOfControl": {
+                    "$ref": "LevelOfControl",
+                    "description": "The level of control this extension has over the setting."
+                  }
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setFont",
+        "description": "Sets the font for a given script and generic font family.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "script": {
+                "$ref": "ScriptCode",
+                "description": "The script code which the font should be set. If omitted, the font setting for the global script (script code \"Zyyy\") is set.",
+                "optional": true
+              },
+              "genericFamily": {
+                "$ref": "GenericFamily",
+                "description": "The generic font family for which the font should be set."
+              },
+              "fontId": {
+                "type": "string",
+                "description": "The font ID. The empty string means to fallback to the global script font setting."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "getFontList",
+        "description": "Gets a list of fonts on the system.",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "results",
+                "type": "array",
+                "items": { "$ref": "FontName" }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "clearDefaultFontSize",
+        "description": "Clears the default font size set by this extension, if any.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "optional": true,
+            "description": "This parameter is currently unused.",
+            "properties": {}
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "getDefaultFontSize",
+        "description": "Gets the default font size.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "optional": true,
+            "description": "This parameter is currently unused.",
+            "properties": {}
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "details",
+                "type": "object",
+                "properties": {
+                  "pixelSize": {
+                    "type": "integer",
+                    "description": "The font size in pixels."
+                  },
+                  "levelOfControl": {
+                    "$ref": "LevelOfControl",
+                    "description": "The level of control this extension has over the setting."
+                  }
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setDefaultFontSize",
+        "description": "Sets the default font size.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "pixelSize": {
+                "type": "integer",
+                "description": "The font size in pixels."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "clearDefaultFixedFontSize",
+        "description": "Clears the default fixed font size set by this extension, if any.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "optional": true,
+            "description": "This parameter is currently unused.",
+            "properties": {}
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "getDefaultFixedFontSize",
+        "description": "Gets the default size for fixed width fonts.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "optional": true,
+            "description": "This parameter is currently unused.",
+            "properties": {}
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "details",
+                "type": "object",
+                "properties": {
+                  "pixelSize": {
+                    "type": "integer",
+                    "description": "The font size in pixels."
+                  },
+                  "levelOfControl": {
+                    "$ref": "LevelOfControl",
+                    "description": "The level of control this extension has over the setting."
+                  }
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setDefaultFixedFontSize",
+        "description": "Sets the default size for fixed width fonts.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "pixelSize": {
+                "type": "integer",
+                "description": "The font size in pixels."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "clearMinimumFontSize",
+        "description": "Clears the minimum font size set by this extension, if any.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "optional": true,
+            "description": "This parameter is currently unused.",
+            "properties": {}
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "getMinimumFontSize",
+        "description": "Gets the minimum font size.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "optional": true,
+            "description": "This parameter is currently unused.",
+            "properties": {}
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "details",
+                "type": "object",
+                "properties": {
+                  "pixelSize": {
+                    "type": "integer",
+                    "description": "The font size in pixels."
+                  },
+                  "levelOfControl": {
+                    "$ref": "LevelOfControl",
+                    "description": "The level of control this extension has over the setting."
+                  }
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setMinimumFontSize",
+        "description": "Sets the minimum font size.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "pixelSize": {
+                "type": "integer",
+                "description": "The font size in pixels."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onFontChanged",
+        "description": "Fired when a font setting changes.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "fontId": {
+                "type": "string",
+                "description": "The font ID. See the description in <code>getFont</code>."
+              },
+              "script": {
+                "$ref": "ScriptCode",
+                "description": "The script code for which the font setting has changed.",
+                "optional": true
+              },
+              "genericFamily": {
+                "$ref": "GenericFamily",
+                "description": "The generic font family for which the font setting has changed."
+              },
+              "levelOfControl": {
+                "$ref": "LevelOfControl",
+                "description": "The level of control this extension has over the setting."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "onDefaultFontSizeChanged",
+        "description": "Fired when the default font size setting changes.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "pixelSize": {
+                "type": "integer",
+                "description": "The font size in pixels."
+              },
+              "levelOfControl": {
+                "$ref": "LevelOfControl",
+                "description": "The level of control this extension has over the setting."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "onDefaultFixedFontSizeChanged",
+        "description": "Fired when the default fixed font size setting changes.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "pixelSize": {
+                "type": "integer",
+                "description": "The font size in pixels."
+              },
+              "levelOfControl": {
+                "$ref": "LevelOfControl",
+                "description": "The level of control this extension has over the setting."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "onMinimumFontSizeChanged",
+        "description": "Fired when the minimum font size setting changes.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "pixelSize": {
+                "type": "integer",
+                "description": "The font size in pixels."
+              },
+              "levelOfControl": {
+                "$ref": "LevelOfControl",
+                "description": "The level of control this extension has over the setting."
+              }
+            }
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/history.json b/chrome/common/extensions/api/history.json
new file mode 100644
index 0000000..c72caee
--- /dev/null
+++ b/chrome/common/extensions/api/history.json
@@ -0,0 +1,180 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "history",
+    "types": [
+      {
+        "id": "HistoryItem",
+        "type": "object",
+        "description": "An object encapsulating one result of a history query.",
+        "properties": {
+          "id": {"type": "string", "minimum": 0, "description": "The unique identifier for the item."},
+          "url": {"type": "string", "optional": true, "description": "The URL navigated to by a user."},
+          "title": {"type": "string", "optional": true, "description": "The title of the page when it was last loaded."},
+          "lastVisitTime": {"type": "number", "optional": true, "description": "When this page was last loaded, represented in milliseconds since the epoch."},
+          "visitCount": {"type": "integer", "optional": true, "description": "The number of times the user has navigated to this page."},
+          "typedCount": {"type": "integer", "optional": true, "description": "The number of times the user has navigated to this page by typing in the address."}
+        }
+      },
+      {
+        "id": "VisitItem",
+        "type": "object",
+        "description": "An object encapsulating one visit to a URL.",
+        "properties": {
+          "id": {"type": "string", "minimum": 0, "description": "The unique identifier for the item."},
+          "visitId": {"type": "string", "description": "The unique identifier for this visit."},
+          "visitTime": {"type": "number", "optional": true, "description": "When this visit occurred, represented in milliseconds since the epoch."},
+          "referringVisitId": {"type": "string", "description": "The visit ID of the referrer."},
+          "transition": {
+            "type": "string",
+            "enum": ["link", "typed", "auto_bookmark", "auto_subframe", "manual_subframe", "generated", "auto_toplevel", "form_submit", "reload", "keyword", "keyword_generated"],
+            "description": "The <a href='#transition_types'>transition type</a> for this visit from its referrer."
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "search",
+        "type": "function",
+        "description": "Searches the history for the last visit time of each page matching the query.",
+        "parameters": [
+          {
+            "name": "query",
+            "type": "object",
+            "properties": {
+              "text": {"type": "string", "description": "A free-text query to the history service.  Leave empty to retrieve all pages."},
+              "startTime": {"type": "number", "optional": true, "description": "Limit results to those visited after this date, represented in milliseconds since the epoch."},
+              "endTime": {"type": "number", "optional": true, "description": "Limit results to those visited before this date, represented in milliseconds since the epoch."},
+              "maxResults": {"type": "integer", "optional": true, "minimum": 0, "description": "The maximum number of results to retrieve.  Defaults to 100."}
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              { "name": "results", "type": "array", "items": { "$ref": "HistoryItem"} }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getVisits",
+        "type": "function",
+        "description": "Retrieves information about visits to a URL.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "url": {"type": "string", "description": "The URL for which to retrieve visit information.  It must be in the format as returned from a call to history.search."}
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              { "name": "results", "type": "array", "items": { "$ref": "VisitItem"} }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "addUrl",
+        "type": "function",
+        "description": "Adds a URL to the history at the current time with a <a href='#transition_types'>transition type</a> of \"link\".",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "url": {"type": "string", "description": "The URL to add."}
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "deleteUrl",
+        "type": "function",
+        "description": "Removes all occurrences of the given URL from the history.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "url": {"type": "string", "description": "The URL to remove."}
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "deleteRange",
+        "type": "function",
+        "description": "Removes all items within the specified date range from the history.  Pages will not be removed from the history unless all visits fall within the range.",
+        "parameters": [
+          {
+            "name": "range",
+            "type": "object",
+            "properties": {
+              "startTime": { "type": "number", "description": "Items added to history after this date, represented in milliseconds since the epoch." },
+              "endTime": { "type": "number", "description": "Items added to history before this date, represented in milliseconds since the epoch." }
+            }
+          },
+          {
+            "name": "callback", "type": "function", "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "deleteAll",
+        "type": "function",
+        "description": "Deletes all items from the history.",
+        "parameters": [
+          {
+            "name": "callback", "type": "function", "parameters": []
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onVisited",
+        "type": "function",
+        "description": "Fired when a URL is visited, providing the HistoryItem data for that URL.  This event fires before the page has loaded.",
+        "parameters": [
+          { "name": "result", "$ref": "HistoryItem"}
+        ]
+      },
+      {
+        "name": "onVisitRemoved",
+        "type": "function",
+        "description": "Fired when one or more URLs are removed from the history service.  When all visits have been removed the URL is purged from history.",
+        "parameters": [
+          {
+            "name": "removed",
+            "type": "object",
+            "properties": {
+              "allHistory": { "type": "boolean", "description": "True if all history was removed.  If true, then urls will be empty." },
+              "urls": { "type": "array", "items": { "type": "string" }, "optional": true}
+            }
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/i18n.json b/chrome/common/extensions/api/i18n.json
new file mode 100644
index 0000000..db3c3f6
--- /dev/null
+++ b/chrome/common/extensions/api/i18n.json
@@ -0,0 +1,50 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "i18n",
+    "types": [],
+    "functions": [
+      {
+        "name": "getAcceptLanguages",
+        "type": "function",
+        "description": "Gets the accept-languages of the browser. This is different from the locale used by the browser; to get the locale, use <code>window.navigator.language</code>.",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {"name": "languages", "type": "array", "items": {"type": "string"}, "description": "Array of the accept languages of the browser, such as en-US,en,zh-CN"}
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getMessage",
+        "type": "function",
+        "unprivileged": true,
+        "description": "Gets the localized string for the specified message. If the message is missing, this method returns an empty string (''). If the format of the <code>getMessage()</code> call is wrong &mdash; for example, <em>messageName</em> is not a string or the <em>substitutions</em> array has more than 9 elements &mdash; this method returns <code>undefined</code>.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "messageName",
+            "description": "The name of the message, as specified in the <a href='i18n-messages.html'><code>messages.json</code></a> file."
+          },
+          {
+            "type": "any",
+            "name": "substitutions",
+            "optional": true,
+            "description": "Up to 9 substitution strings, if the message requires any."
+          }
+        ],
+        "returns": {
+          "type": "string",
+          "description": "Message localized for current locale."
+        }
+      }
+    ],
+    "events": []
+  }
+]
diff --git a/chrome/common/extensions/api/idle.json b/chrome/common/extensions/api/idle.json
new file mode 100644
index 0000000..03ad9af
--- /dev/null
+++ b/chrome/common/extensions/api/idle.json
@@ -0,0 +1,50 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "idle",
+    "types": [],
+    "functions": [
+      {
+        "name": "queryState",
+        "type": "function",
+        "description": "Returns the current state of the browser.",
+        "parameters": [
+          {
+            "name": "thresholdSeconds",
+            "type": "integer",
+            "minimum": 15,
+            "description": "Threshold, in seconds, used to determine when a machine is in the idle state."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name": "newState",
+                "type": "string",
+                "enum": ["active", "idle", "locked"]
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onStateChanged",
+        "type": "function",
+        "description": "Fired when the browser changes to an active state.  Currently only reports the transition from idle to active.",
+        "parameters": [
+          {
+            "name": "newState",
+            "type": "string",
+            "enum": ["active"]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/input_ime.json b/chrome/common/extensions/api/input_ime.json
new file mode 100644
index 0000000..f21cd39
--- /dev/null
+++ b/chrome/common/extensions/api/input_ime.json
@@ -0,0 +1,537 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "input.ime",
+    "platforms": ["chromeos"],
+    "types": [
+      {
+        "id": "KeyboardEvent",
+        "type": "object",
+        "description": "See http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent",
+        "properties": {
+          "type": {"type": "string", "description": "One of keyup or keydown.", "enum": ["keyup", "keydown"]},
+          "requestId": {"type": "string", "description": "The ID of the request."},
+          "key": {"type": "string", "description": "Value of the key being pressed"},
+          "altKey": {"type": "boolean", "optional": true, "description": "Whether or not the ALT key is pressed."},
+          "ctrlKey": {"type": "boolean", "optional": true, "description": "Whether or not the CTRL key is pressed."},
+          "shiftKey": {"type": "boolean", "optional": true, "description": "Whether or not the SHIFT key is pressed."}
+        }
+      },
+      {
+        "id": "InputContext",
+        "type": "object",
+        "description": "Describes an input Context",
+        "properties": {
+          "contextID": {"type": "integer", "description": "This is used to specify targets of text field operations.  This ID becomes invalid as soon as onBlur is called."},
+          "type": {"type": "string", "description": "Type of value this text field edits, (Text, Number, Password, etc)", "enum": ["text", "number", "password"]}
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "setComposition",
+        "type": "function",
+        "description": "Set the current composition. If this extension does not own the active IME, this fails.",
+        "parameters": [
+          {
+            "name": "parameters",
+            "type": "object",
+            "properties": {
+              "contextID": {
+                "description": "ID of the context where the composition text will be set",
+                "type": "integer"
+              },
+              "text": {
+                "description": "Text to set",
+                "type": "string"
+              },
+              "selectionStart": {
+                "description": "Position in the text that the selection starts at.",
+                "optional": true,
+                "type": "integer"
+              },
+              "selectionEnd": {
+                "description": "Position in the text that the selection ends at.",
+                "optional": true,
+                "type": "integer"
+              },
+              "cursor": {
+                "description": "Position in the text of the cursor.",
+                "type": "integer"
+              },
+              "segments": {
+                "description": "List of segments and their associated types.",
+                "type": "array",
+                "optional": true,
+                "items": {
+                  "type": "object",
+                  "properties": {
+                    "start": {
+                      "description": "Index of the character to start this segment at",
+                      "type": "integer"
+                    },
+                    "end": {
+                      "description": "Index of the character to end this segment after.",
+                      "type": "integer"
+                    },
+                    "style": {
+                      "description": "How to render this segment",
+                      "enum": ["underline", "doubleUnderline"],
+                      "type": "string"
+                    }
+                  }
+                }
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "description": "Called when the operation completes with a boolean indicating if the text was accepted or not. On failure, chrome.extension.lastError is set.",
+            "parameters": [
+              {
+                "name": "success",
+                "type": "boolean"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "clearComposition",
+        "type": "function",
+        "description": "Clear the current composition. If this extension does not own the active IME, this fails.",
+        "parameters": [
+          {
+            "name": "parameters",
+            "type": "object",
+            "properties": {
+              "contextID": {
+                "description": "ID of the context where the composition will be cleared",
+                "type": "integer"
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "description": "Called when the operation completes with a boolean indicating if the text was accepted or not. On failure, chrome.extension.lastError is set.",
+            "parameters": [
+              {
+                "name": "success",
+                "type": "boolean"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "commitText",
+        "type": "function",
+        "description": "Commits the provided text to the current input.",
+        "parameters": [
+          {
+            "name": "parameters",
+            "type": "object",
+            "properties": {
+              "contextID": {
+                "description": "ID of the context where the text will be committed",
+                "type": "integer"
+              },
+              "text": {
+                "description": "The text to commit",
+                "type": "string"
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "description": "Called when the operation completes with a boolean indicating if the text was accepted or not. On failure, chrome.extension.lastError is set.",
+            "parameters": [
+              {
+                "name": "success",
+                "type": "boolean"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setCandidateWindowProperties",
+        "type": "function",
+        "description": "Sets the properties of the candidate window. This fails if the extension doesn’t own the active IME",
+        "parameters": [
+          {
+            "name": "parameters",
+            "type": "object",
+            "properties": {
+              "engineID": {
+                "description": "ID of the engine to set properties on.",
+                "type": "string"
+              },
+              "properties": {
+                "type": "object",
+                "properties": {
+                  "visible": {
+                    "type": "boolean",
+                    "optional": true,
+                    "description": "True to show the Candidate window, false to hide it."
+                  },
+                  "cursorVisible": {
+                    "type": "boolean",
+                    "optional": true,
+                    "description": "True to show the cursor, false to hide it."
+                  },
+                  "vertical": {
+                    "type": "boolean",
+                    "optional": true,
+                    "description": "True if the candidate window should be rendered vertical, false to make it horizontal."
+                  },
+                  "pageSize": {
+                    "type": "integer",
+                    "optional": true,
+                    "description": "The number of candidates to display per page."
+                  },
+                  "auxiliaryText": {
+                    "type": "string",
+                    "optional": true,
+                    "description": "Text that is shown at the bottom of the candidate window."
+                  },
+                  "auxiliaryTextVisible": {
+                    "type": "boolean",
+                    "optional": true,
+                    "description": "True to display the auxiliary text, false to hide it."
+                  }
+                }
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "description": "Called when the operation completes.",
+            "parameters": [
+              {
+                "name": "success",
+                "type": "boolean"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setCandidates",
+        "type": "function",
+        "description": "Sets the current candidate list. This fails if this extension doesn’t own the active IME",
+        "parameters": [
+          {
+            "name": "parameters",
+            "type": "object",
+            "properties": {
+              "contextID": {
+                "description": "ID of the context that owns the candidate window.",
+                "type": "integer"
+              },
+              "candidates": {
+                "description": "List of candidates to show in the candidate window",
+                "type": "array",
+                "items": {
+                  "type": "object",
+                  "properties": {
+                    "candidate": {"type": "string", "description": "The candidate"},
+                    "id": {"type": "integer", "description": "The candidate's id"},
+                    "parentId": {"type": "integer", "optional": true, "description": "The id to add these candidates under"},
+                    "label": {"type": "string", "optional": true, "description": "Short string displayed to next to the candidate, often the shortcut key or index"},
+                    "annotation": {"type": "string", "optional": true, "description": "Additional text describing the candidate"}
+                  }
+                }
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "description": "Called when the operation completes.",
+            "parameters": [
+              {
+                "name": "success",
+                "type": "boolean"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setCursorPosition",
+        "type": "function",
+        "description": "Set the position of the cursor in the candidate window. This is a no-op if this extension does not own the active IME.",
+        "parameters": [
+          {
+            "name": "parameters",
+            "type": "object",
+            "properties": {
+              "contextID": {
+                "description": "ID of the context that owns the candidate window.",
+                "type": "integer"
+              },
+              "candidateID": {
+                "description": "ID of the candidate to select.",
+                "type": "integer"
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "description": "Called when the operation completes",
+            "parameters": [
+              {
+                "name": "success",
+                "type": "boolean"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setMenuItems",
+        "type": "function",
+        "description": "Adds the provided menu items to the language menu when this IME is active.",
+        "parameters": [
+          {
+            "name": "parameters",
+            "type": "object",
+            "properties": {
+              "engineID": {
+                "description": "ID of the engine to use",
+                "type": "string"
+              },
+              "items": {
+                "description": "MenuItems to add. They will be added in the order they exist in the array.",
+                "type": "array",
+                "items": {
+                  "type": "object",
+                  "description": "A menu item used by an input method to interact with the user from the language menu.",
+                  "properties": {
+                    "id": {"type": "string", "description": "String that will be passed to callbacks referencing this MenuItem."},
+                    "label": {"type": "string", "optional": true, "description": "Text displayed in the menu for this item."},
+                    "style": {
+                      "type": "string",
+                      "optional": true,
+                      "description": "Enum representing if this item is: none, check, radio, or a separator.  Radio buttons between separators are considered grouped.",
+                      "enum": ["none", "check", "radio", "separator"]
+                    },
+                    "visible": {"type": "boolean", "optional": true, "description": "Indicates this item is visible."},
+                    "checked": {"type": "boolean", "optional": true, "description": "Indicates this item should be drawn with a check."},
+                    "enabled": {"type": "boolean", "optional": true, "description": "Indicates this item is enabled."}
+                  }
+                }
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "description": "",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "updateMenuItems",
+        "type": "function",
+        "description": "Updates the state of the MenuItems specified",
+        "parameters": [
+          {
+            "name": "parameters",
+            "type": "object",
+            "properties": {
+              "engineID": {
+                "description": "ID of the engine to use",
+                "type": "string"
+              },
+              "items": {
+                "description": "Array of MenuItems to update",
+                "type": "array",
+                "items": {
+                  "type": "object",
+                  "description": "A menu item used by an input method to interact with the user from the language menu.",
+                  "properties": {
+                    "id": {"type": "string", "description": "String that will be passed to callbacks referencing this MenuItem."},
+                    "label": {"type": "string", "optional": true, "description": "Text displayed in the menu for this item."},
+                    "style": {
+                      "type": "string",
+                      "optional": true,
+                      "description": "Enum representing if this item is: none, check, radio, or a separator.  Radio buttons between separators are considered grouped.",
+                      "enum": ["none", "check", "radio", "separator"]
+                    },
+                    "visible": {"type": "boolean", "optional": true, "description": "Indicates this item is visible."},
+                    "checked": {"type": "boolean", "optional": true, "description": "Indicates this item should be drawn with a check."},
+                    "enabled": {"type": "boolean", "optional": true, "description": "Indicates this item is enabled."}
+                  }
+                }
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "description": "Called when the operation completes",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "eventHandled",
+        "nodoc": true,
+        "type": "function",
+        "description": "Used internally to send a response for onKeyEvent.",
+        "parameters": [
+          {"type": "string", "name": "requestId"},
+          {"type": "boolean", "name": "response"}
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onActivate",
+        "type": "function",
+        "description": "This event is sent when an IME is activated. It signals that the IME will be receiving onKeyPress events.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "engineID",
+            "description": "ID of the engine receiving the event"
+          }
+        ]
+      },
+      {
+        "name": "onDeactivated",
+        "type": "function",
+        "description": "This event is sent when an IME is deactivated. It signals that the IME will no longer be receiving onKeyPress events.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "engineID",
+            "description": "ID of the engine receiving the event"
+          }
+        ]
+      },
+      {
+        "name": "onFocus",
+        "type": "function",
+        "description": "This event is sent when focus enters a text box. It is sent to all extensions that are listening to this event, and enabled by the user.",
+        "parameters": [
+          {
+            "$ref": "InputContext",
+            "name": "context",
+            "description": "Describes the text field that has acquired focus."
+          }
+        ]
+      },
+      {
+        "name": "onBlur",
+        "type": "function",
+        "description": "This event is sent when focus leaves a text box. It is sent to all extensions that are listening to this event, and enabled by the user.",
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "contextID",
+            "description": "The ID of the text field that has lost focus. The ID is invalid after this call"
+          }
+        ]
+      },
+      {
+        "name": "onInputContextUpdate",
+        "type": "function",
+        "description": "This event is sent when the properties of the current InputContext change, such as the the type. It is sent to all extensions that are listening to this event, and enabled by the user.",
+        "parameters": [
+          {
+            "$ref": "InputContext",
+            "name": "context",
+            "description": "An InputContext object describing the text field that has changed."
+          }
+        ]
+      },
+      {
+        "name": "onKeyEvent",
+        "type": "function",
+        "description": "This event is sent if this extension owns the active IME.",
+        "options": {
+          "supportsFilters": false,
+          "supportsListeners": true,
+          "supportsRules": false,
+          "maxListeners": 1
+        },
+        "parameters": [
+          {
+            "type": "string",
+            "name": "engineID",
+            "description": "ID of the engine receiving the event"
+          },
+          {
+            "$ref": "KeyboardEvent",
+            "name": "keyData",
+            "description": "Data on the key event"
+          }
+        ],
+        "returns": {
+          "type": "boolean",
+          "description": "True if the keystroke was handled, false if not"
+        }
+      },
+      {
+        "name": "onCandidateClicked",
+        "type": "function",
+        "description": "This event is sent if this extension owns the active IME.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "engineID",
+            "description": "ID of the engine receiving the event"
+          },
+          {
+            "type": "integer",
+            "name": "candidateID",
+            "description": "ID of the candidate that was clicked."
+          },
+          {
+            "type": "string",
+            "name": "button",
+            "description": "Which mouse buttons was clicked.",
+            "enum": ["left", "middle", "right"]
+          }
+        ]
+      },
+      {
+        "name": "onMenuItemActivated",
+        "type": "function",
+        "description": "Called when the user selects a menu item",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "engineID",
+            "description": "ID of the engine receiving the event"
+          },
+          {
+            "type": "string",
+            "name": "name",
+            "description": "Name of the MenuItem which was activated"
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/input_method_private.json b/chrome/common/extensions/api/input_method_private.json
new file mode 100644
index 0000000..6c44192
--- /dev/null
+++ b/chrome/common/extensions/api/input_method_private.json
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "inputMethodPrivate",
+    "nodoc": true,
+    "platforms": ["chromeos"],
+    "types": [],
+    "functions": [
+      {
+        "name": "get",
+        "type": "function",
+        "description": "Gets the current input method.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": false,
+            "description": "Callback which is called with the current input method.",
+            "parameters": [
+              {
+                "name": "inputMethodId",
+                "type": "string",
+                "description": "Current input method."
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onChanged",
+        "type": "function",
+        "description": "Fired when the input method is changed.",
+        "parameters": [
+          {
+            "name": "newInputMethodId",
+            "type": "string",
+            "description": "New input method which is being used."
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/managed_mode_private.json b/chrome/common/extensions/api/managed_mode_private.json
new file mode 100644
index 0000000..8c5a63b
--- /dev/null
+++ b/chrome/common/extensions/api/managed_mode_private.json
@@ -0,0 +1,127 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "managedModePrivate",
+    "nodoc": true,
+    "functions": [
+      {
+        "name": "enter",
+        "type": "function",
+        "description": "Shows a confirmation dialog, then puts the browser into managed mode. The callback parameter will be true if managed mode was entered successfully, false if the user cancelled the confirmation. If managed mode is already on, trying to enter it again will have no effect.",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "result",
+                "type": "object",
+                "description": "The result of the attempt to enter managed mode.",
+                "properties": {
+                  "success": {
+                    "description": "True if managed mode was entered successfully, false if the user cancelled the confirmation.",
+                    "type": "boolean"
+                  }
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "get",
+        "type": "function",
+        "description": "Gets the value of the setting describing whether managed mode is in effect.",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "details",
+                "type": "object",
+                "description": "Details of the currently effective value.",
+                "properties": {
+                  "value": {
+                    "description": "The value of the setting.",
+                    "type": "boolean"
+                  }
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setPolicy",
+        "type": "function",
+        "description": "Sets a policy.",
+        "parameters": [
+          {
+            "name": "key",
+            "type": "string",
+            "description": "Policy key."
+          },
+          {
+            "name": "value",
+            "type": "any",
+            "description": "Policy value."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "getPolicy",
+        "type": "function",
+        "description": "Gets a policy value.",
+        "parameters": [
+          {
+            "name": "key",
+            "type": "string",
+            "description": "Policy key."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "value",
+                "type": "any",
+                "description": "Policy value or null if no policy is set.",
+                "optional": true
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onChange",
+        "description": "Fired when the value of the setting changes.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "value": {
+                "description": "The value of the setting.",
+                "type": "any"
+              }
+            }
+          }
+        ]
+      }
+    ]
+  }
+]
\ No newline at end of file
diff --git a/chrome/common/extensions/api/management.json b/chrome/common/extensions/api/management.json
new file mode 100644
index 0000000..8113906
--- /dev/null
+++ b/chrome/common/extensions/api/management.json
@@ -0,0 +1,306 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace":"management",
+    "types": [
+      {
+        "id": "IconInfo",
+        "description": "Information about an icon belonging to an extension, app, or theme.",
+        "type": "object",
+        "properties": {
+          "size": { "type": "integer", "description": "A number representing the width and height of the icon. Likely values include (but are not limited to) 128, 48, 24, and 16."  },
+          "url": { "type": "string", "description": "The URL for this icon image. To display a grayscale version of the icon (to indicate that an extension is disabled, for example), append <code>?grayscale=true</code> to the URL." }
+        }
+      },
+      {
+        "id": "ExtensionInfo",
+        "description": "Information about an installed extension, app, or theme.",
+        "type": "object",
+        "properties": {
+          "id": {
+            "description": "The extension's unique identifier.",
+            "type": "string"
+          },
+          "name": {
+            "description": "The name of this extension, app, or theme.",
+            "type": "string"
+          },
+          "description": {
+            "description": "The description of this extension, app, or theme.",
+            "type": "string"
+          },
+          "version": {
+            "description": "The <a href='manifest.html#version'>version</a> of this extension, app, or theme.",
+            "type": "string"
+          },
+          "mayDisable": {
+            "description": "Whether this extension can be disabled or uninstalled by the user.",
+            "type": "boolean"
+          },
+          "enabled": {
+            "description": "Whether it is currently enabled or disabled.",
+            "type": "boolean"
+          },
+          "disabledReason": {
+            "description": "A reason the item is disabled.",
+            "type": "string",
+            "enum": ["unknown", "permissions_increase"],
+            "optional": true
+          },
+          "isApp": {
+            "description": "True if this is an app.",
+            "type": "boolean",
+            "nodoc": true
+          },
+          "type": {
+            "description": "The type of this extension, app, or theme.",
+            "type": "string",
+            "enum": ["extension", "hosted_app", "packaged_app", "legacy_packaged_app", "theme"]
+          },
+          "appLaunchUrl": {
+            "description": "The launch url (only present for apps).",
+            "type": "string",
+            "optional": true
+          },
+          "homepageUrl": {
+            "description": "The URL of the homepage of this extension, app, or theme.",
+            "type": "string",
+            "optional": true
+          },
+          "updateUrl": {
+            "description": "The update URL of this extension, app, or theme.",
+            "type": "string",
+            "optional": true
+          },
+          "offlineEnabled": {
+            "description": "Whether the extension, app, or theme declares that it supports offline.",
+            "type": "boolean"
+          },
+          "optionsUrl": {
+            "description": "The url for the item's options page, if it has one.",
+            "type": "string"
+          },
+          "icons": {
+            "description": "A list of icon information. Note that this just reflects what was declared in the manifest, and the actual image at that url may be larger or smaller than what was declared, so you might consider using explicit width and height attributes on img tags referencing these images. See the <a href='manifest.html#icons'>manifest documentation on icons</a> for more details.",
+            "type": "array",
+            "optional": true,
+            "items": {
+              "$ref": "IconInfo"
+            }
+          },
+          "permissions": {
+            "description": "Returns a list of API based permissions.",
+            "type": "array",
+            "items" : {
+              "type": "string"
+            }
+          },
+          "hostPermissions": {
+            "description": "Returns a list of host based permissions.",
+            "type": "array",
+            "items" : {
+              "type": "string"
+            }
+          },
+          "installType": {
+            "description": "How the extension was installed. One of<br><var>admin</var>: The extension was installed because of an administrative policy,<br><var>development</var>: The extension was loaded unpacked in developer mode,<br><var>normal</var>: The extension was installed normally via a .crx file,<br><var>sideload</var>: The extension was installed by other software on the machine,<br><var>other</var>: The extension was installed by other means.",
+            "type": "string",
+            "enum": ["admin", "development", "normal", "sideload", "other"]
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "getAll",
+        "description": "Returns a list of information about installed extensions and apps.",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "type": "array",
+                "name": "result",
+                "items": {
+                  "$ref": "ExtensionInfo"
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "get",
+        "description": "Returns information about the installed extension, app, or theme that has the given ID.",
+        "parameters": [
+          {
+            "name": "id",
+            "type": "string",
+            "description": "The ID from an item of $ref:ExtensionInfo."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": "true",
+            "parameters": [
+              {
+                "name": "result",
+                "$ref": "ExtensionInfo"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getPermissionWarningsById",
+        "description": "Returns a list of <a href='permission_warnings.html'>permission warnings</a> for the given extension id.",
+        "parameters": [
+          { "name": "id",
+            "type": "string",
+            "description": "The ID of an already installed extension."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": "true",
+            "parameters": [
+              {
+                "name": "permissionWarnings",
+                "type": "array",
+                "items": { "type": "string" }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getPermissionWarningsByManifest",
+        "description": "Returns a list of <a href='permission_warnings.html'>permission warnings</a> for the given extension manifest string. Note: This function can be used without requesting the 'management' permission in the manifest.",
+        "parameters": [
+          {
+            "name": "manifestStr",
+            "type": "string",
+            "description": "Extension manifest JSON string."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": "true",
+            "parameters": [
+              {
+                "name": "permissionWarnings",
+                "type": "array",
+                "items": { "type": "string" }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setEnabled",
+        "description": "Enables or disables an app or extension.",
+        "parameters": [
+          {
+            "name": "id",
+            "type": "string",
+            "description": "This should be the id from an item of $ref:ExtensionInfo."
+          },
+          {
+            "name": "enabled",
+            "type": "boolean",
+            "description": "Whether this item should be enabled or disabled."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": "true",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "uninstall",
+        "description": "Uninstalls a currently installed app or extension.",
+        "parameters": [
+          {
+            "name": "id",
+            "type": "string",
+            "description": "This should be the id from an item of $ref:ExtensionInfo."
+          },
+          {
+            "type": "object",
+            "name": "options",
+            "optional": "true",
+            "properties": {
+              "showConfirmDialog": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether or not a confirm-uninstall dialog should prompt the user. Defaults to false."
+              }
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": "true",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "launchApp",
+        "description": "Launches an application.",
+        "parameters": [
+          {
+            "name": "id",
+            "type": "string",
+            "description": "The extension id of the application."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": "true",
+            "parameters": []
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onInstalled",
+        "description": "Fired when an app or extension has been installed.",
+        "type": "function",
+        "parameters": [{"name": "info", "$ref":"ExtensionInfo"}]
+      },
+      {
+        "name": "onUninstalled",
+        "description": "Fired when an app or extension has been uninstalled.",
+        "type": "function",
+        "parameters": [
+          {
+           "name": "id",
+           "type": "string",
+           "description": "The id of the extension, app, or theme that was uninstalled."
+          }
+        ]
+      },
+      {
+        "name": "onEnabled",
+        "description": "Fired when an app or extension has been enabled.",
+        "type": "function",
+        "parameters": [{"name": "info", "$ref":"ExtensionInfo"}]
+      },
+      {
+        "name": "onDisabled",
+        "description": "Fired when an app or extension has been disabled",
+        "type": "function",
+        "parameters": [{"name": "info", "$ref":"ExtensionInfo"}]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/media_galleries.idl b/chrome/common/extensions/api/media_galleries.idl
new file mode 100644
index 0000000..d18359d
--- /dev/null
+++ b/chrome/common/extensions/api/media_galleries.idl
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 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.
+
+namespace mediaGalleries {
+
+  [inline_doc] enum GetMediaFileSystemsInteractivity {
+    // Do not act interactively.
+    no,
+    // Ask the user to manage permitted media galleries.
+    yes,
+    // Ask the user to manage permitted galleries only if the return set would
+    // otherwise be empty.
+    if_needed
+  };
+
+  [inline_doc] dictionary MediaFileSystemsDetails {
+    // Whether to prompt the user for permission to additional media galleries
+    // before returning the permitted set. Default is silent.  If the value
+    // 'yes' is passed, or if the application has not been granted access to
+    // any media galleries and the value 'if_needed' is passed, then the
+    // media gallery configuration dialog will be displayed.
+    GetMediaFileSystemsInteractivity? interactive;
+  };
+
+  callback MediaFileSystemsCallback =
+      void ([instanceOf=LocalFileSystem] optional object[] mediaFileSystems);
+
+  interface Functions {
+    // Get the media galleries configured in this user agent. If none are
+    // configured or available, the callback will receive an empty array.
+    static void getMediaFileSystems(optional MediaFileSystemsDetails details,
+                                    MediaFileSystemsCallback callback);
+  };
+
+};
diff --git a/chrome/common/extensions/api/media_galleries_private.idl b/chrome/common/extensions/api/media_galleries_private.idl
new file mode 100644
index 0000000..5b8b2f4
--- /dev/null
+++ b/chrome/common/extensions/api/media_galleries_private.idl
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 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.
+
+// This is a private API for M23. This will be superceded by the
+// systeminfo.storage API in M24.
+
+namespace mediaGalleriesPrivate {
+    // A dictionary that describes an attached device.
+  [inline_doc] dictionary DeviceAttachmentDetails {
+    // The name of the device.
+    DOMString deviceName;
+
+    // A transient id that unique identifies the device.
+    DOMString deviceId;
+  };
+
+  // A dictionary that describes a detached device.
+  [inline_doc] dictionary DeviceDetachmentDetails {
+    // A transient id that unique identifies the device.
+    DOMString deviceId;
+  };
+
+  interface Events {
+    // Fired when a media device gets attached.
+    static void onDeviceAttached(DeviceAttachmentDetails details);
+
+    // Fired when a media device gets detached.
+    static void onDeviceDetached(DeviceDetachmentDetails details);
+  };
+};
diff --git a/chrome/common/extensions/api/media_player_private.json b/chrome/common/extensions/api/media_player_private.json
new file mode 100644
index 0000000..5d77ab9
--- /dev/null
+++ b/chrome/common/extensions/api/media_player_private.json
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "mediaPlayerPrivate",
+    "nodoc": "true",
+    "types": [
+      {
+        "id": "Playlist",
+        "type": "object",
+        "description": "Mediaplayer playlist stored in the browser (it extsts even if the mediaplayer is closed).",
+        "properties": {
+          "items": {
+            "name": "items",
+            "type": "array",
+            "description": "Array of URLs for media files (in 'filesystem:' scheme for local files)",
+            "items": { "type": "string" }
+          },
+          "position": {
+            "type": "integer",
+            "description": "A position in the playlist."
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "play",
+        "description": "Plays a new playlist from a given position.",
+        "parameters": [
+          {
+            "name": "items",
+            "type": "array",
+            "description": "Array of URLs for media files (in 'filesystem:' scheme for local files)",
+            "items": { "type": "string" }
+          },
+          {
+            "name": "position",
+            "type": "integer",
+            "description": "A position in the playlist."
+          }
+        ]
+      },
+      {
+        "name": "getPlaylist",
+        "type": "function",
+        "description": "Returns current playlist and position.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Callback to retrieve the playlist.",
+            "parameters": [
+              {
+                "name": "playlist",
+                "$ref": "Playlist",
+                "description": "Mediaplayer playlist stored in the browser (it extsts even if the mediaplayer is closed)."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setWindowHeight",
+        "description": "Changes the height of the media player window.",
+        "parameters": [
+          {
+            "name": "height",
+            "type": "integer",
+            "description": "Height of the media player window (not including window title or borders)."
+          }
+        ]
+      },
+      {
+        "name": "closeWindow",
+        "description": "Closes the media player window.",
+        "parameters": []
+      }
+    ],
+    "events": [
+      {
+        "name": "onNextTrack",
+        "type": "function",
+        "description": "Notifies that the next track was requested.",
+        "parameters": []
+      },
+      {
+        "name": "onPlaylistChanged",
+        "type": "function",
+        "description": "Notifies that playlist content or state has been changed. Data could be retrieved via 'getPlaylist'.",
+        "parameters": []
+      },
+      {
+        "name": "onPrevTrack",
+        "type": "function",
+        "description": "Notifies that the previous tack was requested.",
+        "parameters": []
+      },
+      {
+        "name": "onTogglePlayState",
+        "type": "function",
+        "description": "Notifies that a play/pause toggle was requested.",
+        "parameters": []
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/metrics_private.json b/chrome/common/extensions/api/metrics_private.json
new file mode 100644
index 0000000..0113563
--- /dev/null
+++ b/chrome/common/extensions/api/metrics_private.json
@@ -0,0 +1,111 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "metricsPrivate",
+    "nodoc": true,
+    "types": [
+      {
+        "id": "MetricType",
+        "type":  "object",
+        "description": "Describes the type of metric that is to be collected.",
+        "properties": {
+          "metricName": {"type": "string", "description": "A unique name within the extension for the metric."},
+          "type": {
+            "type": "string",
+            "enum": ["histogram-log", "histogram-linear"],
+            "description": "The type of metric, such as 'histogram-log' or 'histogram-linear'."
+          },
+          "min": {"type": "integer", "description": "The minimum sample value to be recoded.  Must be greater than zero."},
+          "max": {"type": "integer", "description": "The maximum sample value to be recoded."},
+          "buckets": {"type": "integer", "description": "The number of buckets to use when separating the recorded values."}
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "recordUserAction",
+        "type": "function",
+        "description": "Records an action performed by the user.",
+        "parameters": [
+          {"name": "name", "type": "string"}
+        ]
+      },
+      {
+        "name": "recordPercentage",
+        "type": "function",
+        "description": "Records a percentage value from 1 to 100.",
+        "parameters": [
+          {"name": "metricName", "type": "string"},
+          {"name": "value", "type": "integer"}
+        ]
+      },
+      {
+        "name": "recordCount",
+        "type": "function",
+        "description": "Records a value than can range from 1 to 1,000,000.",
+        "parameters": [
+          {"name": "metricName", "type": "string"},
+          {"name": "value", "type": "integer"}
+        ]
+      },
+      {
+        "name": "recordSmallCount",
+        "type": "function",
+        "description": "Records a value than can range from 1 to 100.",
+        "parameters": [
+          {"name": "metricName", "type": "string"},
+          {"name": "value", "type": "integer"}
+        ]
+      },
+      {
+        "name": "recordMediumCount",
+        "type": "function",
+        "description": "Records a value than can range from 1 to 10,000.",
+        "parameters": [
+          {"name": "metricName", "type": "string"},
+          {"name": "value", "type": "integer"}
+        ]
+      },
+      {
+        "name": "recordTime",
+        "type": "function",
+        "description": "Records an elapsed time of no more than 10 seconds.  The sample value is specified in milliseconds.",
+        "parameters": [
+          {"name": "metricName", "type": "string"},
+          {"name": "value", "type": "integer"}
+        ]
+      },
+      {
+        "name": "recordMediumTime",
+        "type": "function",
+        "description": "Records an elapsed time of no more than 3 minutes.  The sample value is specified in milliseconds.",
+        "parameters": [
+          {"name": "metricName", "type": "string"},
+          {"name": "value", "type": "integer"}
+        ]
+      },
+      {
+        "name": "recordLongTime",
+        "type": "function",
+        "description": "Records an elapsed time of no more than 1 hour.  The sample value is specified in milliseconds.",
+        "parameters": [
+          {"name": "metricName", "type": "string"},
+          {"name": "value", "type": "integer"}
+        ]
+      },
+      {
+        "name": "recordValue",
+        "type": "function",
+        "description": "Adds a value to the given metric.",
+        "parameters": [
+          {"name": "metric", "$ref": "MetricType"},
+          {"name": "value", "type": "integer"}
+        ]
+      }
+    ],
+    "events": []
+  }
+]
diff --git a/chrome/common/extensions/api/omnibox.json b/chrome/common/extensions/api/omnibox.json
new file mode 100644
index 0000000..bcad742
--- /dev/null
+++ b/chrome/common/extensions/api/omnibox.json
@@ -0,0 +1,118 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "omnibox",
+    "types": [
+      {
+        "id": "SuggestResult",
+        "type": "object",
+        "description": "A suggest result.",
+        "properties": {
+          "content": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The text that is put into the URL bar, and that is sent to the extension when the user chooses this entry."
+          },
+          "description": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The text that is displayed in the URL dropdown. Can contain XML-style markup for styling. The supported tags are 'url' (for a literal URL), 'match' (for highlighting text that matched what the user's query), and 'dim' (for dim helper text). The styles can be nested, eg. <dim><match>dimmed match</match></dim>."
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "sendSuggestions",
+        "nodoc": true,
+        "type": "function",
+        "description": "A callback passed to the onInputChanged event used for sending suggestions back to the browser.",
+        "parameters": [
+          {"type": "integer", "name": "requestId"},
+          {
+            "name": "suggestResults",
+            "type": "array",
+            "description": "Array of suggest results",
+            "items": {
+              "type": "object",
+              "additionalProperties": { "type": "any" }
+            }
+          }
+        ]
+      },
+      {
+        "name": "setDefaultSuggestion",
+        "type": "function",
+        "description": "Sets the description and styling for the default suggestion. The default suggestion is the text that is displayed in the first suggestion row underneath the URL bar.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "suggestion",
+            "description": "A partial SuggestResult object, without the 'content' parameter. See SuggestResult for a description of the parameters.",
+            "properties": {
+              "description": {
+                "type": "string",
+                "minLength": 1,
+                "description": "The text to display in the default suggestion. The placeholder string '%s' can be included and will be replaced with the user's input."
+              }
+            }
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onInputStarted",
+        "type": "function",
+        "description": "User has started a keyword input session by typing the extension's keyword. This is guaranteed to be sent exactly once per input session, and before any onInputChanged events.",
+        "parameters": []
+      },
+      {
+        "name": "onInputChanged",
+        "type": "function",
+        "description": "User has changed what is typed into the omnibox.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "text"
+          },
+          {
+            "name": "suggest",
+            "type": "function",
+            "description": "A callback passed to the onInputChanged event used for sending suggestions back to the browser.",
+            "parameters": [
+              {
+                "name": "suggestResults",
+                "type": "array",
+                "description": "Array of suggest results",
+                "items": {
+                  "$ref": "SuggestResult"
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "onInputEntered",
+        "type": "function",
+        "description": "User has accepted what is typed into the omnibox.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "text"
+          }
+        ]
+      },
+      {
+        "name": "onInputCancelled",
+        "type": "function",
+        "description": "User has ended the keyword input session without accepting the input.",
+        "parameters": []
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/page_action.json b/chrome/common/extensions/api/page_action.json
new file mode 100644
index 0000000..513f4dd
--- /dev/null
+++ b/chrome/common/extensions/api/page_action.json
@@ -0,0 +1,191 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "pageAction",
+    "dependencies": [ "tabs" ],
+    "types": [
+      {
+        "id": "ImageDataType",
+        "type": "object",
+        "isInstanceOf": "ImageData",
+        "additionalProperties": { "type": "any" },
+        "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)."
+      }
+    ],
+    "functions": [
+      {
+        "name": "show",
+        "type": "function",
+        "description": "Shows the page action. The page action is shown whenever the tab is selected.",
+        "parameters": [
+          {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."}
+        ]
+      },
+      {
+        "name": "hide",
+        "type": "function",
+        "description": "Hides the page action.",
+        "parameters": [
+          {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."}
+        ]
+      },
+      {
+        "name": "setTitle",
+        "type": "function",
+        "description": "Sets the title of the page action. This is displayed in a tooltip over the page action.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
+              "title": {"type": "string", "description": "The tooltip string."}
+            }
+          }
+        ]
+      },
+      {
+        "name": "getTitle",
+        "type": "function",
+        "description": "Gets the title of the browser action.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {
+                "type": "integer",
+                "description": "Specify the tab to get the title from."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "string"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setIcon",
+        "type": "function",
+        "description": "Sets the icon for the page action. The icon can be specified either as the path to an image file or as the pixel data from a canvas element, or as dictionary of either one of those. Either the <b>path</b> or the <b>imageData</b> property must be specified.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
+              "imageData": {
+                "choices": [
+                  { "$ref": "ImageDataType" },
+                  {
+                    "type": "object",
+                    "properties": {
+                      "19": {"$ref": "ImageDataType", "optional": true},
+                      "38": {"$ref": "ImageDataType", "optional": true}
+                     }
+                  }
+                ],
+                "optional": true,
+                "description": "Either an ImageData object or a dictionary {size -> ImageData} representing icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.imageData = foo' is equivalent to 'details.imageData = {'19': foo}'"
+              },
+              "path": {
+                "choices": [
+                  { "type": "string" },
+                  {
+                    "type": "object",
+                    "properties": {
+                      "19": {"type": "string", "optional": true},
+                      "38": {"type": "string", "optional": true}
+                    }
+                  }
+                ],
+                "optional": true,
+                "description": "Either a relative image path or a dictionary {size -> relative image path} pointing to icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.path = foo' is equivalent to 'details.imageData = {'19': foo}'"
+              },
+              "iconIndex": {
+                "type": "integer",
+                "minimum": 0,
+                "description": "<b>Deprecated.</b> This argument is ignored.",
+                "optional": true
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "setPopup",
+        "type": "function",
+        "description": "Sets the html document to be opened as a popup when the user clicks on the page action's icon.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
+              "popup": {
+                "type": "string",
+                "description": "The html file to show in a popup.  If set to the empty string (''), no popup is shown."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "getPopup",
+        "type": "function",
+        "description": "Gets the html document set as the popup for this browser action.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {
+                "type": "integer",
+                "description": "Specify the tab to get the popup from."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "string"
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onClicked",
+        "type": "function",
+        "description": "Fired when a page action icon is clicked.  This event will not fire if the page action has a popup.",
+        "parameters": [
+          {
+            "name": "tab",
+            "$ref": "tabs.Tab"
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/page_actions.json b/chrome/common/extensions/api/page_actions.json
new file mode 100644
index 0000000..7283439
--- /dev/null
+++ b/chrome/common/extensions/api/page_actions.json
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "pageActions",
+    "nodoc": true,
+    "maximumManifestVersion": 1,
+    "types": [],
+    "functions": [
+      {
+        "name": "enableForTab",
+        "type": "function",
+        "description": "Enables a page action for a particular tab+URL combination (makes its icon visible in the OmniBox when a certain URL is active in a given tab). The page action will automatically be disabled (its icon hidden) if the user navigates to a new URL or closes the tab. The action will also automatically be enabled/disabled as the user switches tabs.",
+        "parameters": [
+          {"type": "string", "name": "pageActionId", "description": "An extension can have multiple page actions specified in the manifest, each with a unique identifier. This string identifies which page action you want to enable (and must match a page action id declared in the manifest)."},
+          {
+            "type": "object",
+            "name": "action",
+            "description": "An object specifing what action should be applied to the page action. Contains the following properties:",
+            "properties": {
+              "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to enable the page action."},
+              "url": {"type": "string", "description": "The URL of the page you want the page action to apply to. If the URL specified does not match the currently navigated URL (user has navigated to another page) then no action is taken."},
+              "title": {"type": "string", "optional": true, "description": "Specifying <b>title</b> allows you to change the tooltip that appears when you hover over the page action icon in the OmniBox. This parameter is optional and if omitted then the page action <b>name</b> property declared in the manifest is used."},
+              "iconId": {"type": "integer", "minimum": 0, "optional": true, "description": "A zero-based index into the <b>icons</b> vector specified in the manifest. This parameter is optional and if omitted then the first icon in the <b>icons</b> vector of the page action is used. This id is useful to represent different page action states. Example: An RSS feed icon could have a 'subscribe now' icon and an 'already subscribed' icon."}
+            },
+            "optional": false
+          }
+        ]
+      },
+      {
+        "name": "disableForTab",
+        "type": "function",
+        "description": "Disables a page action for a particular tab+URL combination (makes its OmniBox page action icon hidden when a certain URL is active in a given tab). This can be useful to disable a page action before the user navigates away from a page containing an enabled page action.",
+        "parameters": [
+          {"type": "string", "name": "pageActionId", "description": "An extension can have multiple page actions specified in the manifest, each with a unique identifier. This string identifies which page action you want to disable (and must match a page action id declared in the manifest)."},
+          {
+            "type": "object",
+            "name": "action",
+            "description": "An object specifying what action should be applied to the page action. Contains the following properties:",
+            "properties": {
+              "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to disable the page action."},
+              "url": {"type": "string", "description": "The URL of the page you want the page action to not apply to. If the URL specified does not match the currently navigated URL (user has navigated to another page) then no action is taken."}
+            },
+            "optional": false
+          }
+        ]
+      }
+    ],
+    "events": []
+  }
+]
diff --git a/chrome/common/extensions/api/page_capture.json b/chrome/common/extensions/api/page_capture.json
new file mode 100644
index 0000000..5c97410
--- /dev/null
+++ b/chrome/common/extensions/api/page_capture.json
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "pageCapture",
+    "functions": [
+      {
+        "name": "saveAsMHTML",
+        "type": "function",
+        "description": "Saves the content of the tab with given id as MHTML.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {
+                "type": "integer",
+                "minimum": 0,
+                "description": "The id of the tab to save as MHTML."
+              }
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the MHTML has been generated.",
+            "parameters": [
+              {
+                "name": "mhtmlData",
+                "type": "binary",
+                "optional": "true",
+                "description": "The MHTML data as a Blob."
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/permissions.json b/chrome/common/extensions/api/permissions.json
new file mode 100644
index 0000000..4f4b362
--- /dev/null
+++ b/chrome/common/extensions/api/permissions.json
@@ -0,0 +1,143 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "permissions",
+    "types": [
+      {
+        "id": "Permissions",
+        "type": "object",
+        "properties": {
+          "permissions": {
+            "type": "array",
+            "items": {"type": "string"},
+            "optional": true,
+            "description": "List of named permissions (does not include hosts or origins)."
+          },
+          "origins": {
+            "type": "array",
+            "items": {"type": "string"},
+            "optional": true,
+            "description": "List of origin permissions."
+          }
+        }
+      }
+    ],
+    "events": [
+      {
+        "name": "onAdded",
+        "type": "function",
+        "description": "Fired when the extension acquires new permissions.",
+        "parameters": [
+          {
+            "$ref": "Permissions",
+            "name": "permissions",
+            "description": "The newly acquired permissions."
+          }
+        ]
+      },
+      {
+        "name": "onRemoved",
+        "type": "function",
+        "description": "Fired when access to permissions has been removed from the extension.",
+        "parameters": [
+          {
+            "$ref": "Permissions",
+            "name": "permissions",
+            "description": "The permissions that have been removed."
+          }
+        ]
+      }
+     ],
+    "functions": [
+      {
+        "name": "getAll",
+        "type": "function",
+        "description": "Gets the extension's current set of permissions.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+               {
+                "name": "permissions",
+                "$ref": "Permissions",
+                "description": "The extension's active permissions."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "contains",
+        "type": "function",
+        "description": "Checks if the extension has the specified permissions.",
+        "parameters": [
+          {
+            "name": "permissions",
+            "$ref": "Permissions"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "boolean",
+                "description": "True if the extension has the specified permissions."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "request",
+        "type": "function",
+        "description": "Requests access to the specified permissions. These permissions must be defined in the optional_permissions field of the manifest. If there are any problems requesting the permissions, <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will be set.",
+        "parameters": [
+          {
+            "name": "permissions",
+            "$ref": "Permissions"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "granted",
+                "type": "boolean",
+                "description": "True if the user granted the specified permissions."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "remove",
+        "type": "function",
+        "description": "Removes access to the specified permissions. If there are any problems removing the permissions, <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will be set.",
+        "parameters": [
+          {
+            "name": "permissions",
+            "$ref": "Permissions"
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "removed",
+                "type": "boolean",
+                "description": "True if the permissions were removed."
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/privacy.json b/chrome/common/extensions/api/privacy.json
new file mode 100644
index 0000000..a74b505
--- /dev/null
+++ b/chrome/common/extensions/api/privacy.json
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "privacy",
+    "dependencies": [ "types" ],
+    "properties": {
+      "network": {
+        "type": "object",
+        "value": {},
+        "description": "Settings that influence Chrome's handling of network connections in general.",
+        "properties": {
+          "networkPredictionEnabled": {
+            "$ref": "types.ChromeSetting",
+            "value": ["networkPredictionEnabled", {"type":"boolean"}],
+            "description": "If enabled, Chrome attempts to speed up your web browsing experience by pre-resolving DNS entries, prerendering sites (<code>&lt;link rel='prefetch' ...&gt;</code>), and preemptively opening TCP and SSL connections to servers.  This preference's value is a boolean, defaulting to <code>true</code>."
+          }
+        }
+      },
+      "services": {
+        "type": "object",
+        "value": {},
+        "description": "Settings that enable or disable features that require third-party network services provided by Google and your default search provider.",
+        "properties": {
+          "alternateErrorPagesEnabled": {
+            "$ref": "types.ChromeSetting",
+            "value": ["alternateErrorPagesEnabled", {"type":"boolean"}],
+            "description": "If enabled, Chrome uses a web service to help resolve navigation errors. This preference's value is a boolean, defaulting to <code>true</code>."
+          },
+          "autofillEnabled": {
+            "$ref": "types.ChromeSetting",
+            "value": ["autofillEnabled", {"type":"boolean"}],
+            "description": "If enabled, Chrome offers to automatically fill in forms. This preference's value is a boolean, defaulting to <code>true</code>."
+          },
+          "instantEnabled": {
+            "$ref": "types.ChromeSetting",
+            "value": ["instantEnabled", {"type":"boolean"}],
+            "description": "If enabled, Chrome automatically performs and displays search requests for text you type into the Omnibox as you type it. This preference's value is a boolean, defaulting to <code>true</code>."
+          },
+          "safeBrowsingEnabled": {
+            "$ref": "types.ChromeSetting",
+            "value": ["safeBrowsingEnabled", {"type":"boolean"}],
+            "description": "If enabled, Chrome does its best to protect you from phishing and malware. This preference's value is a boolean, defaulting to <code>true</code>."
+          },
+          "searchSuggestEnabled": {
+            "$ref": "types.ChromeSetting",
+            "value": ["searchSuggestEnabled", {"type":"boolean"}],
+            "description": "If enabled, Chrome sends the text you type into the Omnibox to your default search engine, which provides predictions of websites and searches that are likely completions of what you've typed so far. This preference's value is a boolean, defaulting to <code>true</code>."
+          },
+          "spellingServiceEnabled": {
+            "$ref": "types.ChromeSetting",
+            "value": ["spellingServiceEnabled", {"type":"boolean"}],
+            "description": "If enabled, Chrome uses a web service to help correct spelling errors. This preference's value is a boolean, defaulting to <code>false</code>."
+          },
+          "translationServiceEnabled": {
+            "$ref": "types.ChromeSetting",
+            "value": ["translationServiceEnabled", {"type":"boolean"}],
+            "description": "If enabled, Chrome offers to translate pages that aren't in a language you read. This preference's value is a boolean, defaulting to <code>true</code>."
+          }
+        }
+      },
+      "websites": {
+        "type": "object",
+        "value": {},
+        "description": "Settings that determine what information Chrome makes available to websites.",
+        "properties": {
+          "thirdPartyCookiesAllowed": {
+            "$ref": "types.ChromeSetting",
+            "value": ["thirdPartyCookiesAllowed", {"type": "boolean"}],
+            "description": "If disabled, Chrome blocks third-party sites from setting cookies. The value of this preference is of type boolean, and the default value is <code>true</code>."
+          },
+          "hyperlinkAuditingEnabled": {
+            "$ref": "types.ChromeSetting",
+            "value": ["hyperlinkAuditingEnabled", {"type":"boolean"}],
+            "description": "If enabled, Chrome sends auditing pings when requested by a website (<code>&lt;a ping&gt;</code>). The value of this preference is of type boolean, and the default value is <code>true</code>."
+          },
+          "referrersEnabled": {
+            "$ref": "types.ChromeSetting",
+            "value": ["referrersEnabled", {"type":"boolean"}],
+            "description": "If enabled, Chrome sends <code>referer</code> headers with your requests. Yes, the name of this preference doesn't match the misspelled header. No, we're not going to change it. The value of this preference is of type boolean, and the default value is <code>true</code>."
+          },
+          "protectedContentEnabled": {
+            "$ref": "types.ChromeSetting",
+            "value": ["protectedContentEnabled", {"type":"boolean"}],
+            "description": "<strong>Available on ChromeOS only</strong>: If enabled, Chrome provides a unique ID to plugins in order to run protected content. The value of this preference is of type boolean, and the default value is <code>true</code>.",
+            "platforms": ["cros", "cros touch"]
+          }
+        }
+      }
+    }
+  }
+]
diff --git a/chrome/common/extensions/api/proxy.json b/chrome/common/extensions/api/proxy.json
new file mode 100644
index 0000000..eeb957a
--- /dev/null
+++ b/chrome/common/extensions/api/proxy.json
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "proxy",
+    "dependencies": [ "types" ],
+    "types": [
+      {
+        "id": "ProxyServer",
+        "type": "object",
+        "description": "An object encapsulating a single proxy server's specification.",
+        "properties": {
+          "scheme": {"type": "string", "optional": true, "enum": ["http", "https", "socks4", "socks5"], "description": "The scheme (protocol) of the proxy server itself. Defaults to 'http'."},
+          "host": {"type": "string", "description": "The URI of the proxy server. This must be an ASCII hostname (in Punycode format). IDNA is not supported, yet."},
+          "port": {"type": "integer", "optional": true, "description": "The port of the proxy server. Defaults to a port that depends on the scheme."}
+        }
+      },
+      {
+        "id": "ProxyRules",
+        "type": "object",
+        "description": "An object encapsulating the set of proxy rules for all protocols. Use either 'singleProxy' or (a subset of) 'proxyForHttp', 'proxyForHttps', 'proxyForFtp' and 'fallbackProxy'.",
+        "properties": {
+          "singleProxy": {"$ref": "ProxyServer", "optional": true, "description": "The proxy server to be used for all per-URL requests (that is http, https, and ftp)."},
+          "proxyForHttp": {"$ref": "ProxyServer", "optional": true, "description": "The proxy server to be used for HTTP requests."},
+          "proxyForHttps": {"$ref": "ProxyServer", "optional": true, "description": "The proxy server to be used for HTTPS requests."},
+          "proxyForFtp": {"$ref": "ProxyServer", "optional": true, "description": "The proxy server to be used for FTP requests."},
+          "fallbackProxy": {"$ref": "ProxyServer", "optional": true, "description": "The proxy server to be used for everthing else or if any of the specific proxyFor... is not specified."},
+          "bypassList": {"type": "array", "items": {"type": "string"}, "optional": true, "description": "List of servers to connect to without a proxy server."}
+        }
+      },
+      {
+        "id": "PacScript",
+        "type": "object",
+        "description": "An object holding proxy auto-config information. Exactly one of the fields should be non-empty.",
+        "properties": {
+          "url": {"type": "string", "optional": true, "description": "URL of the PAC file to be used."},
+          "data": {"type": "string", "optional": true, "description": "A PAC script."},
+          "mandatory": {"type": "boolean", "optional": true, "description": "If true, an invalid PAC script will prevent the network stack from falling back to direct connections. Defaults to false."}
+        }
+      },
+      {
+        "id": "ProxyConfig",
+        "type": "object",
+        "description": "An object encapsulating a complete proxy configuration.",
+        "properties": {
+          "rules": {"$ref": "ProxyRules", "optional": true, "description": "The proxy rules describing this configuration. Use this for 'fixed_servers' mode."},
+          "pacScript": {"$ref": "PacScript", "optional": true, "description": "The proxy auto-config (PAC) script for this configuration. Use this for 'pac_script' mode."},
+          "mode": {
+            "type": "string",
+            "enum": ["direct", "auto_detect", "pac_script", "fixed_servers", "system"],
+            "description": "'direct' = Never use a proxy<br>'auto_detect' = Auto detect proxy settings<br>'pac_script' = Use specified PAC script<br>'fixed_servers' = Manually specify proxy servers<br>'system' = Use system proxy settings"
+          }
+        }
+      }
+    ],
+    "properties": {
+      "settings": {
+        "$ref": "types.ChromeSetting",
+        "description": "Proxy settings to be used. The value of this setting is a ProxyConfig object.",
+        "value": [
+          "proxy",
+          {"$ref": "ProxyConfig"}
+        ]
+      }
+    },
+    "events": [
+      {
+        "name": "onProxyError",
+        "type": "function",
+        "description": "Notifies about proxy errors.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "fatal": {
+                "type": "boolean",
+                "description": "If true, the error was fatal and the network transaction was aborted. Otherwise, a direct connection is used instead."
+              },
+              "error": {
+                "type": "string",
+                "description": "The error description."
+              },
+              "details": {
+                "type": "string",
+                "description": "Additional details about the error such as a JavaScript runtime error."
+              }
+            }
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/push_messaging.idl b/chrome/common/extensions/api/push_messaging.idl
new file mode 100644
index 0000000..e8a2840
--- /dev/null
+++ b/chrome/common/extensions/api/push_messaging.idl
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 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.
+
+namespace pushMessaging{
+
+  dictionary Message {
+    // The subchannel the message was sent on.
+    long subchannelId;
+
+    // The payload associated with the message, if any.
+    DOMString payload;
+  };
+
+  dictionary ChannelIdResult {
+    // The channel ID for this app to use for push messaging.
+    DOMString channelId;
+  };
+
+  callback ChannelIdCallback = void (ChannelIdResult channelId);
+
+  interface Functions {
+    // Retrieves the channel ID associated with this app or extension.
+    // Typically an app or extension will want to send this value
+    // to its application server so the server can use it
+    // to trigger push messages back to the app or extension.
+    // If the interactive flag is set, we will ask the user to log in
+    // when they are not already logged in.
+    static void getChannelId(optional boolean interactive,
+                             ChannelIdCallback callback);
+  };
+
+  interface Events {
+    // Fired when a push message has been received.
+    // |message| : The details associated with the message.
+    static void onMessage(Message message);
+  };
+};
diff --git a/chrome/common/extensions/api/rtc_private.idl b/chrome/common/extensions/api/rtc_private.idl
new file mode 100644
index 0000000..88e775f
--- /dev/null
+++ b/chrome/common/extensions/api/rtc_private.idl
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 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.
+
+namespace rtcPrivate {
+  // Launch action type.
+  enum ActionType {chat, voice, video};
+
+  dictionary LaunchIntent {
+    // Launch action.
+    ActionType action;
+
+    // Launch data payload.
+    object data;
+
+    // MIME type.
+    DOMString type;
+  };
+
+  dictionary LaunchData {
+    // Launch intent.
+    LaunchIntent intent;
+  };
+
+  interface Events {
+    // Fired when an RTC launch event is raised.
+    static void onLaunch(optional LaunchData data);
+  };
+};
diff --git a/chrome/common/extensions/api/runtime.json b/chrome/common/extensions/api/runtime.json
new file mode 100644
index 0000000..36c024b
--- /dev/null
+++ b/chrome/common/extensions/api/runtime.json
@@ -0,0 +1,151 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "runtime",
+    "documentation_permissions_required": ["runtime"],
+    "properties": {
+      "lastError": {
+        "type": "object",
+        "optional": true,
+        "description": "This will be defined during an API method callback if there was an error",
+        "unprivileged": true,
+        "properties": {
+          "message": {
+            "optional": true,
+            "type": "string",
+            "description": "Details about the error which occurred."
+          }
+        }
+      },
+      "id": {
+        "type": "string",
+        "description": "The ID of the extension/app.",
+        "unprivileged": true
+      }
+    },
+    "functions": [
+      {
+        "name": "getBackgroundPage",
+        "type": "function",
+        "description": "Retrieves the JavaScript 'window' object for the background page running inside the current extension. If the background page is an event page, the system will ensure it is loaded before calling the callback. If there is no background page, an error is set.",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "backgroundPage",
+                // Note: Only optional because we don't support validation
+                // for custom callbacks.
+                "optional": true,
+                "type": "object",
+                "isInstanceOf": "global",
+                "additionalProperties": { "type": "any" },
+                "description": "The JavaScript 'window' object for the background page."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getManifest",
+        "description": "Returns details about the app or extension from the manifest. The object returned is a serialization of the full <a href=\"manifest.html\">manifest file</a>.",
+        "type": "function",
+        "unprivileged": true,
+        "parameters": [],
+        "returns": {
+          "type": "object",
+          "properties": {},
+          "additionalProperties": { "type": "any" },
+          "description": "The manifest details."
+        }
+      },
+      {
+        "name": "getURL",
+        "type": "function",
+        "unprivileged": true,
+        "description": "Converts a relative path within an app/extension install directory to a fully-qualified URL.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "path",
+            "description": "A path to a resource within an app/extension expressed relative to its install directory."
+          }
+        ],
+        "returns": {
+          "type": "string",
+          "description": "The fully-qualified URL to the resource."
+        }
+      },
+      {
+        "name": "reload",
+        "description": "Reloads the app or extension.",
+        "type": "function",
+        "unprivileged": true,
+        "parameters": []
+      }
+    ],
+    "events": [
+      {
+        "name": "onStartup",
+        "type": "function",
+        "description": "Fired when the browser first starts up."
+      },
+      {
+        "name": "onInstalled",
+        "type": "function",
+        "description": "Fired when the extension is first installed, when the extension is updated to a new version, and when Chrome is updated to a new version.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "reason": {
+                "type": "string",
+                "enum": ["install", "update", "chrome_update"],
+                "description": "The reason that this event is being dispatched."
+              },
+              "previousVersion": {
+                "type": "string",
+                "optional": true,
+                "description": "Indicates the previous version of the extension, which has just been updated. This is present only if 'reason' is 'update'."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "onSuspend",
+        "type": "function",
+        "description": "Sent to the event page just before it is unloaded. This gives the extension opportunity to do some clean up. Note that since the page is unloading, any asynchronous operations started while handling this event are not guaranteed to complete. If more activity for the event page occurs before it gets unloaded the onSuspendCanceled event will be sent and the page won't be unloaded. "
+      },
+      {
+        "name": "onSuspendCanceled",
+        "type": "function",
+        "description": "Sent after onSuspend() to indicate that the app won't be unloaded after all."
+      },
+      {
+        "name": "onUpdateAvailable",
+        "type": "function",
+        "description": "Fired when an update is available, but isn't installed immediately because the app is currently running. If you do nothing, the update will be installed the next time the background page gets unloaded, if you want it to be installed sooner you can explicitly call chrome.runtime.reload().",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "version": {
+                "type": "string",
+                "description": "The version number of the available update."
+              }
+            },
+            "additionalProperties": { "type": "any" },
+            "description": "The manifest details of the available update."
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/script_badge.json b/chrome/common/extensions/api/script_badge.json
new file mode 100644
index 0000000..e2e3ef6
--- /dev/null
+++ b/chrome/common/extensions/api/script_badge.json
@@ -0,0 +1,89 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "scriptBadge",
+    "dependencies": [ "tabs" ],
+    "functions": [
+      {
+        "name": "setPopup",
+        "type": "function",
+        "description": "Sets the html document to be opened as a popup when the user clicks on the script badge's icon.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the script badge."},
+              "popup": {
+                "type": "string",
+                "description": "The html file to show in a popup.  If set to the empty string (''), no popup is shown."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "getPopup",
+        "type": "function",
+        "description": "Gets the html document set as the popup for this script badge.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {
+                "type": "integer",
+                "description": "Specify the tab to get the popup from."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "string"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getAttention",
+        "type": "function",
+
+        "description": "Brings the script badge to the attention of the user, imploring her to click.  You should call this when you detect that you can do something to a particular tab.  Do not call this for every tab. That's tacky.  If the user clicks on the badge, the activeTab APIs become available. If the extension has already run on this tab, this call does nothing.",
+
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "tabId": {
+                "type": "integer",
+                "description": "Specify the tab to request to act on."
+              }
+            }
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onClicked",
+        "type": "function",
+        "description": "Fired when a script badge icon is clicked.  This event will not fire if the script badge has a popup.",
+        "parameters": [
+          {
+            "name": "tab",
+            "$ref": "tabs.Tab"
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/serial.idl b/chrome/common/extensions/api/serial.idl
new file mode 100644
index 0000000..6aad781
--- /dev/null
+++ b/chrome/common/extensions/api/serial.idl
@@ -0,0 +1,133 @@
+// Copyright (c) 2012 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.
+
+namespace serial {
+
+  callback GetPortsCallback = void (DOMString[] ports);
+
+  dictionary OpenOptions {
+    // The requested bitrate of the connection to be opened. For compatibility
+    // with the widest range of hardware, this number should match one of
+    // commonly-available bitrates, such as 110, 300, 1200, 2400, 4800, 9600,
+    // 14400, 19200, 38400, 57600, 115200. There is no guarantee, of course,
+    // that the device connected to the serial port will support the requested
+    // bitrate, even if the port itself supports that bitrate.
+    long bitrate;
+  };
+
+  dictionary OpenInfo {
+    // The id of the opened connection.
+    long connectionId;
+  };
+
+  callback OpenCallback = void (OpenInfo openInfo);
+
+  // Returns true if operation was successful.
+  callback CloseCallback = void (boolean result);
+
+  dictionary ReadInfo {
+    // The number of bytes received, or a negative number if an error occurred.
+    // This number will be smaller than the number of bytes requested in the
+    // original read call if the call would need to block to read that number
+    // of bytes.
+    long bytesRead;
+
+    // The data received.
+    ArrayBuffer data;
+  };
+
+  callback ReadCallback = void (ReadInfo readInfo);
+
+  dictionary WriteInfo {
+    // The number of bytes written.
+    long bytesWritten;
+  };
+
+  callback WriteCallback = void (WriteInfo writeInfo);
+
+  // Returns true if operation was successful.
+  callback FlushCallback = void (boolean result);
+
+  // Boolean true = mark signal (negative serial voltage).
+  // Boolean false = space signal (positive serial voltage).
+  //
+  // For SetControlSignals, include the sendable signals that you wish to
+  // change. Signals not included in the dictionary will be left unchanged.
+  //
+  // GetControlSignals includes all receivable signals.
+  dictionary ControlSignalOptions {
+    // Serial control signals that your machine can send. Missing fields will
+    // be set to false.
+    boolean? dtr;
+    boolean? rts;
+
+    // Serial control signals that your machine can receive. If a get operation
+    // fails, success will be false, and these fields will be absent.
+    //
+    // DCD (Data Carrier Detect) is equivalent to RLSD (Receive Line Signal
+    // Detect) on some platforms.
+    boolean? dcd;
+    boolean? cts;
+  };
+
+  // Returns a snapshot of current control signals.
+  callback GetControlSignalsCallback = void (ControlSignalOptions options);
+
+  // Returns true if operation was successful.
+  callback SetControlSignalsCallback = void (boolean result);
+
+  interface Functions {
+    // Returns names of valid ports on this machine, each of which is likely to
+    // be valid to pass as the port argument to open(). The list is regenerated
+    // each time this method is called, as port validity is dynamic.
+    //
+    // |callback| : Called with the list of ports.
+    static void getPorts(GetPortsCallback callback);
+
+    // Opens a connection to the given serial port.
+    // |port| : The name of the serial port to open.
+    // |options| : Connection options.
+    // |callback| : Called when the connection has been opened.
+    static void open(DOMString port,
+                     optional OpenOptions options,
+                     OpenCallback callback);
+
+    // Closes an open connection.
+    // |connectionId| : The id of the opened connection.
+    // |callback| : Called when the connection has been closed.
+    static void close(long connectionId,
+                      CloseCallback callback);
+
+    // Reads a byte from the given connection.
+    // |connectionId| : The id of the connection.
+    // |bytesToRead| : The number of bytes to read.
+    // |callback| : Called when all the requested bytes have been read or
+    //              when the read blocks.
+    static void read(long connectionId,
+                     long bytesToRead,
+                     ReadCallback callback);
+
+    // Writes a string to the given connection.
+    // |connectionId| : The id of the connection.
+    // |data| : The string to write.
+    // |callback| : Called when the string has been written.
+    static void write(long connectionId,
+                      ArrayBuffer data,
+                      WriteCallback callback);
+
+    // Flushes all bytes in the given connection's input and output buffers.
+    // |connectionId| : The id of the connection.
+    // |callback| : Called when the flush is complete.
+    static void flush(long connectionId,
+                      FlushCallback callback);
+
+    static void getControlSignals(long connectionId,
+                                  GetControlSignalsCallback callback);
+
+    static void setControlSignals(long connectionId,
+                                  ControlSignalOptions options,
+                                  SetControlSignalsCallback callback);
+  };
+
+};
diff --git a/chrome/common/extensions/api/socket.idl b/chrome/common/extensions/api/socket.idl
new file mode 100644
index 0000000..d332592
--- /dev/null
+++ b/chrome/common/extensions/api/socket.idl
@@ -0,0 +1,257 @@
+// Copyright (c) 2012 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.
+
+namespace socket {
+  enum SocketType {
+    tcp,
+    udp
+  };
+
+  // The socket options.
+  dictionary CreateOptions {
+  };
+
+  dictionary CreateInfo {
+    // The id of the newly created socket.
+    long socketId;
+  };
+
+  callback CreateCallback = void (CreateInfo createInfo);
+
+  callback ConnectCallback = void (long result);
+
+  callback BindCallback = void (long result);
+
+  callback ListenCallback = void (long result);
+
+  dictionary AcceptInfo {
+    long resultCode;
+    // The id of the accepted socket.
+    long? socketId;
+  };
+
+  callback AcceptCallback = void (AcceptInfo acceptInfo);
+
+  dictionary ReadInfo {
+    // The resultCode returned from the underlying read() call.
+    long resultCode;
+
+    ArrayBuffer data;
+  };
+
+  callback ReadCallback = void (ReadInfo readInfo);
+
+  dictionary WriteInfo {
+    // The number of bytes sent, or a negative error code.
+    long bytesWritten;
+  };
+
+  callback WriteCallback = void (WriteInfo writeInfo);
+
+  dictionary RecvFromInfo {
+    // The resultCode returned from the underlying recvfrom() call.
+    long resultCode;
+
+    ArrayBuffer data;
+
+    // The address of the remote machine.
+    DOMString address;
+
+    long port;
+  };
+
+  dictionary SocketInfo {
+    // The type of the passed socket. This will be <code>tcp</code> or
+    // <code>udp</code>.
+    SocketType socketType;
+
+    // Whether or not the underlying socket is connected.
+    //
+    // For <code>tcp</code> sockets, this will remain true even if the remote
+    // peer has disconnected. Reading or writing to the socket may then result
+    // in an error, hinting that this socket should be disconnected via
+    // <code>disconnect()</code>.
+    //
+    // For <code>udp</code> sockets, this just represents whether a default
+    // remote address has been specified for reading and writing packets.
+    boolean connected;
+
+    // If the underlying socket is connected, contains the IPv4/6 address of
+    // the peer.
+    DOMString? peerAddress;
+
+    // If the underlying socket is connected, contains the port of the
+    // connected peer.
+    long? peerPort;
+
+    // If the underlying socket is bound or connected, contains its local
+    // IPv4/6 address.
+    DOMString? localAddress;
+
+    // If the underlying socket is bound or connected, contains its local port.
+    long? localPort;
+  };
+
+  dictionary NetworkInterface {
+    // The underlying name of the adapter. On *nix, this will typically be
+    // "eth0", "lo", etc.
+    DOMString name;
+
+    // The available IPv4/6 address.
+    DOMString address;
+  };
+
+  callback RecvFromCallback = void (RecvFromInfo recvFromInfo);
+
+  callback SendToCallback = void (WriteInfo writeInfo);
+
+  callback SetKeepAliveCallback = void (boolean result);
+
+  callback SetNoDelayCallback = void (boolean result);
+
+  callback GetInfoCallback = void (SocketInfo result);
+
+  callback GetNetworkCallback = void (NetworkInterface[] result);
+
+  interface Functions {
+    // Creates a socket of the specified type that will connect to the specified
+    // remote machine.
+    // |type| : The type of socket to create. Must be <code>tcp</code> or
+    // <code>udp</code>.
+    // |options| : The socket options.
+    // |callback| : Called when the socket has been created.
+    static void create(SocketType type,
+                       optional CreateOptions options,
+                       CreateCallback callback);
+
+    // Destroys the socket. Each socket created should be destroyed after use.
+    // |socketId| : The socketId.
+    static void destroy(long socketId);
+
+    // Connects the socket to the remote machine (for a <code>tcp</code>
+    // socket). For a <code>udp</code> socket, this sets the default address
+    // which packets are sent to and read from for <code>read()</code>
+    // and <code>write()</code> calls.
+    // |socketId| : The socketId.
+    // |hostname| : The hostname or IP address of the remote machine.
+    // |port| : The port of the remote machine.
+    // |callback| : Called when the connection attempt is complete.
+    static void connect(long socketId,
+                        DOMString hostname,
+                        long port,
+                        ConnectCallback callback);
+
+    // Binds the local address for socket. Currently, it does not support
+    // TCP socket.
+    // |socketId| : The socketId.
+    // |address| : The address of the local machine.
+    // |port| : The port of the local machine.
+    // |callback| : Called when the bind attempt is complete.
+    static void bind(long socketId,
+                     DOMString address,
+                     long port,
+                     BindCallback callback);
+
+    // Disconnects the socket. For UDP sockets, <code>disconnect</code> is a
+    // non-operation but is safe to call.
+    // |socketId| : The socketId.
+    static void disconnect(long socketId);
+
+    // Reads data from the given connected socket.
+    // |socketId| : The socketId.
+    // |bufferSize| : The read buffer size.
+    // |callback| : Delivers data that was available to be read without
+    // blocking.
+    static void read(long socketId,
+                     optional long bufferSize,
+                     ReadCallback callback);
+
+    // Writes data on the given connected socket.
+    // |socketId| : The socketId.
+    // |data| : The data to write.
+    // |callback| : Called when the write operation completes without blocking
+    // or an error occurs.
+    static void write(long socketId,
+                      ArrayBuffer data,
+                      WriteCallback callback);
+
+    // Receives data from the given UDP socket.
+    // |socketId| : The socketId.
+    // |bufferSize| : The receive buffer size.
+    // |callback| : Returns result of the recvFrom operation.
+    static void recvFrom(long socketId,
+                         optional long bufferSize,
+                         RecvFromCallback callback);
+
+    // Sends data on the given UDP socket to the given address and port.
+    // |socketId| : The socketId.
+    // |data| : The data to write.
+    // |address| : The address of the remote machine.
+    // |port| : The port of the remote machine.
+    // |callback| : Called when the send operation completes without blocking
+    // or an error occurs.
+    static void sendTo(long socketId,
+                       ArrayBuffer data,
+                       DOMString address,
+                       long port,
+                       SendToCallback callback);
+
+    // This method is experimental and requires experimental API permission.
+    // This method applies to TCP sockets only.
+    // Listens for connections on the specified port and address. This
+    // effectively makes this a server socket, and client socket
+    // functions (connect, read, write) can no longer be used on this socket.
+    // |socketId| : The socketId.
+    // |address| : The address of the local machine.
+    // |port| : The port of the local machine.
+    // |backlog| : Length of the socket's listen queue.
+    static void listen(long socketId,
+                       DOMString address,
+                       long port,
+                       optional long backlog,
+                       ListenCallback callback);
+
+    // This method is experimental and requires experimental API permission.
+    // This method applies to TCP sockets only.
+    // Registers a callback function to be called when a connection is
+    // accepted on this listening server socket. Listen must be called first.
+    // If there is already an active accept callback, this callback will be
+    // invoked immediately with an error as the resultCode.
+    // |socketId| : The socketId.
+    // |callback| : The callback is invoked when a new socket is accepted.
+    static void accept(long socketId,
+                       AcceptCallback callback);
+
+    // Enables or disables the keep-alive functionality for a TCP connection.
+    // |socketId| : The socketId.
+    // |enable| : If true, enable keep-alive functionality.
+    // |delay| : Set the delay seconds between the last data packet received
+    // and the first keepalive probe. Default is 0.
+    // |callback| : Called when the setKeepAlive attempt is complete.
+    static void setKeepAlive(long socketId,
+                             boolean enable,
+                             optional long delay,
+                             SetKeepAliveCallback callback);
+
+    // Sets or clears <code>TCP_NODELAY</code> for a TCP connection. Nagle's
+    // algorithm will be disabled when <code>TCP_NODELAY</code> is set.
+    // |socketId| : The socketId.
+    // |noDelay| : If true, disables Nagle's algorithm.
+    // |callback| : Called when the setNoDelay attempt is complete.
+    static void setNoDelay(long socketId,
+                           boolean noDelay,
+                           SetNoDelayCallback callback);
+
+    // Retrieves the state of the given socket.
+    // |socketId| : The socketId.
+    // |callback| : Called when the state is available.
+    static void getInfo(long socketId,
+                        GetInfoCallback callback);
+
+    // Retrieves information about local adapters on this system.
+    // |callback| : Called when local adapter information is available.
+    static void getNetworkList(GetNetworkCallback callback);
+  };
+
+};
diff --git a/chrome/common/extensions/api/storage.json b/chrome/common/extensions/api/storage.json
new file mode 100644
index 0000000..8995566
--- /dev/null
+++ b/chrome/common/extensions/api/storage.json
@@ -0,0 +1,218 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "storage",
+    "unprivileged": true,
+    "types": [
+      {
+        "id": "StorageChange",
+        "type": "object",
+        "properties": {
+          "oldValue": {
+            "type": "any",
+            "description": "The old value of the item, if there was an old value.",
+            "optional": true
+          },
+          "newValue": {
+            "type": "any",
+            "description": "The new value of the item, if there is a new value.",
+            "optional": true
+          }
+        }
+      },
+      {
+        "id": "StorageArea",
+        "type": "object",
+        "functions": [
+          {
+            "name": "get",
+            "type": "function",
+            "description": "Gets one or more items from storage.",
+            "parameters": [
+              {
+                "name": "keys",
+                "choices": [
+                  { "type": "string" },
+                  { "type": "array", "items": { "type": "string" } },
+                  {
+                    "type": "object",
+                    "description": "Storage items to return in the callback, where the values are replaced with those from storage if they exist.",
+                    "additionalProperties": { "type": "any" }
+                  }
+                ],
+                "description": "A single key to get, list of keys to get, or a dictionary specifying default values (see description of the object).  An empty list or object will return an empty result object.  Pass in <code>null</code> to get the entire contents of storage.",
+                "optional": true
+              },
+              {
+                "name": "callback",
+                "type": "function",
+                "description": "Callback with storage items, or on failure (in which case $ref:runtime.lastError will be set).",
+                "parameters": [
+                  {
+                    "name": "items",
+                    "type": "object",
+                    "additionalProperties": { "type": "any" },
+                    "description": "Object with items in their key-value mappings."
+                  }
+                ]
+              }
+            ]
+          },
+          {
+            "name": "getBytesInUse",
+            "type": "function",
+            "description": "Gets the amount of space (in bytes) being used by one or more items.",
+            "parameters": [
+              {
+                "name": "keys",
+                "choices": [
+                  { "type": "string" },
+                  { "type": "array", "items": { "type": "string" } }
+                ],
+                "description": "A single key or list of keys to get the total usage for. An empty list will return 0. Pass in <code>null</code> to get the total usage of all of storage.",
+                "optional": true
+              },
+              {
+                "name": "callback",
+                "type": "function",
+                "description": "Callback with the amount of space being used by storage, or on failure (in which case $ref:runtime.lastError will be set).",
+                "parameters": [
+                  {
+                    "name": "bytesInUse",
+                    "type": "integer",
+                    "description": "Amount of space being used in storage, in bytes."
+                  }
+                ]
+              }
+            ]
+          },
+          {
+            "name": "set",
+            "type": "function",
+            "description": "Sets multiple items.",
+            "parameters": [
+              {
+                "name": "items",
+                "type": "object",
+                "additionalProperties": { "type": "any" },
+                "description": "Object specifying items to augment storage with. Values that cannot be serialized (functions, etc) will be ignored."
+              },
+              {
+                "name": "callback",
+                "type": "function",
+                "description": "Callback on success, or on failure (in which case $ref:runtime.lastError will be set).",
+                "parameters": [],
+                "optional": true
+              }
+            ]
+          },
+          {
+            "name": "remove",
+            "type": "function",
+            "description": "Removes one or more items from storage.",
+            "parameters": [
+              {
+                "name": "keys",
+                "choices": [
+                  {"type": "string"},
+                  {"type": "array", "items": {"type": "string"}, "minItems": 1}
+                ],
+                "description": "A single key or a list of keys for items to remove."
+              },
+              {
+                "name": "callback",
+                "type": "function",
+                "description": "Callback on success, or on failure (in which case $ref:runtime.lastError will be set).",
+                "parameters": [],
+                "optional": true
+              }
+            ]
+          },
+          {
+            "name": "clear",
+            "type": "function",
+            "description": "Removes all items from storage.",
+            "parameters": [
+              {
+                "name": "callback",
+                "type": "function",
+                "description": "Callback on success, or on failure (in which case $ref:runtime.lastError will be set).",
+                "parameters": [],
+                "optional": true
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onChanged",
+        "type": "function",
+        "description": "Fired when one or more items change.",
+        "parameters": [
+          {
+            "name": "changes",
+            "type": "object",
+            "additionalProperties": { "$ref": "StorageChange" },
+            "description": "Object mapping each key that changed to its corresponding <a href='#type-StorageChange'>StorageChange</a> for that item."
+          },
+          {
+            "name": "areaName",
+            "type": "string",
+            "description": "The name of the storage area (<code>sync</code> or <code>local</code>) the changes are for."
+          }
+        ]
+      }
+    ],
+    "properties": {
+      "sync": {
+        "$ref": "StorageArea",
+        "description": "Items in the <code>sync</code> storage area are synced using Chrome Sync.",
+        "value": [ "sync" ],
+        "properties": {
+          "QUOTA_BYTES": {
+            "value": 102400,
+            "description": "The maximum total amount (in bytes) of data that can be stored in sync storage, as measured by the JSON stringification of every value plus every key's length. Updates that would cause this limit to be exceeded fail immediately and set $ref:runtime.lastError."
+          },
+          "QUOTA_BYTES_PER_ITEM": {
+            "value": 4096,
+            "description": "The maximum size (in bytes) of each individual item in sync storage, as measured by the JSON stringification of its value plus its key length. Updates containing items larger than this limit will fail immediately and set $ref:runtime.lastError."
+          },
+          "MAX_ITEMS": {
+            "value": 512,
+            "description": "The maximum number of items that can be stored in sync storage. Updates that would cause this limit to be exceeded will fail immediately and set $ref:runtime.lastError."
+          },
+          "MAX_WRITE_OPERATIONS_PER_HOUR": {
+            "value": 1000,
+            "description": "The maximum number of <code>set</code>, <code>remove</code>, or <code>clear</code> operations that can be performed each hour. Updates that would cause this limit to be exceeded fail immediately and set $ref:runtime.lastError."
+          },
+          "MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE": {
+            "value": 10,
+            "description": "The maximum number of <code>set</code>, <code>remove</code>, or <code>clear</code> operations that can be performed each minute, sustained over 10 minutes. Updates that would cause this limit to be exceeded fail immediately and set $ref:runtime.lastError."
+          }
+        }
+      },
+      "local": {
+        "$ref": "StorageArea",
+        "description": "Items in the <code>local</code> storage area are local to each machine.",
+        "value": [ "local" ],
+        "properties": {
+          "QUOTA_BYTES": {
+            "value": 5242880,
+            "description": "The maximum amount (in bytes) of data that can be stored in local storage, as measured by the JSON stringification of every value plus every key's length. This value will be ignored if the extension has the <code>unlimitedStorage</code> permission. Updates that would cause this limit to be exceeded fail immediately and set $ref:runtime.lastError."
+          }
+        }
+      },
+      "managed": {
+        "$ref": "StorageArea",
+        "description": "Items in the <code>managed</code> storage area are set by the domain administrator, and are read-only by the extension; trying to modify this namespace results in an error.",
+        "value": [ "managed" ],
+        "nodoc": true
+      }
+    }
+  }
+]
diff --git a/chrome/common/extensions/api/sync_file_system.idl b/chrome/common/extensions/api/sync_file_system.idl
new file mode 100644
index 0000000..caec419
--- /dev/null
+++ b/chrome/common/extensions/api/sync_file_system.idl
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 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.
+
+namespace syncFileSystem {
+  dictionary StorageInfo {
+    long usage_bytes;
+    long quota_bytes;
+  };
+
+  // [nodoc] A callback type for requestFileSystem.
+  callback GetFileSystemCallback =
+      void ([instanceOf=DOMFileSystem] object fileSystem);
+      
+  // [nodoc] A callback type for getUsageAndQuota.
+  callback QuotaAndUsageCallback = void (StorageInfo info);
+
+  // Returns true if operation was successful.  
+  callback DeleteFileSystemCallback = void (boolean result);
+
+  interface Functions {
+    // Get a sync file system backed by |serviceName|.
+    // Calling this multiple times from the same app with the same |serviceName|
+    // will return the same handle to the same file system.
+    static void requestFileSystem(DOMString serviceName,
+                                  GetFileSystemCallback callback);
+                                  
+    // Get usage and quota in bytes for sync file system with |serviceName|.
+    static void getUsageAndQuota([instanceOf=DOMFileSystem] object fileSystem,
+                                 QuotaAndUsageCallback callback);
+                                 
+    // Deletes everything in the syncable filesystem.
+    static void deleteFileSystem([instanceOf=DOMFileSystem] object fileSystem,
+                                  DeleteFileSystemCallback callback);
+  };
+};
diff --git a/chrome/common/extensions/api/system_private.json b/chrome/common/extensions/api/system_private.json
new file mode 100644
index 0000000..ec6cc53
--- /dev/null
+++ b/chrome/common/extensions/api/system_private.json
@@ -0,0 +1,130 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "systemPrivate",
+    "nodoc": true,
+    "types": [
+      {
+        "id": "UpdateStatus",
+        "type": "object",
+        "description": "Information about the system update.",
+        "properties": {
+          "state": {
+            "type": "string",
+            "enum": ["NotAvailable", "Updating", "NeedRestart"],
+            "description": "State of system update.  NotAvailable when there is no available update or the update system is in error state, Updating when a system update is in progress, NeedRestart when a system update is finished and restart is needed."
+          },
+          "downloadProgress": {
+            "type": "number",
+            "description": "Value between 0 and 1 describing the progress of system update download.  This value will be set to 0 when |state| is NotAvailable, 1 when NeedRestart."
+          }
+        }
+      },
+      {
+        "id": "VolumeInfo",
+        "type": "object",
+        "description": "Information about the volume.",
+        "properties": {
+          "volume": {"type": "number", "description": "The value of the volume percent. This must be between 0.0 and 100.0."},
+          "isVolumeMuted": {"type": "boolean", "description": "True if the volume is muted."}
+        }
+      },
+      {
+        "id": "BrightnessChangeInfo",
+        "type": "object",
+        "description": "Information about a change to the screen brightness.",
+        "properties": {
+          "brightness": {
+            "type": "number",
+            "description": "The value of the current screen brightness in percent, between 0.0 and 100.0."
+          },
+          "userInitiated": {
+            "type": "boolean",
+            "description": "Whether this change was initiated by user."
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "getIncognitoModeAvailability",
+        "type": "function",
+        "description": "Returns whether the incognito mode is enabled, disabled or forced",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called with the result.",
+            "parameters": [
+              {
+                "name": "value",
+                "type": "string",
+                "enum": ["enabled", "disabled", "forced"],
+                "description": "Exposes whether the incognito mode is available to windows. One of 'enabled', 'disabled' (user cannot browse pages in Incognito mode), 'forced' (all pages/sessions are forced into Incognito mode)."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getUpdateStatus",
+        "type": "function",
+        "description": "Gets information about the system update.",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "$ref": "UpdateStatus",
+                "name": "status",
+                "description": "Details of the system update"
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onVolumeChanged",
+        "type": "function",
+        "description": "Fired when the volume is changed.",
+        "parameters": [
+          {
+            "$ref": "VolumeInfo",
+            "name": "volume",
+            "description": "Information about the current state of the system volume control, including whether it is muted."
+          }
+        ]
+      },
+      {
+        "name": "onBrightnessChanged",
+        "type": "function",
+        "description": "Fired when the screen brightness is changed.",
+        "parameters": [
+          {
+            "$ref": "BrightnessChangeInfo",
+            "name": "brightness",
+            "description": "Information about a change to the screen brightness."
+          }
+        ]
+      },
+      {
+        "name": "onScreenUnlocked",
+        "type": "function",
+        "description": "Fired when the screen is unlocked.",
+        "parameters": []
+      },
+      {
+        "name": "onWokeUp",
+        "type": "function",
+        "description": "Fired when the device wakes up from sleep.",
+        "parameters": []
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/tab_capture.idl b/chrome/common/extensions/api/tab_capture.idl
new file mode 100644
index 0000000..4771fef
--- /dev/null
+++ b/chrome/common/extensions/api/tab_capture.idl
@@ -0,0 +1,62 @@
+// Copyright (c) 2012 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.
+
+// An API for tab media streams.
+
+namespace tabCapture {
+
+  enum TabCaptureState {
+    requested,
+    pending,
+    active,
+    stopped,
+    error
+  };
+
+  dictionary CaptureInfo {
+    // The id of the tab whose status changed.
+    long tabId;
+
+    // The new capture status of the tab.
+    TabCaptureState status;
+  };
+
+  dictionary CaptureOptions {
+    boolean? audio;
+    boolean? video;
+  };
+
+  callback GetTabMediaCallback =
+      void ([instanceOf=LocalMediaStream] optional object stream);
+
+  callback GetCapturedTabsCallback =
+      void (CaptureInfo[] result);
+
+  interface Functions {
+    // Captures the visible area of the tab with the given tabId.
+    // Extensions must have the "tabCapture" permission to use this method.
+    // |tabId| : The tabId of the tab to capture.  Defaults to the active tab.
+    // |options| : Configures the returned media stream.
+    // |callback| : Callback with either the stream returned or null.
+    static void capture(optional long tabId,
+                        optional CaptureOptions options,
+                        GetTabMediaCallback callback);
+
+    // Returns a list of tabs that have requested capture or are being
+    // captured, i.e. status != stopped and status != error.
+    // This allows extensions to inform the user that there is an existing
+    // tab capture that would prevent a new tab capture from succeeding (or
+    // to prevent redundant requests for the same tab).
+    static void getCapturedTabs(GetCapturedTabsCallback callback);
+
+  };
+
+  interface Events {
+    // Event fired when the capture status of a tab changes.
+    // This allows extension authors to keep track of the capture status of
+    // tabs to keep UI elements like page actions and infobars in sync.
+    static void onStatusChanged(CaptureInfo info);
+  };
+
+};
diff --git a/chrome/common/extensions/api/tabs.json b/chrome/common/extensions/api/tabs.json
new file mode 100644
index 0000000..7f88a83
--- /dev/null
+++ b/chrome/common/extensions/api/tabs.json
@@ -0,0 +1,923 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "tabs",
+    "dependencies": [ "extension", "windows" ],
+    "types": [
+      {
+        "id": "Tab",
+        "type": "object",
+        "properties": {
+          "id": {"type": "integer", "minimum": 0, "description": "The ID of the tab. Tab IDs are unique within a browser session."},
+          "index": {"type": "integer", "minimum": 0, "description": "The zero-based index of the tab within its window."},
+          "windowId": {"type": "integer", "minimum": 0, "description": "The ID of the window the tab is contained within."},
+          "openerTabId": {"type": "integer", "minimum": 0, "optional": true, "description": "The ID of the tab that opened this tab, if any. This will only be present if the opener tab still exists."},
+          "selected": {"type": "boolean", "description": "Whether the tab is selected.", "nodoc": true},
+          "highlighted": {"type": "boolean", "description": "Whether the tab is highlighted."},
+          "active": {"type": "boolean", "description": "Whether the tab is active in its window."},
+          "pinned": {"type": "boolean", "description": "Whether the tab is pinned."},
+          "url": {"type": "string", "optional": true, "description": "The URL the tab is displaying. This will only be present if the extension has the 'tabs' or 'webNavigation' permission."},
+          "title": {"type": "string", "optional": true, "optional": true, "description": "The title of the tab. This will only be present if the extension has the 'tabs' or 'webNavigation' permission. It may also be an empty string if the tab is loading."},
+          "favIconUrl": {"type": "string", "optional": true, "optional": true, "description": "The URL of the tab's favicon. This will only be present if the extension has the 'tabs' or 'webNavigation' permission. It may also be an empty string if the tab is loading."},
+          "status": {"type": "string", "optional": true, "description": "Either <em>loading</em> or <em>complete</em>."},
+          "incognito": {"type": "boolean", "description": "Whether the tab is in an incognito window."}
+        }
+      },
+      {
+        "id": "InjectDetails",
+        "type": "object",
+        "description": "Details of the script or CSS to inject. Either the code or the file property must be set, but both may not be set at the same time.",
+        "properties": {
+          "code": {"type": "string", "optional": true, "description": "JavaScript or CSS code to inject."},
+          "file": {"type": "string", "optional": true, "description": "JavaScript or CSS file to inject."},
+          "allFrames": {"type": "boolean", "optional": true, "description": "If allFrames is <code>true</code>, implies that the JavaScript or CSS should be injected into all frames of current page. By default, it's <code>false</code> and will only be injected into the top frame."},
+          "runAt": {
+            "type": "string",
+            "optional": true,
+            "enum": ["document_start", "document_end", "document_idle"],
+            "description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"."
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "get",
+        "type": "function",
+        "description": "Retrieves details about the specified tab.",
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "tabId",
+            "minimum": 0
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {"name": "tab", "$ref": "Tab"}
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getCurrent",
+        "type": "function",
+        "description": "Gets the tab that this script call is being made from. May be undefined if called from a non-tab context (for example: a background page or popup view).",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "tab",
+                "$ref": "Tab",
+                "optional": true
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "connect",
+        "nocompile": true,
+        "type": "function",
+        "description": "Connects to the content script(s) in the specified tab. The <a href='extension.html#event-onConnect'>chrome.extension.onConnect</a> event is fired in each content script running in the specified tab for the current extension. For more details, see <a href='content_scripts.html#messaging'>Content Script Messaging</a>.",
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "tabId",
+            "minimum": 0
+          },
+          {
+            "type": "object",
+            "name": "connectInfo",
+            "properties": {
+              "name": { "type": "string", "optional": true, "description": "Will be passed into onConnect for content scripts that are listening for the connection event." }
+            },
+            "optional": true
+          }
+        ],
+        "returns": {
+          "$ref": "extension.Port",
+          "description": "A port that can be used to communicate with the content scripts running in the specified tab. The port's <a href='extension.html#type-Port'>onDisconnect</a> event is fired if the tab closes or does not exist. "
+        }
+      },
+      {
+        "name": "sendRequest",
+        "nodoc": true,
+        "nocompile": true,
+        "type": "function",
+        "description": "Deprecated: Please use sendMessage.",
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "tabId",
+            "minimum": 0
+          },
+          {
+            "type": "any",
+            "name": "request"
+          },
+          {
+            "type": "function",
+            "name": "responseCallback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "response",
+                "type": "any",
+                "description": "The JSON response object sent by the handler of the request. If an error occurs while connecting to the specified tab, the callback will be called with no arguments and <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will be set to the error message."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "sendMessage",
+        "nocompile": true,
+        "type": "function",
+        "description": "Sends a single message to the content script(s) in the specified tab, with an optional callback to run when a response is sent back.  The <a href='extension.html#event-onMessage'>chrome.extension.onMessage</a> event is fired in each content script running in the specified tab for the current extension.",
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "tabId",
+            "minimum": 0
+          },
+          {
+            "type": "any",
+            "name": "message"
+          },
+          {
+            "type": "function",
+            "name": "responseCallback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "response",
+                "type": "any",
+                "description": "The JSON response object sent by the handler of the message. If an error occurs while connecting to the specified tab, the callback will be called with no arguments and <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will be set to the error message."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getSelected",
+        "nodoc": true,
+        "type": "function",
+        "description": "Deprecated. Please use query({'active': true}). Gets the tab that is selected in the specified window.",
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "windowId",
+            "minimum": -2,
+            "optional": true,
+            "description": "Defaults to the <a href='windows.html#current-window'>current window</a>."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {"name": "tab", "$ref": "Tab"}
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getAllInWindow",
+        "type": "function",
+        "nodoc": true,
+        "description": "Deprecated. Please use query({'windowId': windowId}). Gets details about all tabs in the specified window.",
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "windowId",
+            "minimum": -2,
+            "optional": true,
+            "description": "Defaults to the <a href='windows.html#current-window'>current window</a>."
+            },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {"name": "tabs", "type": "array", "items": { "$ref": "Tab" } }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "create",
+        "type": "function",
+        "description": "Creates a new tab. Note: This function can be used without requesting the 'tabs' permission in the manifest.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "createProperties",
+            "properties": {
+              "windowId": {
+                "type": "integer",
+                "minimum": -2,
+                "optional": true,
+                "description": "The window to create the new tab in. Defaults to the <a href='windows.html#current-window'>current window</a>."
+              },
+              "index": {
+                "type": "integer",
+                "minimum": 0,
+                "optional": true,
+                "description": "The position the tab should take in the window. The provided value will be clamped to between zero and the number of tabs in the window."
+              },
+              "url": {
+                "type": "string",
+                "optional": true,
+                "description": "The URL to navigate the tab to initially. Fully-qualified URLs must include a scheme (i.e. 'http://www.google.com', not 'www.google.com'). Relative URLs will be relative to the current page within the extension. Defaults to the New Tab Page."
+              },
+              "active": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the tab should become the active tab in the window. Defaults to <var>true</var>"
+              },
+              "selected": {
+                "nodoc": true,
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the tab should become the selected tab in the window. Defaults to <var>true</var>"
+              },
+              "pinned": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the tab should be pinned. Defaults to <var>false</var>"
+              },
+              "openerTabId": {
+                "type": "integer",
+                "minimum": 0,
+                "optional": true,
+                "description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as the newly created tab."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "tab",
+                "$ref": "Tab",
+                "description": "Details about the created tab. Will contain the ID of the new tab."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "duplicate",
+        "type": "function",
+        "description": "Duplicates a tab. Note: This function can be used without requesting the 'tabs' permission in the manifest.",
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "tabId",
+            "minimum": 0,
+            "description": "The ID of the tab which is to be duplicated."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "tab",
+                "optional": true,
+                "description": "Details about the duplicated tab. The Tab object doesn't contain url, title and faviconUrl if the 'tabs' permission has not been requested.",
+                "$ref": "Tab"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "query",
+        "type": "function",
+        "description": "Gets all tabs that have the specified properties, or all tabs if no properties are specified.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "queryInfo",
+            "properties": {
+              "active": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the tabs are active in their windows."
+              },
+              "pinned": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the tabs are pinned."
+              },
+              "highlighted": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the tabs are highlighted."
+              },
+              "currentWindow": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the tabs are in the <a href='windows.html#current-window'>current window</a>."
+              },
+              "lastFocusedWindow": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the tabs are in the last focused window."
+              },
+              "status": {
+                "type": "string",
+                "optional": true,
+                "enum": ["loading", "complete"],
+                "description": "Whether the tabs have completed loading."
+              },
+              "title": {
+                "type": "string",
+                "optional": true,
+                "description": "Match page titles against a pattern."
+              },
+              "url": {
+                "type": "string",
+                "optional": true,
+                "description": "Match tabs against a URL pattern."
+              },
+              "windowId": {
+                "type": "integer",
+                "optional": true,
+                "minimum": -2,
+                "description": "The ID of the parent window, or <a href='windows.html#property-WINDOW_ID_CURRENT'>chrome.windows.WINDOW_ID_CURRENT</a> for the <a href='windows.html#current-window'>current window</a>."
+              },
+              "windowType": {
+                "type": "string",
+                "optional": true,
+                "enum": ["normal", "popup", "panel", "app"],
+                "description": "The type of window the tabs are in."
+              },
+              "index": {
+                "type": "integer",
+                "optional": true,
+                "minimum": 0,
+                "description": "The position of the tabs within their windows."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "array",
+                "items": {
+                  "$ref": "Tab"
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "highlight",
+        "type": "function",
+        "description": "Highlights the given tabs.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "highlightInfo",
+            "properties": {
+               "windowId": {
+                 "type": "integer",
+                 "optional": true,
+                 "description": "The window that contains the tabs.",
+                 "minimum": -2
+               },
+               "tabs": {
+                 "description": "One or more tab indices to highlight.",
+                 "choices": [
+                   {"type": "array", "items": {"type": "integer", "minimum": 0}},
+                   {"type": "integer"}
+                 ]
+               }
+             }
+           },
+           {
+             "type": "function",
+             "name": "callback",
+             "parameters": [
+               {
+                 "name": "window",
+                 "$ref": "windows.Window",
+                 "description": "Contains details about the window whose tabs were highlighted."
+               }
+             ]
+           }
+        ]
+      },
+      {
+        "name": "update",
+        "type": "function",
+        "description": "Modifies the properties of a tab. Properties that are not specified in <var>updateProperties</var> are not modified. Note: This function can be used without requesting the 'tabs' permission in the manifest.",
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "tabId",
+            "minimum": 0,
+            "optional": true,
+            "description": "Defaults to the selected tab of the <a href='windows.html#current-window'>current window</a>."
+          },
+          {
+            "type": "object",
+            "name": "updateProperties",
+            "properties": {
+              "url": {
+                "type": "string",
+                "optional": true,
+                "description": "A URL to navigate the tab to."
+              },
+              "active": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the tab should be active."
+              },
+              "highlighted": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Adds or removes the tab from the current selection."
+              },
+              "selected": {
+                "nodoc": true,
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the tab should be selected."
+              },
+              "pinned": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the tab should be pinned."
+              },
+              "openerTabId": {
+                "type": "integer",
+                "minimum": 0,
+                "optional": true,
+                "description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as this tab."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "tab",
+                "$ref": "Tab",
+                "optional": true,
+                "description": "Details about the updated tab, or <code>null</code> if the 'tabs' permission has not been requested."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "move",
+        "type": "function",
+        "description": "Moves one or more tabs to a new position within its window, or to a new window. Note that tabs can only be moved to and from normal (window.type === \"normal\") windows.",
+        "parameters": [
+          {
+            "name": "tabIds",
+            "description": "The tab or list of tabs to move.",
+            "choices": [
+              {"type": "integer", "minimum": 0},
+              {"type": "array", "items": {"type": "integer", "minimum": 0}}
+            ]
+          },
+          {
+            "type": "object",
+            "name": "moveProperties",
+            "properties": {
+              "windowId": {
+                "type": "integer",
+                "minimum": -2,
+                "optional": true,
+                "description": "Defaults to the window the tab is currently in."
+              },
+              "index": {
+                "type": "integer",
+                "minimum": -1,
+                "description": "The position to move the window to. -1 will place the tab at the end of the window."
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "tabs",
+                "description": "Details about the moved tabs.",
+                "choices": [
+                  {"$ref": "Tab"},
+                  {"type": "array", "items": {"$ref": "Tab"}}
+                ]
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "reload",
+        "type": "function",
+        "description": "Reload a tab.",
+        "parameters": [
+          {"type": "integer", "name": "tabId", "optional": true, "description": "The ID of the tab to reload; defaults to the selected tab of the current window."},
+          {
+            "type": "object",
+            "name": "reloadProperties",
+            "optional": true,
+            "properties": {
+              "bypassCache": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether using any local cache. Default is false."
+              }
+            }
+          },
+          {"type": "function", "name": "callback", "optional": true, "parameters": []}
+        ]
+      },
+      {
+        "name": "remove",
+        "type": "function",
+        "description": "Closes one or more tabs. Note: This function can be used without requesting the 'tabs' permission in the manifest.",
+        "parameters": [
+          {
+            "name": "tabIds",
+            "description": "The tab or list of tabs to close.",
+            "choices": [
+              {"type": "integer", "minimum": 0},
+              {"type": "array", "items": {"type": "integer", "minimum": 0}}
+            ]
+          },
+          {"type": "function", "name": "callback", "optional": true, "parameters": []}
+        ]
+      },
+      {
+        "name": "detectLanguage",
+        "type": "function",
+        "description": "Detects the primary language of the content in a tab.",
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "tabId",
+            "minimum": 0,
+            "optional": true,
+            "description": "Defaults to the active tab of the <a href='windows.html#current-window'>current window</a>."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "type": "string",
+                "name": "language",
+                "description": "An ISO language code such as <code>en</code> or <code>fr</code>. For a complete list of languages supported by this method, see <a href='http://src.chromium.org/viewvc/chrome/trunk/src/third_party/cld/languages/internal/languages.cc'>kLanguageInfoTable</a>. The 2nd to 4th columns will be checked and the first non-NULL value will be returned except for Simplified Chinese for which zh-CN will be returned. For an unknown language, <code>und</code> will be returned."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "captureVisibleTab",
+        "type": "function",
+        "description": "Captures the visible area of the currently active tab in the specified window. You must have <a href='manifest.html#permissions'>host permission</a> for the URL displayed by the tab.",
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "windowId",
+            "minimum": -2,
+            "optional": true,
+            "description": "The target window. Defaults to the <a href='windows.html#current-window'>current window</a>."
+          },
+          {
+            "type": "object",
+            "name": "options",
+            "optional": true,
+            "description": "Set parameters of image capture, such as the format of the resulting image.",
+            "properties": {
+              "format": {
+                "type": "string",
+                "optional": true,
+                "enum": ["jpeg", "png"],
+                "description": "The format of the resulting image.  Default is jpeg."
+              },
+              "quality": {
+                "type": "integer",
+                "optional": true,
+                "minimum": 0,
+                "maximum": 100,
+                "description": "When format is 'jpeg', controls the quality of the resulting image.  This value is ignored for PNG images.  As quality is decreased, the resulting image will have more visual artifacts, and the number of bytes needed to store it will decrease."
+              }
+            }
+          },
+          {
+            "type": "function", "name": "callback", "parameters": [
+              {"type": "string", "name": "dataUrl", "description": "A data URL which encodes an image of the visible area of the captured tab. May be assigned to the 'src' property of an HTML Image element for display."}
+            ]
+          }
+        ]
+      },
+      {
+        "name": "executeScript",
+        "type": "function",
+        "description": "Injects JavaScript code into a page. For details, see the <a href='content_scripts.html#pi'>programmatic injection</a> section of the content scripts doc.",
+        "parameters": [
+          {"type": "integer", "name": "tabId", "optional": true, "description": "The ID of the tab in which to run the script; defaults to the active tab of the current window."},
+          {
+            "$ref": "tabs.InjectDetails",
+            "name": "details",
+            "description": "Details of the script to run."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "description": "Called after all the JavaScript has been executed.",
+            "parameters": [
+              {
+                "name": "result",
+                "optional": true,
+                "type": "array",
+                "items": {"type": "any", "minimum": 0},
+                "description": "The result of the script in every injected frame."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "insertCSS",
+        "type": "function",
+        "description": "Injects CSS into a page. For details, see the <a href='content_scripts.html#pi'>programmatic injection</a> section of the content scripts doc.",
+        "parameters": [
+          {"type": "integer", "name": "tabId", "optional": true, "description": "The ID of the tab in which to insert the CSS; defaults to the active tab of the current window."},
+          {
+            "$ref": "tabs.InjectDetails",
+            "name": "details",
+            "description": "Details of the CSS text to insert."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "description": "Called when all the CSS has been inserted.",
+            "parameters": []
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onCreated",
+        "type": "function",
+        "description": "Fired when a tab is created. Note that the tab's URL may not be set at the time this event fired, but you can listen to onUpdated events to be notified when a URL is set.",
+        "parameters": [
+          {
+            "$ref": "Tab",
+            "name": "tab",
+            "description": "Details of the tab that was created."
+          }
+        ]
+      },
+      {
+        "name": "onUpdated",
+        "type": "function",
+        "description": "Fired when a tab is updated.",
+        "parameters": [
+          {"type": "integer", "name": "tabId", "minimum": 0},
+          {
+            "type": "object",
+            "name": "changeInfo",
+            "description": "Lists the changes to the state of the tab that was updated.",
+            "properties": {
+              "status": {
+                "type": "string",
+                "optional": true,
+                "description": "The status of the tab. Can be either <em>loading</em> or <em>complete</em>."
+              },
+              "url": {
+                "type": "string",
+                "optional": true,
+                "description": "The tab's URL if it has changed."
+              },
+              "pinned": {
+                "type": "boolean",
+                "optional": true,
+                "description": "The tab's new pinned state."
+              }
+            }
+          },
+          {
+            "$ref": "Tab",
+            "name": "tab",
+            "description": "Gives the state of the tab that was updated."
+          }
+        ]
+      },
+      {
+        "name": "onMoved",
+        "type": "function",
+        "description": "Fired when a tab is moved within a window. Only one move event is fired, representing the tab the user directly moved. Move events are not fired for the other tabs that must move in response. This event is not fired when a tab is moved between windows. For that, see <a href='#event-onDetached'>onDetached</a>.",
+        "parameters": [
+          {"type": "integer", "name": "tabId", "minimum": 0},
+          {
+            "type": "object",
+            "name": "moveInfo",
+            "properties": {
+              "windowId": {"type": "integer", "minimum": 0},
+              "fromIndex": {"type": "integer", "minimum": 0},
+              "toIndex": {"type": "integer", "minimum": 0}
+            }
+          }
+        ]
+      },
+      {
+        "name": "onSelectionChanged",
+        "nodoc": true,
+        "type": "function",
+        "description": "Deprecated. Please use onActivated.",
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "tabId",
+            "minimum": 0,
+            "description": "The ID of the tab that has become active."
+          },
+          {
+            "type": "object",
+            "name": "selectInfo",
+            "properties": {
+              "windowId": {
+                "type": "integer",
+                "minimum": 0,
+                "description": "The ID of the window the selected tab changed inside of."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "onActiveChanged",
+        "nodoc": true,
+        "type": "function",
+        "description": "Deprecated. Please use onActivated.",
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "tabId",
+            "minimum": 0,
+            "description": "The ID of the tab that has become active."
+          },
+          {
+            "type": "object",
+            "name": "selectInfo",
+            "properties": {
+              "windowId": {
+                "type": "integer",
+                "minimum": 0,
+                "description": "The ID of the window the selected tab changed inside of."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "onActivated",
+        "type": "function",
+        "description": "Fires when the active tab in a window changes. Note that the tab's URL may not be set at the time this event fired, but you can listen to onUpdated events to be notified when a URL is set.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "activeInfo",
+            "properties": {
+              "tabId": {
+                "type": "integer",
+                "minimum": 0,
+                "description": "The ID of the tab that has become active."
+              },
+              "windowId": {
+                "type": "integer",
+                "minimum": 0,
+                "description": "The ID of the window the active tab changed inside of."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "onHighlightChanged",
+        "type": "function",
+        "nodoc": true,
+        "description": "Deprecated. Please use onHighlighted.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "selectInfo",
+            "properties": {
+              "windowId": {
+                "type": "integer",
+                "minimum": 0,
+                "description": "The window whose tabs changed."
+              },
+              "tabIds": {
+                "type": "array",
+                "name": "tabIds",
+                "items": {"type": "integer", "minimum": 0},
+                "description": "All highlighted tabs in the window."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "onHighlighted",
+        "type": "function",
+        "description": "Fired when the highlighted or selected tabs in a window changes.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "highlightInfo",
+            "properties": {
+              "windowId": {
+                "type": "integer",
+                "minimum": 0,
+                "description": "The window whose tabs changed."
+              },
+              "tabIds": {
+                "type": "array",
+                "name": "tabIds",
+                "items": {"type": "integer", "minimum": 0},
+                "description": "All highlighted tabs in the window."
+              }
+            }
+          }
+        ]
+      },
+      {
+        "name": "onDetached",
+        "type": "function",
+        "description": "Fired when a tab is detached from a window, for example because it is being moved between windows.",
+        "parameters": [
+          {"type": "integer", "name": "tabId", "minimum": 0},
+          {
+            "type": "object",
+            "name": "detachInfo",
+            "properties": {
+              "oldWindowId": {"type": "integer", "minimum": 0},
+              "oldPosition": {"type": "integer", "minimum": 0}
+            }
+          }
+        ]
+      },
+      {
+        "name": "onAttached",
+        "type": "function",
+        "description": "Fired when a tab is attached to a window, for example because it was moved between windows.",
+        "parameters": [
+          {"type": "integer", "name": "tabId", "minimum": 0},
+          {
+            "type": "object",
+            "name": "attachInfo",
+            "properties": {
+              "newWindowId": {"type": "integer", "minimum": 0},
+              "newPosition": {"type": "integer", "minimum": 0}
+            }
+          }
+        ]
+      },
+      {
+        "name": "onRemoved",
+        "type": "function",
+        "description": "Fired when a tab is closed. Note: A listener can be registered for this event without requesting the 'tabs' permission in the manifest.",
+        "parameters": [
+          {"type": "integer", "name": "tabId", "minimum": 0},
+          {
+            "type": "object",
+            "name": "removeInfo",
+            "properties": {
+              "isWindowClosing": {"type": "boolean", "description": "True when the tab is being closed because its window is being closed." }
+            }
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/terminal_private.json b/chrome/common/extensions/api/terminal_private.json
new file mode 100644
index 0000000..7b8802b
--- /dev/null
+++ b/chrome/common/extensions/api/terminal_private.json
@@ -0,0 +1,151 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "terminalPrivate",
+    "nodoc": true,
+    "platforms": ["chromeos"],
+    "types": [],
+    "functions": [
+      {
+        "name": "openTerminalProcess",
+        "type": "function",
+        "description": "Starts new process.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "processName",
+            "description": "Name of the process to open. Initially only 'crosh' is supported. Another processes may be added in future."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": false,
+            "description": "Returns pid of the launched process. If no process was launched returns -1.",
+            "parameters": [
+              {
+                "name": "pid",
+                "description": "Pid of the launched process.",
+                "type": "integer"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "closeTerminalProcess",
+        "type": "function",
+        "description": "Closes previousy opened process.",
+        "parameters": [
+          {
+            "name": "pid",
+            "type": "integer",
+            "description": "Process id of the process we want to close."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "description": "Function that gets called when close operation is started for the process. Returns success of the function.",
+            "parameters": [
+              {
+                "name": "success",
+                "type": "boolean"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "sendInput",
+        "type": "function",
+        "description": "Sends input that will be routed to stdin of the process with the specified pid.",
+        "parameters": [
+          {
+            "name": "pid",
+            "type": "integer",
+            "description": "The pid of the process to which we want to send input."
+          },
+          {
+            "name": "input",
+            "type": "string",
+            "description": "Input we are sending to the process."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "description": "Callback that will be called when sendInput method ends. Returns success.",
+            "parameters": [
+              {
+                "name": "success",
+                "type": "boolean"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "onTerminalResize",
+        "type": "function",
+        "description": "Notify the process with the id pid that terminal window size has changed.",
+        "parameters": [
+          {
+            "name": "pid",
+            "type": "integer",
+            "description": "The pid of the process."
+          },
+          {
+            "name": "width",
+            "type": "integer",
+            "description": "New window width (as column count)."
+          },
+          {
+            "name": "height",
+            "type": "integer",
+            "description": "New window height (as row count)."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": true,
+            "description": "Callback that will be called when sendInput method ends. Returns success.",
+            "parameters": [
+              {
+                "name": "success",
+                "type": "boolean"
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onProcessOutput",
+        "type": "function",
+        "description": "Fired when an opened process writes something to its output.",
+        "parameters": [
+          {
+            "name": "pid",
+            "type": "integer",
+            "description": "Pid of the process from which the output came."
+          },
+          {
+            "name": "type",
+            "type": "string",
+            "description": "Type of the output stream from which output came. When process exits, output type will be set to exit",
+            "enum": ["stdout", "stderr", "exit"]
+          },
+          {
+            "name": "text",
+            "type": "string",
+            "description": "Text that was written to the output stream."
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/test.json b/chrome/common/extensions/api/test.json
new file mode 100644
index 0000000..17ad531
--- /dev/null
+++ b/chrome/common/extensions/api/test.json
@@ -0,0 +1,128 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "test",
+    "nodoc": true,
+    "types": [],
+    "functions": [
+      {
+        "name": "getConfig",
+        "type": "function",
+        "description": "Gives configuration options set by the test.",
+        "parameters": [
+          {
+            "type": "function", "name": "callback", "parameters": [
+              {
+                "type": "object",
+                "name": "testConfig",
+                "properties": {
+                  "testServer": {
+                    "type": "object",
+                    "optional": true,
+                    "description": "Details on the test server used to mock network responses.  Will be set only if test calls ExtensionApiTest::StartTestServer().",
+                    "properties": {
+                      "port": {
+                        "type": "integer",
+                        "description": "The port on which the test server is listening.",
+                        "minimum": 1024,
+                        "maximum": 65535
+                      }
+                    }
+                  },
+                  "testDataDirectory": {
+                    "type": "string",
+                    "description": "file:/// URL for the API test data directory."
+                  },
+                  "testWebSocketPort": {
+                    "type": "integer",
+                    "description": "The port on which the test WebSocket server is listening.",
+                    "minimum": 0,
+                    "maximum": 65535
+                  }
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "notifyFail",
+        "unprivileged": true,
+        "type": "function",
+        "description": "Notifies the browser process that test code running in the extension failed.  This is only used for internal unit testing.",
+        "parameters": [
+          {"type": "string", "name": "message"}
+        ]
+      },
+      {
+        "name": "notifyPass",
+        "unprivileged": true,
+        "type": "function",
+        "description": "Notifies the browser process that test code running in the extension passed.  This is only used for internal unit testing.",
+        "parameters": [
+          {"type": "string", "name": "message", "optional": true}
+        ]
+      },
+      {
+        "name": "resetQuota",
+        "type": "function",
+        "description": "Resets all accumulated quota state for all extensions.  This is only used for internal unit testing.",
+        "parameters": []
+      },
+      {
+        "name": "log",
+        "unprivileged": true,
+        "type": "function",
+        "description": "Logs a message during internal unit testing.",
+        "parameters": [
+          {"type": "string", "name": "message"}
+        ]
+      },
+      {
+        "name": "createIncognitoTab",
+        "type": "function",
+        "description": "Creates an incognito tab during internal testing. Succeeds even if the extension is not enabled in incognito mode.",
+        "parameters": [
+          {"type": "string", "name": "url"}
+        ]
+      },
+      {
+        "name": "sendMessage",
+        "type": "function",
+        "description": "Sends a string message to the browser process, generating a Notification that C++ test code can wait for.",
+        "parameters": [
+          {"type": "string", "name": "message"},
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {"type": "string", "name": "response"}
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onMessage",
+        "type": "function",
+        "unprivileged": true,
+        "description": "Used to test sending messages to extensions.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "info",
+            "properties": {
+              "data": { "type": "string", "description": "Additional information." },
+              "lastMessage": { "type": "boolean", "description": "True if this was the last message for this test" }
+            }
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/top_sites.json b/chrome/common/extensions/api/top_sites.json
new file mode 100644
index 0000000..e220447
--- /dev/null
+++ b/chrome/common/extensions/api/top_sites.json
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "topSites",
+    "types": [
+      {
+        "id": "MostVisitedURL",
+        "type": "object",
+        "description": "An object encapsulating a most visited URL, such as the URLs on the new tab page.",
+        "properties": {
+          "url": {"type": "string", "description": "The most visited URL."},
+          "title": {"type": "string", "description": "The title of the page"}
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "get",
+        "type": "function",
+        "description": "Gets a list of top sites.",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "type": "array",
+                "name": "data",
+                "items": {"$ref": "MostVisitedURL"}
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/tts.json b/chrome/common/extensions/api/tts.json
new file mode 100644
index 0000000..601edbf
--- /dev/null
+++ b/chrome/common/extensions/api/tts.json
@@ -0,0 +1,239 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "tts",
+    "types": [
+      {
+        "id": "TtsEvent",
+        "type": "object",
+        "description": "An event from the TTS engine to communicate the status of an utterance.",
+        "properties": {
+          "type": {
+            "type": "string",
+            "enum": ["start", "end", "word", "sentence", "marker", "interrupted", "cancelled", "error"],
+            "description": "The type can be 'start' as soon as speech has started, 'word' when a word boundary is reached, 'sentence' when a sentence boundary is reached, 'marker' when an SSML mark element is reached, 'end' when the end of the utterance is reached, 'interrupted' when the utterance is stopped or interrupted before reaching the end, 'cancelled' when it's removed from the queue before ever being synthesized, or 'error' when any other error occurs."
+          },
+          "charIndex": {
+            "type": "number",
+            "optional": true,
+            "description": "The index of the current character in the utterance."
+          },
+          "errorMessage": {
+            "type": "string",
+            "description": "The error description, if the event type is 'error'.",
+            "optional": true
+          },
+          "srcId": {
+            "type": "number",
+            "description": "An ID unique to the calling function's context so that events can get routed back to the correct tts.speak call.",
+            "nodoc": true,
+            "optional": true
+          },
+          "isFinalEvent": {
+            "type": "boolean",
+            "description": "True if this is the final event that will be sent to this handler.",
+            "nodoc": true,
+            "optional": true
+          }
+        }
+      },
+      {
+        "id": "TtsVoice",
+        "type": "object",
+        "description": "A description of a voice available for speech synthesis.",
+        "properties": {
+          "voiceName": {
+            "type": "string",
+            "optional": true,
+            "description": "The name of the voice."
+          },
+          "lang": {
+            "type": "string",
+            "optional": true,
+            "description": "The language that this voice supports, in the form <em>language</em>-<em>region</em>. Examples: 'en', 'en-US', 'en-GB', 'zh-CN'."
+          },
+          "gender": {
+            "type": "string",
+            "optional": true,
+            "description": "This voice's gender.",
+            "enum": ["male", "female"]
+          },
+          "extensionId": {
+            "type": "string",
+            "optional": true,
+            "description": "The ID of the extension providing this voice."
+          },
+          "eventTypes": {
+            "type": "array",
+            "items": {"type": "string"},
+            "optional": true,
+            "description": "All of the callback event types that this voice is capable of sending."
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "speak",
+        "type": "function",
+        "description": "Speaks text using a text-to-speech engine.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "utterance",
+            "description": "The text to speak, either plain text or a complete, well-formed SSML document. Speech engines that do not support SSML will strip away the tags and speak the text. The maximum length of the text is 32,768 characters."
+          },
+          {
+            "type": "object",
+            "name": "options",
+            "optional": true,
+            "description": "The speech options.",
+            "properties": {
+              "enqueue": {
+                "type": "boolean",
+                "optional": true,
+                "description": "If true, enqueues this utterance if TTS is already in progress. If false (the default), interrupts any current speech and flushes the speech queue before speaking this new utterance."
+              },
+              "voiceName": {
+                "type": "string",
+                "optional": true,
+                "description": "The name of the voice to use for synthesis. If empty, uses any available voice."
+              },
+              "extensionId": {
+                "type": "string",
+                "optional": true,
+                "description": "The extension ID of the speech engine to use, if known."
+              },
+              "lang": {
+                "type": "string",
+                "optional": true,
+                "description": "The language to be used for synthesis, in the form <em>language</em>-<em>region</em>. Examples: 'en', 'en-US', 'en-GB', 'zh-CN'."
+              },
+              "gender": {
+                "type": "string",
+                "optional": true,
+                "description": "Gender of voice for synthesized speech.",
+                "enum": ["male", "female"]
+              },
+              "rate": {
+                "type": "number",
+                "optional": true,
+                "minimum": 0.1,
+                "maximum": 10,
+                "description": "Speaking rate relative to the default rate for this voice. 1.0 is the default rate, normally around 180 to 220 words per minute. 2.0 is twice as fast, and 0.5 is half as fast. Values below 0.1 or above 10.0 are strictly disallowed, but many voices will constrain the minimum and maximum rates further&mdash;for example a particular voice may not actually speak faster than 3 times normal even if you specify a value larger than 3.0."
+              },
+              "pitch": {
+                "type": "number",
+                "optional": true,
+                "minimum": 0,
+                "maximum": 2,
+                "description": "Speaking pitch between 0 and 2 inclusive, with 0 being lowest and 2 being highest. 1.0 corresponds to a voice's default pitch."
+              },
+              "volume": {
+                "type": "number",
+                "optional": true,
+                "minimum": 0,
+                "maximum": 1,
+                "description": "Speaking volume between 0 and 1 inclusive, with 0 being lowest and 1 being highest, with a default of 1.0."
+              },
+              "requiredEventTypes": {
+                "type": "array",
+                "items": {"type": "string"},
+                "optional": true,
+                "description": "The TTS event types the voice must support."
+              },
+              "desiredEventTypes": {
+                "type": "array",
+                "items": {"type": "string"},
+                "optional": true,
+                "description": "The TTS event types that you are interested in listening to. If missing, all event types may be sent."
+              },
+              "onEvent": {
+                "type": "function",
+                "optional": true,
+                "description": "This function is called with events that occur in the process of speaking the utterance.",
+                "parameters": [
+                  {
+                    "name": "event",
+                    "$ref": "TtsEvent",
+                    "description": "The update event from the text-to-speech engine indicating the status of this utterance."
+                  }
+                ]
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "description": "Called right away, before speech finishes. Check chrome.extension.lastError to make sure there were no errors. Use options.onEvent to get more detailed feedback.",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "stop",
+        "type": "function",
+        "description": "Stops any current speech.",
+        "parameters": []
+      },
+      {
+        "name": "isSpeaking",
+        "type": "function",
+        "description": "Checks whether the engine is currently speaking. On Mac OS X, the result is true whenever the system speech engine is speaking, even if the speech wasn't initiated by Chrome.",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "speaking",
+                "type": "boolean",
+                "description": "True if speaking, false otherwise."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getVoices",
+        "type": "function",
+        "description": "Gets an array of all available voices.",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "type": "array",
+                "name": "voices",
+                "items": { "$ref": "TtsVoice" },
+                "description": "Array of $ref:TtsVoice objects representing the available voices for speech synthesis."
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onEvent",
+        "type": "function",
+        "nodoc": true,
+        "parameters": [
+          {
+            "name": "event",
+            "$ref": "TtsEvent",
+            "description": "The event from the text-to-speech engine indicating the status of this utterance."
+          }
+        ],
+        "description": "Used to pass events back to the function calling speak()."
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/tts_engine.json b/chrome/common/extensions/api/tts_engine.json
new file mode 100644
index 0000000..6279ccc
--- /dev/null
+++ b/chrome/common/extensions/api/tts_engine.json
@@ -0,0 +1,104 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "ttsEngine",
+    "dependencies": [ "tts" ],
+    "functions": [
+      {
+        "name": "sendTtsEvent",
+        "nodoc": true,
+        "type": "function",
+        "description": "Routes a TTS event from a speech engine to a client.",
+        "parameters": [
+          {
+            "type": "integer",
+            "name": "requestId"
+          },
+          {
+            "name": "event",
+            "$ref": "tts.TtsEvent",
+            "description": "The update event from the text-to-speech engine indicating the status of this utterance."
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onSpeak",
+        "type": "function",
+        "description": "Called when the user makes a call to tts.speak() and one of the voices from this extension's manifest is the first to match the options object.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "utterance",
+            "description": "The text to speak, specified as either plain text or an SSML document. If your engine does not support SSML, you should strip out all XML markup and synthesize only the underlying text content. The value of this parameter is guaranteed to be no more than 32,768 characters. If this engine does not support speaking that many characters at a time, the utterance should be split into smaller chunks and queued internally without returning an error."
+          },
+          {
+            "type": "object",
+            "name": "options",
+            "description": "Options specified to the tts.speak() method.",
+            "properties": {
+              "voiceName": {
+                "type": "string",
+                "optional": true,
+                "description": "The name of the voice to use for synthesis."
+              },
+              "lang": {
+                "type": "string",
+                "optional": true,
+                "description": "The language to be used for synthesis, in the form <em>language</em>-<em>region</em>. Examples: 'en', 'en-US', 'en-GB', 'zh-CN'."
+              },
+              "gender": {
+                "type": "string",
+                "optional": true,
+                "description": "Gender of voice for synthesized speech.",
+                "enum": ["male", "female"]
+              },
+              "rate": {
+                "type": "number",
+                "optional": true,
+                "minimum": 0.1,
+                "maximum": 10.0,
+                "description": "Speaking rate relative to the default rate for this voice. 1.0 is the default rate, normally around 180 to 220 words per minute. 2.0 is twice as fast, and 0.5 is half as fast. This value is guaranteed to be between 0.1 and 10.0, inclusive. When a voice does not support this full range of rates, don't return an error. Instead, clip the rate to the range the voice supports."
+              },
+              "pitch": {
+                "type": "number",
+                "optional": true,
+                "minimum": 0,
+                "maximum": 2,
+                "description": "Speaking pitch between 0 and 2 inclusive, with 0 being lowest and 2 being highest. 1.0 corresponds to this voice's default pitch."
+              },
+              "volume": {
+                "type": "number",
+                "optional": true,
+                "minimum": 0,
+                "maximum": 1,
+                "description": "Speaking volume between 0 and 1 inclusive, with 0 being lowest and 1 being highest, with a default of 1.0."
+              }
+            }
+          },
+          {
+            "name": "sendTtsEvent",
+            "type": "function",
+            "description": "Call this function with events that occur in the process of speaking the utterance.",
+            "parameters": [
+              {
+                "name": "event",
+                "$ref": "tts.TtsEvent",
+                "description": "The event from the text-to-speech engine indicating the status of this utterance."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "onStop",
+        "type": "function",
+        "description": "Fired when a call is made to tts.stop and this extension may be in the middle of speaking. If an extension receives a call to onStop and speech is already stopped, it should do nothing (not raise an error)."
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/types.json b/chrome/common/extensions/api/types.json
new file mode 100644
index 0000000..0463ebd
--- /dev/null
+++ b/chrome/common/extensions/api/types.json
@@ -0,0 +1,151 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "types",
+    "types": [
+      {
+        "id": "ChromeSetting",
+        "type": "object",
+        "customBindings": "ChromeSetting",
+        "description": "An interface which allows access to a Chrome browser setting.",
+        "functions": [
+          {
+            "name": "get",
+            "type": "function",
+            "description": "Gets the value of a setting.",
+            "parameters": [
+              {
+                "name": "details",
+                "type": "object",
+                "description": "What setting to consider.",
+                "properties": {
+                  "incognito": {
+                    "type": "boolean",
+                    "optional": true,
+                    "description": "Whether to return the setting that applies to the incognito session (default false)."
+                  }
+                }
+              },
+              {
+                "name": "callback",
+                "type": "function",
+                "parameters": [
+                  {
+                    "name": "details",
+                    "type": "object",
+                    "description": "Details of the currently effective value.",
+                    "properties": {
+                      "value": {
+                        "description": "The value of the setting.",
+                        "type": "any"
+                      },
+                      "levelOfControl": {
+                        "description": "One of<br><var>not_controllable</var>: cannot be controlled by any extension<br><var>controlled_by_other_extensions</var>: controlled by extensions with higher precedence<br><var>controllable_by_this_extension</var>: can be controlled by this extension<br><var>controlled_by_this_extension</var>: controlled by this extension",
+                        "type": "string",
+                        "enum": ["not_controllable", "controlled_by_other_extensions", "controllable_by_this_extension", "controlled_by_this_extension"]
+                      },
+                      "incognitoSpecific": {
+                        "description": "Whether the effective value is specific to the incognito session.<br>This property will <em>only</em> be present if the <var>incognito</var> property in the <var>details</var> parameter of <code>get()</code> was true.",
+                        "type": "boolean",
+                        "optional": true
+                      }
+                    }
+                  }
+                ]
+              }
+            ]
+          },
+          {
+            "name": "set",
+            "type": "function",
+            "description": "Sets the value of a setting.",
+            "parameters": [
+              {
+                "name": "details",
+                "type": "object",
+                "description": "What setting to change.",
+                "properties": {
+                  "value": {
+                    "description": "The value of the setting. <br>Note that every setting has a specific value type, which is described together with the setting. An extension should <em>not</em> set a value of a different type.",
+                    "type": "any"
+                  },
+                  "scope": {
+                    "type": "string",
+                    "enum": ["regular", "regular_only", "incognito_persistent", "incognito_session_only"],
+                    "optional": true,
+                    "description": "Where to set the setting (default: regular). One of<br><var>regular</var>: setting for the regular profile (which is inherited by the incognito profile if not overridden elsewhere),<br><var>regular_only</var>: setting for the regular profile only (not inherited by the incognito profile),<br><var>incognito_persistent</var>: setting for the incognito profile that survives browser restarts (overrides regular preferences),<br><var>incognito_session_only</var>: setting for the incognito profile that can only be set during an incognito session and is deleted when the incognito session ends (overrides regular and incognito_persistent preferences)."
+                  }
+                }
+              },
+              {
+                "name": "callback",
+                "type": "function",
+                "description": "Called after the setting has been set.",
+                "optional": true,
+                "parameters": []
+              }
+            ]
+          },
+          {
+            "name": "clear",
+            "type": "function",
+            "description": "Clears the setting. This way default settings can become effective again.",
+            "parameters": [
+              {
+                "name": "details",
+                "type": "object",
+                "description": "What setting to clear.",
+                "properties": {
+                  "scope": {
+                    "type": "string",
+                    "enum": ["regular", "regular_only", "incognito_persistent", "incognito_session_only"],
+                    "optional": true,
+                    "description": "Where to clear the setting (default: regular). One of<br><var>regular</var>: setting for the regular profile (which is inherited by the incognito profile if not overridden elsewhere),<br><var>regular_only</var>: setting for the regular profile only (not inherited by the incognito profile),<br><var>incognito_persistent</var>: setting for the incognito profile that survives browser restarts (overrides regular preferences),<br><var>incognito_session_only</var>: setting for the incognito profile that can only be set during an incognito session and is deleted when the incognito session ends (overrides regular and incognito_persistent preferences)."
+                  }
+                }
+              },
+              {
+                "name": "callback",
+                "type": "function",
+                "description": "Called after the setting has been cleared.",
+                "optional": true,
+                "parameters": []
+              }
+            ]
+          }
+        ],
+        "events": [
+          {
+            "name": "onChange",
+            "description": "Fired when the value of the setting changes.",
+            "parameters": [
+              {
+                "type": "object",
+                "name": "details",
+                "properties": {
+                  "value": {
+                    "description": "The value of the setting.",
+                    "type": "any"
+                  },
+                  "levelOfControl": {
+                    "description": "One of<br><var>not_controllable</var>: cannot be controlled by any extension<br><var>controlled_by_other_extensions</var>: controlled by extensions with higher precedence<br><var>controllable_by_this_extension</var>: can be controlled by this extension<br><var>controlled_by_this_extension</var>: controlled by this extension",
+                    "type": "string",
+                    "enum": ["not_controllable", "controlled_by_other_extensions", "controllable_by_this_extension", "controlled_by_this_extension"]
+                  },
+                  "incognitoSpecific": {
+                    "description": "Whether the value that has changed is specific to the incognito session.<br>This property will <em>only</em> be present if the user has enabled the extension in incognito mode.",
+                    "type": "boolean",
+                    "optional": true
+                  }
+                }
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/usb.idl b/chrome/common/extensions/api/usb.idl
new file mode 100644
index 0000000..96a9821
--- /dev/null
+++ b/chrome/common/extensions/api/usb.idl
@@ -0,0 +1,168 @@
+// Copyright (c) 2012 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.
+
+namespace usb {
+
+  // A Device encapsulates everything that is needed to communicate with a USB
+  // device. They are returned by findDevice calls and have all of their
+  // fields populated before being returned.
+  dictionary Device {
+    long handle;
+    long vendorId;
+    long productId;
+  };
+
+  // ControlTransferInfo represents that parameters to a single USB control
+  // transfer.
+  dictionary ControlTransferInfo {
+    // The direction of this transfer. Must be one of either in or out.
+    DOMString direction;
+
+    // The intended recipient for this transfer. Must be one of device,
+    // interface, endpoint, or other.
+    DOMString recipient;
+
+    // The type of this request. Must be one of standard, class, vendor,
+    // or reserved.
+    DOMString requestType;
+
+    long request;
+    long value;
+    long index;
+
+    // If this transfer is an input transfer, then this field must be set to
+    // indicate the expected data length. If this is an output transfer, then
+    // this field is ignored.
+    long? length;
+
+    // The data payload carried by this transfer. If this is an output tranfer
+    // then this field must be set.
+    ArrayBuffer? data;
+  };
+
+  // GenericTransferInfo is used by both bulk and interrupt transfers to
+  // specify the parameters of the transfer.
+  dictionary GenericTransferInfo {
+    // The direction of this transfer. Must be one of in or out.
+    DOMString direction;
+
+    long endpoint;
+
+    // If this is an input transfer then this field indicates the size of the
+    // input buffer. If this is an output transfer then this field is ignored.
+    long? length;
+
+    // If this is an output transfer then this field must be populated.
+    // Otherwise, it will be ignored.
+    ArrayBuffer? data;
+  };
+
+  // IsochronousTransferInfo describes a single multi-packet isochronous
+  // transfer.
+  dictionary IsochronousTransferInfo {
+    // All of the normal transfer parameters are encapsulated in the
+    // transferInfo parameters. Note that the data specified in this parameter
+    // block is split along packetLength boundaries to form the individual
+    // packets of the transfer.
+    GenericTransferInfo transferInfo;
+
+    // The total number of packets in this transfer.
+    long packets;
+
+    // The length of each of the packets in this transfer.
+    long packetLength;
+  };
+
+  dictionary TransferResultInfo {
+    // A value of 0 indicates that the transfer was a success. Other values
+    // indicate failure.
+    long? resultCode;
+
+    // If the transfer was an input transfer then this field will contain all
+    // of the input data requested.
+    ArrayBuffer? data;
+  };
+
+  dictionary FindDevicesOptions {};
+
+  callback VoidCallback = void ();
+
+  callback FindDevicesCallback = void (Device[] device);
+  callback CloseDeviceCallback = void ();
+  callback TransferCallback = void (TransferResultInfo info);
+
+  interface Functions {
+    // Finds the first instance of the USB device specified by the vendorId/
+    // productId pair and, if permissions allow, opens it for use.
+    // Upon successfully opening a device the callback is invoked with a
+    // populated Device object. On failure, the callback is invoked with null.
+    // |vendorId|: The vendor ID of the USB device to find.
+    // |productId|: The product ID of the USB device to find.
+    // |callback|: Invoked with the opened Device on success.
+    static void findDevices(long vendorId, long productId,
+        FindDevicesOptions options, FindDevicesCallback callback);
+
+    // Closes an open device instance. Invoking operations on a device after it
+    // has been closed is a safe operation, but causes no action to be taken.
+    // |device|: The device to close.
+    // |callback|: The callback to invoke once the device is closed.
+    static void closeDevice(Device device,
+        optional CloseDeviceCallback callback);
+
+    // Claims an interface on the specified USB device.
+    // |device|: The device on which the interface is to be claimed.
+    // |interface|: The interface number to be claimed.
+    // |callback|: The callback to invoke once the interface is claimed.
+    static long claimInterface(Device device, long interfaceNumber,
+        VoidCallback callback);
+
+    // Releases a claim to an interface on the provided device.
+    // |device|: The device on which the interface is to be released.
+    // |interface|: The interface number to be released.
+    // |callback|: The callback to invoke once the interface is released.
+    static long releaseInterface(Device device, long interfaceNumber,
+        VoidCallback callback);
+
+    // Selects an alternate setting on a previously claimed interface on a
+    // device.
+    // |device|: The device on which the interface settings are to be set.
+    // |interface|: The interface number to be set.
+    // |alternateSetting|: The alternate setting to set.
+    // |callback|: The callback to invoke once the interface setting is set.
+    static long setInterfaceAlternateSetting(Device device,
+        long interfaceNumber, long alternateSetting, VoidCallback callback);
+
+    // Performs a control transfer on the specified device. See the
+    // ControlTransferInfo structure for the parameters required to make a
+    // transfer.
+    // |device|: An open device to make the transfer on.
+    // |transferInfo|: The parameters to the transfer. See ControlTransferInfo.
+    // |callback|: Invoked once the transfer has completed.
+    static void controlTransfer(Device device,
+        ControlTransferInfo transferInfo, TransferCallback callback);
+
+    // Performs a bulk transfer on the specified device.
+    // |device|: An open device to make the transfer on.
+    // |transferInfo|: The paramters to the transfer. See GenericTransferInfo.
+    // |callback|: Invoked once the transfer has completed.
+    static void bulkTransfer(Device device, GenericTransferInfo transferInfo,
+        TransferCallback callback);
+
+    // Performs an interrupt transfer on the specified device.
+    // |device|: An open device to make the transfer on.
+    // |transferInfo|: The paramters to the transfer. See GenericTransferInfo.
+    // |callback|: Invoked once the transfer has completed.
+    static void interruptTransfer(Device device,
+        GenericTransferInfo transferInfo, TransferCallback callback);
+
+    // Performs an isochronous transfer on the specific device.
+    // |device|: An open device to make the transfer on.
+    // |transferInfo|: The parameters to the transfer. See
+    // IsochronousTransferInfo.
+    // |callback|: Invoked once the transfer has been completed.
+    static void isochronousTransfer(Device device,
+        IsochronousTransferInfo transferInfo,
+        TransferCallback callback);
+  };
+};
diff --git a/chrome/common/extensions/api/wallpaper_private.json b/chrome/common/extensions/api/wallpaper_private.json
new file mode 100644
index 0000000..ebdf1a3
--- /dev/null
+++ b/chrome/common/extensions/api/wallpaper_private.json
@@ -0,0 +1,93 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace":"wallpaperPrivate",
+    "nodoc": "true",
+    "functions": [
+      {
+        "name": "getStrings",
+        "type": "function",
+        "description": "Gets translated strings.",
+        "nodoc": "true",
+        "parameters": [
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "object",
+                "additionalProperties": {"type": "string"}
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setWallpaper",
+        "type": "function",
+        "description": "Sets wallpaper to the image from url with specified layout",
+        "nodoc": "true",
+        "parameters": [
+          {
+            "type": "binary",
+            "name": "wallpaper"
+          },
+          {
+            "type": "string",
+            "name": "layout",
+            "enum": [ "STRETCH", "CENTER", "CENTER_CROPPED"]
+          },
+          {
+            "type": "string",
+            "name": "url"
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "setCustomWallpaper",
+        "type": "function",
+        "description": "Sets wallpaper to the image from local file with specified layout",
+        "nodoc": "true",
+        "parameters": [
+          {
+            "type": "binary",
+            "name": "wallpaper"
+          },
+          {
+            "type": "string",
+            "name": "layout",
+            "enum": [ "STRETCH", "CENTER", "CENTER_CROPPED"]
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "minimizeInactiveWindows",
+        "type": "function",
+        "description": "Minimizes all inactive open windows.",
+        "nodoc": "true",
+        "parameters": []
+      },
+      {
+        "name": "restoreMinimizedWindows",
+        "type": "function",
+        "description": "Restores all previously minimized windows.",
+        "nodoc": "true",
+        "parameters": []
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/web_navigation.json b/chrome/common/extensions/api/web_navigation.json
new file mode 100644
index 0000000..643e7f3
--- /dev/null
+++ b/chrome/common/extensions/api/web_navigation.json
@@ -0,0 +1,338 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "webNavigation",
+    "types": [],
+    "functions": [
+      {
+        "name": "getFrame",
+        "type": "function",
+        "description": "Retrieves information about the given frame. A frame refers to an &lt;iframe&gt; or a &lt;frame&gt; of a web page and is identified by a tab ID and a frame ID.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "description": "Information about the frame to retrieve information about.",
+            "properties": {
+              "tabId": { "type": "integer", "minimum": 0, "description": "The ID of the tab in which the frame is." },
+              "processId": {"type": "integer", "description": "The ID of the process runs the renderer for this tab."},
+              "frameId": { "type": "integer", "minimum": 0, "description": "The ID of the frame in the given tab." }
+            }
+          },
+          {
+            "type": "function", "name": "callback", "parameters": [
+              {
+                "type": "object",
+                "name": "details",
+                "optional": true,
+                "description": "Information about the requested frame, null if the specified frame ID and/or tab ID are invalid.",
+                "properties": {
+                  "errorOccurred": {
+                    "type": "boolean",
+                    "description": "True if the last navigation in this frame was interrupted by an error, i.e. the onErrorOccurred event fired."
+                  },
+                  "url": {
+                    "type": "string",
+                    "description": "The URL currently associated with this frame, if the frame identified by the frameId existed at one point in the given tab. The fact that an URL is associated with a given frameId does not imply that the corresponding frame still exists."
+                  },
+                  "parentFrameId": {
+                    "type": "integer",
+                    "description": "ID of frame that wraps the frame. Set to -1 of no parent frame exists."
+                  }
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getAllFrames",
+        "type": "function",
+        "description": "Retrieves information about all frames of a given tab.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "description": "Information about the tab to retrieve all frames from.",
+            "properties": {
+              "tabId": { "type": "integer", "minimum": 0, "description": "The ID of the tab." }
+            }
+          },
+          {
+            "type": "function", "name": "callback", "parameters": [
+              {
+                "name": "details",
+                "type": "array",
+                "description": "A list of frames in the given tab, null if the specified tab ID is invalid.",
+                "optional": true,
+                "items": {
+                  "type": "object",
+                  "properties": {
+                    "errorOccurred": {
+                      "type": "boolean",
+                      "description": "True if the last navigation in this frame was interrupted by an error, i.e. the onErrorOccurred event fired."
+                    },
+                    "processId": {
+                      "type": "integer",
+                      "description": "The ID of the process runs the renderer for this tab."
+                    },
+                    "frameId": {
+                      "type": "integer",
+                      "description": "The ID of the frame. 0 indicates that this is the main frame; a positive value indicates the ID of a subframe."
+                    },
+                    "parentFrameId": {
+                      "type": "integer",
+                      "description": "ID of frame that wraps the frame. Set to -1 of no parent frame exists."
+                    },
+                    "url": {
+                      "type": "string",
+                      "description": "The URL currently associated with this frame, if the frame identified by the frameId existed at one point in the given tab. The fact that an URL is associated with a given frameId does not imply that the corresponding frame still exists."
+                    }
+                  }
+                }
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onBeforeNavigate",
+        "type": "function",
+        "description": "Fired when a navigation is about to occur.",
+        "filters": [
+          {
+            "name": "url",
+            "type": "array",
+            "items": { "$ref": "events.UrlFilter" },
+            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
+          }
+        ],
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation is about to occur."},
+              "url": {"type": "string"},
+              "processId": {"type": "integer", "description": "The ID of the process runs the renderer for this tab."},
+              "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique for a given tab and process."},
+              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame. Set to -1 of no parent frame exists."},
+              "timeStamp": {"type": "number", "description": "The time when the browser was about to start the navigation, in milliseconds since the epoch."}
+            }
+          }
+        ]
+      },
+      {
+        "name": "onCommitted",
+        "type": "function",
+        "description": "Fired when a navigation is committed. The document (and the resources it refers to, such as images and subframes) might still be downloading, but at least part of the document has been received from the server and the browser has decided to switch to the new document.",
+        "filters": [
+          {
+            "name": "url",
+            "type": "array",
+            "items": { "$ref": "events.UrlFilter" },
+            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
+          }
+        ],
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."},
+              "url": {"type": "string"},
+              "processId": {"type": "integer", "description": "The ID of the process runs the renderer for this tab."},
+              "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."},
+              "transitionType": {"type": "string", "enum": ["link", "typed", "auto_bookmark", "auto_subframe", "manual_subframe", "generated", "start_page", "form_submit", "reload", "keyword", "keyword_generated"], "description": "Cause of the navigation. The same transition types as defined in the history API are used."},
+              "transitionQualifiers": {"type": "array", "description": "A list of transition qualifiers.", "items": {"type": "string", "enum": ["client_redirect", "server_redirect", "forward_back", "from_address_bar"]}},
+              "timeStamp": {"type": "number", "description": "The time when the navigation was committed, in milliseconds since the epoch."}
+            }
+          }
+        ]
+      },
+      {
+        "name": "onDOMContentLoaded",
+        "type": "function",
+        "description": "Fired when the page's DOM is fully constructed, but the referenced resources may not finish loading.",
+        "filters": [
+          {
+            "name": "url",
+            "type": "array",
+            "items": { "$ref": "events.UrlFilter" },
+            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
+          }
+        ],
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."},
+              "url": {"type": "string"},
+              "processId": {"type": "integer", "description": "The ID of the process runs the renderer for this tab."},
+              "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."},
+              "timeStamp": {"type": "number", "description": "The time when the page's DOM was fully constructed, in milliseconds since the epoch."}
+            }
+          }
+        ]
+      },
+      {
+        "name": "onCompleted",
+        "type": "function",
+        "description": "Fired when a document, including the resources it refers to, is completely loaded and initialized.",
+        "filters": [
+          {
+            "name": "url",
+            "type": "array",
+            "items": { "$ref": "events.UrlFilter" },
+            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
+          }
+        ],
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."},
+              "url": {"type": "string"},
+              "processId": {"type": "integer", "description": "The ID of the process runs the renderer for this tab."},
+              "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."},
+              "timeStamp": {"type": "number", "description": "The time when the document finished loading, in milliseconds since the epoch."}
+            }
+          }
+        ]
+      },
+      {
+        "name": "onErrorOccurred",
+        "type": "function",
+        "description": "Fired when an error occurs and the navigation is aborted. This can happen if either a network error occurred, or the user aborted the navigation.",
+        "filters": [
+          {
+            "name": "url",
+            "type": "array",
+            "items": { "$ref": "events.UrlFilter" },
+            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
+          }
+        ],
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."},
+              "url": {"type": "string"},
+              "processId": {"type": "integer", "description": "The ID of the process runs the renderer for this tab."},
+              "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."},
+              "error": {"type": "string", "description": "The error description."},
+              "timeStamp": {"type": "number", "description": "The time when the error occurred, in milliseconds since the epoch."}
+            }
+          }
+        ]
+      },
+      {
+        "name": "onCreatedNavigationTarget",
+        "type": "function",
+        "description": "Fired when a new window, or a new tab in an existing window, is created to host a navigation.",
+        "filters": [
+          {
+            "name": "url",
+            "type": "array",
+            "items": { "$ref": "events.UrlFilter" },
+            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
+          }
+        ],
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "sourceTabId": {"type": "integer", "description": "The ID of the tab in which the navigation is triggered."},
+              "sourceProcessId": {"type": "integer", "description": "The ID of the process runs the renderer for the source tab."},
+              "sourceFrameId": {"type": "integer", "description": "The ID of the frame with sourceTabId in which the navigation is triggered. 0 indicates the main frame."},
+              "url": {"type": "string", "description": "The URL to be opened in the new window."},
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the url is opened"},
+              "timeStamp": {"type": "number", "description": "The time when the browser was about to create a new view, in milliseconds since the epoch."}
+            }
+          }
+        ]
+      },
+      {
+        "name": "onReferenceFragmentUpdated",
+        "type": "function",
+        "description": "Fired when the reference fragment of a frame was updated. All future events for that frame will use the updated URL.",
+        "filters": [
+          {
+            "name": "url",
+            "type": "array",
+            "items": { "$ref": "events.UrlFilter" },
+            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
+          }
+        ],
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."},
+              "url": {"type": "string"},
+              "processId": {"type": "integer", "description": "The ID of the process runs the renderer for this tab."},
+              "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."},
+              "transitionType": {"type": "string", "enum": ["link", "typed", "auto_bookmark", "auto_subframe", "manual_subframe", "generated", "start_page", "form_submit", "reload", "keyword", "keyword_generated"], "description": "Cause of the navigation. The same transition types as defined in the history API are used."},
+              "transitionQualifiers": {"type": "array", "description": "A list of transition qualifiers.", "items": {"type": "string", "enum": ["client_redirect", "server_redirect", "forward_back", "from_address_bar"]}},
+              "timeStamp": {"type": "number", "description": "The time when the navigation was committed, in milliseconds since the epoch."}
+            }
+          }
+        ]
+      },
+      {
+        "name": "onTabReplaced",
+        "type": "function",
+        "description": "Fired when the contents of the tab is replaced by a different (usually previously pre-rendered) tab.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "replacedTabId": {"type": "integer", "description": "The ID of the tab that was replaced."},
+              "tabId": {"type": "integer", "description": "The ID of the tab that replaced the old tab."},
+              "timeStamp": {"type": "number", "description": "The time when the replacement happened, in milliseconds since the epoch."}
+            }
+          }
+        ]
+      },
+      {
+        "name": "onHistoryStateUpdated",
+        "type": "function",
+        "description": "Fired when the frame's history was updated to a new URL. All future events for that frame will use the updated URL.",
+        "filters": [
+          {
+            "name": "url",
+            "type": "array",
+            "items": { "$ref": "events.UrlFilter" },
+            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
+          }
+        ],
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the navigation occurs."},
+              "url": {"type": "string"},
+              "processId": {"type": "integer", "description": "The ID of the process runs the renderer for this tab."},
+              "frameId": {"type": "integer", "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."},
+              "transitionType": {"type": "string", "enum": ["link", "typed", "auto_bookmark", "auto_subframe", "manual_subframe", "generated", "start_page", "form_submit", "reload", "keyword", "keyword_generated"], "description": "Cause of the navigation. The same transition types as defined in the history API are used."},
+              "transitionQualifiers": {"type": "array", "description": "A list of transition qualifiers.", "items": {"type": "string", "enum": ["client_redirect", "server_redirect", "forward_back", "from_address_bar"]}},
+              "timeStamp": {"type": "number", "description": "The time when the navigation was committed, in milliseconds since the epoch."}
+            }
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/web_request.json b/chrome/common/extensions/api/web_request.json
new file mode 100644
index 0000000..708931e
--- /dev/null
+++ b/chrome/common/extensions/api/web_request.json
@@ -0,0 +1,542 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "webRequest",
+    "properties": {
+      "MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES": {
+        "value": 20,
+        "description": "The maximum number of times that <code>handlerBehaviorChanged</code> can be called per 10 minute sustained interval. <code>handlerBehaviorChanged</code> is an expensive function call that shouldn't be called often."
+      }
+    },
+    "types": [
+      {
+        "id": "RequestFilter",
+        "type": "object",
+        "description": "An object describing filters to apply to webRequest events.",
+        "properties": {
+          "urls": {
+            "type": "array",
+            "description": "A list of URLs or URL patterns. Requests that cannot match any of the URLs will be filtered out.",
+            "items": { "type": "string" }
+          },
+          "types": {
+            "type": "array",
+            "optional": true,
+            "description": "A list of request types. Requests that cannot match any of the types will be filtered out.",
+            "items": { "type": "string", "enum": ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"] }
+          },
+          "tabId": { "type": "integer", "optional": true },
+          "windowId": { "type": "integer", "optional": true }
+        }
+      },
+      {
+        "id": "HttpHeaders",
+        "nocompile": true,
+        "type": "array",
+        "description": "An array of HTTP headers. Each header is represented as a dictionary containing the keys <code>name</code> and either <code>value</code> or <code>binaryValue</code>.",
+        "items": {
+          "type": "object",
+          "properties": {
+            "name": {"type": "string", "description": "Name of the HTTP header."},
+            "value": {"type": "string", "optional": true, "description": "Value of the HTTP header if it can be represented by UTF-8."},
+            "binaryValue": {
+              "type": "array",
+              "optional": true,
+              "description": "Value of the HTTP header if it cannot be represented by UTF-8, stored as individual byte values (0..255).",
+              "items": {"type": "integer"}
+            }
+          }
+        }
+      },
+      {
+        "id": "BlockingResponse",
+        "nocompile": true,
+        "type": "object",
+        "description": "Returns value for event handlers that have the 'blocking' extraInfoSpec applied. Allows the event handler to modify network requests.",
+        "properties": {
+          "cancel": {
+            "type": "boolean",
+            "optional": true,
+            "description": "If true, the request is cancelled. Used in onBeforeRequest, this prevents the request from being sent."
+          },
+          "redirectUrl": {
+            "type": "string",
+            "optional": true,
+            "description": "Only used as a response to the onBeforeRequest event. If set, the original request is prevented from being sent and is instead redirected to the given URL."
+          },
+          "requestHeaders": {
+            "$ref": "HttpHeaders",
+            "optional": true,
+            "description": "Only used as a response to the onBeforeSendHeaders event. If set, the request is made with these request headers instead."
+          },
+          "responseHeaders": {
+            "$ref": "HttpHeaders",
+            "optional": true,
+            "description": "Only used as a response to the onHeadersReceived event. If set, the server is assumed to have responded with these response headers instead. Only return <code>responseHeaders</code> if you really want to modify the headers in order to limit the number of conflicts (only one extension may modify <code>responseHeaders</code> for each request)."
+          },
+          "authCredentials": {
+            "type": "object",
+            "description": "Only used as a response to the onAuthRequired event. If set, the request is made using the supplied credentials.",
+            "optional": true,
+            "properties": {
+              "username": {"type": "string"},
+              "password": {"type": "string"}
+            }
+          }
+        }
+      },
+      {
+        "id": "UploadData",
+        "type": "object",
+        "properties": {
+          "bytes": {
+            "type": "any",
+            "optional": true,
+            "description": "An ArrayBuffer with a copy of the data."
+          },
+          "file": {
+            "type": "string",
+            "optional": true,
+            "description": "A string with the file's path and name."
+          }
+        },
+        "description": "Contains data uploaded in a URL request."
+      }
+    ],
+    "functions": [
+      {
+        "name": "handlerBehaviorChanged",
+        "type": "function",
+        "description": "Needs to be called when the behavior of the webRequest handlers has changed to prevent incorrect handling due to caching. This function call is expensive. Don't call it often.",
+        "parameters": [
+          {"type": "function", "name": "callback", "optional": true, "parameters": []}
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onBeforeRequest",
+        "type": "function",
+        "description": "Fired when a request is about to occur.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+              "url": {"type": "string"},
+              "method": {"type": "string", "description": "Standard HTTP method."},
+              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "requestBody": {
+                "type": "object",
+                "optional": true,
+                "description": "Contains the HTTP request body data. Experimental feature, only available in DEV or CANARY channels. Only provided if extraInfoSpec contains 'requestBody'.",
+                "properties": {
+                  "error": {"type": "string", "optional": true, "description": "Errors when obtaining request body data."},
+                  "formData": {
+                    "type": "object",
+                    "optional": true,
+                    "description": "If the request method is POST and the body is a sequence of key-value pairs encoded in UTF8, encoded as either multipart/form-data, or application/x-www-form-urlencoded, this dictionary is present and for each key contains the list of all values for that key. If the data is of another media type, or if it is malformed, the dictionary is not present. An example value of this dictionary is {'key': ['value1', 'value2']}.",
+                    "properties": {},
+                    "additionalProperties": {
+                      "type": "array",
+                      "items": { "type": "string" }
+                    }
+                  },
+                  "raw" : {
+                    "type": "array",
+                    "optional": true,
+                    "items": {"$ref": "UploadData"},
+                    "description": "If the request method is PUT or POST, and the body is not already parsed in formData, then the unparsed request body elements are contained in this array."
+                  }
+                }
+              },
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+              "type": {"type": "string", "enum": ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"], "description": "How the requested resource will be used."},
+              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."}
+            }
+          }
+        ],
+        "extraParameters": [
+          {
+            "$ref": "RequestFilter",
+            "name": "filter",
+            "description": "A set of filters that restricts the events that will be sent to this listener."
+          },
+          {
+            "type": "array",
+            "optional": true,
+            "name": "extraInfoSpec",
+            "description": "Array of extra information that should be passed to the listener function.",
+            "items": {
+              "type": "string",
+              "enum": ["blocking", "requestBody"]
+            }
+          }
+        ],
+        "returns": {
+          "$ref": "BlockingResponse",
+          "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
+          "optional": true
+        }
+      },
+      {
+        "name": "onBeforeSendHeaders",
+        "nocompile": true,
+        "type": "function",
+        "description": "Fired before sending an HTTP request, once the request headers are available. This may occur after a TCP connection is made to the server, but before any HTTP data is sent. ",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+              "url": {"type": "string"},
+              "method": {"type": "string", "description": "Standard HTTP method."},
+              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+              "type": {"type": "string", "enum": ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"], "description": "How the requested resource will be used."},
+              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+              "requestHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP request headers that are going to be sent out with this request."}
+            }
+          }
+        ],
+        "extraParameters": [
+          {
+            "$ref": "RequestFilter",
+            "name": "filter",
+            "description": "A set of filters that restricts the events that will be sent to this listener."
+          },
+          {
+            "type": "array",
+            "optional": true,
+            "name": "extraInfoSpec",
+            "description": "Array of extra information that should be passed to the listener function.",
+            "items": {
+              "type": "string",
+              "enum": ["requestHeaders", "blocking"]
+            }
+          }
+        ],
+        "returns": {
+          "$ref": "BlockingResponse",
+          "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
+          "optional": true
+        }
+      },
+      {
+        "name": "onSendHeaders",
+        "nocompile": true,
+        "type": "function",
+        "description": "Fired just before a request is going to be sent to the server (modifications of previous onBeforeSendHeaders callbacks are visible by the time onSendHeaders is fired).",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+              "url": {"type": "string"},
+              "method": {"type": "string", "description": "Standard HTTP method."},
+              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+              "type": {"type": "string", "enum": ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"], "description": "How the requested resource will be used."},
+              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+              "requestHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP request headers that have been sent out with this request."}
+            }
+          }
+        ],
+        "extraParameters": [
+          {
+            "$ref": "RequestFilter",
+            "name": "filter",
+            "description": "A set of filters that restricts the events that will be sent to this listener."
+          },
+          {
+            "type": "array",
+            "optional": true,
+            "name": "extraInfoSpec",
+            "description": "Array of extra information that should be passed to the listener function.",
+            "items": {
+              "type": "string",
+              "enum": ["requestHeaders"]
+            }
+          }
+        ]
+      },
+      {
+        "name": "onHeadersReceived",
+        "nocompile": true,
+        "type": "function",
+        "description": "Fired when HTTP response headers of a request have been received.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "deails",
+            "properties": {
+              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+              "url": {"type": "string"},
+              "method": {"type": "string", "description": "Standard HTTP method."},
+              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+              "type": {"type": "string", "enum": ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"], "description": "How the requested resource will be used."},
+              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+              "statusLine": {"type": "string", "optional": true, "description": "HTTP status line of the response."},
+              "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that have been received with this response."}
+             }
+          }
+        ],
+        "extraParameters": [
+          {
+            "$ref": "RequestFilter",
+            "name": "filter",
+            "description": "A set of filters that restricts the events that will be sent to this listener."
+          },
+          {
+            "type": "array",
+            "optional": true,
+            "name": "extraInfoSpec",
+            "description": "Array of extra information that should be passed to the listener function.",
+            "items": {
+              "type": "string",
+              "enum": ["blocking", "responseHeaders"]
+            }
+          }
+        ],
+        "returns": {
+          "$ref": "BlockingResponse",
+          "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
+          "optional": true
+        }
+      },
+      {
+        "name": "onAuthRequired",
+        "nocompile": true,
+        "type": "function",
+        "description": "Fired when an authentication failure is received. The listener has three options: it can provide authentication credentials, it can cancel the request and display the error page, or it can take no action on the challenge. If bad user credentials are provided, this may be called multiple times for the same request.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+              "url": {"type": "string"},
+              "method": {"type": "string", "description": "Standard HTTP method."},
+              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+              "type": {"type": "string", "enum": ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"], "description": "How the requested resource will be used."},
+              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+              "scheme": {"type": "string", "description": "The authentication scheme, e.g. Basic or Digest."},
+              "realm": {"type": "string", "description": "The authentication realm provided by the server, if there is one.", "optional": true},
+              "challenger": {"type": "object", "description": "The server requesting authentication.", "properties": {"host": {"type": "string"}, "port": {"type": "integer"}}},
+              "isProxy": {"type": "boolean", "description": "True for Proxy-Authenticate, false for WWW-Authenticate."},
+              "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this response."},
+              "statusLine": {"type": "string", "optional": true, "description": "HTTP status line of the response."}
+            }
+          },
+          {
+            "type": "function",
+            "optional": true,
+            "name": "callback",
+            "parameters": [
+              {"name": "response", "$ref": "BlockingResponse"}
+            ]
+          }
+        ],
+        "extraParameters": [
+          {
+            "$ref": "RequestFilter",
+            "name": "filter",
+            "description": "A set of filters that restricts the events that will be sent to this listener."
+          },
+          {
+            "type": "array",
+            "optional": true,
+            "name": "extraInfoSpec",
+            "description": "Array of extra information that should be passed to the listener function.",
+            "items": {
+              "type": "string",
+              "enum": ["responseHeaders", "blocking", "asyncBlocking"]
+            }
+          }
+        ],
+        "returns": {
+          "$ref": "BlockingResponse",
+          "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
+          "optional": true
+        }
+      },
+      {
+        "name": "onResponseStarted",
+        "nocompile": true,
+        "type": "function",
+        "description": "Fired when the first byte of the response body is received. For HTTP requests, this means that the status line and response headers are available.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+              "url": {"type": "string"},
+              "method": {"type": "string", "description": "Standard HTTP method."},
+              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+              "type": {"type": "string", "enum": ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"], "description": "How the requested resource will be used."},
+              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+              "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
+              "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
+              "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."},
+              "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this response."},
+              "statusLine": {"type": "string", "optional": true, "description": "HTTP status line of the response."}
+            }
+          }
+        ],
+        "extraParameters": [
+          {
+            "$ref": "RequestFilter",
+            "name": "filter",
+            "description": "A set of filters that restricts the events that will be sent to this listener."
+          },
+          {
+            "type": "array",
+            "optional": true,
+            "name": "extraInfoSpec",
+            "description": "Array of extra information that should be passed to the listener function.",
+            "items": {
+              "type": "string",
+              "enum": ["responseHeaders"]
+            }
+          }
+        ]
+      },
+      {
+        "name": "onBeforeRedirect",
+        "type": "function",
+        "nocompile": true,
+        "description": "Fired when a server-initiated redirect is about to occur.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+              "url": {"type": "string"},
+              "method": {"type": "string", "description": "Standard HTTP method."},
+              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+              "type": {"type": "string", "enum": ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"], "description": "How the requested resource will be used."},
+              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+              "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
+              "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
+              "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."},
+              "redirectUrl": {"type": "string", "description": "The new URL."},
+              "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this redirect."},
+              "statusLine": {"type": "string", "optional": true, "description": "HTTP status line of the response."}
+            }
+          }
+        ],
+        "extraParameters": [
+          {
+            "$ref": "RequestFilter",
+            "name": "filter",
+            "description": "A set of filters that restricts the events that will be sent to this listener."
+          },
+          {
+            "type": "array",
+            "optional": true,
+            "name": "extraInfoSpec",
+            "description": "Array of extra information that should be passed to the listener function.",
+            "items": {
+              "type": "string",
+              "enum": ["responseHeaders"]
+            }
+          }
+        ]
+      },
+      {
+        "name": "onCompleted",
+        "type": "function",
+        "nocompile": true,
+        "description": "Fired when a request is completed.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+              "url": {"type": "string"},
+              "method": {"type": "string", "description": "Standard HTTP method."},
+              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+              "type": {"type": "string", "enum": ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"], "description": "How the requested resource will be used."},
+              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+              "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
+              "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
+              "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."},
+              "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that were received along with this response."},
+              "statusLine": {"type": "string", "optional": true, "description": "HTTP status line of the response."}
+            }
+          }
+        ],
+        "extraParameters": [
+          {
+            "$ref": "RequestFilter",
+            "name": "filter",
+            "description": "A set of filters that restricts the events that will be sent to this listener."
+          },
+          {
+            "type": "array",
+            "optional": true,
+            "name": "extraInfoSpec",
+            "description": "Array of extra information that should be passed to the listener function.",
+            "items": {
+              "type": "string",
+              "enum": ["responseHeaders"]
+            }
+          }
+        ]
+      },
+      {
+        "name": "onErrorOccurred",
+        "type": "function",
+        "description": "Fired when an error occurs.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "details",
+            "properties": {
+              "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
+              "url": {"type": "string"},
+              "method": {"type": "string", "description": "Standard HTTP method."},
+              "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
+              "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
+              "type": {"type": "string", "enum": ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"], "description": "How the requested resource will be used."},
+              "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
+              "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
+              "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
+              "error": {"type": "string", "description": "The error description. This string is <em>not</em> guaranteed to remain backwards compatible between releases. You must not parse and act based upon its content."}
+            }
+          }
+        ],
+        "extraParameters": [
+          {
+            "$ref": "RequestFilter",
+            "name": "filter",
+            "description": "A set of filters that restricts the events that will be sent to this listener."
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/web_request_internal.json b/chrome/common/extensions/api/web_request_internal.json
new file mode 100644
index 0000000..e401a01
--- /dev/null
+++ b/chrome/common/extensions/api/web_request_internal.json
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "webRequestInternal",
+    "nodoc": true,
+    "internal": true,
+    "functions": [
+      {
+        "name": "addEventListener",
+        "type": "function",
+        "description": "Used internally to implement the special form of addListener for the webRequest events.",
+        "parameters": [
+          {"type": "function", "name": "callback"},
+          {
+            "$ref": "webRequest.RequestFilter",
+            "name": "filter",
+            "description": "A set of filters that restricts the events that will be sent to this listener."
+          },
+          {
+            "type": "array",
+            "optional": true,
+            "name": "extraInfoSpec",
+            "description": "Array of extra information that should be passed to the listener function.",
+            "items": {
+              "type": "string",
+              "enum": ["requestHeaders", "responseHeaders", "blocking", "asyncBlocking", "requestBody"]
+            }
+          },
+          {"type": "string", "name": "eventName"},
+          {"type": "string", "name": "subEventName"}
+        ]
+      },
+      {
+        "name": "eventHandled",
+        "type": "function",
+        "description": "Used internally to send a response for a blocked event.",
+        "parameters": [
+          {"type": "string", "name": "eventName"},
+          {"type": "string", "name": "subEventName"},
+          {"type": "string", "name": "requestId"},
+          {
+            "$ref": "webRequest.BlockingResponse",
+            "optional": true,
+            "name": "response"
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/web_socket_proxy_private.json b/chrome/common/extensions/api/web_socket_proxy_private.json
new file mode 100644
index 0000000..4afa792
--- /dev/null
+++ b/chrome/common/extensions/api/web_socket_proxy_private.json
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "webSocketProxyPrivate",
+    "nodoc": true,
+    "types": [],
+    "functions": [
+      {
+        "name": "getPassportForTCP",
+        "description": "requests authorization token for websocket to TCP proxy.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "hostname",
+            "minLength": 1,
+            "description": "hostname to which TCP connection is requested."
+          },
+          {
+            "type": "integer",
+            "name": "port",
+            "minimum": 1,
+            "maximum": 65535,
+            "description": "TCP port number."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "type": "string",
+                "name": "passport",
+                "description": "Passport for passing to proxy."
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getURLForTCP",
+        "description": "requests specific websocket URL that can be used as TCP proxy.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "hostname",
+            "minLength": 1,
+            "description": "hostname to which TCP connection is requested."
+          },
+          {
+            "type": "integer",
+            "name": "port",
+            "minimum": 1,
+            "maximum": 65535,
+            "description": "TCP port number."
+          },
+          {
+            "type": "object",
+            "name": "details",
+            "description": "Dictionary which contains requested parameters of connection",
+            "properties": {
+              "tls": {"type": "boolean", "optional": "true", "description": "whether TLS over TCP is requested"}
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "type": "string",
+                "name": "url",
+                "description": "URL for opening as WebSocket."
+              }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": []
+  }
+]
diff --git a/chrome/common/extensions/api/webstore.json b/chrome/common/extensions/api/webstore.json
new file mode 100644
index 0000000..0252a35
--- /dev/null
+++ b/chrome/common/extensions/api/webstore.json
@@ -0,0 +1,45 @@
+// Copyright (c) 2012 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.
+
+[
+  { 
+    "namespace": "webstore",
+    "unprivileged": true,
+    "matches": [ "http://*/*", "https://*/*" ],
+    "functions": [
+      {
+        "name": "install",
+        "allowAmbiguousOptionalArguments": true,
+        "parameters": [
+          {
+            "name": "url",
+            "type": "string",
+            "optional": true,
+            "description": "If you have more than one <code>&lt;link&gt;</code> tag on your page with the <code>chrome-webstore-item</code> relation, you can choose which item you'd like to install by passing in its URL here. If it is omitted, then the first (or only) link will be used. An exception will be thrown if the passed in URL does not exist on the page."
+          },
+          {
+            "name": "successCallback",
+            "type": "function",
+            "optional": true,
+            "parameters": [],
+            "description": "This function is invoked when inline installation successfully completes (after the dialog is shown and the user agrees to add the item to Chrome). You may wish to use this to hide the user interface element that prompted the user to install the app or extension."
+          },
+          {
+            "name": "failureCallback",
+            "type": "function",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "error",
+                "type": "string",
+                "description": "The failure detail. You may wish to inspect or log this for debugging purposes, but you should not rely on specific strings being passed back."
+              }
+            ],
+            "description": "This function is invoked when inline installation does not successfully complete. Possible reasons for this include the user canceling the dialog, the linked item not being found in the store, or the install being initiated from a non-verified site."
+          }
+        ]
+      }  // install
+    ]
+  }  // webstore
+]
diff --git a/chrome/common/extensions/api/webstore_private.json b/chrome/common/extensions/api/webstore_private.json
new file mode 100644
index 0000000..c128edc
--- /dev/null
+++ b/chrome/common/extensions/api/webstore_private.json
@@ -0,0 +1,239 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace":"webstorePrivate",
+    "nodoc": "true",
+    "functions": [
+      {
+        "name": "install",
+        "description": "Installs the extension corresponding to the given id",
+        "parameters": [
+          {
+            "name": "expected_id",
+            "type": "string",
+            "description": "The id of the extension to install."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": "true",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "installBundle",
+        "description": "Initiates the install process for the given bundle of extensions.",
+        "parameters": [
+          {
+            "name": "details",
+            "description": "An array of extension details to be installed.",
+            "type": "array",
+            "items": {
+              "type": "object",
+              "properties": {
+                "id": {
+                  "type": "string",
+                  "description": "The id of the extension to be installed.",
+                  "minLength": 32,
+                  "maxLength": 32
+                },
+                "manifest": {
+                  "type": "string",
+                  "description": "A string with the contents of the extension's manifest.json file. During the install process, the browser will check that the downloaded extension's manifest matches what was passed in here.",
+                  "minLength": 1
+                },
+                "localizedName": {
+                  "type": "string",
+                  "description": "A string to use instead of the raw value of the 'name' key from manifest.json."
+                }
+              }
+            }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the install process completes. Upon failures, chrome.extension.lastError will be set to 'user_canceled' or 'unknown_error'.",
+            "optional": "true",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "beginInstallWithManifest3",
+        "description": "Initiates the install process for the given extension.",
+        "parameters": [
+          {
+            "name": "details",
+            "type": "object",
+            "properties": {
+              "id": {
+                "type": "string",
+                "description": "The id of the extension to be installled.",
+                "minLength": 32,
+                "maxLength": 32
+              },
+              "manifest": {
+                "type": "string",
+                "description": "A string with the contents of the extension's manifest.json file. During the install process, the browser will check that the downloaded extension's manifest matches what was passed in here.",
+                "minLength": 1
+              },
+              "iconUrl": {
+                "type": "string",
+                "optional": true,
+                "desciption": "A URL for the image to display in the confirmation dialog"
+              },
+              "iconData": {
+                "type": "string",
+                "optional": true,
+                "description": "An icon as a base64-encoded image, displayed in a confirmation dialog."
+              },
+              "localizedName": {
+                "type": "string",
+                "optional": true,
+                "description": "A string to use instead of the raw value of the 'name' key from manifest.json."
+              },
+              "locale": {
+                "type": "string",
+                "optional": true,
+                "description": "The name of the locale used for generating localizedName. This should be the name of one of the directories in the _locales folder of the extension, or the default_locale setting from the manifest."
+              },
+              "appInstallBubble": {
+                "type": "boolean",
+                "optional": true,
+                "description": "A flag to change the UI we show when an app is installed - a value of true means to show a bubble pointing at the new tab button (instead of the default behavior of opening the new tab page and animating the app icon)."
+              }
+            },
+            "additionalProperties": { "type": "any" }
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "description": "Called when the user has either accepted/rejected the dialog, or some error occurred (such as invalid manifest or icon image data).",
+            "optional": "true",
+            "parameters": [
+              {
+                "name": "result",
+                "type": "string",
+                "description": "A string result code, which will be empty upon success. The possible values in the case of errors include 'unknown_error', 'user_cancelled', 'manifest_error', 'icon_error', 'invalid_id', 'permission_denied', and 'invalid_icon_url'."
+              }
+            ]
+          }
+        ]
+
+      },
+      {
+        "name": "completeInstall",
+        "description": "",
+        "parameters": [
+          {
+            "name": "expected_id",
+            "type": "string",
+            "description": "The id of the extension to be installed. This should match a previous call to beginInstallWithManifest3."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": "true",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "getBrowserLogin",
+        "description": "Returns the logged-in sync user login if there is one, or the empty string otherwise.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": "false",
+            "parameters": [
+              {
+                "name": "info",
+                "type": "object",
+                "properties": {
+                  "login": { "type": "string" },
+                  "token": { "type": "string", "optional": true }
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getStoreLogin",
+        "description": "Returns the previous value set by setStoreLogin, or the empty string if there is none.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": "false",
+            "parameters": [
+              { "name": "login", "type": "string" }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "setStoreLogin",
+        "description": "Sets a preference value with the store login.",
+        "parameters": [
+          { "name": "login", "type": "string" },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": "true",
+            "parameters": []
+          }
+        ]
+      },
+      {
+        "name": "promptBrowserLogin",
+        "description": "Causes the browser to bring up the browser login UI.",
+        "parameters": [
+          {
+            "name": "preferred_email",
+            "type": "string",
+            "description": "The email address to use to pre-populate the login dialog (can be an empty string)."
+          },
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": "true",
+            "parameters": [
+              {
+                "name": "info",
+                "type": "object",
+                "properties": {
+                  "login": { "type": "string", "optional": true },
+                  "token": { "type": "string", "optional": true }
+                }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getWebGLStatus",
+        "description": "Invokes a callback that returns whether WebGL is blacklisted or not.",
+        "parameters": [
+          {
+            "name": "callback",
+            "type": "function",
+            "optional": "false",
+            "parameters": [
+              {
+                "name": "webgl_status",
+                "type": "string",
+                "enum": ["webgl_allowed", "webgl_blocked"]
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/api/windows.json b/chrome/common/extensions/api/windows.json
new file mode 100644
index 0000000..dbfe080
--- /dev/null
+++ b/chrome/common/extensions/api/windows.json
@@ -0,0 +1,272 @@
+// Copyright (c) 2012 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.
+
+[
+  {
+    "namespace": "windows",
+    "dependencies": [ "tabs" ],
+    "types": [
+      {
+        "id": "Window",
+        "type": "object",
+        "properties": {
+          "id": {"type": "integer", "minimum": 0, "description": "The ID of the window. Window IDs are unique within a browser session."},
+          "focused": {"type": "boolean", "description": "Whether the window is currently the focused window."},
+          "top": {"type": "integer", "description": "The offset of the window from the top edge of the screen in pixels."},
+          "left": {"type": "integer", "description": "The offset of the window from the left edge of the screen in pixels."},
+          "width": {"type": "integer", "description": "The width of the window in pixels."},
+          "height": {"type": "integer", "description": "The height of the window in pixels."},
+          "tabs": {"type": "array", "items": { "$ref": "tabs.Tab" }, "optional": true, "description": "Array of $ref:tabs.Tab objects representing the current tabs in the window."},
+          "incognito": {"type": "boolean", "description": "Whether the window is incognito."},
+          "type": {
+            "type": "string",
+            "description": "The type of browser window this is.",
+            "enum": ["normal", "popup", "panel", "app"]
+          },
+          "state": {
+            "type": "string",
+            "description": "The state of this browser window.",
+            "enum": ["normal", "minimized", "maximized", "fullscreen"]
+          },
+          "alwaysOnTop": {"type": "boolean", "description": "Whether the window is set to be always on top."}
+        }
+      }
+    ],
+    "properties": {
+      "WINDOW_ID_NONE": {
+        "value": -1,
+        "description": "The windowId value that represents the absence of a chrome browser window."
+      },
+      "WINDOW_ID_CURRENT": {
+        "value": -2,
+        "description": "The windowId value that represents the <a href='windows.html#current-window'>current window</a>."
+      }
+    },
+    "functions": [
+      {
+        "name": "get",
+        "type": "function",
+        "description": "Gets details about a window.",
+        "parameters": [
+          {"type": "integer", "name": "windowId", "minimum": -2},
+          {
+            "type": "object",
+            "name": "getInfo",
+            "optional": true,
+            "description": "",
+            "properties": {
+              "populate": {"type": "boolean", "optional": true, "description": "If true, the window object will have a <var>tabs</var> property that contains a list of the $ref:tabs.Tab objects" }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "window", "$ref": "Window"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getCurrent",
+        "type": "function",
+        "description": "Gets the <a href='#current-window'>current window</a>.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "getInfo",
+            "optional": true,
+            "description": "",
+            "properties": {
+              "populate": {"type": "boolean", "optional": true, "description": "If true, the window object will have a <var>tabs</var> property that contains a list of the $ref:tabs.Tab objects" }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "window", "$ref": "Window"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getLastFocused",
+        "type": "function",
+        "description": "Gets the window that was most recently focused &mdash; typically the window 'on top'.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "getInfo",
+            "optional": true,
+            "description": "",
+            "properties": {
+              "populate": {"type": "boolean", "optional": true, "description": "If true, the window object will have a <var>tabs</var> property that contains a list of the $ref:tabs.Tab objects" }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "window", "$ref": "Window"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "getAll",
+        "type": "function",
+        "description": "Gets all windows.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "getInfo",
+            "optional": true,
+            "description": "",
+            "properties": {
+              "populate": {"type": "boolean", "optional": true, "description": "If true, each window object will have a <var>tabs</var> property that contains a list of the $ref:tabs.Tab objects for that window." }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "name": "windows", "type": "array", "items": { "$ref": "Window" }
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "create",
+        "type": "function",
+        "description": "Creates (opens) a new browser with any optional sizing, position or default URL provided.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "createData",
+            "properties": {
+              "url": {
+                "type": "string",
+                "description": "A URL or list of URLs to open as tabs in the window. Fully-qualified URLs must include a scheme (i.e. 'http://www.google.com', not 'www.google.com'). Relative URLs will be relative to the current page within the extension. Defaults to the New Tab Page.",
+                "optional": true,
+                "choices": [
+                  {"type": "string"},
+                  {"type": "array", "items": {"type": "string"}}
+                ]
+              },
+              "tabId": {"type": "integer", "minimum": 0, "optional": true, "description": "The id of the tab for which you want to adopt to the new window."},
+              "left": {"type": "integer", "optional": true, "description": "The number of pixels to position the new window from the left edge of the screen. If not specified, the new window is offset naturally from the last focused window. This value is ignored for panels."},
+              "top": {"type": "integer", "optional": true, "description": "The number of pixels to position the new window from the top edge of the screen. If not specified, the new window is offset naturally from the last focused window. This value is ignored for panels."},
+              "width": {"type": "integer", "minimum": 0, "optional": true, "description": "The width in pixels of the new window. If not specified defaults to a natural width."},
+              "height": {"type": "integer", "minimum": 0, "optional": true, "description": "The height in pixels of the new window. If not specified defaults to a natural height."},
+              "focused": {"type": "boolean", "optional": true, "description": "If true, opens an active window. If false, opens an inactive window."},
+              "incognito": {"type": "boolean", "optional": true, "description": "Whether the new window should be an incognito window."},
+              "type": {
+                "type": "string",
+                "optional": true,
+                "description": "Specifies what type of browser window to create. The 'panel' and 'detached_panel' types create a popup unless the '--enable-panels' flag is set.",
+                "enum": ["normal", "popup", "panel", "detached_panel"]
+              }
+            },
+            "optional": true
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "window", "$ref": "Window", "description": "Contains details about the created window.",
+                "optional": true
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "update",
+        "type": "function",
+        "description": "Updates the properties of a window. Specify only the properties that you want to change; unspecified properties will be left unchanged.",
+        "parameters": [
+          {"type": "integer", "name": "windowId", "minimum": -2},
+          {
+            "type": "object",
+            "name": "updateInfo",
+            "properties": {
+              "left": {"type": "integer", "optional": true, "description": "The offset from the left edge of the screen to move the window to in pixels. This value is ignored for panels."},
+              "top": {"type": "integer", "optional": true, "description": "The offset from the top edge of the screen to move the window to in pixels. This value is ignored for panels."},
+              "width": {"type": "integer", "minimum": 0, "optional": true, "description": "The width to resize the window to in pixels. This value is ignored for panels."},
+              "height": {"type": "integer", "minimum": 0, "optional": true, "description": "The height to resize the window to in pixels. This value is ignored for panels."},
+              "focused": {"type": "boolean", "optional": true, "description": "If true, brings the window to the front. If false, brings the next window in the z-order to the front."},
+              "drawAttention": {"type": "boolean", "optional": true, "description": "If true, causes the window to be displayed in a manner that draws the user's attention to the window, without changing the focused window. The effect lasts until the user changes focus to the window. This option has no effect if the window already has focus. Set to false to cancel a previous draw attention request."},
+              "state": {
+                "type": "string",
+                "optional": true,
+                "description": "The new state of the window. The 'minimized', 'maximized' and 'fullscreen' states cannot be combined with 'left', 'top', 'width' or 'height'.",
+                "enum": ["normal", "minimized", "maximized", "fullscreen"]
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": [
+              {
+                "name": "window", "$ref": "Window"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "remove",
+        "type": "function",
+        "description": "Removes (closes) a window, and all the tabs inside it.",
+        "parameters": [
+          {"type": "integer", "name": "windowId", "minimum": 0},
+          {"type": "function", "name": "callback", "optional": true, "parameters": []}
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "onCreated",
+        "type": "function",
+        "description": "Fired when a window is created.",
+        "parameters": [
+          {
+            "$ref": "Window",
+            "name": "window",
+            "description": "Details of the window that was created."
+          }
+        ]
+      },
+      {
+        "name": "onRemoved",
+        "type": "function",
+        "description": "Fired when a window is removed (closed).",
+        "parameters": [
+          {"type": "integer", "name": "windowId", "minimum": 0, "description": "ID of the removed window."}
+        ]
+      },
+      {
+        "name": "onFocusChanged",
+        "type": "function",
+        "description": "Fired when the currently focused window changes. Will be chrome.windows.WINDOW_ID_NONE if all chrome windows have lost focus. Note: On some Linux window managers, WINDOW_ID_NONE will always be sent immediately preceding a switch from one chrome window to another.",
+        "parameters": [
+          {"type": "integer", "name": "windowId", "minimum": -1, "description": "ID of the newly focused window."}
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/command.cc b/chrome/common/extensions/command.cc
new file mode 100644
index 0000000..1b2ec9d
--- /dev/null
+++ b/chrome/common/extensions/command.cc
@@ -0,0 +1,350 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/command.h"
+
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace errors = extension_manifest_errors;
+namespace keys = extension_manifest_keys;
+namespace values = extension_manifest_values;
+
+namespace {
+
+static const char kMissing[] = "Missing";
+
+static const char kCommandKeyNotSupported[] =
+    "Command key is not supported. Note: Ctrl means Command on Mac";
+
+
+ui::Accelerator ParseImpl(const std::string& accelerator,
+                          const std::string& platform_key,
+                          int index,
+                          string16* error) {
+  if (platform_key != values::kKeybindingPlatformWin &&
+      platform_key != values::kKeybindingPlatformMac &&
+      platform_key != values::kKeybindingPlatformChromeOs &&
+      platform_key != values::kKeybindingPlatformLinux &&
+      platform_key != values::kKeybindingPlatformDefault) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidKeyBindingUnknownPlatform,
+        base::IntToString(index),
+        platform_key);
+    return ui::Accelerator();
+  }
+
+  std::vector<std::string> tokens;
+  base::SplitString(accelerator, '+', &tokens);
+  if (tokens.size() < 2 || tokens.size() > 3) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidKeyBinding,
+        base::IntToString(index),
+        platform_key,
+        accelerator);
+    return ui::Accelerator();
+  }
+
+  // Now, parse it into an accelerator.
+  int modifiers = ui::EF_NONE;
+  ui::KeyboardCode key = ui::VKEY_UNKNOWN;
+  for (size_t i = 0; i < tokens.size(); i++) {
+    if (tokens[i] == "Ctrl") {
+      modifiers |= ui::EF_CONTROL_DOWN;
+    } else if (tokens[i] == "Command") {
+      if (platform_key == "mac") {
+        // Either the developer specified Command+foo in the manifest for Mac or
+        // they specified Ctrl and it got normalized to Command (to get Ctrl on
+        // Mac the developer has to specify MacCtrl). Therefore we treat this
+        // as Command.
+        modifiers |= ui::EF_COMMAND_DOWN;
+#if defined(OS_MACOSX)
+      } else if (platform_key == "default") {
+        // If we see "Command+foo" in the Default section it can mean two
+        // things, depending on the platform:
+        // The developer specified "Ctrl+foo" for Default and it got normalized
+        // on Mac to "Command+foo". This is fine. Treat it as Command.
+        modifiers |= ui::EF_COMMAND_DOWN;
+#endif
+      } else {
+        // No other platform supports Command.
+        key = ui::VKEY_UNKNOWN;
+        break;
+      }
+    } else if (tokens[i] == "Alt") {
+      modifiers |= ui::EF_ALT_DOWN;
+    } else if (tokens[i] == "Shift") {
+      modifiers |= ui::EF_SHIFT_DOWN;
+    } else if (tokens[i].size() == 1) {
+      if (key != ui::VKEY_UNKNOWN) {
+        // Multiple key assignments.
+        key = ui::VKEY_UNKNOWN;
+        break;
+      }
+      if (tokens[i][0] >= 'A' && tokens[i][0] <= 'Z') {
+        key = static_cast<ui::KeyboardCode>(ui::VKEY_A + (tokens[i][0] - 'A'));
+      } else if (tokens[i][0] >= '0' && tokens[i][0] <= '9') {
+        key = static_cast<ui::KeyboardCode>(ui::VKEY_0 + (tokens[i][0] - '0'));
+      } else {
+        key = ui::VKEY_UNKNOWN;
+        break;
+      }
+    } else {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidKeyBinding,
+          base::IntToString(index),
+          platform_key,
+          accelerator);
+      return ui::Accelerator();
+    }
+  }
+  bool command = (modifiers & ui::EF_COMMAND_DOWN) != 0;
+  bool ctrl = (modifiers & ui::EF_CONTROL_DOWN) != 0;
+  bool alt = (modifiers & ui::EF_ALT_DOWN) != 0;
+  bool shift = (modifiers & ui::EF_SHIFT_DOWN) != 0;
+
+  // We support Ctrl+foo, Alt+foo, Ctrl+Shift+foo, Alt+Shift+foo, but not
+  // Ctrl+Alt+foo and not Shift+foo either. For a more detailed reason why we
+  // don't support Ctrl+Alt+foo see this article:
+  // http://blogs.msdn.com/b/oldnewthing/archive/2004/03/29/101121.aspx.
+  // On Mac Command can also be used in combination with Shift or on its own,
+  // as a modifier.
+  if (key == ui::VKEY_UNKNOWN || (ctrl && alt) || (command && alt) ||
+      (shift && !ctrl && !alt && !command)) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidKeyBinding,
+        base::IntToString(index),
+        platform_key,
+        accelerator);
+    return ui::Accelerator();
+  }
+
+  return ui::Accelerator(key, modifiers);
+}
+
+// For Mac, we convert "Ctrl" to "Command" and "MacCtrl" to "Ctrl". Other
+// platforms leave the shortcut untouched.
+std::string NormalizeShortcutSuggestion(const std::string& suggestion,
+                                        const std::string& platform) {
+  bool normalize = false;
+  if (platform == "mac") {
+    normalize = true;
+  } else if (platform == "default") {
+#if defined(OS_MACOSX)
+    normalize = true;
+#endif
+  }
+
+  if (!normalize)
+    return suggestion;
+
+  std::string key;
+  std::vector<std::string> tokens;
+  base::SplitString(suggestion, '+', &tokens);
+  for (size_t i = 0; i < tokens.size(); i++) {
+    if (tokens[i] == "Ctrl")
+      tokens[i] = "Command";
+    else if (tokens[i] == "MacCtrl")
+      tokens[i] = "Ctrl";
+  }
+  return JoinString(tokens, '+');
+}
+
+}  // namespace
+
+namespace extensions {
+
+Command::Command() {}
+
+Command::Command(const std::string& command_name,
+                 const string16& description,
+                 const std::string& accelerator)
+    : command_name_(command_name),
+      description_(description) {
+  string16 error;
+  accelerator_ = ParseImpl(accelerator, CommandPlatform(), 0, &error);
+}
+
+Command::~Command() {}
+
+// static
+std::string Command::CommandPlatform() {
+#if defined(OS_WIN)
+  return values::kKeybindingPlatformWin;
+#elif defined(OS_MACOSX)
+  return values::kKeybindingPlatformMac;
+#elif defined(OS_CHROMEOS)
+  return values::kKeybindingPlatformChromeOs;
+#elif defined(OS_LINUX)
+  return values::kKeybindingPlatformLinux;
+#else
+  return "";
+#endif
+}
+
+// static
+ui::Accelerator Command::StringToAccelerator(const std::string& accelerator) {
+  string16 error;
+  Command command;
+  ui::Accelerator parsed =
+      ParseImpl(accelerator, Command::CommandPlatform(), 0, &error);
+  return parsed;
+}
+
+bool Command::Parse(DictionaryValue* command,
+                    const std::string& command_name,
+                    int index,
+                    string16* error) {
+  DCHECK(!command_name.empty());
+
+  // We'll build up a map of platform-to-shortcut suggestions.
+  typedef std::map<const std::string, std::string> SuggestionMap;
+  SuggestionMap suggestions;
+
+  // First try to parse the |suggested_key| as a dictionary.
+  DictionaryValue* suggested_key_dict;
+  if (command->GetDictionary(keys::kSuggestedKey, &suggested_key_dict)) {
+    DictionaryValue::key_iterator iter = suggested_key_dict->begin_keys();
+    for ( ; iter != suggested_key_dict->end_keys(); ++iter) {
+      // For each item in the dictionary, extract the platforms specified.
+      std::string suggested_key_string;
+      if (suggested_key_dict->GetString(*iter, &suggested_key_string) &&
+          !suggested_key_string.empty()) {
+        // Found a platform, add it to the suggestions list.
+        suggestions[*iter] = suggested_key_string;
+      } else {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidKeyBinding,
+            base::IntToString(index),
+            keys::kSuggestedKey,
+            kMissing);
+        return false;
+      }
+    }
+  } else {
+    // No dictionary was found, fall back to using just a string, so developers
+    // don't have to specify a dictionary if they just want to use one default
+    // for all platforms.
+    std::string suggested_key_string;
+    if (command->GetString(keys::kSuggestedKey, &suggested_key_string) &&
+        !suggested_key_string.empty()) {
+      // If only a single string is provided, it must be default for all.
+      suggestions["default"] = suggested_key_string;
+    } else {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidKeyBinding,
+          base::IntToString(index),
+          keys::kSuggestedKey,
+          kMissing);
+       return false;
+    }
+  }
+
+  // Normalize the suggestions.
+  for (SuggestionMap::iterator iter = suggestions.begin();
+       iter != suggestions.end(); ++iter) {
+    // Before we normalize Ctrl to Command we must detect when the developer
+    // specified Command in the Default section, which will work on Mac after
+    // normalization but only fail on other platforms when they try it out on
+    // other platforms, which is not what we want.
+    if (iter->first == "default" &&
+        iter->second.find("Command+") != std::string::npos) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidKeyBinding,
+          base::IntToString(index),
+          keys::kSuggestedKey,
+          kCommandKeyNotSupported);
+      return false;
+    }
+
+    suggestions[iter->first] = NormalizeShortcutSuggestion(iter->second,
+                                                           iter->first);
+  }
+
+  std::string platform = CommandPlatform();
+  std::string key = platform;
+  if (suggestions.find(key) == suggestions.end())
+    key = values::kKeybindingPlatformDefault;
+  if (suggestions.find(key) == suggestions.end()) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidKeyBindingMissingPlatform,
+        base::IntToString(index),
+        keys::kSuggestedKey,
+        platform);
+    return false;  // No platform specified and no fallback. Bail.
+  }
+
+  // For developer convenience, we parse all the suggestions (and complain about
+  // errors for platforms other than the current one) but use only what we need.
+  std::map<const std::string, std::string>::const_iterator iter =
+      suggestions.begin();
+  for ( ; iter != suggestions.end(); ++iter) {
+    // Note that we pass iter->first to pretend we are on a platform we're not
+    // on.
+    ui::Accelerator accelerator =
+        ParseImpl(iter->second, iter->first, index, error);
+    if (accelerator.key_code() == ui::VKEY_UNKNOWN) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidKeyBinding,
+          base::IntToString(index),
+          iter->first,
+          iter->second);
+      return false;
+    }
+
+    if (iter->first == key) {
+      // This platform is our platform, so grab this key.
+      accelerator_ = accelerator;
+      command_name_ = command_name;
+
+      if (command_name !=
+              extension_manifest_values::kPageActionCommandEvent &&
+          command_name !=
+              extension_manifest_values::kBrowserActionCommandEvent &&
+          command_name !=
+              extension_manifest_values::kScriptBadgeCommandEvent) {
+        if (!command->GetString(keys::kDescription, &description_) ||
+            description_.empty()) {
+          *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+              errors::kInvalidKeyBindingDescription,
+              base::IntToString(index));
+          return false;
+        }
+      }
+    }
+  }
+  return true;
+}
+
+DictionaryValue* Command::ToValue(const Extension* extension,
+                                  bool active) const {
+  DictionaryValue* extension_data = new DictionaryValue();
+
+  string16 command_description;
+  if (command_name() == values::kBrowserActionCommandEvent ||
+      command_name() == values::kPageActionCommandEvent ||
+      command_name() == values::kScriptBadgeCommandEvent) {
+    command_description =
+        l10n_util::GetStringUTF16(IDS_EXTENSION_COMMANDS_GENERIC_ACTIVATE);
+  } else {
+    command_description = description();
+  }
+  extension_data->SetString("description", command_description);
+  extension_data->SetBoolean("active", active);
+  extension_data->SetString("keybinding", accelerator().GetShortcutText());
+  extension_data->SetString("command_name", command_name());
+  extension_data->SetString("extension_id", extension->id());
+
+  return extension_data;
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/command.h b/chrome/common/extensions/command.h
new file mode 100644
index 0000000..ee81e11
--- /dev/null
+++ b/chrome/common/extensions/command.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_COMMAND_H_
+#define CHROME_COMMON_EXTENSIONS_COMMAND_H_
+
+#include <string>
+#include <map>
+
+#include "base/string16.h"
+#include "ui/base/accelerators/accelerator.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+class Extension;
+}
+
+namespace extensions {
+
+class Command {
+ public:
+  Command();
+  Command(const std::string& command_name,
+          const string16& description,
+          const std::string& accelerator);
+  ~Command();
+
+  // The platform value for the Command.
+  static std::string CommandPlatform();
+
+  // Parse a string as an accelerator. If the accelerator is unparsable then
+  // a generic ui::Accelerator object will be returns (with key_code Unknown).
+  static ui::Accelerator StringToAccelerator(const std::string& accelerator);
+
+  // Parse the command.
+  bool Parse(base::DictionaryValue* command,
+             const std::string& command_name,
+             int index,
+             string16* error);
+
+  // Convert a Command object from |extension| to a DictionaryValue.
+  // |active| specifies whether the command is active or not.
+  base::DictionaryValue* ToValue(
+      const Extension* extension, bool active) const;
+
+  // Accessors:
+  const std::string& command_name() const { return command_name_; }
+  const ui::Accelerator& accelerator() const { return accelerator_; }
+  const string16& description() const { return description_; }
+
+  // Setter:
+  void set_accelerator(ui::Accelerator accelerator) {
+    accelerator_ = accelerator;
+  }
+
+ private:
+  std::string command_name_;
+  ui::Accelerator accelerator_;
+  string16 description_;
+};
+
+// A mapping of command name (std::string) to a command object.
+typedef std::map<std::string, Command> CommandMap;
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_COMMAND_H_
diff --git a/chrome/common/extensions/command_unittest.cc b/chrome/common/extensions/command_unittest.cc
new file mode 100644
index 0000000..15cb88c
--- /dev/null
+++ b/chrome/common/extensions/command_unittest.cc
@@ -0,0 +1,206 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/command.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class CommandTest : public testing::Test {
+};
+
+TEST(CommandTest, ExtensionCommandParsing) {
+  const ui::Accelerator none = ui::Accelerator();
+  const ui::Accelerator shift_f = ui::Accelerator(ui::VKEY_F,
+                                                  ui::EF_SHIFT_DOWN);
+#if defined(OS_MACOSX)
+  int ctrl = ui::EF_COMMAND_DOWN;
+#else
+  int ctrl = ui::EF_CONTROL_DOWN;
+#endif
+
+  const ui::Accelerator ctrl_f = ui::Accelerator(ui::VKEY_F, ctrl);
+  const ui::Accelerator alt_f = ui::Accelerator(ui::VKEY_F, ui::EF_ALT_DOWN);
+  const ui::Accelerator ctrl_shift_f =
+      ui::Accelerator(ui::VKEY_F, ctrl | ui::EF_SHIFT_DOWN);
+  const ui::Accelerator alt_shift_f =
+      ui::Accelerator(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN);
+  const ui::Accelerator ctrl_1 = ui::Accelerator(ui::VKEY_1, ctrl);
+
+  const struct {
+    bool expected_result;
+    ui::Accelerator accelerator;
+    const char* command_name;
+    const char* key;
+    const char* description;
+  } kTests[] = {
+    // Negative test (one or more missing required fields). We don't need to
+    // test |command_name| being blank as it is used as a key in the manifest,
+    // so it can't be blank (and we CHECK() when it is).
+    { false, none, "command", "",       "" },
+    { false, none, "command", "Ctrl+f", "" },
+    { false, none, "command", "",       "description" },
+    // Ctrl+Alt is not permitted, see MSDN link in comments in Parse function.
+    { false, none, "command", "Ctrl+Alt+F", "description" },
+    // Unsupported shortcuts/too many, or missing modifier.
+    { false, none, "command", "A",                "description" },
+    { false, none, "command", "F10",              "description" },
+    { false, none, "command", "Ctrl+F+G",         "description" },
+    { false, none, "command", "Ctrl+Alt+Shift+G", "description" },
+    // Shift on its own is not supported.
+    { false, shift_f, "command", "Shift+F",       "description" },
+    { false, shift_f, "command", "F+Shift",       "description" },
+    // Basic tests.
+    { true, ctrl_f,       "command", "Ctrl+F",       "description" },
+    { true, alt_f,        "command", "Alt+F",        "description" },
+    { true, ctrl_shift_f, "command", "Ctrl+Shift+F", "description" },
+    { true, alt_shift_f,  "command", "Alt+Shift+F",  "description" },
+    { true, ctrl_1,       "command", "Ctrl+1",       "description" },
+    // Shortcut token order tests.
+    { true, ctrl_f,       "command", "F+Ctrl",       "description" },
+    { true, alt_f,        "command", "F+Alt",        "description" },
+    { true, ctrl_shift_f, "command", "F+Ctrl+Shift", "description" },
+    { true, ctrl_shift_f, "command", "F+Shift+Ctrl", "description" },
+    { true, alt_shift_f,  "command", "F+Alt+Shift",  "description" },
+    { true, alt_shift_f,  "command", "F+Shift+Alt",  "description" },
+    // Case insensitivity is not OK.
+    { false, ctrl_f, "command", "Ctrl+f", "description" },
+    { false, ctrl_f, "command", "cTrL+F", "description" },
+    // Skipping description is OK for browser- and pageActions.
+    { true, ctrl_f, "_execute_browser_action", "Ctrl+F", "" },
+    { true, ctrl_f, "_execute_page_action",    "Ctrl+F", "" },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) {
+    // First parse the command as a simple string.
+    scoped_ptr<DictionaryValue> input(new DictionaryValue);
+    input->SetString("suggested_key", kTests[i].key);
+    input->SetString("description", kTests[i].description);
+
+    SCOPED_TRACE(std::string("Command name: |") + kTests[i].command_name +
+                 "| key: |" + kTests[i].key +
+                 "| description: |" + kTests[i].description +
+                 "| index: " + base::IntToString(i));
+
+    extensions::Command command;
+    string16 error;
+    bool result =
+        command.Parse(input.get(), kTests[i].command_name, i, &error);
+
+    EXPECT_EQ(kTests[i].expected_result, result);
+    if (result) {
+      EXPECT_STREQ(kTests[i].description,
+                   UTF16ToASCII(command.description()).c_str());
+      EXPECT_STREQ(kTests[i].command_name, command.command_name().c_str());
+      EXPECT_EQ(kTests[i].accelerator, command.accelerator());
+    }
+
+    // Now parse the command as a dictionary of multiple values.
+    input.reset(new DictionaryValue);
+    DictionaryValue* key_dict = new DictionaryValue();
+    key_dict->SetString("default", kTests[i].key);
+    key_dict->SetString("windows", kTests[i].key);
+    key_dict->SetString("mac", kTests[i].key);
+    input->Set("suggested_key", key_dict);
+    input->SetString("description", kTests[i].description);
+
+    result = command.Parse(input.get(), kTests[i].command_name, i, &error);
+
+    EXPECT_EQ(kTests[i].expected_result, result);
+    if (result) {
+      EXPECT_STREQ(kTests[i].description,
+                   UTF16ToASCII(command.description()).c_str());
+      EXPECT_STREQ(kTests[i].command_name, command.command_name().c_str());
+      EXPECT_EQ(kTests[i].accelerator, command.accelerator());
+    }
+  }
+}
+
+TEST(CommandTest, ExtensionCommandParsingFallback) {
+  std::string description = "desc";
+  std::string command_name = "foo";
+
+  // Test that platform specific keys are honored on each platform, despite
+  // fallback being given.
+  scoped_ptr<DictionaryValue> input(new DictionaryValue);
+  DictionaryValue* key_dict = new DictionaryValue();
+  key_dict->SetString("default",  "Ctrl+Shift+D");
+  key_dict->SetString("windows",  "Ctrl+Shift+W");
+  key_dict->SetString("mac",      "Ctrl+Shift+M");
+  key_dict->SetString("linux",    "Ctrl+Shift+L");
+  key_dict->SetString("chromeos", "Ctrl+Shift+C");
+  input->Set("suggested_key", key_dict);
+  input->SetString("description", description);
+
+  extensions::Command command;
+  string16 error;
+  EXPECT_TRUE(command.Parse(input.get(), command_name, 0, &error));
+  EXPECT_STREQ(description.c_str(),
+               UTF16ToASCII(command.description()).c_str());
+  EXPECT_STREQ(command_name.c_str(), command.command_name().c_str());
+
+#if defined(OS_WIN)
+  ui::Accelerator accelerator(ui::VKEY_W,
+                              ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
+#elif defined(OS_MACOSX)
+  ui::Accelerator accelerator(ui::VKEY_M,
+                              ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN);
+#elif defined(OS_CHROMEOS)
+  ui::Accelerator accelerator(ui::VKEY_C,
+                              ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
+#elif defined(OS_LINUX)
+  ui::Accelerator accelerator(ui::VKEY_L,
+                              ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
+#else
+  ui::Accelerator accelerator(ui::VKEY_D,
+                              ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
+#endif
+  EXPECT_EQ(accelerator, command.accelerator());
+
+  // Misspell a platform.
+  key_dict->SetString("windosw", "Ctrl+M");
+  EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
+  EXPECT_TRUE(key_dict->Remove("windosw", NULL));
+
+  // Now remove platform specific keys (leaving just "default") and make sure
+  // every platform falls back to the default.
+  EXPECT_TRUE(key_dict->Remove("windows", NULL));
+  EXPECT_TRUE(key_dict->Remove("mac", NULL));
+  EXPECT_TRUE(key_dict->Remove("linux", NULL));
+  EXPECT_TRUE(key_dict->Remove("chromeos", NULL));
+  EXPECT_TRUE(command.Parse(input.get(), command_name, 0, &error));
+  EXPECT_EQ(ui::VKEY_D, command.accelerator().key_code());
+
+  // Now remove "default", leaving no option but failure. Or, in the words of
+  // the immortal Adam Savage: "Failure is always an option".
+  EXPECT_TRUE(key_dict->Remove("default", NULL));
+  EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
+
+  // Make sure Command is not supported for non-Mac platforms.
+  key_dict->SetString("default", "Command+M");
+  EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
+  EXPECT_TRUE(key_dict->Remove("default", NULL));
+  key_dict->SetString("windows", "Command+M");
+  EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
+  EXPECT_TRUE(key_dict->Remove("windows", NULL));
+
+  // Now add only a valid platform that we are not running on to make sure devs
+  // are notified of errors on other platforms.
+#if defined(OS_WIN)
+  key_dict->SetString("mac", "Ctrl+Shift+M");
+#else
+  key_dict->SetString("windows", "Ctrl+Shift+W");
+#endif
+  EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
+
+  // Make sure Mac specific keys are not processed on other platforms.
+#if !defined(OS_MACOSX)
+  key_dict->SetString("windows", "Command+Shift+M");
+  EXPECT_FALSE(command.Parse(input.get(), command_name, 0, &error));
+#endif
+}
diff --git a/chrome/common/extensions/csp_validator.cc b/chrome/common/extensions/csp_validator.cc
new file mode 100644
index 0000000..3ce6192
--- /dev/null
+++ b/chrome/common/extensions/csp_validator.cc
@@ -0,0 +1,200 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/csp_validator.h"
+
+#include "base/string_split.h"
+#include "base/string_tokenizer.h"
+#include "base/string_util.h"
+
+namespace extensions {
+
+namespace csp_validator {
+
+namespace {
+
+const char kDefaultSrc[] = "default-src";
+const char kScriptSrc[] = "script-src";
+const char kObjectSrc[] = "object-src";
+
+const char kSandboxDirectiveName[] = "sandbox";
+const char kAllowSameOriginToken[] = "allow-same-origin";
+const char kAllowTopNavigation[] = "allow-top-navigation";
+const char kAllowPopups[] = "allow-popups";
+
+struct DirectiveStatus {
+  explicit DirectiveStatus(const char* name)
+    : directive_name(name)
+    , seen_in_policy(false)
+    , is_secure(false) {
+  }
+
+  const char* directive_name;
+  bool seen_in_policy;
+  bool is_secure;
+};
+
+bool HasOnlySecureTokens(StringTokenizer& tokenizer, Extension::Type type) {
+  while (tokenizer.GetNext()) {
+    std::string source = tokenizer.token();
+    StringToLowerASCII(&source);
+
+    // Don't alow whitelisting of all hosts. This boils down to:
+    //   1. Maximum of 2 '*' characters.
+    //   2. Each '*' is either followed by a '.' or preceded by a ':'
+    int wildcards = 0;
+    size_t length = source.length();
+    for (size_t i = 0; i < length; ++i) {
+      if (source[i] == L'*') {
+        wildcards++;
+        if (wildcards > 2)
+          return false;
+
+        bool isWildcardPort = i > 0 && source[i - 1] == L':';
+        bool isWildcardSubdomain = i + 1 < length && source[i + 1] == L'.';
+        if (!isWildcardPort && !isWildcardSubdomain)
+          return false;
+      }
+    }
+
+    // We might need to relax this whitelist over time.
+    if (source == "'self'" ||
+        source == "'none'" ||
+        source == "http://127.0.0.1" ||
+        LowerCaseEqualsASCII(source, "blob:") ||
+        LowerCaseEqualsASCII(source, "filesystem:") ||
+        LowerCaseEqualsASCII(source, "http://localhost") ||
+        StartsWithASCII(source, "http://127.0.0.1:", false) ||
+        StartsWithASCII(source, "http://localhost:", false) ||
+        StartsWithASCII(source, "https://", true) ||
+        StartsWithASCII(source, "chrome://", true) ||
+        StartsWithASCII(source, "chrome-extension://", true) ||
+        StartsWithASCII(source, "chrome-extension-resource:", true)) {
+      continue;
+    }
+
+    // crbug.com/146487
+    if (type == Extension::TYPE_EXTENSION ||
+        type == Extension::TYPE_LEGACY_PACKAGED_APP) {
+      if (source == "'unsafe-eval'")
+        continue;
+    }
+
+    return false;
+  }
+
+  return true;  // Empty values default to 'none', which is secure.
+}
+
+// Returns true if |directive_name| matches |status.directive_name|.
+bool UpdateStatus(const std::string& directive_name,
+                  StringTokenizer& tokenizer,
+                  DirectiveStatus* status,
+                  Extension::Type type) {
+  if (status->seen_in_policy)
+    return false;
+  if (directive_name != status->directive_name)
+    return false;
+  status->seen_in_policy = true;
+  status->is_secure = HasOnlySecureTokens(tokenizer, type);
+  return true;
+}
+
+}  //  namespace
+
+bool ContentSecurityPolicyIsLegal(const std::string& policy) {
+  // We block these characters to prevent HTTP header injection when
+  // representing the content security policy as an HTTP header.
+  const char kBadChars[] = {',', '\r', '\n', '\0'};
+
+  return policy.find_first_of(kBadChars, 0, arraysize(kBadChars)) ==
+      std::string::npos;
+}
+
+bool ContentSecurityPolicyIsSecure(const std::string& policy,
+                                   Extension::Type type) {
+  // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm.
+  std::vector<std::string> directives;
+  base::SplitString(policy, ';', &directives);
+
+  DirectiveStatus default_src_status(kDefaultSrc);
+  DirectiveStatus script_src_status(kScriptSrc);
+  DirectiveStatus object_src_status(kObjectSrc);
+
+  for (size_t i = 0; i < directives.size(); ++i) {
+    std::string& input = directives[i];
+    StringTokenizer tokenizer(input, " \t\r\n");
+    if (!tokenizer.GetNext())
+      continue;
+
+    std::string directive_name = tokenizer.token();
+    StringToLowerASCII(&directive_name);
+
+    if (UpdateStatus(directive_name, tokenizer, &default_src_status, type))
+      continue;
+    if (UpdateStatus(directive_name, tokenizer, &script_src_status, type))
+      continue;
+    if (UpdateStatus(directive_name, tokenizer, &object_src_status, type))
+      continue;
+  }
+
+  if (script_src_status.seen_in_policy && !script_src_status.is_secure)
+    return false;
+
+  if (object_src_status.seen_in_policy && !object_src_status.is_secure)
+    return false;
+
+  if (default_src_status.seen_in_policy && !default_src_status.is_secure) {
+    return script_src_status.seen_in_policy &&
+           object_src_status.seen_in_policy;
+  }
+
+  return default_src_status.seen_in_policy ||
+      (script_src_status.seen_in_policy && object_src_status.seen_in_policy);
+}
+
+bool ContentSecurityPolicyIsSandboxed(
+    const std::string& policy, Extension::Type type) {
+  // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm.
+  std::vector<std::string> directives;
+  base::SplitString(policy, ';', &directives);
+
+  bool seen_sandbox = false;
+
+  for (size_t i = 0; i < directives.size(); ++i) {
+    std::string& input = directives[i];
+    StringTokenizer tokenizer(input, " \t\r\n");
+    if (!tokenizer.GetNext())
+      continue;
+
+    std::string directive_name = tokenizer.token();
+    StringToLowerASCII(&directive_name);
+
+    if (directive_name != kSandboxDirectiveName)
+      continue;
+
+    seen_sandbox = true;
+
+    while (tokenizer.GetNext()) {
+      std::string token = tokenizer.token();
+      StringToLowerASCII(&token);
+
+      // The same origin token negates the sandboxing.
+      if (token == kAllowSameOriginToken)
+        return false;
+
+      // Platform apps don't allow navigation.
+      if (type == Extension::TYPE_PLATFORM_APP) {
+        if (token == kAllowTopNavigation)
+          return false;
+      }
+    }
+  }
+
+  return seen_sandbox;
+}
+
+}  // csp_validator
+
+}  // extensions
diff --git a/chrome/common/extensions/csp_validator.h b/chrome/common/extensions/csp_validator.h
new file mode 100644
index 0000000..1934495
--- /dev/null
+++ b/chrome/common/extensions/csp_validator.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_CSP_VALIDATOR_H_
+#define CHROME_COMMON_EXTENSIONS_CSP_VALIDATOR_H_
+
+#include <string>
+
+#include "chrome/common/extensions/extension.h"
+
+namespace extensions {
+
+namespace csp_validator {
+
+// Checks whether the given |policy| is legal for use in the extension system.
+// This check just ensures that the policy doesn't contain any characters that
+// will cause problems when we transmit the policy in an HTTP header.
+bool ContentSecurityPolicyIsLegal(const std::string& policy);
+
+// Checks whether the given |policy| meets the minimum security requirements
+// for use in the extension system.
+//
+// Ideally, we would like to say that an XSS vulnerability in the extension
+// should not be able to execute script, even in the precense of an active
+// network attacker.
+//
+// However, we found that it broke too many deployed extensions to limit
+// 'unsafe-eval' in the script-src directive, so that is allowed as a special
+// case for extensions. Platform apps disallow it.
+bool ContentSecurityPolicyIsSecure(
+    const std::string& policy, Extension::Type type);
+
+// Checks whether the given |policy| enforces a unique origin sandbox as
+// defined by http://www.whatwg.org/specs/web-apps/current-work/multipage/
+// the-iframe-element.html#attr-iframe-sandbox. The policy must have the
+// "sandbox" directive, and the sandbox tokens must not include
+// "allow-same-origin". Additional restrictions may be imposed depending on
+// |type|.
+bool ContentSecurityPolicyIsSandboxed(
+    const std::string& policy, Extension::Type type);
+
+}  // namespace csp_validator
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_CSP_VALIDATOR_H_
diff --git a/chrome/common/extensions/csp_validator_unittest.cc b/chrome/common/extensions/csp_validator_unittest.cc
new file mode 100644
index 0000000..92c681d
--- /dev/null
+++ b/chrome/common/extensions/csp_validator_unittest.cc
@@ -0,0 +1,179 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/csp_validator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::csp_validator::ContentSecurityPolicyIsLegal;
+using extensions::csp_validator::ContentSecurityPolicyIsSecure;
+using extensions::csp_validator::ContentSecurityPolicyIsSandboxed;
+using extensions::Extension;
+
+TEST(ExtensionCSPValidator, IsLegal) {
+  EXPECT_TRUE(ContentSecurityPolicyIsLegal("foo"));
+  EXPECT_TRUE(ContentSecurityPolicyIsLegal(
+      "default-src 'self'; script-src http://www.google.com"));
+  EXPECT_FALSE(ContentSecurityPolicyIsLegal(
+      "default-src 'self';\nscript-src http://www.google.com"));
+  EXPECT_FALSE(ContentSecurityPolicyIsLegal(
+      "default-src 'self';\rscript-src http://www.google.com"));
+  EXPECT_FALSE(ContentSecurityPolicyIsLegal(
+      "default-src 'self';,script-src http://www.google.com"));
+}
+
+TEST(ExtensionCSPValidator, IsSecure) {
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "img-src https://google.com", Extension::TYPE_EXTENSION));
+
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src *", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self'", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'none'", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' ftp://google.com", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' https://google.com", Extension::TYPE_EXTENSION));
+
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src *; default-src 'self'", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self'; default-src *", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self'; default-src *; script-src *; script-src 'self'",
+       Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self'; default-src *; script-src 'self'; script-src *",
+      Extension::TYPE_EXTENSION));
+
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src *; script-src 'self'", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src *; script-src 'self'; img-src 'self'",
+      Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src *; script-src 'self'; object-src 'self'",
+      Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "script-src 'self'; object-src 'self'", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'unsafe-eval'", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'unsafe-eval'", Extension::TYPE_LEGACY_PACKAGED_APP));
+
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'unsafe-eval'", Extension::TYPE_PLATFORM_APP));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'unsafe-inline'", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'unsafe-inline' 'none'", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' http://google.com", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' https://google.com", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' chrome://resources", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' chrome-extension://aabbcc",
+      Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+     "default-src 'self' chrome-extension-resource://aabbcc",
+     Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' https:", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' http:", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' google.com", Extension::TYPE_EXTENSION));
+
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' *", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' *:*", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' *:*/", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' *:*/path", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' https://*:*", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' https://*:*/", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' https://*:*/path", Extension::TYPE_EXTENSION));
+
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' https://*.google.com", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' https://*.google.com:1", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' https://*.google.com:*", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' https://*.google.com:1/", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' https://*.google.com:*/", Extension::TYPE_EXTENSION));
+
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' http://127.0.0.1", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' http://localhost", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' http://lOcAlHoSt", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' http://127.0.0.1:9999", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' http://localhost:8888", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' http://127.0.0.1.example.com",
+      Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' http://localhost.example.com",
+      Extension::TYPE_EXTENSION));
+
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' blob:", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' blob:http://example.com/XXX",
+      Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' filesystem:", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSecure(
+      "default-src 'self' filesystem:http://example.com/XXX",
+      Extension::TYPE_EXTENSION));
+}
+
+TEST(ExtensionCSPValidator, IsSandboxed) {
+  EXPECT_FALSE(ContentSecurityPolicyIsSandboxed("", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSandboxed(
+      "img-src https://google.com", Extension::TYPE_EXTENSION));
+
+  // Sandbox directive is required.
+  EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+      "sandbox", Extension::TYPE_EXTENSION));
+
+  // Additional sandbox tokens are OK.
+  EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+      "sandbox allow-scripts", Extension::TYPE_EXTENSION));
+  // Except for allow-same-origin.
+  EXPECT_FALSE(ContentSecurityPolicyIsSandboxed(
+      "sandbox allow-same-origin", Extension::TYPE_EXTENSION));
+
+  // Additional directives are OK.
+  EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+      "sandbox; img-src https://google.com", Extension::TYPE_EXTENSION));
+
+  // Extensions allow navigation, platform apps don't.
+  EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+      "sandbox allow-top-navigation", Extension::TYPE_EXTENSION));
+  EXPECT_FALSE(ContentSecurityPolicyIsSandboxed(
+      "sandbox allow-top-navigation", Extension::TYPE_PLATFORM_APP));
+
+  // Popups are OK.
+  EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+      "sandbox allow-popups", Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+      "sandbox allow-popups", Extension::TYPE_PLATFORM_APP));
+}
diff --git a/chrome/common/extensions/docs/OWNERS b/chrome/common/extensions/docs/OWNERS
new file mode 100644
index 0000000..66d65a9
--- /dev/null
+++ b/chrome/common/extensions/docs/OWNERS
@@ -0,0 +1,3 @@
+cduvall@chromium.org
+kalman@chromium.org
+mkwst@chromium.org
diff --git a/chrome/common/extensions/docs/README b/chrome/common/extensions/docs/README
new file mode 100644
index 0000000..dac5300
--- /dev/null
+++ b/chrome/common/extensions/docs/README
@@ -0,0 +1,85 @@
+--------
+Overview
+
+This is the content and its server for the extensions/apps documentation served
+from http://developer.chrome.com/(extensions|apps).
+
+Documentation for apps and extensions are partly generated from API definitions
+(for the reference material), and partly hand-written.
+
+All documentation sources are checked into source control, just like any other
+Chrome source code. The server consumes these sources and generates the
+documentation web pages dynamically.
+
+The goals of this system are:
+
+* Docs are generated from API definitions; it isn't possible to add or modify
+  APIs without creating stub reference documentation at the same time.
+
+* Docs are editable by anyone with Chrome commit access. This encourages
+  developers to do their part to keep doc up to date, and allows part-time
+  contributors to help too.
+
+* Docs go live automatically and immediately, upon check-in. There's no separate
+  push process for docs.
+
+* Docs are branched automatically with Chrome's source code; the docs for each
+  Chrome release are kept with the corresponding source code.
+
+* Users can always find the current doc for any Chrome release channel (i.e.,
+  /trunk/extensions/, /beta/apps/, etc.). These URLs are updated automatically
+  with Chrome's release process.
+
+
+------------
+Editing docs
+
+  1. Edit files.
+
+     - If you are not updating the static HTML for a docs page, you will most
+       likely not have to do anything. The docs server will automatically pick
+       up changes to the JSON or IDL schemas.
+
+     - Otherwise, they will be in chrome/common/extensions/docs/templates/.
+       See the "Overview of directories" section for more information.
+       Chances are you'll want to change a file in either "intros" (if changing
+       API documentation) or "articles" (if changing non-API documentation).
+       If adding files or APIs you'll also need to add something to "public".
+
+     - Files in templates directory use the Handlebar template language. It is
+       extremely simple, essentially: write HTML.
+       See third_party/handlebar/README.md.
+
+  2. Run 'server2/preview.py'
+
+  3. Check your work at http://localhost:8000/(apps|extensions)/<doc_name>
+
+  4. Send a CL and commit files as with any other Chrome change. The live
+     server will update within 5-10 minutes.
+
+
+-----------------------
+Overview of directories
+
+* examples: The source for the sample extensions. Note that the sample apps are
+  checked into github at https://github.com/GoogleChrome/chrome-app-samples.
+
+* server2: The Python AppEngine server which serves all the content (living at
+  developer.chrome.com). Unless you're developing the server itself, you won't
+  need to worry about this (and if you are, see the README in there).
+
+* static: The static content (images, CSS, JavaScript, etc).
+
+* templates: These are the templates that server2 interprets and generates HTML
+  content with. This has four subdirectories:
+    - intros: The static content that appears before the API reference on API
+              pages.
+    - articles: The static content that appears on non-API pages.
+    - public: The top level templates for all pages.
+    - private: Helper templates used in rendering the docs.
+
+
+--------------------
+The AppEngine server
+
+See server2/README.
diff --git a/chrome/common/extensions/docs/examples/README.chromium b/chrome/common/extensions/docs/examples/README.chromium
new file mode 100644
index 0000000..034abed
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/README.chromium
@@ -0,0 +1,5 @@
+Name: Extensions examples
+URL: None
+License: Apache 2.0, MIT, X11, BSD, and GPL v2 licenses
+License File: NOT_SHIPPED
+Security Critical: no
diff --git a/chrome/common/extensions/docs/examples/README.txt b/chrome/common/extensions/docs/examples/README.txt
new file mode 100644
index 0000000..de6e8e3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/README.txt
@@ -0,0 +1,7 @@
+Chromium Extension Examples
+
+The directory structure is as follows:
+* api/ - trivial extensions focused on a single API package
+* howto/ - simple extensions showing how to perform a particular task
+* tutorials/ - multi-step walkthroughs referenced inline in the docs
+* extensions/ - full featured extensions spanning multiple API packages
diff --git a/chrome/common/extensions/docs/examples/api/bookmarks/basic/icon.png b/chrome/common/extensions/docs/examples/api/bookmarks/basic/icon.png
new file mode 100644
index 0000000..84c4be3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/bookmarks/basic/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/bookmarks/basic/manifest.json b/chrome/common/extensions/docs/examples/api/bookmarks/basic/manifest.json
new file mode 100644
index 0000000..8288e96
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/bookmarks/basic/manifest.json
@@ -0,0 +1,15 @@
+{
+  "name": "My Bookmarks",
+  "version": "1.0",
+  "description": "A browser action with a popup dump of all bookmarks, including search, add, edit and delete.",
+  "permissions": [
+    "bookmarks"
+  ],
+  "browser_action": {
+      "default_title": "My Bookmarks.",
+      "default_icon": "icon.png",
+      "default_popup": "popup.html"
+  },
+  "manifest_version": 2,
+  "content_security_policy": "script-src 'self' https://ajax.googleapis.com; object-src 'self'"
+}
diff --git a/chrome/common/extensions/docs/examples/api/bookmarks/basic/popup.html b/chrome/common/extensions/docs/examples/api/bookmarks/basic/popup.html
new file mode 100644
index 0000000..9baa86e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/bookmarks/basic/popup.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<link type="text/css" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/base/jquery-ui.css" rel="stylesheet">
+<style>
+div, td, th { color: black; }
+</style>
+<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
+<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js"></script>
+<script src="popup.js"></script>
+<script>
+</script>
+</head>
+<body style="width: 400px">
+<div>Search Bookmarks: <input id="search"></div>
+<div id="bookmarks"></div>
+<div id="editdialog"></div>
+<div id="deletedialog"></div>
+<div id="adddialog"></div>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/bookmarks/basic/popup.js b/chrome/common/extensions/docs/examples/api/bookmarks/basic/popup.js
new file mode 100644
index 0000000..ca72daa
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/bookmarks/basic/popup.js
@@ -0,0 +1,128 @@
+// Copyright (c) 2012 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.
+
+// Search the bookmarks when entering the search keyword.
+$(function() {
+  $('#search').change(function() {
+     $('#bookmarks').empty();
+     dumpBookmarks($('#search').val());
+  });
+});
+// Traverse the bookmark tree, and print the folder and nodes.
+function dumpBookmarks(query) {
+  var bookmarkTreeNodes = chrome.bookmarks.getTree(
+    function(bookmarkTreeNodes) {
+      $('#bookmarks').append(dumpTreeNodes(bookmarkTreeNodes, query));
+    });
+}
+function dumpTreeNodes(bookmarkNodes, query) {
+  var list = $('<ul>');
+  var i;
+  for (i = 0; i < bookmarkNodes.length; i++) {
+    list.append(dumpNode(bookmarkNodes[i], query));
+  }
+  return list;
+}
+function dumpNode(bookmarkNode, query) {
+  if (bookmarkNode.title) {
+    if (query && !bookmarkNode.children) {
+      if (String(bookmarkNode.title).indexOf(query) == -1) {
+        return $('<span></span>');
+      }
+    }
+    var anchor = $('<a>');
+    anchor.attr('href', bookmarkNode.url);
+    anchor.text(bookmarkNode.title);
+    /*
+     * When clicking on a bookmark in the extension, a new tab is fired with
+     * the bookmark url.
+     */
+    anchor.click(function() {
+      chrome.tabs.create({url: bookmarkNode.url});
+    });
+    var span = $('<span>');
+    var options = bookmarkNode.children ?
+      $('<span>[<a href="#" id="addlink">Add</a>]</span>') :
+      $('<span>[<a id="editlink" href="#">Edit</a> <a id="deletelink" ' +
+        'href="#">Delete</a>]</span>');
+    var edit = bookmarkNode.children ? $('<table><tr><td>Name</td><td>' +
+      '<input id="title"></td></tr><tr><td>URL</td><td><input id="url">' +
+      '</td></tr></table>') : $('<input>');
+    // Show add and edit links when hover over.
+        span.hover(function() {
+        span.append(options);
+        $('#deletelink').click(function() {
+          $('#deletedialog').empty().dialog({
+                 autoOpen: false,
+                 title: 'Confirm Deletion',
+                 resizable: false,
+                 height: 140,
+                 modal: true,
+                 overlay: {
+                   backgroundColor: '#000',
+                   opacity: 0.5
+                 },
+                 buttons: {
+                   'Yes, Delete It!': function() {
+                      chrome.bookmarks.remove(String(bookmarkNode.id));
+                      span.parent().remove();
+                      $(this).dialog('destroy');
+                    },
+                    Cancel: function() {
+                      $(this).dialog('destroy');
+                    }
+                 }
+               }).dialog('open');
+         });
+        $('#addlink').click(function() {
+          $('#adddialog').empty().append(edit).dialog({autoOpen: false,
+            closeOnEscape: true, title: 'Add New Bookmark', modal: true,
+            buttons: {
+            'Add' : function() {
+               chrome.bookmarks.create({parentId: bookmarkNode.id,
+                 title: $('#title').val(), url: $('#url').val()});
+               $('#bookmarks').empty();
+               $(this).dialog('destroy');
+               window.dumpBookmarks();
+             },
+            'Cancel': function() {
+               $(this).dialog('destroy');
+            }
+          }}).dialog('open');
+        });
+        $('#editlink').click(function() {
+         edit.val(anchor.text());
+         $('#editdialog').empty().append(edit).dialog({autoOpen: false,
+           closeOnEscape: true, title: 'Edit Title', modal: true,
+           show: 'slide', buttons: {
+              'Save': function() {
+                 chrome.bookmarks.update(String(bookmarkNode.id), {
+                   title: edit.val()
+                 });
+                 anchor.text(edit.val());
+                 options.show();
+                 $(this).dialog('destroy');
+              },
+             'Cancel': function() {
+                 $(this).dialog('destroy');
+             }
+         }}).dialog('open');
+        });
+        options.fadeIn();
+      },
+      // unhover
+      function() {
+        options.remove();
+      }).append(anchor);
+  }
+  var li = $(bookmarkNode.title ? '<li>' : '<div>').append(span);
+  if (bookmarkNode.children && bookmarkNode.children.length > 0) {
+    li.append(dumpTreeNodes(bookmarkNode.children, query));
+  }
+  return li;
+}
+
+document.addEventListener('DOMContentLoaded', function () {
+  dumpBookmarks();
+});
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/make_page_red/background.js b/chrome/common/extensions/docs/examples/api/browserAction/make_page_red/background.js
new file mode 100644
index 0000000..59e3ff8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/make_page_red/background.js
@@ -0,0 +1,17 @@
+// 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.
+
+// Called when the user clicks on the browser action.
+chrome.browserAction.onClicked.addListener(function(tab) {
+  chrome.tabs.executeScript(
+      null, {code:"document.body.style.background='red !important'"});
+});
+
+chrome.browserAction.setBadgeBackgroundColor({color:[0, 200, 0, 100]});
+
+var i = 0;
+window.setInterval(function() {
+  chrome.browserAction.setBadgeText({text:String(i)});
+  i++;
+}, 10);
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/make_page_red/icon.png b/chrome/common/extensions/docs/examples/api/browserAction/make_page_red/icon.png
new file mode 100644
index 0000000..84c4be3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/make_page_red/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/make_page_red/manifest.json b/chrome/common/extensions/docs/examples/api/browserAction/make_page_red/manifest.json
new file mode 100644
index 0000000..8a68222
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/make_page_red/manifest.json
@@ -0,0 +1,13 @@
+{
+  "name": "A browser action with no icon that makes the page red",
+  "version": "1.0",
+  "background": { "scripts": ["background.js"] },
+  "permissions": [
+    "http://*/*"
+  ],
+  "browser_action": {
+    "name": "Make this page red",
+    "icons": ["icon.png"]
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/print/background.js b/chrome/common/extensions/docs/examples/api/browserAction/print/background.js
new file mode 100644
index 0000000..79edd36
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/print/background.js
@@ -0,0 +1,9 @@
+// Copyright (c) 2012 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.
+
+// Called when the user clicks on the browser action.
+chrome.browserAction.onClicked.addListener(function(tab) {
+  var action_url = "javascript:window.print();";
+  chrome.tabs.update(tab.id, {url: action_url});
+});
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/print/manifest.json b/chrome/common/extensions/docs/examples/api/browserAction/print/manifest.json
new file mode 100644
index 0000000..c4ea105
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/print/manifest.json
@@ -0,0 +1,16 @@
+{
+  "name": "Print this page",
+  "description": "Adds a print button to the browser.",
+  "version": "1.1",
+  "background": {
+    "scripts": ["background.js"]
+  },
+  "permissions": [
+    "tabs", "http://*/*", "https://*/*"
+  ],
+  "browser_action": {
+      "default_title": "Print this page",
+      "default_icon": "print_16x16.png"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/print/print_16x16.png b/chrome/common/extensions/docs/examples/api/browserAction/print/print_16x16.png
new file mode 100644
index 0000000..a38a537
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/print/print_16x16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/background.js b/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/background.js
new file mode 100644
index 0000000..92c1a90
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/background.js
@@ -0,0 +1,18 @@
+// 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.
+
+var min = 1;
+var max = 5;
+var current = min;
+
+function updateIcon() {
+  chrome.browserAction.setIcon({path:"icon" + current + ".png"});
+  current++;
+
+  if (current > max)
+    current = min;
+}
+
+chrome.browserAction.onClicked.addListener(updateIcon);
+updateIcon();
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/icon1.png b/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/icon1.png
new file mode 100644
index 0000000..84c4be3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/icon1.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/icon2.png b/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/icon2.png
new file mode 100644
index 0000000..9bb6f33
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/icon2.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/icon3.png b/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/icon3.png
new file mode 100644
index 0000000..1659dbf
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/icon3.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/icon4.png b/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/icon4.png
new file mode 100644
index 0000000..2e5aeee
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/icon4.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/icon5.png b/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/icon5.png
new file mode 100644
index 0000000..b4eea57
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/icon5.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/manifest.json b/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/manifest.json
new file mode 100644
index 0000000..156ec1a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/set_icon_path/manifest.json
@@ -0,0 +1,12 @@
+{
+  "name": "A browser action which changes its icon when clicked.",
+  "version": "1.1",
+  "background": { "scripts": ["background.js"] },
+  "permissions": [
+    "tabs", "http://*/*"
+  ],
+  "browser_action": {
+      "name": "Click to change the icon's color"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/icon.png b/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/icon.png
new file mode 100644
index 0000000..4924db1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/manifest.json b/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/manifest.json
new file mode 100644
index 0000000..4bcdf09
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/manifest.json
@@ -0,0 +1,13 @@
+{
+  "name": "A browser action with a popup that changes the page color.",
+  "version": "1.0",
+  "permissions": [
+    "tabs", "http://*/*", "https://*/*"
+  ],
+  "browser_action": {
+      "default_title": "Set this page's color.",
+      "default_icon": "icon.png",
+      "default_popup": "popup.html"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/popup.html b/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/popup.html
new file mode 100644
index 0000000..bf1b42b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/popup.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Set Page Color Popup</title>
+    <style>
+    body {
+      overflow: hidden;
+      margin: 0px;
+      padding: 0px;
+      background: white;
+    }
+
+    div:first-child {
+      margin-top: 0px;
+    }
+
+    div {
+      cursor: pointer;
+      text-align: center;
+      padding: 1px 3px;
+      font-family: sans-serif;
+      font-size: 0.8em;
+      width: 100px;
+      margin-top: 1px;
+      background: #cccccc;
+    }
+    div:hover {
+      background: #aaaaaa;
+    }
+    #red {
+      border: 1px solid red;
+      color: red;
+    }
+    #blue {
+      border: 1px solid blue;
+      color: blue;
+    }
+    #green {
+      border: 1px solid green;
+      color: green;
+    }
+    #yellow {
+      border: 1px solid yellow;
+      color: yellow;
+    }
+    </style>
+    <script src="popup.js"></script>
+  </head>
+  <body>
+    <div id="red">red</div>
+    <div id="blue">blue</div>
+    <div id="green">green</div>
+    <div id="yellow">yellow</div>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/popup.js b/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/popup.js
new file mode 100644
index 0000000..516c2ba
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browserAction/set_page_color/popup.js
@@ -0,0 +1,17 @@
+// Copyright (c) 2012 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.
+
+
+function click(e) {
+  chrome.tabs.executeScript(null,
+      {code:"document.body.style.backgroundColor='" + e.target.id + "'"});
+  window.close();
+}
+
+document.addEventListener('DOMContentLoaded', function () {
+  var divs = document.querySelectorAll('div');
+  for (var i = 0; i < divs.length; i++) {
+    divs[i].addEventListener('click', click);
+  }
+});
diff --git a/chrome/common/extensions/docs/examples/api/browsingData/basic/icon.png b/chrome/common/extensions/docs/examples/api/browsingData/basic/icon.png
new file mode 100644
index 0000000..346e1fd
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browsingData/basic/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/browsingData/basic/manifest.json b/chrome/common/extensions/docs/examples/api/browsingData/basic/manifest.json
new file mode 100644
index 0000000..7a13118
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browsingData/basic/manifest.json
@@ -0,0 +1,13 @@
+{
+  "name" : "BrowsingData API: Basics",
+  "version" : "1.1",
+  "description" : "A trivial usage example.",
+  "permissions": [
+    "browsingData"
+  ],
+  "browser_action": {
+     "default_icon": "icon.png",
+     "popup": "popup.html"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/browsingData/basic/popup.css b/chrome/common/extensions/docs/examples/api/browsingData/basic/popup.css
new file mode 100644
index 0000000..372376d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browsingData/basic/popup.css
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2012 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.
+ */
+
+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;
+}
+
+label {
+  color: #222;
+  font: 18px/1.4 Helvetica, sans-serif;
+  margin: 0.5em 0;
+  display: inline-block;
+}
+
+form {
+  width: 563px;
+  -webkit-transition: -webkit-transform 0.25s ease;
+}
+
+button {
+  display: block;
+  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: 500px;
+  padding: 20px;
+  margin: -40px 0 0 -270px;
+  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;
+  -webkit-transform: scale(0);
+}
+
+.overlay a {
+  color:  #FFF;
+}
+
+.overlay.visible {
+  opacity: 1;
+  -webkit-transform: scale(1);
+}
diff --git a/chrome/common/extensions/docs/examples/api/browsingData/basic/popup.html b/chrome/common/extensions/docs/examples/api/browsingData/basic/popup.html
new file mode 100644
index 0000000..64ed40e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browsingData/basic/popup.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Popup</title>
+    <link href="popup.css" rel="stylesheet">
+    <script src="popup.js"></script>
+  </head>
+  <body>
+    <h1>BrowsingData API Sample</h1>
+    <div role="main">
+      <form>
+        <label for="timeframe">Remove all browsing data from:</label>
+        <select id="timeframe">
+          <option value="hour">the past hour</option>
+          <option value="day">the past day</option>
+          <option value="week">the past week</option>
+          <option value="4weeks">the past four weeks</option>
+          <option value="forever">the beginning of time</option>
+        </select>
+        <button id="button">OBLITERATE!</button>
+      </form>
+    </div>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/browsingData/basic/popup.js b/chrome/common/extensions/docs/examples/api/browsingData/basic/popup.js
new file mode 100644
index 0000000..1b46ea9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/browsingData/basic/popup.js
@@ -0,0 +1,133 @@
+// Copyright (c) 2012 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.
+
+/**
+ * This class wraps the popup's form, and performs the proper clearing of data
+ * based on the user's selections. It depends on the form containing a single
+ * select element with an id of 'timeframe', and a single button with an id of
+ * 'button'. When you write actual code you should probably be a little more
+ * accepting of variance, but this is just a sample app. :)
+ *
+ * Most of this is boilerplate binding the controller to the UI. The bits that
+ * specifically will be useful when using the BrowsingData API are contained in
+ * `parseMilliseconds_`, `handleCallback_`, and `handleClick_`.
+ *
+ * @constructor
+ */
+var PopupController = function () {
+  this.button_ = document.getElementById('button');
+  this.timeframe_ = document.getElementById('timeframe');
+  this.addListeners_();
+};
+
+PopupController.prototype = {
+  /**
+   * A cached reference to the button element.
+   *
+   * @type {Element}
+   * @private
+   */
+  button_: null,
+
+  /**
+   * A cached reference to the select element.
+   *
+   * @type {Element}
+   * @private
+   */
+  timeframe_: null,
+
+  /**
+   * Adds event listeners to the button in order to capture a user's click, and
+   * perform some action in response.
+   *
+   * @private
+   */
+  addListeners_: function () {
+    this.button_.addEventListener('click', this.handleClick_.bind(this));
+  },
+
+  /**
+   * Given a string, return milliseconds since epoch. If the string isn't
+   * valid, returns undefined.
+   *
+   * @param {string} timeframe One of 'hour', 'day', 'week', '4weeks', or
+   *     'forever'.
+   * @returns {number} Milliseconds since epoch.
+   * @private
+   */
+  parseMilliseconds_: function (timeframe) {
+    var now = new Date().getTime();
+    var milliseconds = {
+      'hour': 60 * 60 * 1000,
+      'day': 24 * 60 * 60 * 1000,
+      'week': 7 * 24 * 60 * 60 * 1000,
+      '4weeks': 4 * 7 * 24 * 60 * 60 * 1000
+    };
+
+    if (milliseconds[timeframe])
+      return now - milliseconds[timeframe];
+
+    if (timeframe === 'forever')
+      return 0;
+
+    return null;
+  },
+
+  /**
+   * Handle a success/failure callback from the `browsingData` API methods,
+   * updating the UI appropriately.
+   *
+   * @private
+   */
+  handleCallback_: function () {
+    var success = document.createElement('div');
+    success.classList.add('overlay');
+    success.setAttribute('role', 'alert');
+    success.textContent = 'Data has been cleared.';
+    document.body.appendChild(success);
+
+    setTimeout(function() { success.classList.add('visible'); }, 10);
+    setTimeout(function() {
+      if (close === false)
+        success.classList.remove('visible');
+      else
+        window.close();
+    }, 4000);
+  },
+
+  /**
+   * When a user clicks the button, this method is called: it reads the current
+   * state of `timeframe_` in order to pull a timeframe, then calls the clearing
+   * method with appropriate arguments.
+   *
+   * @private
+   */
+  handleClick_: function () {
+    var removal_start = this.parseMilliseconds_(this.timeframe_.value);
+    if (removal_start !== undefined) {
+      this.button_.setAttribute('disabled', 'disabled');
+      this.button_.innerText = 'Clearing...';
+      chrome.browsingData.remove(removal_start, {
+        "appcache": true,
+        "cache": true,
+        "cookies": true,
+        "downloads": true,
+        "fileSystems": true,
+        "formData": true,
+        "history": true,
+        "indexedDB": true,
+        "localStorage": true,
+        "serverBoundCertificates": true,
+        "pluginData": true,
+        "passwords": true,
+        "webSQL": true
+      }, this.handleCallback_.bind(this));
+    }
+  }
+};
+
+document.addEventListener('DOMContentLoaded', function () {
+  window.PC = new PopupController();
+});
diff --git a/chrome/common/extensions/docs/examples/api/commands/background.js b/chrome/common/extensions/docs/examples/api/commands/background.js
new file mode 100644
index 0000000..5661f24
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/commands/background.js
@@ -0,0 +1,7 @@
+// Copyright (c) 2012 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.
+
+chrome.commands.onCommand.addListener(function(command) {
+  console.log('onCommand event received for message: ', command);
+});
diff --git a/chrome/common/extensions/docs/examples/api/commands/browser_action.html b/chrome/common/extensions/docs/examples/api/commands/browser_action.html
new file mode 100644
index 0000000..499473a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/commands/browser_action.html
@@ -0,0 +1 @@
+This is a sample browser action popup.
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/commands/manifest.json b/chrome/common/extensions/docs/examples/api/commands/manifest.json
new file mode 100644
index 0000000..25bbc1e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/commands/manifest.json
@@ -0,0 +1,25 @@
+{
+  "name": "Sample Extension Commands extension",
+  "description": "Press Ctrl+F to open the browser action popup, press Ctrl+Shift+Y to send an event (Command+Shift+Y on a Mac).",
+  "version": "1.0",
+  "manifest_version": 2,
+  "background": {
+    "scripts": ["background.js"],
+    "persistent": false
+  },
+  "browser_action": {
+    "default_popup": "browser_action.html"
+  },
+  "commands": {
+    "toggle-feature": {
+      "suggested_key": { "default": "Ctrl+Shift+Y" },
+      "description": "Send a 'toggle-feature' event to the extension"
+    },
+    "_execute_browser_action": {
+      "suggested_key": {
+        "default": "Ctrl+Shift+F",
+        "mac": "MacCtrl+Shift+F"
+      }
+    }
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/api/contentSettings/contentSettings.png b/chrome/common/extensions/docs/examples/api/contentSettings/contentSettings.png
new file mode 100644
index 0000000..346e1fd
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/contentSettings/contentSettings.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/contentSettings/manifest.json b/chrome/common/extensions/docs/examples/api/contentSettings/manifest.json
new file mode 100644
index 0000000..ece0a73
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/contentSettings/manifest.json
@@ -0,0 +1,11 @@
+{

+  "name" : "Content settings",

+  "version" : "0.2",

+  "description" : "Shows the content settings for the current site.",

+  "permissions": [ "contentSettings", "tabs" ],

+  "browser_action": {

+     "default_icon": "contentSettings.png",

+     "default_popup": "popup.html"

+  },

+  "manifest_version": 2

+}

diff --git a/chrome/common/extensions/docs/examples/api/contentSettings/popup.html b/chrome/common/extensions/docs/examples/api/contentSettings/popup.html
new file mode 100644
index 0000000..dc3c9f0
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/contentSettings/popup.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Popup</title>
+    <script src="popup.js"></script>
+  </head>
+  <body>
+    <fieldset>
+      <dl>
+        <dt><label for="cookies">Cookies: </label></dt>
+        <dd><select id="cookies">
+          <option value="allow">Allow</option>
+          <option value="session_only">Session only</option>
+          <option value="block">Block</option>
+        </select></dd>
+        <dt><label for="images">Images: </label></dt>
+        <dd><select id="images">
+          <option value="allow">Allow</option>
+          <option value="block">Block</option>
+        </select>
+        <dt><label for="javascript">Javascript: </label></dt>
+        <dd><select id="javascript">
+          <option value="allow">Allow</option>
+          <option value="block">Block</option>
+        </select></dd>
+        <dt><label for="plugins">Plug-ins: </label></dt>
+        <dd><select id="plugins">
+          <option value="allow">Allow</option>
+          <option value="block">Block</option>
+        </select></dd>
+        <dt><label for="popups">Pop-ups: </label></dt>
+        <dd><select id="popups">
+          <option value="allow">Allow</option>
+          <option value="block">Block</option>
+        </select></dd>
+        <dt><label for="location">Location: </label></dt>
+        <dd><select id="location" disabled>
+          <option value="allow">Allow</option>
+          <option value="ask">Ask</option>
+          <option value="block">Block</option>
+        </select></dd>
+        <dt><label for="notifications">Notifications: </label></dt>
+        <dd><select id="notifications">
+          <option value="allow">Allow</option>
+          <option value="ask">Ask</option>
+          <option value="block">Block</option>
+        </select></dd>
+      </dl>
+    </fieldset>
+
+
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/contentSettings/popup.js b/chrome/common/extensions/docs/examples/api/contentSettings/popup.js
new file mode 100644
index 0000000..45aeea5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/contentSettings/popup.js
@@ -0,0 +1,42 @@
+// 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.
+
+var incognito;
+var url;
+
+function settingChanged() {
+  var type = this.id;
+  var setting = this.value;
+  var pattern = /^file:/.test(url) ? url : url.replace(/\/[^\/]*?$/, '/*');
+  console.log(type+' setting for '+pattern+': '+setting);
+  chrome.contentSettings[type].set({
+        'primaryPattern': pattern,
+        'setting': setting,
+        'scope': (incognito ? 'incognito_session_only' : 'regular')
+      });
+}
+
+document.addEventListener('DOMContentLoaded', function () {
+  chrome.tabs.getSelected(undefined, function(tab) {
+    incognito = tab.incognito;
+    url = tab.url;
+    var types = ['cookies', 'images', 'javascript', 'plugins', 'popups',
+                 'notifications'];
+    types.forEach(function(type) {
+      chrome.contentSettings[type].get({
+            'primaryUrl': url,
+            'incognito': incognito
+          },
+          function(details) {
+            document.getElementById(type).value = details.setting;
+          });
+    });
+  });
+
+  var selects = document.querySelectorAll('select');
+  for (var i = 0; i < selects.length; i++) {
+    selects[i].addEventListener('change', settingChanged);
+  }
+});
+
diff --git a/chrome/common/extensions/docs/examples/api/contextMenus/basic/manifest.json b/chrome/common/extensions/docs/examples/api/contextMenus/basic/manifest.json
new file mode 100644
index 0000000..78173fe
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/contextMenus/basic/manifest.json
@@ -0,0 +1,10 @@
+{

+  "name": "Context Menus Sample",

+  "description": "Shows some of the features of the Context Menus API",

+  "version": "0.6",

+  "permissions": ["contextMenus"],

+  "background": {

+    "scripts": ["sample.js"]

+  },

+  "manifest_version": 2

+}

diff --git a/chrome/common/extensions/docs/examples/api/contextMenus/basic/sample.js b/chrome/common/extensions/docs/examples/api/contextMenus/basic/sample.js
new file mode 100644
index 0000000..a58be3a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/contextMenus/basic/sample.js
@@ -0,0 +1,69 @@
+// Copyright (c) 2010 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.

+

+// A generic onclick callback function.

+function genericOnClick(info, tab) {

+  console.log("item " + info.menuItemId + " was clicked");

+  console.log("info: " + JSON.stringify(info));

+  console.log("tab: " + JSON.stringify(tab));

+}

+

+// Create one test item for each context type.

+var contexts = ["page","selection","link","editable","image","video",

+                "audio"];

+for (var i = 0; i < contexts.length; i++) {

+  var context = contexts[i];

+  var title = "Test '" + context + "' menu item";

+  var id = chrome.contextMenus.create({"title": title, "contexts":[context],

+                                       "onclick": genericOnClick});

+  console.log("'" + context + "' item:" + id);

+}

+

+

+// Create a parent item and two children.

+var parent = chrome.contextMenus.create({"title": "Test parent item"});

+var child1 = chrome.contextMenus.create(

+  {"title": "Child 1", "parentId": parent, "onclick": genericOnClick});

+var child2 = chrome.contextMenus.create(

+  {"title": "Child 2", "parentId": parent, "onclick": genericOnClick});

+console.log("parent:" + parent + " child1:" + child1 + " child2:" + child2);

+

+

+// Create some radio items.

+function radioOnClick(info, tab) {

+  console.log("radio item " + info.menuItemId +

+              " was clicked (previous checked state was "  +

+              info.wasChecked + ")");

+}

+var radio1 = chrome.contextMenus.create({"title": "Radio 1", "type": "radio",

+                                         "onclick":radioOnClick});

+var radio2 = chrome.contextMenus.create({"title": "Radio 2", "type": "radio",

+                                         "onclick":radioOnClick});

+console.log("radio1:" + radio1 + " radio2:" + radio2);

+

+

+// Create some checkbox items.

+function checkboxOnClick(info, tab) {

+  console.log(JSON.stringify(info));

+  console.log("checkbox item " + info.menuItemId +

+              " was clicked, state is now: " + info.checked +

+              "(previous state was " + info.wasChecked + ")");

+

+}

+var checkbox1 = chrome.contextMenus.create(

+  {"title": "Checkbox1", "type": "checkbox", "onclick":checkboxOnClick});

+var checkbox2 = chrome.contextMenus.create(

+  {"title": "Checkbox2", "type": "checkbox", "onclick":checkboxOnClick});

+console.log("checkbox1:" + checkbox1 + " checkbox2:" + checkbox2);

+

+

+// Intentionally create an invalid item, to show off error checking in the

+// create callback.

+console.log("About to try creating an invalid item - an error about " +

+            "item 999 should show up");

+chrome.contextMenus.create({"title": "Oops", "parentId":999}, function() {

+  if (chrome.extension.lastError) {

+    console.log("Got expected error: " + chrome.extension.lastError.message);

+  }

+});

diff --git a/chrome/common/extensions/docs/examples/api/contextMenus/event_page/manifest.json b/chrome/common/extensions/docs/examples/api/contextMenus/event_page/manifest.json
new file mode 100644
index 0000000..7c9ff52
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/contextMenus/event_page/manifest.json
@@ -0,0 +1,11 @@
+{

+  "name": "Context Menus Sample (with Event Page)",

+  "description": "Shows some of the features of the Context Menus API using an event page",

+  "version": "0.7",

+  "permissions": ["contextMenus"],

+  "background": {

+    "persistent": false,

+    "scripts": ["sample.js"]

+  },

+  "manifest_version": 2

+}

diff --git a/chrome/common/extensions/docs/examples/api/contextMenus/event_page/sample.js b/chrome/common/extensions/docs/examples/api/contextMenus/event_page/sample.js
new file mode 100644
index 0000000..860b95b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/contextMenus/event_page/sample.js
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 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.

+

+// The onClicked callback function.

+function onClickHandler(info, tab) {

+  if (info.menuItemId == "radio1" || info.menuItemId == "radio2") {

+    console.log("radio item " + info.menuItemId +

+                " was clicked (previous checked state was "  +

+                info.wasChecked + ")");

+  } else if (info.menuItemId == "checkbox1" || info.menuItemId == "checkbox2") {

+    console.log(JSON.stringify(info));

+    console.log("checkbox item " + info.menuItemId +

+                " was clicked, state is now: " + info.checked +

+                " (previous state was " + info.wasChecked + ")");

+

+  } else {

+    console.log("item " + info.menuItemId + " was clicked");

+    console.log("info: " + JSON.stringify(info));

+    console.log("tab: " + JSON.stringify(tab));

+  }

+};

+

+chrome.contextMenus.onClicked.addListener(onClickHandler);

+

+// Set up context menu tree at install time.

+chrome.runtime.onInstalled.addListener(function() {

+  // Create one test item for each context type.

+  var contexts = ["page","selection","link","editable","image","video",

+                  "audio"];

+  for (var i = 0; i < contexts.length; i++) {

+    var context = contexts[i];

+    var title = "Test '" + context + "' menu item";

+    var id = chrome.contextMenus.create({"title": title, "contexts":[context],

+                                         "id": "context" + context});

+    console.log("'" + context + "' item:" + id);

+  }

+

+  // Create a parent item and two children.

+  chrome.contextMenus.create({"title": "Test parent item", "id": "parent"});

+  chrome.contextMenus.create(

+      {"title": "Child 1", "parentId": "parent", "id": "child1"});

+  chrome.contextMenus.create(

+      {"title": "Child 2", "parentId": "parent", "id": "child2"});

+  console.log("parent child1 child2");

+

+  // Create some radio items.

+  chrome.contextMenus.create({"title": "Radio 1", "type": "radio",

+                              "id": "radio1"});

+  chrome.contextMenus.create({"title": "Radio 2", "type": "radio",

+                              "id": "radio2"});

+  console.log("radio1 radio2");

+

+  // Create some checkbox items.

+  chrome.contextMenus.create(

+      {"title": "Checkbox1", "type": "checkbox", "id": "checkbox1"});

+  chrome.contextMenus.create(

+      {"title": "Checkbox2", "type": "checkbox", "id": "checkbox2"});

+  console.log("checkbox1 checkbox2");

+

+  // Intentionally create an invalid item, to show off error checking in the

+  // create callback.

+  console.log("About to try creating an invalid item - an error about " +

+      "duplicate item child1 should show up");

+  chrome.contextMenus.create({"title": "Oops", "id": "child1"}, function() {

+    if (chrome.extension.lastError) {

+      console.log("Got expected error: " + chrome.extension.lastError.message);

+    }

+  });

+});

diff --git a/chrome/common/extensions/docs/examples/api/cookies/background.js b/chrome/common/extensions/docs/examples/api/cookies/background.js
new file mode 100644
index 0000000..0e42ddb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/cookies/background.js
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 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.
+
+chrome.cookies.onChanged.addListener(function(info) {
+  console.log("onChanged" + JSON.stringify(info));
+});
+
+function focusOrCreateTab(url) {
+  chrome.windows.getAll({"populate":true}, function(windows) {
+    var existing_tab = null;
+    for (var i in windows) {
+      var tabs = windows[i].tabs;
+      for (var j in tabs) {
+        var tab = tabs[j];
+        if (tab.url == url) {
+          existing_tab = tab;
+          break;
+        }
+      }
+    }
+    if (existing_tab) {
+      chrome.tabs.update(existing_tab.id, {"selected":true});
+    } else {
+      chrome.tabs.create({"url":url, "selected":true});
+    }
+  });
+}
+
+chrome.browserAction.onClicked.addListener(function(tab) {
+  var manager_url = chrome.extension.getURL("manager.html");
+  focusOrCreateTab(manager_url);
+});
diff --git a/chrome/common/extensions/docs/examples/api/cookies/cookie.png b/chrome/common/extensions/docs/examples/api/cookies/cookie.png
new file mode 100644
index 0000000..0f787601
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/cookies/cookie.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/cookies/manager.html b/chrome/common/extensions/docs/examples/api/cookies/manager.html
new file mode 100644
index 0000000..527a25c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/cookies/manager.html
@@ -0,0 +1,43 @@
+<html>

+<head>

+<style>

+table {

+  border-collapse:collapse;

+}

+

+td {

+  border: 1px solid black;

+  padding-left: 5px;

+}

+

+td.button {

+  border: none;

+}

+

+td.cookie_count {

+  text-align: right;

+}

+

+</style>

+<script src="manager.js"></script>

+</head>

+<body>

+  <h2>Cookies! ... Nom Nom Nom...</h2>

+  <button id="remove_button">DELETE ALL!</button>

+  <div id="filter_div">

+    Filter: <input id="filter" type="text">

+    <button>x</button>

+  </div>

+  <br />

+  <div id="summary_div">

+    Showing <span id="filter_count"></span> of <span id="total_count"></span> cookie domains.

+    <span id="delete_all_button"></span>

+  </div>

+  <br />

+  <table id="cookies">

+    <tr class="header">

+      <th>Name</th>

+      <th>#Cookies</th>

+    </tr>

+  </body>

+</html>

diff --git a/chrome/common/extensions/docs/examples/api/cookies/manager.js b/chrome/common/extensions/docs/examples/api/cookies/manager.js
new file mode 100644
index 0000000..bcf3500
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/cookies/manager.js
@@ -0,0 +1,260 @@
+// Copyright (c) 2012 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.
+
+if (!chrome.cookies) {
+  chrome.cookies = chrome.experimental.cookies;
+}
+
+// A simple Timer class.
+function Timer() {
+  this.start_ = new Date();
+
+  this.elapsed = function() {
+    return (new Date()) - this.start_;
+  }
+
+  this.reset = function() {
+    this.start_ = new Date();
+  }
+}
+
+// Compares cookies for "key" (name, domain, etc.) equality, but not "value"
+// equality.
+function cookieMatch(c1, c2) {
+  return (c1.name == c2.name) && (c1.domain == c2.domain) &&
+         (c1.hostOnly == c2.hostOnly) && (c1.path == c2.path) &&
+         (c1.secure == c2.secure) && (c1.httpOnly == c2.httpOnly) &&
+         (c1.session == c2.session) && (c1.storeId == c2.storeId);
+}
+
+// Returns an array of sorted keys from an associative array.
+function sortedKeys(array) {
+  var keys = [];
+  for (var i in array) {
+    keys.push(i);
+  }
+  keys.sort();
+  return keys;
+}
+
+// Shorthand for document.querySelector.
+function select(selector) {
+  return document.querySelector(selector);
+}
+
+// An object used for caching data about the browser's cookies, which we update
+// as notifications come in.
+function CookieCache() {
+  this.cookies_ = {};
+
+  this.reset = function() {
+    this.cookies_ = {};
+  }
+
+  this.add = function(cookie) {
+    var domain = cookie.domain;
+    if (!this.cookies_[domain]) {
+      this.cookies_[domain] = [];
+    }
+    this.cookies_[domain].push(cookie);
+  };
+
+  this.remove = function(cookie) {
+    var domain = cookie.domain;
+    if (this.cookies_[domain]) {
+      var i = 0;
+      while (i < this.cookies_[domain].length) {
+        if (cookieMatch(this.cookies_[domain][i], cookie)) {
+          this.cookies_[domain].splice(i, 1);
+        } else {
+          i++;
+        }
+      }
+      if (this.cookies_[domain].length == 0) {
+        delete this.cookies_[domain];
+      }
+    }
+  };
+
+  // Returns a sorted list of cookie domains that match |filter|. If |filter| is
+  //  null, returns all domains.
+  this.getDomains = function(filter) {
+    var result = [];
+    sortedKeys(this.cookies_).forEach(function(domain) {
+      if (!filter || domain.indexOf(filter) != -1) {
+        result.push(domain);
+      }
+    });
+    return result;
+  }
+
+  this.getCookies = function(domain) {
+    return this.cookies_[domain];
+  };
+}
+
+
+var cache = new CookieCache();
+
+
+function removeAllForFilter() {
+  var filter = select("#filter").value;
+  var timer = new Timer();
+  cache.getDomains(filter).forEach(function(domain) {
+    removeCookiesForDomain(domain);
+  });
+}
+
+function removeAll() {
+  var all_cookies = [];
+  cache.getDomains().forEach(function(domain) {
+    cache.getCookies(domain).forEach(function(cookie) {
+      all_cookies.push(cookie);
+    });
+  });
+  cache.reset();
+  var count = all_cookies.length;
+  var timer = new Timer();
+  for (var i = 0; i < count; i++) {
+    removeCookie(all_cookies[i]);
+  }
+  timer.reset();
+  chrome.cookies.getAll({}, function(cookies) {
+    for (var i in cookies) {
+      cache.add(cookies[i]);
+      removeCookie(cookies[i]);
+    }
+  });
+}
+
+function removeCookie(cookie) {
+  var url = "http" + (cookie.secure ? "s" : "") + "://" + cookie.domain +
+            cookie.path;
+  chrome.cookies.remove({"url": url, "name": cookie.name});
+}
+
+function removeCookiesForDomain(domain) {
+  var timer = new Timer();
+  cache.getCookies(domain).forEach(function(cookie) {
+    removeCookie(cookie);
+  });
+}
+
+function resetTable() {
+  var table = select("#cookies");
+  while (table.rows.length > 1) {
+    table.deleteRow(table.rows.length - 1);
+  }
+}
+
+var reload_scheduled = false;
+
+function scheduleReloadCookieTable() {
+  if (!reload_scheduled) {
+    reload_scheduled = true;
+    setTimeout(reloadCookieTable, 250);
+  }
+}
+
+function reloadCookieTable() {
+  reload_scheduled = false;
+
+  var filter = select("#filter").value;
+
+  var domains = cache.getDomains(filter);
+
+  select("#filter_count").innerText = domains.length;
+  select("#total_count").innerText = cache.getDomains().length;
+
+  select("#delete_all_button").innerHTML = "";
+  if (domains.length) {
+    var button = document.createElement("button");
+    button.onclick = removeAllForFilter;
+    button.innerText = "delete all " + domains.length;
+    select("#delete_all_button").appendChild(button);
+  }
+
+  resetTable();
+  var table = select("#cookies");
+
+  domains.forEach(function(domain) {
+    var cookies = cache.getCookies(domain);
+    var row = table.insertRow(-1);
+    row.insertCell(-1).innerText = domain;
+    var cell = row.insertCell(-1);
+    cell.innerText = cookies.length;
+    cell.setAttribute("class", "cookie_count");
+
+    var button = document.createElement("button");
+    button.innerText = "delete";
+    button.onclick = (function(dom){
+      return function() {
+        removeCookiesForDomain(dom);
+      };
+    }(domain));
+    var cell = row.insertCell(-1);
+    cell.appendChild(button);
+    cell.setAttribute("class", "button");
+  });
+}
+
+function focusFilter() {
+  select("#filter").focus();
+}
+
+function resetFilter() {
+  var filter = select("#filter");
+  filter.focus();
+  if (filter.value.length > 0) {
+    filter.value = "";
+    reloadCookieTable();
+  }
+}
+
+var ESCAPE_KEY = 27;
+window.onkeydown = function(event) {
+  if (event.keyCode == ESCAPE_KEY) {
+    resetFilter();
+  }
+}
+
+function listener(info) {
+  cache.remove(info.cookie);
+  if (!info.removed) {
+    cache.add(info.cookie);
+  }
+  scheduleReloadCookieTable();
+}
+
+function startListening() {
+  chrome.cookies.onChanged.addListener(listener);
+}
+
+function stopListening() {
+  chrome.cookies.onChanged.removeListener(listener);
+}
+
+function onload() {
+  focusFilter();
+  var timer = new Timer();
+  chrome.cookies.getAll({}, function(cookies) {
+    startListening();
+    start = new Date();
+    for (var i in cookies) {
+      cache.add(cookies[i]);
+    }
+    timer.reset();
+    reloadCookieTable();
+  });
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+  onload();
+  document.body.addEventListener('click', focusFilter);
+  document.querySelector('#remove_button').addEventListener('click', removeAll);
+  document.querySelector('#filter_div input').addEventListener(
+      'input', reloadCookieTable);
+  document.querySelector('#filter_div button').addEventListener(
+      'click', resetFilter);
+});
diff --git a/chrome/common/extensions/docs/examples/api/cookies/manifest.json b/chrome/common/extensions/docs/examples/api/cookies/manifest.json
new file mode 100644
index 0000000..09dbbdc
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/cookies/manifest.json
@@ -0,0 +1,14 @@
+{

+  "name" : "Cookie API Test Extension",

+  "version" : "0.8",

+  "description" : "Testing Cookie API",

+  "permissions": [ "cookies", "tabs", "http://*/*", "https://*/*" ],

+  "icons": { "16": "cookie.png", "48": "cookie.png", "128": "cookie.png" },

+  "browser_action": {

+    "default_icon": "cookie.png"

+  },

+  "background": {

+    "scripts": ["background.js"]

+  },

+  "manifest_version": 2

+}

diff --git a/chrome/common/extensions/docs/examples/api/debugger/live-headers/background.js b/chrome/common/extensions/docs/examples/api/debugger/live-headers/background.js
new file mode 100644
index 0000000..ad6595d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/debugger/live-headers/background.js
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 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.
+
+chrome.browserAction.onClicked.addListener(function() {
+  chrome.windows.getCurrent(function(win) {
+    chrome.tabs.getSelected(win.id, actionClicked);
+  });
+});
+
+var version = "1.0";
+
+function actionClicked(tab) {
+  chrome.debugger.attach({tabId:tab.id}, version, onAttach.bind(null, tab.id));
+}
+
+function onAttach(tabId) {
+  if (chrome.extension.lastError) {
+    alert(chrome.extension.lastError.message);
+    return;
+  }
+
+  chrome.windows.create(
+      {url: "headers.html?" + tabId, type: "popup", width: 800, height: 600});
+}
diff --git a/chrome/common/extensions/docs/examples/api/debugger/live-headers/headers.html b/chrome/common/extensions/docs/examples/api/debugger/live-headers/headers.html
new file mode 100644
index 0000000..3c042e9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/debugger/live-headers/headers.html
@@ -0,0 +1,32 @@
+<!--
+Copyright (c) 2012 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.
+-->
+
+<html>
+<head>
+<style>
+body {
+  font-family: monospace;
+  word-wrap: break-word;
+}
+
+#container {
+  white-space: pre;
+}
+
+.request {
+  border-top: 1px solid black;
+  margin-bottom: 10px;
+}
+
+</style>
+
+<script src="headers.js"></script>
+</head>
+
+<body>
+<div id="container"></div>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/debugger/live-headers/headers.js b/chrome/common/extensions/docs/examples/api/debugger/live-headers/headers.js
new file mode 100644
index 0000000..eac021e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/debugger/live-headers/headers.js
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 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.
+
+var tabId = parseInt(window.location.search.substring(1));
+
+window.addEventListener("load", function() {
+  chrome.debugger.sendCommand({tabId:tabId}, "Network.enable");
+  chrome.debugger.onEvent.addListener(onEvent);
+});
+
+window.addEventListener("unload", function() {
+  chrome.debugger.detach({tabId:tabId});
+});
+
+var requests = {};
+
+function onEvent(debuggeeId, message, params) {
+  if (tabId != debuggeeId.tabId)
+    return;
+
+  if (message == "Network.requestWillBeSent") {
+    var requestDiv = requests[params.requestId];
+    if (!requestDiv) {
+      var requestDiv = document.createElement("div");
+      requestDiv.className = "request";
+      requests[params.requestId] = requestDiv;
+      var urlLine = document.createElement("div");
+      urlLine.textContent = params.request.url;
+      requestDiv.appendChild(urlLine);
+    }
+
+    if (params.redirectResponse)
+      appendResponse(params.requestId, params.redirectResponse);
+
+    var requestLine = document.createElement("div");
+    requestLine.textContent = "\n" + params.request.method + " " +
+        parseURL(params.request.url).path + " HTTP/1.1";
+    requestDiv.appendChild(requestLine);
+    document.getElementById("container").appendChild(requestDiv);
+  } else if (message == "Network.responseReceived") {
+    appendResponse(params.requestId, params.response);
+  }
+}
+
+function appendResponse(requestId, response) {
+  var requestDiv = requests[requestId];
+  requestDiv.appendChild(formatHeaders(response.requestHeaders));
+
+  var statusLine = document.createElement("div");
+  statusLine.textContent = "\nHTTP/1.1 " + response.status + " " +
+      response.statusText;
+  requestDiv.appendChild(statusLine);
+  requestDiv.appendChild(formatHeaders(response.headers));
+}
+
+function formatHeaders(headers) {
+  var text = "";
+  for (name in headers)
+    text += name + ": " + headers[name] + "\n";
+  var div = document.createElement("div");
+  div.textContent = text;
+  return div;
+}
+
+function parseURL(url) {
+  var result = {};
+  var match = url.match(
+      /^([^:]+):\/\/([^\/:]*)(?::([\d]+))?(?:(\/[^#]*)(?:#(.*))?)?$/i);
+  if (!match)
+    return result;
+  result.scheme = match[1].toLowerCase();
+  result.host = match[2];
+  result.port = match[3];
+  result.path = match[4] || "/";
+  result.fragment = match[5];
+  return result;
+}
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/debugger/live-headers/icon.png b/chrome/common/extensions/docs/examples/api/debugger/live-headers/icon.png
new file mode 100644
index 0000000..8a3232a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/debugger/live-headers/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/debugger/live-headers/manifest.json b/chrome/common/extensions/docs/examples/api/debugger/live-headers/manifest.json
new file mode 100644
index 0000000..dbd1064
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/debugger/live-headers/manifest.json
@@ -0,0 +1,18 @@
+{
+  "name": "Live HTTP headers",
+  "description": "Displays the live log with the http requests headers",
+  "version": "0.6",
+  "permissions": [
+    "tabs",
+    "debugger"
+  ],
+  "background": {
+    "scripts": ["background.js"]
+  },
+  "browser_action": {
+    "default_icon": "icon.png",
+    "default_title": "Live HTTP headers"
+  },
+  "manifest_version": 2
+}
+
diff --git a/chrome/common/extensions/docs/examples/api/debugger/pause-resume/background.js b/chrome/common/extensions/docs/examples/api/debugger/pause-resume/background.js
new file mode 100644
index 0000000..fb14127
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/debugger/pause-resume/background.js
@@ -0,0 +1,63 @@
+// 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.
+
+var attachedTabs = {};
+var version = "1.0";
+
+chrome.debugger.onEvent.addListener(onEvent);
+chrome.debugger.onDetach.addListener(onDetach);
+
+chrome.browserAction.onClicked.addListener(function() {
+  chrome.windows.getCurrent(function(win) {
+    chrome.tabs.getSelected(win.id, actionClicked);
+  });
+});
+
+function actionClicked(tab) {
+  var tabId = tab.id;
+  var debuggeeId = {tabId:tabId};
+
+  if (attachedTabs[tabId] == "pausing")
+    return;
+
+  if (!attachedTabs[tabId])
+    chrome.debugger.attach(debuggeeId, version, onAttach.bind(null, debuggeeId));
+  else if (attachedTabs[tabId])
+    chrome.debugger.detach(debuggeeId, onDetach.bind(null, debuggeeId));
+}
+
+function onAttach(debuggeeId) {
+  if (chrome.extension.lastError) {
+    alert(chrome.extension.lastError.message);
+    return;
+  }
+
+  var tabId = debuggeeId.tabId;
+  chrome.browserAction.setIcon({tabId: tabId, path:"debuggerPausing.png"});
+  chrome.browserAction.setTitle({tabId: tabId, title:"Pausing JavaScript"});
+  attachedTabs[tabId] = "pausing";
+  chrome.debugger.sendCommand(
+      debuggeeId, "Debugger.enable", {},
+      onDebuggerEnabled.bind(null, debuggeeId));
+}
+
+function onDebuggerEnabled(debuggeeId) {
+  chrome.debugger.sendCommand(debuggeeId, "Debugger.pause");
+}
+
+function onEvent(debuggeeId, method) {
+  var tabId = debuggeeId.tabId;
+  if (method == "Debugger.paused") {
+    attachedTabs[tabId] = "paused";
+    chrome.browserAction.setIcon({tabId:tabId, path:"debuggerContinue.png"});
+    chrome.browserAction.setTitle({tabId:tabId, title:"Resume JavaScript"});
+  }
+}
+
+function onDetach(debuggeeId) {
+  var tabId = debuggeeId.tabId;
+  delete attachedTabs[tabId];
+  chrome.browserAction.setIcon({tabId:tabId, path:"debuggerPause.png"});
+  chrome.browserAction.setTitle({tabId:tabId, title:"Pause JavaScript"});
+}
diff --git a/chrome/common/extensions/docs/examples/api/debugger/pause-resume/debuggerContinue.png b/chrome/common/extensions/docs/examples/api/debugger/pause-resume/debuggerContinue.png
new file mode 100644
index 0000000..01d99ee
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/debugger/pause-resume/debuggerContinue.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/debugger/pause-resume/debuggerPause.png b/chrome/common/extensions/docs/examples/api/debugger/pause-resume/debuggerPause.png
new file mode 100644
index 0000000..21926b2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/debugger/pause-resume/debuggerPause.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/debugger/pause-resume/debuggerPausing.png b/chrome/common/extensions/docs/examples/api/debugger/pause-resume/debuggerPausing.png
new file mode 100644
index 0000000..bac5afe
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/debugger/pause-resume/debuggerPausing.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/debugger/pause-resume/manifest.json b/chrome/common/extensions/docs/examples/api/debugger/pause-resume/manifest.json
new file mode 100644
index 0000000..383de35
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/debugger/pause-resume/manifest.json
@@ -0,0 +1,17 @@
+{
+  "name": "JavaScript pause/resume",
+  "description": "Pauses / resumes JavaScript execution",
+  "version": "0.6",
+  "permissions": [
+    "tabs",
+    "debugger"
+  ],
+  "background": {
+    "scripts": ["background.js"]
+  },
+  "browser_action": {
+    "default_icon": "debuggerPause.png",
+    "default_title": "Pause JavaScript"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/devtools/audits/broken-links/background.js b/chrome/common/extensions/docs/examples/api/devtools/audits/broken-links/background.js
new file mode 100644
index 0000000..6751a24
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/devtools/audits/broken-links/background.js
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 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.
+
+function validateLinks(links, callback) {
+  var results = [];
+  var pendingRequests = 0;
+
+  function fetchLink(link) {
+    if (!/^http(s?):\/\//.test(link.href))
+      return;
+    var xhr = new XMLHttpRequest();
+    xhr.open("HEAD", link.href, true);
+    xhr.onreadystatechange = function() {
+      if (xhr.readyState < xhr.HEADERS_RECEIVED || xhr.processed)
+        return;
+      if (!xhr.status || xhr.status >= 400) {
+        results.push({
+            href: link.href,
+            text: link.text,
+            status: xhr.statusText
+        });
+      }
+      xhr.processed = true;
+      xhr.abort();
+      if (!--pendingRequests) {
+        callback({ total: links.length, badlinks: results });
+      }
+    }
+    try {
+      xhr.send(null);
+      pendingRequests++;
+    } catch (e) {
+      console.error("XHR failed for " + link.href + ", " + e);
+    }
+  }
+
+  for (var i = 0; i < links.length; ++i)
+    fetchLink(links[i]);
+}
+
+chrome.extension.onRequest.addListener(function(request, sender, callback) {
+  var tabId = request.tabId;
+  chrome.tabs.executeScript(tabId, { file: "content.js" }, function() {
+    chrome.tabs.sendRequest(tabId, {}, function(results) {
+      validateLinks(results, callback);
+    });
+  });
+});
diff --git a/chrome/common/extensions/docs/examples/api/devtools/audits/broken-links/content.js b/chrome/common/extensions/docs/examples/api/devtools/audits/broken-links/content.js
new file mode 100644
index 0000000..1cf0456
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/devtools/audits/broken-links/content.js
@@ -0,0 +1,20 @@
+function getLinks() {
+  var links = document.querySelectorAll("a");
+  var results = [];
+  var seenLinks = {};
+  for (var i  = 0; i < links.length; ++i) {
+    var text = links[i].textContent;
+    if (text.length > 100)
+      text = text.substring(0, 100) + "...";
+    var link = links[i].href.replace(/(.*)#?/, "$1");
+    if (seenLinks[link])
+      continue;
+    seenLinks[link] = 1;
+    results.push({ href: link, text: text });
+  }
+  return results;
+};
+
+chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
+  sendResponse(getLinks());
+});
diff --git a/chrome/common/extensions/docs/examples/api/devtools/audits/broken-links/devtools.html b/chrome/common/extensions/docs/examples/api/devtools/audits/broken-links/devtools.html
new file mode 100644
index 0000000..dc6e791
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/devtools/audits/broken-links/devtools.html
@@ -0,0 +1,5 @@
+<html>
+<head>
+<script src="devtools.js"></script>
+</head>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/devtools/audits/broken-links/devtools.js b/chrome/common/extensions/docs/examples/api/devtools/audits/broken-links/devtools.js
new file mode 100644
index 0000000..bc406c9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/devtools/audits/broken-links/devtools.js
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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.
+
+var category = chrome.experimental.devtools.audits.addCategory(
+    "Broken links", 1);
+category.onAuditStarted.addListener(function callback(auditResults) {
+  chrome.extension.sendRequest({ tabId: webInspector.inspectedWindow.tabId },
+      function(results) {
+    if (!results.badlinks.length) {
+      auditResults.addResult("No broken links",
+                             "There are no broken links on the page!",
+                             auditResults.Severity.Info);
+    }
+    else {
+      var details = auditResults.createResult(results.badlinks.length +
+          " links out of " + results.total + " are broken");
+      for (var i = 0; i < results.badlinks.length; ++i) {
+        details.addChild(auditResults.createURL(results.badlinks[i].href,
+                                                results.badlinks[i].text));
+      }
+      auditResults.addResult("Broken links found (" +
+                                 results.badlinks.length +
+                                 ")", "",
+                             auditResults.Severity.Severe,
+                             details);
+    }
+    auditResults.done();
+  });
+});
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/devtools/audits/broken-links/manifest.json b/chrome/common/extensions/docs/examples/api/devtools/audits/broken-links/manifest.json
new file mode 100644
index 0000000..ee3b5b7
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/devtools/audits/broken-links/manifest.json
@@ -0,0 +1,16 @@
+{
+  "name": "Broken Links",
+  "version": "1.1",
+  "description": "Extends the Developer Tools, adding an audit category that finds broken links on the inspected page.",
+  "background": {
+    "scripts": ["background.js"]
+  },
+  "devtools_page": "devtools.html",
+  "permissions":  [
+    "experimental",
+    "tabs",
+    "http://*/*",
+    "https://*/*"
+  ],
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/background.js b/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/background.js
new file mode 100644
index 0000000..8496dcb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/background.js
@@ -0,0 +1,16 @@
+// 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.
+
+const tab_log = function(json_args) {
+  var args = JSON.parse(unescape(json_args));
+  console[args[0]].apply(console, Array.prototype.slice.call(args, 1));
+}
+
+chrome.extension.onRequest.addListener(function(request) {
+  if (request.command !== 'sendToConsole')
+    return;
+  chrome.tabs.executeScript(request.tabId, {
+      code: "("+ tab_log + ")('" + request.args + "');",
+  });
+});
diff --git a/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/devtools.html b/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/devtools.html
new file mode 100644
index 0000000..553696e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/devtools.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Devtools Page</title>
+    <script src="devtools.js"></script>
+    <script src="./background.js" type="text/javascript"></script>
+  </head>
+  <body>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/devtools.js b/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/devtools.js
new file mode 100644
index 0000000..554d380
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/devtools.js
@@ -0,0 +1,185 @@
+// 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.
+
+function Console() {
+}
+
+Console.Type = {
+  LOG: "log",
+  DEBUG: "debug",
+  INFO: "info",
+  WARN: "warn",
+  ERROR: "error",
+  GROUP: "group",
+  GROUP_COLLAPSED: "groupCollapsed",
+  GROUP_END: "groupEnd"
+};
+
+Console.addMessage = function(type, format, args) {
+  chrome.extension.sendRequest({
+      command: "sendToConsole",
+      tabId: chrome.devtools.tabId,
+      args: escape(JSON.stringify(Array.prototype.slice.call(arguments, 0)))
+  });
+};
+
+// Generate Console output methods, i.e. Console.log(), Console.debug() etc.
+(function() {
+  var console_types = Object.getOwnPropertyNames(Console.Type);
+  for (var type = 0; type < console_types.length; ++type) {
+    var method_name = Console.Type[console_types[type]];
+    Console[method_name] = Console.addMessage.bind(Console, method_name);
+  }
+})();
+
+function ChromeFirePHP() {
+};
+
+ChromeFirePHP.handleFirePhpHeaders = function(har_entry) {
+  var response_headers = har_entry.response.headers;
+  var wf_header_map = {};
+  var had_wf_headers = false;
+
+  for (var i = 0; i < response_headers.length; ++i) {
+    var header = response_headers[i];
+    if (/^X-Wf-/.test(header.name)) {
+      wf_header_map[header.name] = header.value;
+      had_wf_headers = true;
+    }
+  }
+
+  var proto_header = wf_header_map["X-Wf-Protocol-1"];
+  if (!had_wf_headers || !this._checkProtoVersion(proto_header))
+    return;
+
+  var message_objects = this._buildMessageObjects(wf_header_map);
+  message_objects.sort(function(a, b) {
+      var aFile = a.File || "";
+      var bFile = b.File || "";
+      if (aFile !== bFile)
+        return aFile.localeCompare(bFile);
+      var aLine = a.Line !== undefined ? a.Line : -1;
+      var bLine = b.Line !== undefined ? b.Line : -1;
+      return aLine - bLine;
+  });
+
+  var context = { pageRef: har_entry.pageref };
+  for (var i = 0; i < message_objects.length; ++i)
+    this._processLogMessage(message_objects[i], context);
+  if (context.groupStarted)
+    Console.groupEnd();
+};
+
+ChromeFirePHP._processLogMessage = function(message, context) {
+  var meta = message[0];
+  if (!meta) {
+    Console.error("No Meta in FirePHP message");
+    return;
+  }
+
+  var body = message[1];
+  var type = meta.Type;
+  if (!type) {
+    Console.error("No Type for FirePHP message");
+    return;
+  }
+
+  switch (type) {
+    case "LOG":
+    case "INFO":
+    case "WARN":
+    case "ERROR":
+      if (!context.groupStarted) {
+        context.groupStarted = true;
+        Console.groupCollapsed(context.pageRef || "");
+      }
+      Console.addMessage(Console.Type[type], "%s%o",
+          (meta.Label ? meta.Label + ": " : ""), body);
+      break;
+    case "EXCEPTION":
+    case "TABLE":
+    case "TRACE":
+    case "GROUP_START":
+    case "GROUP_END":
+     // FIXME: implement
+     break;
+  }
+};
+
+ChromeFirePHP._buildMessageObjects = function(header_map)
+{
+  const normal_header_prefix = "X-Wf-1-1-1-";
+
+  return this._collectMessageObjectsForPrefix(header_map, normal_header_prefix);
+};
+
+ChromeFirePHP._collectMessageObjectsForPrefix = function(header_map, prefix) {
+  var results = [];
+  const header_regexp = /(?:\d+)?\|(.+)/;
+  var json = "";
+  for (var i = 1; ; ++i) {
+    var name = prefix + i;
+    var value = header_map[name];
+    if (!value)
+      break;
+
+    var match = value.match(header_regexp);
+    if (!match) {
+      Console.error("Failed to parse FirePHP log message: " + value);
+      break;
+    }
+    var json_part = match[1];
+    json += json_part.substring(0, json_part.lastIndexOf("|"));
+    if (json_part.charAt(json_part.length - 1) === "\\")
+      continue;
+    try {
+      var message = JSON.parse(json);
+      results.push(message);
+    } catch(e) {
+      Console.error("Failed to parse FirePHP log message: " + json);
+    }
+    json = "";
+  }
+  return results;
+};
+
+ChromeFirePHP._checkProtoVersion = function(proto_header) {
+  if (!proto_header) {
+    Console.warn("WildFire protocol header not found");
+    return;
+  }
+
+  var match = /http:\/\/meta\.wildfirehq\.org\/Protocol\/([^\/]+)\/(.+)/.exec(
+      proto_header);
+  if (!match) {
+    Console.warn("Invalid WildFire protocol header");
+    return;
+  }
+  var proto_name = match[1];
+  var proto_version = match[2];
+  if (proto_name !== "JsonStream" || proto_version !== "0.2") {
+    Console.warn(
+        "Unknown FirePHP protocol version: %s (expecting JsonStream/0.2)",
+        proto_name + "/" + proto_version);
+    return false;
+  }
+  return true;
+};
+
+chrome.devtools.network.addRequestHeaders({
+    "X-FirePHP-Version": "0.0.6"
+});
+
+chrome.devtools.network.getHAR(function(result) {
+  var entries = result.entries;
+  if (!entries.length) {
+    Console.warn("ChromeFirePHP suggests that you reload the page to track" +
+        " FirePHP messages for all the requests");
+  }
+  for (var i = 0; i < entries.length; ++i)
+    ChromeFirePHP.handleFirePhp_headers(entries[i]);
+
+  chrome.devtools.network.onRequestFinished.addListener(
+      ChromeFirePHP.handleFirePhpHeaders.bind(ChromeFirePHP));
+});
diff --git a/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/manifest.json b/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/manifest.json
new file mode 100644
index 0000000..e770f59
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/devtools/network/chrome-firephp/manifest.json
@@ -0,0 +1,14 @@
+{

+  "name": "FirePHP for Chrome",

+  "version": "1.0",

+  "minimum_chrome_version": "10.0",

+  "description": "Extends the Developer Tools, adding support for parsing FirePHP messages from server",

+  "devtools_page": "devtools.html",

+  "background": { "scripts": ["background.js"] },

+  "permissions": [

+    "tabs",

+    "http://*/*",

+    "https://*/*"

+  ],

+  "manifest_version": 2

+}

diff --git a/chrome/common/extensions/docs/examples/api/devtools/panels/chrome-query/devtools.html b/chrome/common/extensions/docs/examples/api/devtools/panels/chrome-query/devtools.html
new file mode 100644
index 0000000..bcf0413
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/devtools/panels/chrome-query/devtools.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<script src="devtools.js"></script>
+</body>
+</html>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/devtools/panels/chrome-query/devtools.js b/chrome/common/extensions/docs/examples/api/devtools/panels/chrome-query/devtools.js
new file mode 100644
index 0000000..5bf6a83
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/devtools/panels/chrome-query/devtools.js
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 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.
+
+// The function below is executed in the context of the inspected page.
+var page_getProperties = function() {
+  var data = window.jQuery && $0 ? jQuery.data($0) : {};
+  // Make a shallow copy with a null prototype, so that sidebar does not
+  // expose prototype.
+  var props = Object.getOwnPropertyNames(data);
+  var copy = { __proto__: null };
+  for (var i = 0; i < props.length; ++i)
+    copy[props[i]] = data[props[i]];
+  return copy;
+}
+
+chrome.devtools.panels.elements.createSidebarPane(
+    "jQuery Properties",
+    function(sidebar) {
+  function updateElementProperties() {
+    sidebar.setExpression("(" + page_getProperties.toString() + ")()");
+  }
+  updateElementProperties();
+  chrome.devtools.panels.elements.onSelectionChanged.addListener(
+      updateElementProperties);
+});
diff --git a/chrome/common/extensions/docs/examples/api/devtools/panels/chrome-query/manifest.json b/chrome/common/extensions/docs/examples/api/devtools/panels/chrome-query/manifest.json
new file mode 100644
index 0000000..a60485d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/devtools/panels/chrome-query/manifest.json
@@ -0,0 +1,7 @@
+{
+  "name": "Chrome Query",
+  "version": "1.1",
+  "description": "Extends the Developer Tools, adding a sidebar that displays the jQuery data associated with the selected DOM element.",
+  "devtools_page": "devtools.html",
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_links/icon.png b/chrome/common/extensions/docs/examples/api/downloads/download_links/icon.png
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_links/icon.png
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_links/manifest.json b/chrome/common/extensions/docs/examples/api/downloads/download_links/manifest.json
new file mode 100644
index 0000000..db7b971
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_links/manifest.json
@@ -0,0 +1,14 @@
+{
+  "name": "Download Selected Links",
+  "description": "Select links on a page and download them.",
+  "version": "0.1",
+  "minimum_chrome_version": "16.0.884",
+  "permissions": [
+    "experimental", "tabs", "http://*/*", "https://*/*"
+  ],
+  "browser_action": {
+    "default_icon": "icon.png",
+    "default_popup": "popup.html"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_links/popup.html b/chrome/common/extensions/docs/examples/api/downloads/download_links/popup.html
new file mode 100644
index 0000000..cba0b76
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_links/popup.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<head>
+<script src='popup.js'></script>
+</head>
+<body>
+<input type=text id=filter placeholder=Filter>
+<input type=checkbox id=regex>
+<label for=regex>Regex</label><br>
+<button id=download0>Download All!</button>
+<table id=links>
+  <tr>
+    <th><input type=checkbox checked id=toggle_all></th>
+    <th align=left>URL</th>
+  </tr>
+</table>
+<button id=download1>Download All!</button>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_links/popup.js b/chrome/common/extensions/docs/examples/api/downloads/download_links/popup.js
new file mode 100644
index 0000000..c889245
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_links/popup.js
@@ -0,0 +1,117 @@
+// Copyright (c) 2012 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.
+
+// This extension demonstrates using chrome.downloads.download() to
+// download URLs.
+
+var allLinks = [];
+var visibleLinks = [];
+
+// Display all visible links.
+function showLinks() {
+  var linksTable = document.getElementById('links');
+  while (linksTable.children.length > 1) {
+    linksTable.removeChild(linksTable.children[linksTable.children.length - 1])
+  }
+  for (var i = 0; i < visibleLinks.length; ++i) {
+    var row = document.createElement('tr');
+    var col0 = document.createElement('td');
+    var col1 = document.createElement('td');
+    var checkbox = document.createElement('input');
+    checkbox.checked = true;
+    checkbox.type = 'checkbox';
+    checkbox.id = 'check' + i;
+    col0.appendChild(checkbox);
+    col1.innerText = visibleLinks[i];
+    col1.style.whiteSpace = 'nowrap';
+    col1.onclick = function() {
+      checkbox.checked = !checkbox.checked;
+    }
+    row.appendChild(col0);
+    row.appendChild(col1);
+    linksTable.appendChild(row);
+  }
+}
+
+// Toggle the checked state of all visible links.
+function toggleAll() {
+  var checked = document.getElementById('toggle_all').checked;
+  for (var i = 0; i < visibleLinks.length; ++i) {
+    document.getElementById('check' + i).checked = checked;
+  }
+}
+
+// Download all visible checked links.
+function downloadCheckedLinks() {
+  for (var i = 0; i < visibleLinks.length; ++i) {
+    if (document.getElementById('check' + i).checked) {
+      chrome.downloads.download({url: visibleLinks[i]},
+                                             function(id) {
+      });
+    }
+  }
+  window.close();
+}
+
+// Re-filter allLinks into visibleLinks and reshow visibleLinks.
+function filterLinks() {
+  var filterValue = document.getElementById('filter').value;
+  if (document.getElementById('regex').checked) {
+    visibleLinks = allLinks.filter(function(link) {
+      return link.match(filterValue);
+    });
+  } else {
+    var terms = filterValue.split(' ');
+    visibleLinks = allLinks.filter(function(link) {
+      for (var termI = 0; termI < terms.length; ++termI) {
+        var term = terms[termI];
+        if (term.length != 0) {
+          var expected = (term[0] != '-');
+          if (!expected) {
+            term = term.substr(1);
+            if (term.length == 0) {
+              continue;
+            }
+          }
+          var found = (-1 !== link.indexOf(term));
+          if (found != expected) {
+            return false;
+          }
+        }
+      }
+      return true;
+    });
+  }
+  showLinks();
+}
+
+// Add links to allLinks and visibleLinks, sort and show them.  send_links.js is
+// injected into all frames of the active tab, so this listener may be called
+// multiple times.
+chrome.extension.onRequest.addListener(function(links) {
+  for (var index in links) {
+    allLinks.push(links[index]);
+  }
+  allLinks.sort();
+  visibleLinks = allLinks;
+  showLinks();
+});
+
+// Set up event handlers and inject send_links.js into all frames in the active
+// tab.
+window.onload = function() {
+  document.getElementById('filter').onkeyup = filterLinks;
+  document.getElementById('regex').onchange = filterLinks;
+  document.getElementById('toggle_all').onchange = toggleAll;
+  document.getElementById('download0').onclick = downloadCheckedLinks;
+  document.getElementById('download1').onclick = downloadCheckedLinks;
+
+  chrome.windows.getCurrent(function (currentWindow) {
+    chrome.tabs.query({active: true, windowId: currentWindow.id},
+                      function(activeTabs) {
+      chrome.tabs.executeScript(
+        activeTabs[0].id, {file: 'send_links.js', allFrames: true});
+    });
+  });
+};
diff --git a/chrome/common/extensions/docs/examples/api/downloads/download_links/send_links.js b/chrome/common/extensions/docs/examples/api/downloads/download_links/send_links.js
new file mode 100644
index 0000000..29a06a6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/downloads/download_links/send_links.js
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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.
+
+// Send back to the popup a sorted deduped list of valid link URLs on this page.
+// The popup injects this script into all frames in the active tab.
+
+var links = [].slice.apply(document.getElementsByTagName('a'));
+links = links.map(function(element) {
+  // Return an anchor's href attribute, stripping any URL fragment (hash '#').
+  // If the html specifies a relative path, chrome converts it to an absolute
+  // URL.
+  var href = element.href;
+  var hashIndex = href.indexOf('#');
+  if (hashIndex >= 0) {
+    href = href.substr(0, hashIndex);
+  }
+  return href;
+});
+
+links.sort();
+
+// Remove duplicates and invalid URLs.
+var kBadPrefix = 'javascript';
+for (var i = 0; i < links.length;) {
+  if (((i > 0) && (links[i] == links[i - 1])) ||
+      (links[i] == '') ||
+      (kBadPrefix == links[i].toLowerCase().substr(0, kBadPrefix.length))) {
+    links.splice(i, 1);
+  } else {
+    ++i;
+  }
+}
+
+chrome.extension.sendRequest(links);
diff --git a/chrome/common/extensions/docs/examples/api/eventPage/basic/background.js b/chrome/common/extensions/docs/examples/api/eventPage/basic/background.js
new file mode 100644
index 0000000..f94543f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/eventPage/basic/background.js
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 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.
+
+// Global variables only exist for the life of the page, so they get reset
+// each time the page is unloaded.
+var counter = 1;
+
+var lastTabId = -1;
+function sendMessage() {
+  chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
+    lastTabId = tabs[0].id;
+    chrome.tabs.sendMessage(lastTabId, "Background page started.");
+  });
+}
+
+sendMessage();
+chrome.browserAction.setBadgeText({text: "ON"});
+console.log("Loaded.");
+
+chrome.runtime.onInstalled.addListener(function() {
+  console.log("Installed.");
+
+  // localStorage is persisted, so it's a good place to keep state that you
+  // need to persist across page reloads.
+  localStorage.counter = 1;
+
+  // Register a webRequest rule to redirect bing to google.
+  var wr = chrome.declarativeWebRequest;
+  chrome.declarativeWebRequest.onRequest.addRules([{
+    id: "0",
+    conditions: [new wr.RequestMatcher({url: {hostSuffix: "bing.com"}})],
+    actions: [new wr.RedirectRequest({redirectUrl: "http://google.com"})]
+  }]);
+});
+
+chrome.bookmarks.onRemoved.addListener(function(id, info) {
+  alert("I never liked that site anyway.");
+});
+
+chrome.browserAction.onClicked.addListener(function() {
+  // The event page will unload after handling this event (assuming nothing
+  // else is keeping it awake). The content script will become the main way to
+  // interact with us.
+  chrome.tabs.create({url: "http://google.com"}, function(tab) {
+    chrome.tabs.executeScript(tab.id, {file: "content.js"}, function() {
+      // Note: we also sent a message above, upon loading the event page,
+      // but the content script will not be loaded at that point, so we send
+      // another here.
+      sendMessage();
+    });
+  });
+});
+
+chrome.experimental.keybinding.onCommand.addListener(function(command) {
+  chrome.tabs.create({url: "http://www.google.com/"});
+});
+
+chrome.extension.onMessage.addListener(function(msg, _, sendResponse) {
+  if (msg.setAlarm) {
+    chrome.alarms.create({delayInMinutes: 0.1});
+  } else if (msg.delayedResponse) {
+    // Note: setTimeout itself does NOT keep the page awake. We return true
+    // from the onMessage event handler, which keeps the message channel open -
+    // in turn keeping the event page awake - until we call sendResponse.
+    setTimeout(function() {
+      sendResponse("Got your message.");
+    }, 5000);
+    return true;
+  } else if (msg.getCounters) {
+    sendResponse({counter: counter++,
+                  persistentCounter: localStorage.counter++});
+  }
+  // If we don't return anything, the message channel will close, regardless
+  // of whether we called sendResponse.
+});
+
+chrome.alarms.onAlarm.addListener(function() {
+  alert("Time's up!");
+});
+
+chrome.runtime.onSuspend.addListener(function() {
+  chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
+    // After the unload event listener runs, the page will unload, so any
+    // asynchronous callbacks will not fire.
+    alert("This does not show up.");
+  });
+  console.log("Unloading.");
+  chrome.browserAction.setBadgeText({text: ""});
+  chrome.tabs.sendMessage(lastTabId, "Background page unloaded.");
+});
diff --git a/chrome/common/extensions/docs/examples/api/eventPage/basic/content.js b/chrome/common/extensions/docs/examples/api/eventPage/basic/content.js
new file mode 100644
index 0000000..cf9c603
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/eventPage/basic/content.js
@@ -0,0 +1,50 @@
+// Copyright (c) 2012 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.
+
+document.body.innerHTML = "";
+
+function addButton(name, cb) {
+  var a = document.createElement("button");
+  a.innerText = name;
+  a.onclick = cb;
+  document.body.appendChild(document.createElement("br"));
+  document.body.appendChild(a);
+}
+
+function log(str) {
+  console.log(str);
+  logDiv.innerHTML += str + "<br>";
+}
+
+addButton("Clear logs", function() {
+  logDiv.innerHTML = "";
+});
+
+addButton("Send message with delayed response", function() {
+  chrome.extension.sendMessage({delayedResponse: true}, function(response) {
+    log("Background page responded: " + response);
+  });
+});
+
+addButton("Show counters", function() {
+  chrome.extension.sendMessage({getCounters: true}, function(response) {
+    log("In-memory counter is: " + response.counter);
+    log("Persisted counter is: " + response.persistentCounter);
+  });
+});
+
+addButton("Set an alarm", function() {
+  chrome.extension.sendMessage({setAlarm: true});
+});
+
+chrome.extension.onMessage.addListener(function(msg, _, sendResponse) {
+  log("Got message from background page: " + msg);
+});
+
+var logDiv = document.createElement("div");
+logDiv.style.border = "1px dashed black";
+document.body.appendChild(document.createElement("br"));
+document.body.appendChild(logDiv);
+
+log("Ready.");
diff --git a/chrome/common/extensions/docs/examples/api/eventPage/basic/icon.png b/chrome/common/extensions/docs/examples/api/eventPage/basic/icon.png
new file mode 100644
index 0000000..e3cdb8a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/eventPage/basic/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/eventPage/basic/manifest.json b/chrome/common/extensions/docs/examples/api/eventPage/basic/manifest.json
new file mode 100644
index 0000000..2c2ba07
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/eventPage/basic/manifest.json
@@ -0,0 +1,24 @@
+{
+  "name": "Event Page Example",
+  "description": "Demonstrates usage and features of the event page",
+  "version": "1.0",
+  "manifest_version": 2,
+  "permissions": ["alarms", "tabs", "bookmarks", "experimental", "keybinding", "http://*.google.com/*"],
+  "background": {
+    "scripts": ["background.js"],
+    "persistent": false
+  },
+  "browser_action": {
+    "default_icon" : "icon.png",
+    "default_title": "Start Event Page"
+  },
+  "commands": {
+    "open-google": {
+      "description": "Open a tab to google.com",
+      "suggested_key": { "default": "Ctrl+Shift+L" }
+    },
+    "_execute_browser_action": {
+      "suggested_key": { "default": "Ctrl+Shift+K" }
+    }
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/manifest.json b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/manifest.json
new file mode 100644
index 0000000..66241fc
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/manifest.json
@@ -0,0 +1,16 @@
+{
+  "name" : "`extension.isAllowedFileSchemeAccess` and `extension.isAllowedIncognitoAccess` Example",
+  "version" : "1.0.0",
+  "description" : "Demonstrates the `extension.isAllowedFileSchemeAccess` and `extesion.isAllowedIncognitoAccess` APIs",
+  "permissions" : [ "file://*" ],
+  "browser_action" : {
+    "default_popup": "popup.html",
+    "default_icon" : "sample-19.png"
+  },
+  "icons" : {
+    "16" : "sample-16.png",
+    "48" : "sample-48.png",
+    "128" : "sample-128.png"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/popup.html b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/popup.html
new file mode 100644
index 0000000..29f8071
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/popup.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!--
+ * 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.
+-->
+<html>
+  <head>
+    <title>extension.isAllowedAccess Sample</title>
+    <link rel="stylesheet" href="./sample.css">
+  </head>
+  <body>
+    <h1>extension.isAllowedAccess Sample</h1>
+    <section>
+      <ol>
+        <li><p>
+          <span>1</span> chrome.extension.isAllowedFileSchemeAccess:
+          <code id="file">unknown</code> (unpacked extensions always have
+          file scheme access, you'll need to install this as a packed
+          extension to toggle it properly)
+        </p></li>
+        <li><p>
+          <span>2</span> chrome.extension.isAllowedIncognitoAccess:
+          <code id="incognito">unknown</code>
+        </p></li>
+      </ol>
+    </section>
+    <script src="./popup.js"></script>
+    <script>
+    </script>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/popup.js b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/popup.js
new file mode 100644
index 0000000..27a7f40
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/popup.js
@@ -0,0 +1,12 @@
+// 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.
+
+chrome.extension.isAllowedFileSchemeAccess(function(state) {
+  var el = document.getElementById('file');
+  el.textContent = el.className = state ? 'true': 'false';
+});
+chrome.extension.isAllowedIncognitoAccess(function(state) {
+  var el = document.getElementById('incognito');
+  el.textContent = el.className = state ? 'true': 'false';
+});
diff --git a/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/sample-128.png b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/sample-128.png
new file mode 100644
index 0000000..d733b1e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/sample-128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/sample-16.png b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/sample-16.png
new file mode 100644
index 0000000..dcc5c14
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/sample-16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/sample-19.png b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/sample-19.png
new file mode 100644
index 0000000..01e0aa8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/sample-19.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/sample-48.png b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/sample-48.png
new file mode 100644
index 0000000..3af1eb8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/sample-48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/sample.css b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/sample.css
new file mode 100644
index 0000000..2f35d4a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/extension/isAllowedAccess/sample.css
@@ -0,0 +1,61 @@
+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;
+}
+
+body > section {
+  border-radius: 5px;
+  background: -webkit-linear-gradient(rgba(234, 238, 243, 0.2), #EAEEF3),
+              -webkit-linear-gradient(left, #EAEEF3, #EAEEF3 97%, #D3D7DB);
+  font: 14px/1 Arial,Sans Serif;
+  padding: 10px;
+  width:  563px;
+  max-height: 400px;
+  overflow-y: auto;
+  box-shadow: inset 0px 2px 5px rgba(0,0,0,0.5);
+}
+
+body > section > ol {
+  padding: 0;
+  margin: 0;
+  list-style: none inside;
+}
+
+body > section > ol > li {
+  position: relative;
+  margin: 0.5em 0 0.5em 40px;
+}
+
+code {
+  word-wrap: break-word;
+  background: rgba(255,255,0, 0.5);
+}
+  code.true {
+    background: rgba(0, 255, 0, 0.5);
+  }
+  code.false {
+    background: rgba(255, 0, 0, 0.5);
+  }
+
+li > p > span:first-child {
+  position: absolute;
+  top: 0px;
+  left: -40px;
+  width: 30px;
+  text-align: right;
+  font: 30px/1 Helvetica, sans-serif;
+  font-weight: 700;
+}
+
+p {
+  min-height: 30px;
+  line-height: 1.2;
+}
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/css/chrome_shared.css b/chrome/common/extensions/docs/examples/api/fontSettings/css/chrome_shared.css
new file mode 100644
index 0000000..48a6c26
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/css/chrome_shared.css
@@ -0,0 +1,100 @@
+/* Copyright (c) 2012 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. */
+
+/* Copy of /resources/shared/css/chrome_shared.css for sample extension. */
+
+/* Prevent CSS from overriding the hidden property. */
+[hidden] {
+  display: none !important;
+}
+
+html.loading * {
+  -webkit-transition-delay: 0 !important;
+  -webkit-transition-duration: 0 !important;
+}
+
+body {
+  cursor: default;
+  margin: 0;
+}
+
+p {
+  line-height: 1.8em;
+}
+
+h1,
+h2,
+h3 {
+  -webkit-user-select: none;
+  font-weight: normal;
+  /* Makes the vertical size of the text the same for all fonts. */
+  line-height: 1;
+}
+
+h1 {
+  font-size: 1.5em;
+}
+
+h2 {
+  font-size: 1.3em;
+  margin-bottom: 0.4em;
+}
+
+h3 {
+  color: black;
+  font-size: 1.2em;
+  margin-bottom: 0.8em;
+}
+
+a {
+  color: rgb(17, 85, 204);
+  text-decoration: underline;
+}
+
+a:active {
+  color: rgb(5, 37, 119);
+}
+
+/* Elements that need to be LTR even in an RTL context, but should align
+ * right. (Namely, URLs, search engine names, etc.)
+ */
+html[dir='rtl'] .weakrtl {
+  direction: ltr;
+  text-align: right;
+}
+
+/* Input fields in search engine table need to be weak-rtl. Since those input
+ * fields are generated for all cr.ListItem elements (and we only want weakrtl
+ * on some), the class needs to be on the enclosing div.
+ */
+html[dir='rtl'] div.weakrtl input {
+  direction: ltr;
+  text-align: right;
+}
+
+html[dir='rtl'] .favicon-cell.weakrtl {
+  -webkit-padding-end: 22px;
+  -webkit-padding-start: 0;
+}
+
+/* weakrtl for selection drop downs needs to account for the fact that
+ * Webkit does not honor the text-align attribute for the select element.
+ * (See Webkit bug #40216)
+ */
+html[dir='rtl'] select.weakrtl {
+  direction: rtl;
+}
+
+html[dir='rtl'] select.weakrtl option {
+  direction: ltr;
+}
+
+/* WebKit does not honor alignment for text specified via placeholder attribute.
+ * This CSS is a workaround. Please remove once WebKit bug is fixed.
+ * https://bugs.webkit.org/show_bug.cgi?id=63367
+ */
+html[dir='rtl'] input.weakrtl::-webkit-input-placeholder,
+html[dir='rtl'] .weakrtl input::-webkit-input-placeholder {
+  direction: rtl;
+}
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/css/list.css b/chrome/common/extensions/docs/examples/api/fontSettings/css/list.css
new file mode 100644
index 0000000..15f433c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/css/list.css
@@ -0,0 +1,113 @@
+/* Copyright (c) 2012 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. */
+
+list,
+grid {
+  display: block;
+  outline: none;
+  overflow: auto;
+  position: relative; /* Make sure that item offsets are relative to the
+                         list. */
+}
+
+list > *,
+grid > * {
+  -webkit-user-select: none;
+  background-color: rgba(255, 255, 255, 0);
+  border: 1px solid rgba(255, 255, 255, 0); /* transparent white */
+  border-radius: 2px;
+  cursor: default;
+  line-height: 20px;
+  margin: -1px 0;
+  overflow: hidden;
+  padding: 0 3px;
+  position: relative; /* to allow overlap */
+  text-overflow: ellipsis;
+  white-space: pre;
+}
+
+list > * {
+  display: block;
+}
+
+grid > * {
+  display: inline-block;
+}
+
+list > [lead],
+grid > [lead] {
+  border-color: transparent;
+}
+
+list:focus > [lead],
+grid:focus > [lead] {
+  border-color: hsl(214, 91%, 65%);
+  z-index: 2;
+}
+
+list > [anchor],
+grid > [anchor] {
+
+}
+
+list:not([disabled]) > :hover,
+grid:not([disabled]) > :hover {
+  background-color: hsl(214, 91%, 97%);
+  border-color: hsl(214, 91%, 85%);
+  z-index: 1;
+}
+
+list > [selected],
+grid > [selected] {
+  background-color: hsl(0, 0%, 90%);
+  background-image: -webkit-linear-gradient(rgba(255, 255, 255, 0.8),
+                                            rgba(255, 255, 255, 0));
+  border-color: hsl(0, 0%, 85%);
+  z-index: 2;
+}
+
+list:focus > [selected],
+grid:focus > [selected] {
+  background-color: hsl(214, 91%, 89%);
+  border-color: hsl(214, 91%, 65%);
+}
+
+list:focus > [lead][selected],
+list > [selected]:hover,
+grid:focus > [lead][selected],
+grid > [selected]:hover {
+  background-color: hsl(214, 91%, 87%);
+  border-color: hsl(214, 91%, 65%);
+}
+
+list > .spacer,
+grid > .spacer {
+  border: 0;
+  box-sizing: border-box;
+  display: block;
+  margin: 0;
+  overflow: hidden;
+  visibility: hidden;
+}
+
+list :-webkit-any(
+    input[type='input'],
+    input[type='password'],
+    input[type='search'],
+    input[type='text'],
+    input[type='url']),
+list :-webkit-any(
+    button,
+    input[type='button'],
+    input[type='submit'],
+    select):not(.custom-appearance):not(.link-button) {
+  line-height: normal;
+  margin: 0;
+  vertical-align: middle;
+}
+
+list > [hidden],
+grid > [hidden] {
+  display: none;
+}
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/css/overlay.css b/chrome/common/extensions/docs/examples/api/fontSettings/css/overlay.css
new file mode 100644
index 0000000..f70d6dc
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/css/overlay.css
@@ -0,0 +1,163 @@
+/* Copyright (c) 2012 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. */
+
+/* The shield that overlays the background. */
+.overlay {
+  -webkit-box-align: center;
+  -webkit-box-orient: vertical;
+  -webkit-box-pack: center;
+  -webkit-transition: 200ms opacity;
+  background-color: rgba(255, 255, 255, 0.75);
+  bottom: 0;
+  display: -webkit-box;
+  left: 0;
+  overflow: auto;
+  padding: 20px;
+  position: fixed;
+  right: 0;
+  top: 0;
+}
+
+/* Used to slide in the overlay. */
+.overlay.transparent .page {
+  /* TODO(flackr): Add perspective(500px) rotateX(5deg) when accelerated
+   * compositing is enabled on chrome:// pages. See http://crbug.com/116800. */
+  -webkit-transform: scale(0.99) translateY(-20px);
+}
+
+/* The foreground dialog. */
+.overlay .page {
+  -webkit-border-radius: 3px;
+  -webkit-box-orient: vertical;
+  -webkit-transition: 200ms -webkit-transform;
+  background: white;
+  box-shadow: 0 4px 23px 5px rgba(0, 0, 0, 0.2), 0 2px 6px rgba(0,0,0,0.15);
+  color: #333;
+  display: -webkit-box;
+  min-width: 400px;
+  padding: 0;
+  position: relative;
+}
+
+/* If the options page is loading don't do the transition. */
+.loading .overlay,
+.loading .overlay .page {
+  -webkit-transition-duration: 0 !important;
+}
+
+/* keyframes used to pulse the overlay */
+@-webkit-keyframes pulse {
+ 0% {
+   -webkit-transform: scale(1);
+ }
+ 40% {
+   -webkit-transform: scale(1.02);
+  }
+ 60% {
+   -webkit-transform: scale(1.02);
+  }
+ 100% {
+   -webkit-transform: scale(1);
+ }
+}
+
+.overlay .page.pulse {
+  -webkit-animation-duration: 180ms;
+  -webkit-animation-iteration-count: 1;
+  -webkit-animation-name: pulse;
+  -webkit-animation-timing-function: ease-in-out;
+}
+
+.overlay .page > .close-button {
+  background-image: url('../images/x.png');
+  background-position: center;
+  background-repeat: no-repeat;
+  height: 14px;
+  position: absolute;
+  right: 7px;
+  top: 7px;
+  width: 14px;
+}
+
+html[dir='rtl'] .overlay .page > .close-button {
+  left: 10px;
+  right: auto;
+}
+
+.overlay .page > .close-button:hover {
+  background-image: url('../images/x-hover.png');
+}
+
+.overlay .page > .close-button:active {
+  background-image: url('../images/x-pressed.png');
+}
+
+.overlay .page h1 {
+  -webkit-padding-end: 24px;
+  -webkit-user-select: none;
+  color: #333;
+  /* 120% of the body's font-size of 84% is 16px. This will keep the relative
+   * size between the body and these titles consistent. */
+  font-size: 120%;
+  /* TODO(flackr): Pages like sync-setup and delete user collapse the margin
+   * above the top of the page. Use padding instead to make sure that the
+   * headers of these pages have the correct spacing, but this should not be
+   * necessary. See http://crbug.com/119029. */
+  margin: 0;
+  padding: 14px 17px 14px;
+  text-shadow: white 0 1px 2px;
+}
+
+.overlay .page .content-area {
+  -webkit-box-flex: 1;
+  overflow: auto;
+  padding: 6px 17px 6px;
+  position: relative;
+}
+
+.overlay .page .action-area {
+  -webkit-box-align: center;
+  -webkit-box-orient: horizontal;
+  -webkit-box-pack: end;
+  display: -webkit-box;
+  padding: 14px 17px;
+}
+
+html[dir='rtl'] .overlay .page .action-area {
+  left: 0;
+}
+
+.overlay .page .action-area-right {
+  display: -webkit-box;
+}
+
+.overlay .page .button-strip {
+  -webkit-box-orient: horizontal;
+  display: -webkit-box;
+}
+
+.overlay .page .button-strip > button {
+  -webkit-margin-start: 10px;
+  display: block;
+}
+
+/* On OSX 10.7, hidden scrollbars may prevent the user from realizing that the
+ * overlay contains scrollable content. To resolve this, style the scrollbars on
+ * OSX so they are always visible. See http://crbug.com/123010. */
+<if expr="is_macosx">
+.overlay .page .content-area::-webkit-scrollbar {
+  -webkit-appearance: none;
+  width: 11px;
+}
+
+.overlay .page .content-area::-webkit-scrollbar-thumb {
+  background-color: rgba(0, 0, 0, 0.2);
+  border: 2px solid white;
+  border-radius: 8px;
+}
+
+.overlay .page .content-area::-webkit-scrollbar-thumb:hover {
+  background-color: rgba(0, 0, 0, 0.5);
+}
+</if>
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/css/uber_shared.css b/chrome/common/extensions/docs/examples/api/fontSettings/css/uber_shared.css
new file mode 100644
index 0000000..9bf8b88
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/css/uber_shared.css
@@ -0,0 +1,162 @@
+/* Copyright (c) 2012 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. */
+
+body.uber-frame {
+  -webkit-margin-start: 155px;
+  color: rgb(48, 57, 66);
+}
+
+html[dir='rtl'] body.uber-frame {
+  /* Enable vertical scrollbar at all times in RTL to avoid visual glitches when
+   * showing sub-pages that vertically overflow. */
+  overflow-y: scroll;
+}
+
+/* TODO(dbeam): Remove .page class from overlays in settings so the junk below
+ * isn't necessary. */
+body.uber-frame #extension-settings.page,
+body.uber-frame #mainview-content .page,
+body.uber-frame .subpage-sheet-container .page,
+body.uber-frame > .page {
+  -webkit-margin-end: 24px;
+  min-width: 576px;
+  padding-bottom: 20px;
+  padding-top: 55px;
+}
+
+body.uber-frame header {
+  background-image: -webkit-linear-gradient(white,
+                                            white 40%,
+                                            rgba(255, 255, 255, 0.92));
+  left: 155px;
+  /* <section>s in options currently amount to 638px total, broken up into
+   * 600px max-width + 18px -webkit-padding-start + 20px -webkit-margin-end
+   * so we mirror this value here so the headers match width and horizontal
+   * alignment when scrolling sideways. */
+  max-width: 738px;
+  min-width: 600px;
+  position: fixed;
+  right: 0;
+  top: 0;
+  /* list.css sets a z-index of up to 2, this is set to 3 to ensure that the
+   * header is in front of the selected list item. */
+  z-index: 3;
+}
+
+html[dir='rtl'] body.uber-frame header {
+  left: 0;
+  right: 155px;
+}
+
+body.uber-frame header > .search-field-container,
+body.uber-frame header > .header-extras,
+body.uber-frame header > button {
+  position: absolute;
+  right: 20px;
+  top: 21px;
+}
+
+html[dir='rtl'] body.uber-frame header > .search-field-container,
+html[dir='rtl'] body.uber-frame header > .header-extras,
+html[dir='rtl'] body.uber-frame header > button {
+  left: 20px;
+  right: auto;
+}
+
+body.uber-frame header input[type='search'],
+body.uber-frame header input[type='text'],
+body.uber-frame header button {
+  margin: 0;
+}
+
+body.uber-frame header > h1 {
+  margin: 0;
+  padding: 21px 0 13px;
+}
+
+/* Create a border under the h1 (but before anything that gets appended
+ * to the end of the header). */
+body.uber-frame header > h1::after {
+  -webkit-margin-end: 20px;
+  background-color: #eee;
+  content: ' ';
+  display: block;
+  height: 1px;
+  position: relative;
+  top: 13px;
+}
+
+body.uber-frame footer {
+  border-top: 1px solid #eee;
+  margin-top: 16px;
+  /* min-width and max-width should match the header */
+  max-width: 638px;
+  min-width: 600px;
+  padding: 8px 0;
+}
+
+/* Sections are used in options pages, help page and history page. This defines
+ * the section metrics to match the header metrics above. */
+body.uber-frame section {
+  -webkit-padding-start: 18px;
+  margin-bottom: 24px;
+  margin-top: 8px;
+  max-width: 600px;
+}
+
+body.uber-frame section:last-of-type {
+  margin-bottom: 0;
+}
+
+body.uber-frame section > h3 {
+  -webkit-margin-start: -18px;
+}
+
+body.uber-frame section > div:only-of-type {
+  -webkit-box-flex: 1;
+}
+
+/* Styles for a hideable notification banner at the top of a page. */
+.page.showing-banner {
+  margin-top: 45px;
+}
+
+.page-banner {
+  background-color: white;
+  width: 100%;
+  z-index: 2;
+}
+
+.page:not(.showing-banner) .page-banner {
+  display: none;
+}
+
+.page-banner-gradient {
+  background: -webkit-linear-gradient(rgb(255, 242, 183),
+                                      rgb(250, 230, 145));
+  border: 1px solid rgb(201, 189, 141);
+  border-radius: 3px;
+  height: 25px;
+  margin: 9px 9px 0 9px;
+}
+
+.page-banner .page-banner-gradient {
+  -webkit-margin-end: 20px;
+  -webkit-margin-start: 0;
+  margin-bottom: 9px;
+}
+
+.page-banner-text {
+  background-image: url('chrome://theme/IDR_MANAGED');
+  background-position: 5px center;
+  background-repeat: no-repeat;
+  display: block;
+  line-height: 24px;
+  padding-left: 26px;
+}
+
+.page-banner.clickable:active .page-banner-text {
+  background: -webkit-linear-gradient(rgb(250, 230, 145),
+                                      rgb(255, 242, 183));
+}
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/css/widgets.css b/chrome/common/extensions/docs/examples/api/fontSettings/css/widgets.css
new file mode 100644
index 0000000..471e354
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/css/widgets.css
@@ -0,0 +1,306 @@
+/* Copyright (c) 2012 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.
+ */
+
+/* This file defines styles for form controls. The order of rule blocks is
+ * important as there are some rules with equal specificity that rely on order
+ * as a tiebreaker. These are marked with OVERRIDE.
+ */
+
+/* Default state **************************************************************/
+
+:-webkit-any(button,
+             input[type='button'],
+             input[type='submit']):not(.custom-appearance):not(.link-button),
+select,
+input[type='checkbox'],
+input[type='radio'] {
+  -webkit-appearance: none;
+  -webkit-user-select: none;
+  background-image: -webkit-linear-gradient(#ededed, #ededed 38%, #dedede);
+  border: 1px solid rgba(0, 0, 0, 0.25);
+  border-radius: 2px;
+  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08),
+      inset 0 1px 2px rgba(255, 255, 255, 0.75);
+  color: #444;
+  font: inherit;
+  margin: 0 1px 0 0;
+  text-shadow: 0 1px 0 rgb(240, 240, 240);
+}
+
+:-webkit-any(button,
+             input[type='button'],
+             input[type='submit']):not(.custom-appearance):not(.link-button),
+select {
+  min-height: 2em;
+  min-width: 4em;
+<if expr="is_win">
+  /* The following platform-specific rule is necessary to get adjacent
+   * buttons, text inputs, and so forth to align on their borders while also
+   * aligning on the text's baselines. */
+  padding-bottom: 1px;
+</if>
+}
+
+:-webkit-any(button,
+             input[type='button'],
+             input[type='submit']):not(.custom-appearance):not(.link-button) {
+  -webkit-padding-end: 10px;
+  -webkit-padding-start: 10px;
+}
+
+select {
+  -webkit-appearance: none;
+  -webkit-padding-end: 20px;
+  -webkit-padding-start: 6px;
+  /* OVERRIDE */
+  background-image: url('../images/select.png'),
+      -webkit-linear-gradient(#ededed, #ededed 38%, #dedede);
+  background-position: right center;
+  background-repeat: no-repeat;
+}
+
+html[dir='rtl'] select {
+  background-position: center left;
+}
+
+input[type='checkbox'] {
+  bottom: 2px;
+  height: 13px;
+  position: relative;
+  vertical-align: middle;
+  width: 13px;
+}
+
+input[type='radio'] {
+  /* OVERRIDE */
+  border-radius: 100%;
+  bottom: 3px;
+  height: 15px;
+  position: relative;
+  vertical-align: middle;
+  width: 15px;
+}
+
+/* TODO(estade): add more types here? */
+input[type='password'],
+input[type='search'],
+input[type='text'],
+input[type='url'],
+input[type='number'],
+input:not([type]),
+textarea {
+  border: 1px solid #bfbfbf;
+  border-radius: 2px;
+  box-sizing: border-box;
+  color: #444;
+  font: inherit;
+  margin: 0;
+  /* Use min-height to accommodate addditional padding for touch as needed. */
+  min-height: 2em;
+  padding: 3px;
+<if expr="is_win or is_macosx">
+  /* For better alignment between adjacent buttons and inputs. */
+  padding-bottom: 4px;
+</if>
+}
+
+input[type='search'] {
+  -webkit-appearance: textfield;
+  /* NOTE: Keep a relatively high min-width for this so we don't obscure the end
+   * of the default text in relatively spacious languages (i.e. German). */
+  min-width: 160px;
+}
+
+/* Checked ********************************************************************/
+
+input[type='checkbox']:checked::before {
+  -webkit-user-select: none;
+  background-image: url('../images/check.png');
+  background-size: 100% 100%;
+  content: '';
+  display: block;
+  height: 100%;
+  width: 100%;
+}
+
+html[dir='rtl'] input[type='checkbox']:checked::before {
+  -webkit-transform: scaleX(-1);
+}
+
+input[type='radio']:checked::before {
+  background-color: #666;
+  border-radius: 100%;
+  bottom: 3px;
+  content: '';
+  display: block;
+  left: 3px;
+  position: absolute;
+  right: 3px;
+  top: 3px;
+}
+
+/* Hover **********************************************************************/
+
+:enabled:hover:-webkit-any(
+    select,
+    input[type='checkbox'],
+    input[type='radio'],
+    :-webkit-any(
+        button,
+        input[type='button'],
+        input[type='submit']):not(.custom-appearance):not(.link-button)) {
+  background-image: -webkit-linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0);
+  border-color: rgba(0, 0, 0, 0.3);
+  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.12),
+      inset 0 1px 2px rgba(255, 255, 255, 0.95);
+  color: black;
+}
+
+:enabled:hover:-webkit-any(select) {
+  /* OVERRIDE */
+  background-image: url('../images/select.png'),
+      -webkit-linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0);
+}
+
+/* Active *********************************************************************/
+
+:enabled:active:-webkit-any(
+    select,
+    input[type='checkbox'],
+    input[type='radio'],
+    :-webkit-any(
+        button,
+        input[type='button'],
+        input[type='submit']):not(.custom-appearance):not(.link-button)) {
+  background-image: -webkit-linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7);
+  box-shadow: none;
+  text-shadow: none;
+}
+
+:enabled:active:-webkit-any(select) {
+  /* OVERRIDE */
+  background-image: url('../images/select.png'),
+      -webkit-linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7);
+}
+
+/* Disabled *******************************************************************/
+
+:disabled:-webkit-any(
+    button,
+    input[type='button'],
+    input[type='submit']):not(.custom-appearance):not(.link-button),
+select:disabled {
+  background-image: -webkit-linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6);
+  border-color: rgba(80, 80, 80, 0.2);
+  box-shadow: 0 1px 0 rgba(80, 80, 80, 0.08),
+      inset 0 1px 2px rgba(255, 255, 255, 0.75);
+  color: #aaa;
+}
+
+select:disabled {
+  /* OVERRIDE */
+  background-image: url('../images/disabled_select.png'),
+      -webkit-linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6);
+}
+
+input:disabled:-webkit-any([type='checkbox'],
+                           [type='radio']) {
+  opacity: .75;
+}
+
+input:disabled:-webkit-any([type='password'],
+                           [type='search'],
+                           [type='text'],
+                           [type='url'],
+                           :not([type])) {
+  color: #999;
+}
+
+/* Focus **********************************************************************/
+
+:enabled:focus:-webkit-any(
+    select,
+    input[type='checkbox'],
+    input[type='password'],
+    input[type='radio'],
+    input[type='search'],
+    input[type='text'],
+    input[type='url'],
+    input:not([type]),
+    :-webkit-any(
+         button,
+         input[type='button'],
+         input[type='submit']):not(.custom-appearance):not(.link-button)) {
+  /* OVERRIDE */
+  -webkit-transition: border-color 200ms;
+  /* We use border color because it follows the border radius (unlike outline).
+   * This is particularly noticeable on mac. */
+  border-color: rgb(77, 144, 254);
+  outline: none;
+}
+
+/* Link buttons ***************************************************************/
+
+.link-button {
+  -webkit-box-shadow: none;
+  background: transparent none;
+  border: none;
+  color: rgb(17, 85, 204);
+  cursor: pointer;
+  /* Input elements have -webkit-small-control which can override the body font.
+   * Resolve this by using 'inherit'. */
+  font: inherit;
+  margin: 0;
+  padding: 0 4px;
+}
+
+.link-button:hover {
+  text-decoration: underline;
+}
+
+.link-button:active {
+  color: rgb(5, 37, 119);
+  text-decoration: underline;
+}
+
+.link-button[disabled] {
+  color: #999;
+  cursor: default;
+  text-decoration: none;
+}
+
+/* Checkbox/radio helpers ******************************************************
+ *
+ * .checkbox and .radio classes wrap labels. Checkboxes and radios should use
+ * these classes with the markup structure:
+ *
+ *   <div class="checkbox">
+ *     <label>
+ *       <input type="checkbox"></input>
+ *       <span>
+ *     </label>
+ *   </div>
+ */
+
+:-webkit-any(.checkbox, .radio) label {
+  /* Don't expand horizontally: <http://crbug.com/112091>. */
+  display: -webkit-inline-box;
+  padding-bottom: 7px;
+  padding-top: 7px;
+}
+
+:-webkit-any(.checkbox, .radio) label input ~ span {
+  -webkit-margin-start: 0.6em;
+  /* Make sure long spans wrap at the same horizontal position they start. */
+  display: block;
+}
+
+:-webkit-any(.checkbox, .radio) label:hover {
+  color: black;
+}
+
+label > input:disabled:-webkit-any([type='checkbox'], [type='radio']) ~ span {
+  color: #999;
+}
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/fonts128.png b/chrome/common/extensions/docs/examples/api/fontSettings/fonts128.png
new file mode 100644
index 0000000..32a435a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/fonts128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/fonts16.png b/chrome/common/extensions/docs/examples/api/fontSettings/fonts16.png
new file mode 100644
index 0000000..990f26b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/fonts16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/images/select.png b/chrome/common/extensions/docs/examples/api/fontSettings/images/select.png
new file mode 100644
index 0000000..bacfe90
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/images/select.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/images/x-hover.png b/chrome/common/extensions/docs/examples/api/fontSettings/images/x-hover.png
new file mode 100644
index 0000000..ee6800c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/images/x-hover.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/images/x-pressed.png b/chrome/common/extensions/docs/examples/api/fontSettings/images/x-pressed.png
new file mode 100644
index 0000000..6af6d1f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/images/x-pressed.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/images/x.png b/chrome/common/extensions/docs/examples/api/fontSettings/images/x.png
new file mode 100644
index 0000000..31317fc
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/images/x.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/js/cr.js b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr.js
new file mode 100644
index 0000000..ce70365
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr.js
@@ -0,0 +1,376 @@
+// Copyright (c) 2012 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.
+
+/**
+ * The global object.
+ * @type {!Object}
+ * @const
+ */
+var global = this;
+
+/** Platform, package, object property, and Event support. **/
+this.cr = (function() {
+  'use strict';
+
+  /**
+   * Builds an object structure for the provided namespace path,
+   * ensuring that names that already exist are not overwritten. For
+   * example:
+   * "a.b.c" -> a = {};a.b={};a.b.c={};
+   * @param {string} name Name of the object that this file defines.
+   * @param {*=} opt_object The object to expose at the end of the path.
+   * @param {Object=} opt_objectToExportTo The object to add the path to;
+   *     default is {@code global}.
+   * @private
+   */
+  function exportPath(name, opt_object, opt_objectToExportTo) {
+    var parts = name.split('.');
+    var cur = opt_objectToExportTo || global;
+
+    for (var part; parts.length && (part = parts.shift());) {
+      if (!parts.length && opt_object !== undefined) {
+        // last part and we have an object; use it
+        cur[part] = opt_object;
+      } else if (part in cur) {
+        cur = cur[part];
+      } else {
+        cur = cur[part] = {};
+      }
+    }
+    return cur;
+  };
+
+  /**
+   * Fires a property change event on the target.
+   * @param {EventTarget} target The target to dispatch the event on.
+   * @param {string} propertyName The name of the property that changed.
+   * @param {*} newValue The new value for the property.
+   * @param {*} oldValue The old value for the property.
+   */
+  function dispatchPropertyChange(target, propertyName, newValue, oldValue) {
+    var e = new cr.Event(propertyName + 'Change');
+    e.propertyName = propertyName;
+    e.newValue = newValue;
+    e.oldValue = oldValue;
+    target.dispatchEvent(e);
+  }
+
+  /**
+   * Converts a camelCase javascript property name to a hyphenated-lower-case
+   * attribute name.
+   * @param {string} jsName The javascript camelCase property name.
+   * @return {string} The equivalent hyphenated-lower-case attribute name.
+   */
+  function getAttributeName(jsName) {
+    return jsName.replace(/([A-Z])/g, '-$1').toLowerCase();
+  }
+
+  /**
+   * The kind of property to define in {@code defineProperty}.
+   * @enum {number}
+   * @const
+   */
+  var PropertyKind = {
+    /**
+     * Plain old JS property where the backing data is stored as a "private"
+     * field on the object.
+     */
+    JS: 'js',
+
+    /**
+     * The property backing data is stored as an attribute on an element.
+     */
+    ATTR: 'attr',
+
+    /**
+     * The property backing data is stored as an attribute on an element. If the
+     * element has the attribute then the value is true.
+     */
+    BOOL_ATTR: 'boolAttr'
+  };
+
+  /**
+   * Helper function for defineProperty that returns the getter to use for the
+   * property.
+   * @param {string} name The name of the property.
+   * @param {cr.PropertyKind} kind The kind of the property.
+   * @return {function():*} The getter for the property.
+   */
+  function getGetter(name, kind) {
+    switch (kind) {
+      case PropertyKind.JS:
+        var privateName = name + '_';
+        return function() {
+          return this[privateName];
+        };
+      case PropertyKind.ATTR:
+        var attributeName = getAttributeName(name);
+        return function() {
+          return this.getAttribute(attributeName);
+        };
+      case PropertyKind.BOOL_ATTR:
+        var attributeName = getAttributeName(name);
+        return function() {
+          return this.hasAttribute(attributeName);
+        };
+    }
+  }
+
+  /**
+   * Helper function for defineProperty that returns the setter of the right
+   * kind.
+   * @param {string} name The name of the property we are defining the setter
+   *     for.
+   * @param {cr.PropertyKind} kind The kind of property we are getting the
+   *     setter for.
+   * @param {function(*):void} opt_setHook A function to run after the property
+   *     is set, but before the propertyChange event is fired.
+   * @return {function(*):void} The function to use as a setter.
+   */
+  function getSetter(name, kind, opt_setHook) {
+    switch (kind) {
+      case PropertyKind.JS:
+        var privateName = name + '_';
+        return function(value) {
+          var oldValue = this[name];
+          if (value !== oldValue) {
+            this[privateName] = value;
+            if (opt_setHook)
+              opt_setHook.call(this, value, oldValue);
+            dispatchPropertyChange(this, name, value, oldValue);
+          }
+        };
+
+      case PropertyKind.ATTR:
+        var attributeName = getAttributeName(name);
+        return function(value) {
+          var oldValue = this[name];
+          if (value !== oldValue) {
+            if (value == undefined)
+              this.removeAttribute(attributeName);
+            else
+              this.setAttribute(attributeName, value);
+            if (opt_setHook)
+              opt_setHook.call(this, value, oldValue);
+            dispatchPropertyChange(this, name, value, oldValue);
+          }
+        };
+
+      case PropertyKind.BOOL_ATTR:
+        var attributeName = getAttributeName(name);
+        return function(value) {
+          var oldValue = this[name];
+          if (value !== oldValue) {
+            if (value)
+              this.setAttribute(attributeName, name);
+            else
+              this.removeAttribute(attributeName);
+            if (opt_setHook)
+              opt_setHook.call(this, value, oldValue);
+            dispatchPropertyChange(this, name, value, oldValue);
+          }
+        };
+    }
+  }
+
+  /**
+   * Defines a property on an object. When the setter changes the value a
+   * property change event with the type {@code name + 'Change'} is fired.
+   * @param {!Object} obj The object to define the property for.
+   * @param {string} name The name of the property.
+   * @param {cr.PropertyKind=} opt_kind What kind of underlying storage to use.
+   * @param {function(*):void} opt_setHook A function to run after the
+   *     property is set, but before the propertyChange event is fired.
+   */
+  function defineProperty(obj, name, opt_kind, opt_setHook) {
+    if (typeof obj == 'function')
+      obj = obj.prototype;
+
+    var kind = opt_kind || PropertyKind.JS;
+
+    if (!obj.__lookupGetter__(name))
+      obj.__defineGetter__(name, getGetter(name, kind));
+
+    if (!obj.__lookupSetter__(name))
+      obj.__defineSetter__(name, getSetter(name, kind, opt_setHook));
+  }
+
+  /**
+   * Counter for use with createUid
+   */
+  var uidCounter = 1;
+
+  /**
+   * @return {number} A new unique ID.
+   */
+  function createUid() {
+    return uidCounter++;
+  }
+
+  /**
+   * Returns a unique ID for the item. This mutates the item so it needs to be
+   * an object
+   * @param {!Object} item The item to get the unique ID for.
+   * @return {number} The unique ID for the item.
+   */
+  function getUid(item) {
+    if (item.hasOwnProperty('uid'))
+      return item.uid;
+    return item.uid = createUid();
+  }
+
+  /**
+   * Dispatches a simple event on an event target.
+   * @param {!EventTarget} target The event target to dispatch the event on.
+   * @param {string} type The type of the event.
+   * @param {boolean=} opt_bubbles Whether the event bubbles or not.
+   * @param {boolean=} opt_cancelable Whether the default action of the event
+   *     can be prevented.
+   * @return {boolean} If any of the listeners called {@code preventDefault}
+   *     during the dispatch this will return false.
+   */
+  function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) {
+    var e = new cr.Event(type, opt_bubbles, opt_cancelable);
+    return target.dispatchEvent(e);
+  }
+
+  /**
+   * Calls |fun| and adds all the fields of the returned object to the object
+   * named by |name|. For example, cr.define('cr.ui', function() {
+   *   function List() {
+   *     ...
+   *   }
+   *   function ListItem() {
+   *     ...
+   *   }
+   *   return {
+   *     List: List,
+   *     ListItem: ListItem,
+   *   };
+   * });
+   * defines the functions cr.ui.List and cr.ui.ListItem.
+   * @param {string} name The name of the object that we are adding fields to.
+   * @param {!Function} fun The function that will return an object containing
+   *     the names and values of the new fields.
+   */
+  function define(name, fun) {
+    var obj = exportPath(name);
+    var exports = fun();
+    for (var propertyName in exports) {
+      // Maybe we should check the prototype chain here? The current usage
+      // pattern is always using an object literal so we only care about own
+      // properties.
+      var propertyDescriptor = Object.getOwnPropertyDescriptor(exports,
+                                                               propertyName);
+      if (propertyDescriptor)
+        Object.defineProperty(obj, propertyName, propertyDescriptor);
+    }
+  }
+
+  /**
+   * Adds a {@code getInstance} static method that always return the same
+   * instance object.
+   * @param {!Function} ctor The constructor for the class to add the static
+   *     method to.
+   */
+  function addSingletonGetter(ctor) {
+    ctor.getInstance = function() {
+      return ctor.instance_ || (ctor.instance_ = new ctor());
+    };
+  }
+
+  /**
+   * Creates a new event to be used with cr.EventTarget or DOM EventTarget
+   * objects.
+   * @param {string} type The name of the event.
+   * @param {boolean=} opt_bubbles Whether the event bubbles.
+   *     Default is false.
+   * @param {boolean=} opt_preventable Whether the default action of the event
+   *     can be prevented.
+   * @constructor
+   * @extends {Event}
+   */
+  function Event(type, opt_bubbles, opt_preventable) {
+    var e = cr.doc.createEvent('Event');
+    e.initEvent(type, !!opt_bubbles, !!opt_preventable);
+    e.__proto__ = global.Event.prototype;
+    return e;
+  };
+
+  /**
+   * Initialization which must be deferred until run-time.
+   */
+  function initialize() {
+    // If 'document' isn't defined, then we must be being pre-compiled,
+    // so set a trap so that we're initialized on first access at run-time.
+    if (!global.document) {
+      var originalCr = cr;
+
+      Object.defineProperty(global, 'cr', {
+        get: function() {
+          Object.defineProperty(global, 'cr', {value: originalCr});
+          originalCr.initialize();
+          return originalCr;
+        },
+        configurable: true
+      });
+
+      return;
+    }
+
+    Event.prototype = {__proto__: global.Event.prototype};
+
+    cr.doc = document;
+
+    /**
+     * Whether we are using a Mac or not.
+     */
+    cr.isMac = /Mac/.test(navigator.platform);
+
+    /**
+     * Whether this is on the Windows platform or not.
+     */
+    cr.isWindows = /Win/.test(navigator.platform);
+
+    /**
+     * Whether this is on chromeOS or not.
+     */
+    cr.isChromeOS = /CrOS/.test(navigator.userAgent);
+
+    /**
+     * Whether this is on vanilla Linux (not chromeOS).
+     */
+    cr.isLinux = /Linux/.test(navigator.userAgent);
+
+    /**
+     * Whether this uses GTK or not.
+     */
+    cr.isGTK = /GTK/.test(chrome.toolkit);
+
+    /**
+     * Whether this uses the views toolkit or not.
+     */
+    cr.isViews = /views/.test(chrome.toolkit);
+  }
+
+  return {
+    addSingletonGetter: addSingletonGetter,
+    createUid: createUid,
+    define: define,
+    defineProperty: defineProperty,
+    dispatchPropertyChange: dispatchPropertyChange,
+    dispatchSimpleEvent: dispatchSimpleEvent,
+    Event: Event,
+    getUid: getUid,
+    initialize: initialize,
+    PropertyKind: PropertyKind
+  };
+})();
+
+
+/**
+ * TODO(kgr): Move this to another file which is to be loaded last.
+ * This will be done as part of future work to make this code pre-compilable.
+ */
+cr.initialize();
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/event_target.js b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/event_target.js
new file mode 100644
index 0000000..5bcb41d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/event_target.js
@@ -0,0 +1,104 @@
+// Copyright (c) 2010 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 contains an implementation of the EventTarget interface
+ * as defined by DOM Level 2 Events.
+ */
+
+cr.define('cr', function() {
+
+  /**
+   * Creates a new EventTarget. This class implements the DOM level 2
+   * EventTarget interface and can be used wherever those are used.
+   * @constructor
+   */
+  function EventTarget() {
+  }
+
+  EventTarget.prototype = {
+
+    /**
+     * Adds an event listener to the target.
+     * @param {string} type The name of the event.
+     * @param {!Function|{handleEvent:Function}} handler The handler for the
+     *     event. This is called when the event is dispatched.
+     */
+    addEventListener: function(type, handler) {
+      if (!this.listeners_)
+        this.listeners_ = Object.create(null);
+      if (!(type in this.listeners_)) {
+        this.listeners_[type] = [handler];
+      } else {
+        var handlers = this.listeners_[type];
+        if (handlers.indexOf(handler) < 0)
+          handlers.push(handler);
+      }
+    },
+
+    /**
+     * Removes an event listener from the target.
+     * @param {string} type The name of the event.
+     * @param {!Function|{handleEvent:Function}} handler The handler for the
+     *     event.
+     */
+    removeEventListener: function(type, handler) {
+      if (!this.listeners_)
+        return;
+      if (type in this.listeners_) {
+        var handlers = this.listeners_[type];
+        var index = handlers.indexOf(handler);
+        if (index >= 0) {
+          // Clean up if this was the last listener.
+          if (handlers.length == 1)
+            delete this.listeners_[type];
+          else
+            handlers.splice(index, 1);
+        }
+      }
+    },
+
+    /**
+     * Dispatches an event and calls all the listeners that are listening to
+     * the type of the event.
+     * @param {!cr.event.Event} event The event to dispatch.
+     * @return {boolean} Whether the default action was prevented. If someone
+     *     calls preventDefault on the event object then this returns false.
+     */
+    dispatchEvent: function(event) {
+      if (!this.listeners_)
+        return true;
+
+      // Since we are using DOM Event objects we need to override some of the
+      // properties and methods so that we can emulate this correctly.
+      var self = this;
+      event.__defineGetter__('target', function() {
+        return self;
+      });
+      event.preventDefault = function() {
+        this.returnValue = false;
+      };
+
+      var type = event.type;
+      var prevented = 0;
+      if (type in this.listeners_) {
+        // Clone to prevent removal during dispatch
+        var handlers = this.listeners_[type].concat();
+        for (var i = 0, handler; handler = handlers[i]; i++) {
+          if (handler.handleEvent)
+            prevented |= handler.handleEvent.call(handler, event) === false;
+          else
+            prevented |= handler.call(this, event) === false;
+        }
+      }
+
+      return !prevented && event.returnValue;
+    }
+  };
+
+  // Export
+  return {
+    EventTarget: EventTarget
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui.js b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui.js
new file mode 100644
index 0000000..d48d3a8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui.js
@@ -0,0 +1,177 @@
+// Copyright (c) 2012 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.
+
+cr.define('cr.ui', function() {
+
+  /**
+   * Decorates elements as an instance of a class.
+   * @param {string|!Element} source The way to find the element(s) to decorate.
+   *     If this is a string then {@code querySeletorAll} is used to find the
+   *     elements to decorate.
+   * @param {!Function} constr The constructor to decorate with. The constr
+   *     needs to have a {@code decorate} function.
+   */
+  function decorate(source, constr) {
+    var elements;
+    if (typeof source == 'string')
+      elements = cr.doc.querySelectorAll(source);
+    else
+      elements = [source];
+
+    for (var i = 0, el; el = elements[i]; i++) {
+      if (!(el instanceof constr))
+        constr.decorate(el);
+    }
+  }
+
+  /**
+   * Helper function for creating new element for define.
+   */
+  function createElementHelper(tagName, opt_bag) {
+    // Allow passing in ownerDocument to create in a different document.
+    var doc;
+    if (opt_bag && opt_bag.ownerDocument)
+      doc = opt_bag.ownerDocument;
+    else
+      doc = cr.doc;
+    return doc.createElement(tagName);
+  }
+
+  /**
+   * Creates the constructor for a UI element class.
+   *
+   * Usage:
+   * <pre>
+   * var List = cr.ui.define('list');
+   * List.prototype = {
+   *   __proto__: HTMLUListElement.prototype,
+   *   decorate: function() {
+   *     ...
+   *   },
+   *   ...
+   * };
+   * </pre>
+   *
+   * @param {string|Function} tagNameOrFunction The tagName or
+   *     function to use for newly created elements. If this is a function it
+   *     needs to return a new element when called.
+   * @return {function(Object=):Element} The constructor function which takes
+   *     an optional property bag. The function also has a static
+   *     {@code decorate} method added to it.
+   */
+  function define(tagNameOrFunction) {
+    var createFunction, tagName;
+    if (typeof tagNameOrFunction == 'function') {
+      createFunction = tagNameOrFunction;
+      tagName = '';
+    } else {
+      createFunction = createElementHelper;
+      tagName = tagNameOrFunction;
+    }
+
+    /**
+     * Creates a new UI element constructor.
+     * @param {Object=} opt_propertyBag Optional bag of properties to set on the
+     *     object after created. The property {@code ownerDocument} is special
+     *     cased and it allows you to create the element in a different
+     *     document than the default.
+     * @constructor
+     */
+    function f(opt_propertyBag) {
+      var el = createFunction(tagName, opt_propertyBag);
+      f.decorate(el);
+      for (var propertyName in opt_propertyBag) {
+        el[propertyName] = opt_propertyBag[propertyName];
+      }
+      return el;
+    }
+
+    /**
+     * Decorates an element as a UI element class.
+     * @param {!Element} el The element to decorate.
+     */
+    f.decorate = function(el) {
+      el.__proto__ = f.prototype;
+      el.decorate();
+    };
+
+    return f;
+  }
+
+  /**
+   * Input elements do not grow and shrink with their content. This is a simple
+   * (and not very efficient) way of handling shrinking to content with support
+   * for min width and limited by the width of the parent element.
+   * @param {HTMLElement} el The element to limit the width for.
+   * @param {number} parentEl The parent element that should limit the size.
+   * @param {number} min The minimum width.
+   * @param {number} opt_scale Optional scale factor to apply to the width.
+   */
+  function limitInputWidth(el, parentEl, min, opt_scale) {
+    // Needs a size larger than borders
+    el.style.width = '10px';
+    var doc = el.ownerDocument;
+    var win = doc.defaultView;
+    var computedStyle = win.getComputedStyle(el);
+    var parentComputedStyle = win.getComputedStyle(parentEl);
+    var rtl = computedStyle.direction == 'rtl';
+
+    // To get the max width we get the width of the treeItem minus the position
+    // of the input.
+    var inputRect = el.getBoundingClientRect();  // box-sizing
+    var parentRect = parentEl.getBoundingClientRect();
+    var startPos = rtl ? parentRect.right - inputRect.right :
+        inputRect.left - parentRect.left;
+
+    // Add up border and padding of the input.
+    var inner = parseInt(computedStyle.borderLeftWidth, 10) +
+        parseInt(computedStyle.paddingLeft, 10) +
+        parseInt(computedStyle.paddingRight, 10) +
+        parseInt(computedStyle.borderRightWidth, 10);
+
+    // We also need to subtract the padding of parent to prevent it to overflow.
+    var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) :
+        parseInt(parentComputedStyle.paddingRight, 10);
+
+    var max = parentEl.clientWidth - startPos - inner - parentPadding;
+    if (opt_scale)
+      max *= opt_scale;
+
+    function limit() {
+      if (el.scrollWidth > max) {
+        el.style.width = max + 'px';
+      } else {
+        el.style.width = 0;
+        var sw = el.scrollWidth;
+        if (sw < min) {
+          el.style.width = min + 'px';
+        } else {
+          el.style.width = sw + 'px';
+        }
+      }
+    }
+
+    el.addEventListener('input', limit);
+    limit();
+  }
+
+  /**
+   * Takes a number and spits out a value CSS will be happy with. To avoid
+   * subpixel layout issues, the value is rounded to the nearest integral value.
+   * @param {number} pixels The number of pixels.
+   * @return {string} e.g. '16px'.
+   */
+  function toCssPx(pixels) {
+    if (!window.isFinite(pixels))
+      console.error('Pixel value is not a number: ' + pixels);
+    return Math.round(pixels) + 'px';
+  }
+
+  return {
+    decorate: decorate,
+    define: define,
+    limitInputWidth: limitInputWidth,
+    toCssPx: toCssPx,
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/array_data_model.js b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/array_data_model.js
new file mode 100644
index 0000000..9d9cc5a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/array_data_model.js
@@ -0,0 +1,409 @@
+// Copyright (c) 2012 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 is a data model representin
+ */
+
+cr.define('cr.ui', function() {
+  /** @const */ var EventTarget = cr.EventTarget;
+  /** @const */ var Event = cr.Event;
+
+  /**
+   * A data model that wraps a simple array and supports sorting by storing
+   * initial indexes of elements for each position in sorted array.
+   * @param {!Array} array The underlying array.
+   * @constructor
+   * @extends {EventTarget}
+   */
+  function ArrayDataModel(array) {
+    this.array_ = array;
+    this.indexes_ = [];
+    this.compareFunctions_ = {};
+
+    for (var i = 0; i < array.length; i++) {
+      this.indexes_.push(i);
+    }
+  }
+
+  ArrayDataModel.prototype = {
+    __proto__: EventTarget.prototype,
+
+    /**
+     * The length of the data model.
+     * @type {number}
+     */
+    get length() {
+      return this.array_.length;
+    },
+
+    /**
+     * Returns the item at the given index.
+     * This implementation returns the item at the given index in the sorted
+     * array.
+     * @param {number} index The index of the element to get.
+     * @return {*} The element at the given index.
+     */
+    item: function(index) {
+      if (index >= 0 && index < this.length)
+        return this.array_[this.indexes_[index]];
+      return undefined;
+    },
+
+    /**
+     * Returns compare function set for given field.
+     * @param {string} field The field to get compare function for.
+     * @return {function(*, *): number} Compare function set for given field.
+     */
+    compareFunction: function(field) {
+      return this.compareFunctions_[field];
+    },
+
+    /**
+     * Sets compare function for given field.
+     * @param {string} field The field to set compare function.
+     * @param {function(*, *): number} Compare function to set for given field.
+     */
+    setCompareFunction: function(field, compareFunction) {
+      if (!this.compareFunctions_) {
+        this.compareFunctions_ = {};
+      }
+      this.compareFunctions_[field] = compareFunction;
+    },
+
+    /**
+     * Returns true if the field has a compare function.
+     * @param {string} field The field to check.
+     * @return {boolean} True if the field is sortable.
+     */
+    isSortable: function(field) {
+      return this.compareFunctions_ && field in this.compareFunctions_;
+    },
+
+    /**
+     * Returns current sort status.
+     * @return {!Object} Current sort status.
+     */
+    get sortStatus() {
+      if (this.sortStatus_) {
+        return this.createSortStatus(
+            this.sortStatus_.field, this.sortStatus_.direction);
+      } else {
+        return this.createSortStatus(null, null);
+      }
+    },
+
+    /**
+     * Returns the first matching item.
+     * @param {*} item The item to find.
+     * @param {number=} opt_fromIndex If provided, then the searching start at
+     *     the {@code opt_fromIndex}.
+     * @return {number} The index of the first found element or -1 if not found.
+     */
+    indexOf: function(item, opt_fromIndex) {
+      for (var i = opt_fromIndex || 0; i < this.indexes_.length; i++) {
+        if (item === this.item(i))
+          return i;
+      }
+      return -1;
+    },
+
+    /**
+     * Returns an array of elements in a selected range.
+     * @param {number=} opt_from The starting index of the selected range.
+     * @param {number=} opt_to The ending index of selected range.
+     * @return {Array} An array of elements in the selected range.
+     */
+    slice: function(opt_from, opt_to) {
+      var arr = this.array_;
+      return this.indexes_.slice(opt_from, opt_to).map(
+          function(index) { return arr[index] });
+    },
+
+    /**
+     * This removes and adds items to the model.
+     * This dispatches a splice event.
+     * This implementation runs sort after splice and creates permutation for
+     * the whole change.
+     * @param {number} index The index of the item to update.
+     * @param {number} deleteCount The number of items to remove.
+     * @param {...*} The items to add.
+     * @return {!Array} An array with the removed items.
+     */
+    splice: function(index, deleteCount, var_args) {
+      var addCount = arguments.length - 2;
+      var newIndexes = [];
+      var deletePermutation = [];
+      var deletedItems = [];
+      var newArray = [];
+      index = Math.min(index, this.indexes_.length);
+      deleteCount = Math.min(deleteCount, this.indexes_.length - index);
+      // Copy items before the insertion point.
+      for (var i = 0; i < index; i++) {
+        newIndexes.push(newArray.length);
+        deletePermutation.push(i);
+        newArray.push(this.array_[this.indexes_[i]]);
+      }
+      // Delete items.
+      for (; i < index + deleteCount; i++) {
+        deletePermutation.push(-1);
+        deletedItems.push(this.array_[this.indexes_[i]]);
+      }
+      // Insert new items instead deleted ones.
+      for (var j = 0; j < addCount; j++) {
+        newIndexes.push(newArray.length);
+        newArray.push(arguments[j + 2]);
+      }
+      // Copy items after the insertion point.
+      for (; i < this.indexes_.length; i++) {
+        newIndexes.push(newArray.length);
+        deletePermutation.push(i - deleteCount + addCount);
+        newArray.push(this.array_[this.indexes_[i]]);
+      }
+
+      this.indexes_ = newIndexes;
+
+      this.array_ = newArray;
+
+      // TODO(arv): Maybe unify splice and change events?
+      var spliceEvent = new Event('splice');
+      spliceEvent.removed = deletedItems;
+      spliceEvent.added = Array.prototype.slice.call(arguments, 2);
+
+      var status = this.sortStatus;
+      // if sortStatus.field is null, this restores original order.
+      var sortPermutation = this.doSort_(this.sortStatus.field,
+                                         this.sortStatus.direction);
+      if (sortPermutation) {
+        var splicePermutation = deletePermutation.map(function(element) {
+          return element != -1 ? sortPermutation[element] : -1;
+        });
+        this.dispatchPermutedEvent_(splicePermutation);
+        spliceEvent.index = sortPermutation[index];
+      } else {
+        this.dispatchPermutedEvent_(deletePermutation);
+        spliceEvent.index = index;
+      }
+
+      this.dispatchEvent(spliceEvent);
+
+      // If real sorting is needed, we should first call prepareSort (data may
+      // change), and then sort again.
+      // Still need to finish the sorting above (including events), so
+      // list will not go to inconsistent state.
+      if (status.field)
+        this.delayedSort_(status.field, status.direction);
+
+      return deletedItems;
+    },
+
+    /**
+     * Appends items to the end of the model.
+     *
+     * This dispatches a splice event.
+     *
+     * @param {...*} The items to append.
+     * @return {number} The new length of the model.
+     */
+    push: function(var_args) {
+      var args = Array.prototype.slice.call(arguments);
+      args.unshift(this.length, 0);
+      this.splice.apply(this, args);
+      return this.length;
+    },
+
+    /**
+     * Use this to update a given item in the array. This does not remove and
+     * reinsert a new item.
+     * This dispatches a change event.
+     * This runs sort after updating.
+     * @param {number} index The index of the item to update.
+     */
+    updateIndex: function(index) {
+      if (index < 0 || index >= this.length)
+        throw Error('Invalid index, ' + index);
+
+      // TODO(arv): Maybe unify splice and change events?
+      var e = new Event('change');
+      e.index = index;
+      this.dispatchEvent(e);
+
+      if (this.sortStatus.field) {
+        var status = this.sortStatus;
+        var sortPermutation = this.doSort_(this.sortStatus.field,
+                                           this.sortStatus.direction);
+        if (sortPermutation)
+          this.dispatchPermutedEvent_(sortPermutation);
+        // We should first call prepareSort (data may change), and then sort.
+        // Still need to finish the sorting above (including events), so
+        // list will not go to inconsistent state.
+        this.delayedSort_(status.field, status.direction);
+      }
+    },
+
+    /**
+     * Creates sort status with given field and direction.
+     * @param {string} field Sort field.
+     * @param {string} direction Sort direction.
+     * @return {!Object} Created sort status.
+     */
+    createSortStatus: function(field, direction) {
+      return {
+        field: field,
+        direction: direction
+      };
+    },
+
+    /**
+     * Called before a sort happens so that you may fetch additional data
+     * required for the sort.
+     *
+     * @param {string} field Sort field.
+     * @param {function()} callback The function to invoke when preparation
+     *     is complete.
+     */
+    prepareSort: function(field, callback) {
+      callback();
+    },
+
+    /**
+     * Sorts data model according to given field and direction and dispathes
+     * sorted event with delay. If no need to delay, use sort() instead.
+     * @param {string} field Sort field.
+     * @param {string} direction Sort direction.
+     * @private
+     */
+    delayedSort_: function(field, direction) {
+      var self = this;
+      setTimeout(function() {
+        // If the sort status has been changed, sorting has already done
+        // on the change event.
+        if (field == self.sortStatus.field &&
+            direction == self.sortStatus.direction) {
+          self.sort(field, direction);
+        }
+      }, 0);
+    },
+
+    /**
+     * Sorts data model according to given field and direction and dispathes
+     * sorted event.
+     * @param {string} field Sort field.
+     * @param {string} direction Sort direction.
+     */
+    sort: function(field, direction) {
+      var self = this;
+
+      this.prepareSort(field, function() {
+        var sortPermutation = self.doSort_(field, direction);
+        if (sortPermutation)
+          self.dispatchPermutedEvent_(sortPermutation);
+        self.dispatchSortEvent_();
+      });
+    },
+
+    /**
+     * Sorts data model according to given field and direction.
+     * @param {string} field Sort field.
+     * @param {string} direction Sort direction.
+     * @private
+     */
+    doSort_: function(field, direction) {
+      var compareFunction = this.sortFunction_(field, direction);
+      var positions = [];
+      for (var i = 0; i < this.length; i++) {
+        positions[this.indexes_[i]] = i;
+      }
+      this.indexes_.sort(compareFunction);
+      this.sortStatus_ = this.createSortStatus(field, direction);
+      var sortPermutation = [];
+      var changed = false;
+      for (var i = 0; i < this.length; i++) {
+        if (positions[this.indexes_[i]] != i)
+          changed = true;
+        sortPermutation[positions[this.indexes_[i]]] = i;
+      }
+      if (changed)
+        return sortPermutation;
+      return null;
+    },
+
+    dispatchSortEvent_: function() {
+      var e = new Event('sorted');
+      this.dispatchEvent(e);
+    },
+
+    dispatchPermutedEvent_: function(permutation) {
+      var e = new Event('permuted');
+      e.permutation = permutation;
+      e.newLength = this.length;
+      this.dispatchEvent(e);
+    },
+
+    /**
+     * Creates compare function for the field.
+     * Returns the function set as sortFunction for given field
+     * or default compare function
+     * @param {string} field Sort field.
+     * @param {function(*, *): number} Compare function.
+     * @private
+     */
+    createCompareFunction_: function(field) {
+      var compareFunction =
+          this.compareFunctions_ ? this.compareFunctions_[field] : null;
+      var defaultValuesCompareFunction = this.defaultValuesCompareFunction;
+      if (compareFunction) {
+        return compareFunction;
+      } else {
+        return function(a, b) {
+          return defaultValuesCompareFunction.call(null, a[field], b[field]);
+        }
+      }
+      return compareFunction;
+    },
+
+    /**
+     * Creates compare function for given field and direction.
+     * @param {string} field Sort field.
+     * @param {string} direction Sort direction.
+     * @param {function(*, *): number} Compare function.
+     * @private
+     */
+    sortFunction_: function(field, direction) {
+      var compareFunction = null;
+      if (field !== null)
+        compareFunction = this.createCompareFunction_(field);
+      var dirMultiplier = direction == 'desc' ? -1 : 1;
+
+      return function(index1, index2) {
+        var item1 = this.array_[index1];
+        var item2 = this.array_[index2];
+
+        var compareResult = 0;
+        if (typeof(compareFunction) === 'function')
+          compareResult = compareFunction.call(null, item1, item2);
+        if (compareResult != 0)
+          return dirMultiplier * compareResult;
+        return dirMultiplier * this.defaultValuesCompareFunction(index1,
+                                                                 index2);
+      }.bind(this);
+    },
+
+    /**
+     * Default compare function.
+     */
+    defaultValuesCompareFunction: function(a, b) {
+      // We could insert i18n comparisons here.
+      if (a < b)
+        return -1;
+      if (a > b)
+        return 1;
+      return 0;
+    }
+  };
+
+  return {
+    ArrayDataModel: ArrayDataModel
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/list.js b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/list.js
new file mode 100644
index 0000000..a440e47
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/list.js
@@ -0,0 +1,1298 @@
+// Copyright (c) 2012 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.
+
+// require: array_data_model.js
+// require: list_selection_model.js
+// require: list_selection_controller.js
+// require: list_item.js
+
+/**
+ * @fileoverview This implements a list control.
+ */
+
+cr.define('cr.ui', function() {
+  /** @const */ var ListSelectionModel = cr.ui.ListSelectionModel;
+  /** @const */ var ListSelectionController = cr.ui.ListSelectionController;
+  /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
+
+  /**
+   * Whether a mouse event is inside the element viewport. This will return
+   * false if the mouseevent was generated over a border or a scrollbar.
+   * @param {!HTMLElement} el The element to test the event with.
+   * @param {!Event} e The mouse event.
+   * @param {boolean} Whether the mouse event was inside the viewport.
+   */
+  function inViewport(el, e) {
+    var rect = el.getBoundingClientRect();
+    var x = e.clientX;
+    var y = e.clientY;
+    return x >= rect.left + el.clientLeft &&
+           x < rect.left + el.clientLeft + el.clientWidth &&
+           y >= rect.top + el.clientTop &&
+           y < rect.top + el.clientTop + el.clientHeight;
+  }
+
+  function getComputedStyle(el) {
+    return el.ownerDocument.defaultView.getComputedStyle(el);
+  }
+
+  /**
+   * Creates a new list element.
+   * @param {Object=} opt_propertyBag Optional properties.
+   * @constructor
+   * @extends {HTMLUListElement}
+   */
+  var List = cr.ui.define('list');
+
+  List.prototype = {
+    __proto__: HTMLUListElement.prototype,
+
+    /**
+     * Measured size of list items. This is lazily calculated the first time it
+     * is needed. Note that lead item is allowed to have a different height, to
+     * accommodate lists where a single item at a time can be expanded to show
+     * more detail.
+     * @type {{height: number, marginTop: number, marginBottom:number,
+     *     width: number, marginLeft: number, marginRight:number}}
+     * @private
+     */
+    measured_: undefined,
+
+    /**
+     * Whether or not the list is autoexpanding. If true, the list resizes
+     * its height to accomadate all children.
+     * @type {boolean}
+     * @private
+     */
+    autoExpands_: false,
+
+    /**
+     * Whether or not the rows on list have various heights. If true, all the
+     * rows have the same fixed height. Otherwise, each row resizes its height
+     * to accommodate all contents.
+     * @type {boolean}
+     * @private
+     */
+    fixedHeight_: true,
+
+    /**
+     * Whether or not the list view has a blank space below the last row.
+     * @type {boolean}
+     * @private
+     */
+    remainingSpace_: true,
+
+    /**
+     * Function used to create grid items.
+     * @type {function(): !ListItem}
+     * @private
+     */
+    itemConstructor_: cr.ui.ListItem,
+
+    /**
+     * Function used to create grid items.
+     * @type {function(): !ListItem}
+     */
+    get itemConstructor() {
+      return this.itemConstructor_;
+    },
+    set itemConstructor(func) {
+      if (func != this.itemConstructor_) {
+        this.itemConstructor_ = func;
+        this.cachedItems_ = {};
+        this.redraw();
+      }
+    },
+
+    dataModel_: null,
+
+    /**
+     * The data model driving the list.
+     * @type {ArrayDataModel}
+     */
+    set dataModel(dataModel) {
+      if (this.dataModel_ != dataModel) {
+        if (!this.boundHandleDataModelPermuted_) {
+          this.boundHandleDataModelPermuted_ =
+              this.handleDataModelPermuted_.bind(this);
+          this.boundHandleDataModelChange_ =
+              this.handleDataModelChange_.bind(this);
+        }
+
+        if (this.dataModel_) {
+          this.dataModel_.removeEventListener(
+              'permuted',
+              this.boundHandleDataModelPermuted_);
+          this.dataModel_.removeEventListener('change',
+                                              this.boundHandleDataModelChange_);
+        }
+
+        this.dataModel_ = dataModel;
+
+        this.cachedItems_ = {};
+        this.cachedItemHeights_ = {};
+        this.selectionModel.clear();
+        if (dataModel)
+          this.selectionModel.adjustLength(dataModel.length);
+
+        if (this.dataModel_) {
+          this.dataModel_.addEventListener(
+              'permuted',
+              this.boundHandleDataModelPermuted_);
+          this.dataModel_.addEventListener('change',
+                                           this.boundHandleDataModelChange_);
+        }
+
+        this.redraw();
+      }
+    },
+
+    get dataModel() {
+      return this.dataModel_;
+    },
+
+
+    /**
+     * Cached item for measuring the default item size by measureItem().
+     * @type {ListItem}
+     */
+    cachedMeasuredItem_: null,
+
+    /**
+     * The selection model to use.
+     * @type {cr.ui.ListSelectionModel}
+     */
+    get selectionModel() {
+      return this.selectionModel_;
+    },
+    set selectionModel(sm) {
+      var oldSm = this.selectionModel_;
+      if (oldSm == sm)
+        return;
+
+      if (!this.boundHandleOnChange_) {
+        this.boundHandleOnChange_ = this.handleOnChange_.bind(this);
+        this.boundHandleLeadChange_ = this.handleLeadChange_.bind(this);
+      }
+
+      if (oldSm) {
+        oldSm.removeEventListener('change', this.boundHandleOnChange_);
+        oldSm.removeEventListener('leadIndexChange',
+                                  this.boundHandleLeadChange_);
+      }
+
+      this.selectionModel_ = sm;
+      this.selectionController_ = this.createSelectionController(sm);
+
+      if (sm) {
+        sm.addEventListener('change', this.boundHandleOnChange_);
+        sm.addEventListener('leadIndexChange', this.boundHandleLeadChange_);
+      }
+    },
+
+    /**
+     * Whether or not the list auto-expands.
+     * @type {boolean}
+     */
+    get autoExpands() {
+      return this.autoExpands_;
+    },
+    set autoExpands(autoExpands) {
+      if (this.autoExpands_ == autoExpands)
+        return;
+      this.autoExpands_ = autoExpands;
+      this.redraw();
+    },
+
+    /**
+     * Whether or not the rows on list have various heights.
+     * @type {boolean}
+     */
+    get fixedHeight() {
+      return this.fixedHeight_;
+    },
+    set fixedHeight(fixedHeight) {
+      if (this.fixedHeight_ == fixedHeight)
+        return;
+      this.fixedHeight_ = fixedHeight;
+      this.redraw();
+    },
+
+    /**
+     * Convenience alias for selectionModel.selectedItem
+     * @type {*}
+     */
+    get selectedItem() {
+      var dataModel = this.dataModel;
+      if (dataModel) {
+        var index = this.selectionModel.selectedIndex;
+        if (index != -1)
+          return dataModel.item(index);
+      }
+      return null;
+    },
+    set selectedItem(selectedItem) {
+      var dataModel = this.dataModel;
+      if (dataModel) {
+        var index = this.dataModel.indexOf(selectedItem);
+        this.selectionModel.selectedIndex = index;
+      }
+    },
+
+    /**
+     * Convenience alias for selectionModel.selectedItems
+     * @type {!Array<*>}
+     */
+    get selectedItems() {
+      var indexes = this.selectionModel.selectedIndexes;
+      var dataModel = this.dataModel;
+      if (dataModel) {
+        return indexes.map(function(i) {
+          return dataModel.item(i);
+        });
+      }
+      return [];
+    },
+
+    /**
+     * The HTML elements representing the items.
+     * @type {HTMLCollection}
+     */
+    get items() {
+      return Array.prototype.filter.call(this.children,
+                                         this.isItem, this);
+    },
+
+    /**
+     * Returns true if the child is a list item. Subclasses may override this
+     * to filter out certain elements.
+     * @param {Node} child Child of the list.
+     * @return {boolean} True if a list item.
+     */
+    isItem: function(child) {
+      return child.nodeType == Node.ELEMENT_NODE &&
+             child != this.beforeFiller_ && child != this.afterFiller_;
+    },
+
+    batchCount_: 0,
+
+    /**
+     * When making a lot of updates to the list, the code could be wrapped in
+     * the startBatchUpdates and finishBatchUpdates to increase performance. Be
+     * sure that the code will not return without calling endBatchUpdates or the
+     * list will not be correctly updated.
+     */
+    startBatchUpdates: function() {
+      this.batchCount_++;
+    },
+
+    /**
+     * See startBatchUpdates.
+     */
+    endBatchUpdates: function() {
+      this.batchCount_--;
+      if (this.batchCount_ == 0)
+        this.redraw();
+    },
+
+    /**
+     * Initializes the element.
+     */
+    decorate: function() {
+      // Add fillers.
+      this.beforeFiller_ = this.ownerDocument.createElement('div');
+      this.afterFiller_ = this.ownerDocument.createElement('div');
+      this.beforeFiller_.className = 'spacer';
+      this.afterFiller_.className = 'spacer';
+      this.textContent = '';
+      this.appendChild(this.beforeFiller_);
+      this.appendChild(this.afterFiller_);
+
+      var length = this.dataModel ? this.dataModel.length : 0;
+      this.selectionModel = new ListSelectionModel(length);
+
+      this.addEventListener('dblclick', this.handleDoubleClickOrTap_);
+      this.addEventListener('mousedown', this.handlePointerDownUp_);
+      this.addEventListener('mouseup', this.handlePointerDownUp_);
+      this.addEventListener('keydown', this.handleKeyDown);
+      this.addEventListener('focus', this.handleElementFocus_, true);
+      this.addEventListener('blur', this.handleElementBlur_, true);
+      this.addEventListener('scroll', this.handleScroll.bind(this));
+      this.setAttribute('role', 'list');
+
+      this.touchHandler_ = new cr.ui.TouchHandler(this);
+      this.addEventListener(cr.ui.TouchHandler.EventType.TOUCH_START,
+                            this.handlePointerDownUp_);
+      this.addEventListener(cr.ui.TouchHandler.EventType.TOUCH_END,
+                            this.handlePointerDownUp_);
+      this.addEventListener(cr.ui.TouchHandler.EventType.TAP,
+                            this.handleDoubleClickOrTap_);
+      this.touchHandler_.enable(false, false);
+      // Make list focusable
+      if (!this.hasAttribute('tabindex'))
+        this.tabIndex = 0;
+
+      // Try to get an unique id prefix from the id of this element or the
+      // nearest ancestor with an id.
+      var element = this;
+      while (element && !element.id)
+        element = element.parentElement;
+      if (element && element.id)
+        this.uniqueIdPrefix_ = element.id;
+      else
+        this.uniqueIdPrefix_ = 'list';
+
+      // The next id suffix to use when giving each item an unique id.
+      this.nextUniqueIdSuffix_ = 0;
+    },
+
+    /**
+     * @param {ListItem=} item The list item to measure.
+     * @return {number} The height of the given item. If the fixed height on CSS
+     * is set by 'px', uses that value as height. Otherwise, measures the size.
+     * @private
+     */
+    measureItemHeight_: function(item) {
+      var height = item.style.height;
+      // Use the fixed height if set it on CSS, to save a time of layout
+      // calculation.
+      if (height && height.substr(-2) == 'px')
+        return parseInt(height.substr(0, height.length - 2));
+
+      return this.measureItem(item).height;
+    },
+
+    /**
+     * @return {number} The height of default item, measuring it if necessary.
+     * @private
+     */
+    getDefaultItemHeight_: function() {
+      return this.getDefaultItemSize_().height;
+    },
+
+    /**
+     * @param {number} index The index of the item.
+     * @return {number} The height of the item, measuring it if necessary.
+     */
+    getItemHeightByIndex_: function(index) {
+      // If |this.fixedHeight_| is true, all the rows have same default height.
+      if (this.fixedHeight_)
+        return this.getDefaultItemHeight_();
+
+      if (this.cachedItemHeights_[index])
+        return this.cachedItemHeights_[index];
+
+      var item = this.getListItemByIndex(index);
+      if (item)
+        return this.measureItemHeight_(item);
+
+      return this.getDefaultItemHeight_();
+    },
+
+    /**
+     * @return {{height: number, width: number}} The height and width
+     *     of default item, measuring it if necessary.
+     * @private
+     */
+    getDefaultItemSize_: function() {
+      if (!this.measured_ || !this.measured_.height) {
+        this.measured_ = this.measureItem();
+      }
+      return this.measured_;
+    },
+
+    /**
+     * Creates an item (dataModel.item(0)) and measures its height. The item is
+     * cached instead of creating a new one every time..
+     * @param {ListItem=} opt_item The list item to use to do the measuring. If
+     *     this is not provided an item will be created based on the first value
+     *     in the model.
+     * @return {{height: number, marginTop: number, marginBottom:number,
+     *     width: number, marginLeft: number, marginRight:number}}
+     *     The height and width of the item, taking
+     *     margins into account, and the top, bottom, left and right margins
+     *     themselves.
+     */
+    measureItem: function(opt_item) {
+      var dataModel = this.dataModel;
+      if (!dataModel || !dataModel.length)
+        return 0;
+      var item = opt_item || this.cachedMeasuredItem_ ||
+          this.createItem(dataModel.item(0));
+      if (!opt_item) {
+        this.cachedMeasuredItem_ = item;
+        this.appendChild(item);
+      }
+
+      var rect = item.getBoundingClientRect();
+      var cs = getComputedStyle(item);
+      var mt = parseFloat(cs.marginTop);
+      var mb = parseFloat(cs.marginBottom);
+      var ml = parseFloat(cs.marginLeft);
+      var mr = parseFloat(cs.marginRight);
+      var h = rect.height;
+      var w = rect.width;
+      var mh = 0;
+      var mv = 0;
+
+      // Handle margin collapsing.
+      if (mt < 0 && mb < 0) {
+        mv = Math.min(mt, mb);
+      } else if (mt >= 0 && mb >= 0) {
+        mv = Math.max(mt, mb);
+      } else {
+        mv = mt + mb;
+      }
+      h += mv;
+
+      if (ml < 0 && mr < 0) {
+        mh = Math.min(ml, mr);
+      } else if (ml >= 0 && mr >= 0) {
+        mh = Math.max(ml, mr);
+      } else {
+        mh = ml + mr;
+      }
+      w += mh;
+
+      if (!opt_item)
+        this.removeChild(item);
+      return {
+          height: Math.max(0, h),
+          marginTop: mt, marginBottom: mb,
+          width: Math.max(0, w),
+          marginLeft: ml, marginRight: mr};
+    },
+
+    /**
+     * Callback for the double click or TAP event.
+     * @param {Event} e The mouse or TouchHandler event object.
+     * @private
+     */
+    handleDoubleClickOrTap_: function(e) {
+      if (this.disabled)
+        return;
+
+      var target = e.target;
+      if (e.touchedElement)
+        target = e.touchedElement;
+
+      target = this.getListItemAncestor(target);
+      if (target)
+        this.activateItemAtIndex(this.getIndexOfListItem(target));
+    },
+
+    /**
+     * Callback for mousedown, mouseup, TOUCH_START and TOUCH_END events.
+     * @param {Event} e The mouse or TouchHandler event object.
+     * @private
+     */
+    handlePointerDownUp_: function(e) {
+      if (this.disabled)
+        return;
+
+      var target = e.target;
+      if (e.touchedElement)
+        target = e.touchedElement;
+
+      // If the target was this element we need to make sure that the user did
+      // not click on a border or a scrollbar.
+      if (target == this) {
+        if (inViewport(target, e))
+          this.selectionController_.handlePointerDownUp(e, -1);
+        return;
+      }
+
+      target = this.getListItemAncestor(target);
+
+      // While target of mouse events always are ancestors TOUCH_END target
+      // may be outside the list (for instance if it happens over a popup
+      // menu). Just ignore it.
+      if (!target)
+        return;
+
+      var index = this.getIndexOfListItem(target);
+      this.selectionController_.handlePointerDownUp(e, index);
+    },
+
+    /**
+     * Called when an element in the list is focused. Marks the list as having
+     * a focused element, and dispatches an event if it didn't have focus.
+     * @param {Event} e The focus event.
+     * @private
+     */
+    handleElementFocus_: function(e) {
+      if (!this.hasElementFocus)
+        this.hasElementFocus = true;
+    },
+
+    /**
+     * Called when an element in the list is blurred. If focus moves outside
+     * the list, marks the list as no longer having focus and dispatches an
+     * event.
+     * @param {Event} e The blur event.
+     * @private
+     */
+    handleElementBlur_: function(e) {
+      // When the blur event happens we do not know who is getting focus so we
+      // delay this a bit until we know if the new focus node is outside the
+      // list.
+      var list = this;
+      var doc = e.target.ownerDocument;
+      window.setTimeout(function() {
+        var activeElement = doc.activeElement;
+        if (!list.contains(activeElement))
+          list.hasElementFocus = false;
+      });
+    },
+
+    /**
+     * Returns the list item element containing the given element, or null if
+     * it doesn't belong to any list item element.
+     * @param {HTMLElement} element The element.
+     * @return {ListItem} The list item containing |element|, or null.
+     */
+    getListItemAncestor: function(element) {
+      var container = element;
+      while (container && container.parentNode != this) {
+        container = container.parentNode;
+      }
+      return container;
+    },
+
+    /**
+     * Handle a keydown event.
+     * @param {Event} e The keydown event.
+     * @return {boolean} Whether the key event was handled.
+     */
+    handleKeyDown: function(e) {
+      if (this.disabled)
+        return;
+
+      return this.selectionController_.handleKeyDown(e);
+    },
+
+    scrollTopBefore_: 0,
+
+    /**
+     * Handle a scroll event.
+     * @param {Event} e The scroll event.
+     */
+    handleScroll: function(e) {
+      var scrollTop = this.scrollTop;
+      if (scrollTop != this.scrollTopBefore_) {
+        this.scrollTopBefore_ = scrollTop;
+        this.redraw();
+      }
+    },
+
+    /**
+     * Callback from the selection model. We dispatch {@code change} events
+     * when the selection changes.
+     * @param {!cr.Event} e Event with change info.
+     * @private
+     */
+    handleOnChange_: function(ce) {
+      ce.changes.forEach(function(change) {
+        var listItem = this.getListItemByIndex(change.index);
+        if (listItem) {
+          listItem.selected = change.selected;
+          if (change.selected) {
+            listItem.setAttribute('aria-posinset', change.index);
+            listItem.setAttribute('aria-setsize', this.dataModel.length);
+            this.setAttribute('aria-activedescendant', listItem.id);
+          } else {
+            listItem.removeAttribute('aria-posinset');
+            listItem.removeAttribute('aria-setsize');
+          }
+        }
+      }, this);
+
+      cr.dispatchSimpleEvent(this, 'change');
+    },
+
+    /**
+     * Handles a change of the lead item from the selection model.
+     * @param {Event} pe The property change event.
+     * @private
+     */
+    handleLeadChange_: function(pe) {
+      var element;
+      if (pe.oldValue != -1) {
+        if ((element = this.getListItemByIndex(pe.oldValue)))
+          element.lead = false;
+      }
+
+      if (pe.newValue != -1) {
+        if ((element = this.getListItemByIndex(pe.newValue)))
+          element.lead = true;
+        if (pe.oldValue != pe.newValue) {
+          this.scrollIndexIntoView(pe.newValue);
+          // If the lead item has a different height than other items, then we
+          // may run into a problem that requires a second attempt to scroll
+          // it into view. The first scroll attempt will trigger a redraw,
+          // which will clear out the list and repopulate it with new items.
+          // During the redraw, the list may shrink temporarily, which if the
+          // lead item is the last item, will move the scrollTop up since it
+          // cannot extend beyond the end of the list. (Sadly, being scrolled to
+          // the bottom of the list is not "sticky.") So, we set a timeout to
+          // rescroll the list after this all gets sorted out. This is perhaps
+          // not the most elegant solution, but no others seem obvious.
+          var self = this;
+          window.setTimeout(function() {
+            self.scrollIndexIntoView(pe.newValue);
+          });
+        }
+      }
+    },
+
+    /**
+     * This handles data model 'permuted' event.
+     * this event is dispatched as a part of sort or splice.
+     * We need to
+     *  - adjust the cache.
+     *  - adjust selection.
+     *  - redraw. (called in this.endBatchUpdates())
+     *  It is important that the cache adjustment happens before selection model
+     *  adjustments.
+     * @param {Event} e The 'permuted' event.
+     */
+    handleDataModelPermuted_: function(e) {
+      var newCachedItems = {};
+      for (var index in this.cachedItems_) {
+        if (e.permutation[index] != -1) {
+          var newIndex = e.permutation[index];
+          newCachedItems[newIndex] = this.cachedItems_[index];
+          newCachedItems[newIndex].listIndex = newIndex;
+        }
+      }
+      this.cachedItems_ = newCachedItems;
+
+      var newCachedItemHeights = {};
+      for (var index in this.cachedItemHeights_) {
+        if (e.permutation[index] != -1) {
+          newCachedItemHeights[e.permutation[index]] =
+              this.cachedItemHeights_[index];
+        }
+      }
+      this.cachedItemHeights_ = newCachedItemHeights;
+
+      this.startBatchUpdates();
+
+      var sm = this.selectionModel;
+      sm.adjustLength(e.newLength);
+      sm.adjustToReordering(e.permutation);
+
+      this.endBatchUpdates();
+    },
+
+    handleDataModelChange_: function(e) {
+      delete this.cachedItems_[e.index];
+      delete this.cachedItemHeights_[e.index];
+      this.cachedMeasuredItem_ = null;
+
+      if (e.index >= this.firstIndex_ &&
+          (e.index < this.lastIndex_ || this.remainingSpace_)) {
+        this.redraw();
+      }
+    },
+
+    /**
+     * @param {number} index The index of the item.
+     * @return {number} The top position of the item inside the list.
+     */
+    getItemTop: function(index) {
+      if (this.fixedHeight_) {
+        var itemHeight = this.getDefaultItemHeight_();
+        return index * itemHeight;
+      } else {
+        var top = 0;
+        for (var i = 0; i < index; i++) {
+          top += this.getItemHeightByIndex_(i);
+        }
+        return top;
+      }
+    },
+
+    /**
+     * @param {number} index The index of the item.
+     * @return {number} The row of the item. May vary in the case
+     *     of multiple columns.
+     */
+    getItemRow: function(index) {
+      return index;
+    },
+
+    /**
+     * @param {number} row The row.
+     * @return {number} The index of the first item in the row.
+     */
+    getFirstItemInRow: function(row) {
+      return row;
+    },
+
+    /**
+     * Ensures that a given index is inside the viewport.
+     * @param {number} index The index of the item to scroll into view.
+     * @return {boolean} Whether any scrolling was needed.
+     */
+    scrollIndexIntoView: function(index) {
+      var dataModel = this.dataModel;
+      if (!dataModel || index < 0 || index >= dataModel.length)
+        return false;
+
+      var itemHeight = this.getItemHeightByIndex_(index);
+      var scrollTop = this.scrollTop;
+      var top = this.getItemTop(index);
+      var clientHeight = this.clientHeight;
+
+      var self = this;
+      // Function to adjust the tops of viewport and row.
+      function scrollToAdjustTop() {
+          self.scrollTop = top;
+          return true;
+      };
+      // Function to adjust the bottoms of viewport and row.
+      function scrollToAdjustBottom() {
+          var cs = getComputedStyle(self);
+          var paddingY = parseInt(cs.paddingTop, 10) +
+                         parseInt(cs.paddingBottom, 10);
+
+          if (top + itemHeight > scrollTop + clientHeight - paddingY) {
+            self.scrollTop = top + itemHeight - clientHeight + paddingY;
+            return true;
+          }
+          return false;
+      };
+
+      // Check if the entire of given indexed row can be shown in the viewport.
+      if (itemHeight <= clientHeight) {
+        if (top < scrollTop)
+          return scrollToAdjustTop();
+        if (scrollTop + clientHeight < top + itemHeight)
+          return scrollToAdjustBottom();
+      } else {
+        if (scrollTop < top)
+          return scrollToAdjustTop();
+        if (top + itemHeight < scrollTop + clientHeight)
+          return scrollToAdjustBottom();
+      }
+      return false;
+    },
+
+    /**
+     * @return {!ClientRect} The rect to use for the context menu.
+     */
+    getRectForContextMenu: function() {
+      // TODO(arv): Add trait support so we can share more code between trees
+      // and lists.
+      var index = this.selectionModel.selectedIndex;
+      var el = this.getListItemByIndex(index);
+      if (el)
+        return el.getBoundingClientRect();
+      return this.getBoundingClientRect();
+    },
+
+    /**
+     * Takes a value from the data model and finds the associated list item.
+     * @param {*} value The value in the data model that we want to get the list
+     *     item for.
+     * @return {ListItem} The first found list item or null if not found.
+     */
+    getListItem: function(value) {
+      var dataModel = this.dataModel;
+      if (dataModel) {
+        var index = dataModel.indexOf(value);
+        return this.getListItemByIndex(index);
+      }
+      return null;
+    },
+
+    /**
+     * Find the list item element at the given index.
+     * @param {number} index The index of the list item to get.
+     * @return {ListItem} The found list item or null if not found.
+     */
+    getListItemByIndex: function(index) {
+      return this.cachedItems_[index] || null;
+    },
+
+    /**
+     * Find the index of the given list item element.
+     * @param {ListItem} item The list item to get the index of.
+     * @return {number} The index of the list item, or -1 if not found.
+     */
+    getIndexOfListItem: function(item) {
+      var index = item.listIndex;
+      if (this.cachedItems_[index] == item) {
+        return index;
+      }
+      return -1;
+    },
+
+    /**
+     * Creates a new list item.
+     * @param {*} value The value to use for the item.
+     * @return {!ListItem} The newly created list item.
+     */
+    createItem: function(value) {
+      var item = new this.itemConstructor_(value);
+      item.label = value;
+      item.id = this.uniqueIdPrefix_ + '-' + this.nextUniqueIdSuffix_++;
+      if (typeof item.decorate == 'function')
+        item.decorate();
+      return item;
+    },
+
+    /**
+     * Creates the selection controller to use internally.
+     * @param {cr.ui.ListSelectionModel} sm The underlying selection model.
+     * @return {!cr.ui.ListSelectionController} The newly created selection
+     *     controller.
+     */
+    createSelectionController: function(sm) {
+      return new ListSelectionController(sm);
+    },
+
+    /**
+     * Return the heights (in pixels) of the top of the given item index within
+     * the list, and the height of the given item itself, accounting for the
+     * possibility that the lead item may be a different height.
+     * @param {number} index The index to find the top height of.
+     * @return {{top: number, height: number}} The heights for the given index.
+     * @private
+     */
+    getHeightsForIndex_: function(index) {
+      var itemHeight = this.getItemHeightByIndex_(index);
+      var top = this.getItemTop(index);
+      return {top: top, height: itemHeight};
+    },
+
+    /**
+     * Find the index of the list item containing the given y offset (measured
+     * in pixels from the top) within the list. In the case of multiple columns,
+     * returns the first index in the row.
+     * @param {number} offset The y offset in pixels to get the index of.
+     * @return {number} The index of the list item. Returns the list size if
+     *     given offset exceeds the height of list.
+     * @private
+     */
+    getIndexForListOffset_: function(offset) {
+      var itemHeight = this.getDefaultItemHeight_();
+      if (!itemHeight)
+        return this.dataModel.length;
+
+      if (this.fixedHeight_)
+        return this.getFirstItemInRow(Math.floor(offset / itemHeight));
+
+      // If offset exceeds the height of list.
+      var lastHeight = 0;
+      if (this.dataModel.length) {
+        var h = this.getHeightsForIndex_(this.dataModel.length - 1);
+        lastHeight = h.top + h.height;
+      }
+      if (lastHeight < offset)
+        return this.dataModel.length;
+
+      // Estimates index.
+      var estimatedIndex = Math.min(Math.floor(offset / itemHeight),
+                                    this.dataModel.length - 1);
+      var isIncrementing = this.getItemTop(estimatedIndex) < offset;
+
+      // Searchs the correct index.
+      do {
+        var heights = this.getHeightsForIndex_(estimatedIndex);
+        var top = heights.top;
+        var height = heights.height;
+
+        if (top <= offset && offset <= (top + height))
+          break;
+
+        isIncrementing ? ++estimatedIndex : --estimatedIndex;
+      } while (0 < estimatedIndex && estimatedIndex < this.dataModel.length);
+
+      return estimatedIndex;
+    },
+
+    /**
+     * Return the number of items that occupy the range of heights between the
+     * top of the start item and the end offset.
+     * @param {number} startIndex The index of the first visible item.
+     * @param {number} endOffset The y offset in pixels of the end of the list.
+     * @return {number} The number of list items visible.
+     * @private
+     */
+    countItemsInRange_: function(startIndex, endOffset) {
+      var endIndex = this.getIndexForListOffset_(endOffset);
+      return endIndex - startIndex + 1;
+    },
+
+    /**
+     * Calculates the number of items fitting in the given viewport.
+     * @param {number} scrollTop The scroll top position.
+     * @param {number} clientHeight The height of viewport.
+     * @return {{first: number, length: number, last: number}} The index of
+     *     first item in view port, The number of items, The item past the last.
+     */
+    getItemsInViewPort: function(scrollTop, clientHeight) {
+      if (this.autoExpands_) {
+        return {
+          first: 0,
+          length: this.dataModel.length,
+          last: this.dataModel.length};
+      } else {
+        var firstIndex = this.getIndexForListOffset_(scrollTop);
+        var lastIndex = this.getIndexForListOffset_(scrollTop + clientHeight);
+
+        return {
+          first: firstIndex,
+          length: lastIndex - firstIndex + 1,
+          last: lastIndex + 1};
+      }
+    },
+
+    /**
+     * Merges list items currently existing in the list with items in the range
+     * [firstIndex, lastIndex). Removes or adds items if needed.
+     * Doesn't delete {@code this.pinnedItem_} if it is present (instead hides
+     * it if it is out of the range). Adds items to {@code newCachedItems}.
+     * @param {number} firstIndex The index of first item, inclusively.
+     * @param {number} lastIndex The index of last item, exclusively.
+     * @param {Object.<string, ListItem>} cachedItems Old items cache.
+     * @param {Object.<string, ListItem>} newCachedItems New items cache.
+     */
+    mergeItems: function(firstIndex, lastIndex, cachedItems, newCachedItems) {
+      var self = this;
+      var dataModel = this.dataModel;
+      var currentIndex = firstIndex;
+
+      function insert() {
+        var dataItem = dataModel.item(currentIndex);
+        var newItem = cachedItems[currentIndex] || self.createItem(dataItem);
+        newItem.listIndex = currentIndex;
+        newCachedItems[currentIndex] = newItem;
+        self.insertBefore(newItem, item);
+        currentIndex++;
+      }
+
+      function remove() {
+        var next = item.nextSibling;
+        if (item != self.pinnedItem_)
+          self.removeChild(item);
+        item = next;
+      }
+
+      for (var item = this.beforeFiller_.nextSibling;
+           item != this.afterFiller_ && currentIndex < lastIndex;) {
+        if (!this.isItem(item)) {
+          item = item.nextSibling;
+          continue;
+        }
+
+        var index = item.listIndex;
+        if (cachedItems[index] != item || index < currentIndex) {
+          remove();
+        } else if (index == currentIndex) {
+          newCachedItems[currentIndex] = item;
+          item = item.nextSibling;
+          currentIndex++;
+        } else {  // index > currentIndex
+          insert();
+        }
+      }
+
+      while (item != this.afterFiller_) {
+        if (this.isItem(item))
+          remove();
+        else
+          item = item.nextSibling;
+      }
+
+      if (this.pinnedItem_) {
+        var index = this.pinnedItem_.listIndex;
+        this.pinnedItem_.hidden = index < firstIndex || index >= lastIndex;
+        newCachedItems[index] = this.pinnedItem_;
+        if (index >= lastIndex)
+          item = this.pinnedItem_;  // Insert new items before this one.
+      }
+
+      while (currentIndex < lastIndex)
+        insert();
+    },
+
+    /**
+     * Ensures that all the item sizes in the list have been already cached.
+     */
+    ensureAllItemSizesInCache: function() {
+      var measuringIndexes = [];
+      var isElementAppended = [];
+      for (var y = 0; y < this.dataModel.length; y++) {
+        if (!this.cachedItemHeights_[y]) {
+          measuringIndexes.push(y);
+          isElementAppended.push(false);
+        }
+      }
+
+      var measuringItems = [];
+      // Adds temporary elements.
+      for (var y = 0; y < measuringIndexes.length; y++) {
+        var index = measuringIndexes[y];
+        var dataItem = this.dataModel.item(index);
+        var listItem = this.cachedItems_[index] || this.createItem(dataItem);
+        listItem.listIndex = index;
+
+        // If |listItems| is not on the list, apppends it to the list and sets
+        // the flag.
+        if (!listItem.parentNode) {
+          this.appendChild(listItem);
+          isElementAppended[y] = true;
+        }
+
+        this.cachedItems_[index] = listItem;
+        measuringItems.push(listItem);
+      }
+
+      // All mesurings must be placed after adding all the elements, to prevent
+      // performance reducing.
+      for (var y = 0; y < measuringIndexes.length; y++) {
+        var index = measuringIndexes[y];
+        this.cachedItemHeights_[index] =
+            this.measureItemHeight_(measuringItems[y]);
+      }
+
+      // Removes all the temprary elements.
+      for (var y = 0; y < measuringIndexes.length; y++) {
+        // If the list item has been appended above, removes it.
+        if (isElementAppended[y])
+          this.removeChild(measuringItems[y]);
+      }
+    },
+
+    /**
+     * Returns the height of after filler in the list.
+     * @param {number} lastIndex The index of item past the last in viewport.
+     * @param {number} itemHeight The height of the item.
+     * @return {number} The height of after filler.
+     */
+    getAfterFillerHeight: function(lastIndex) {
+      if (this.fixedHeight_) {
+        var itemHeight = this.getDefaultItemHeight_();
+        return (this.dataModel.length - lastIndex) * itemHeight;
+      }
+
+      var height = 0;
+      for (var i = lastIndex; i < this.dataModel.length; i++)
+        height += this.getItemHeightByIndex_(i);
+      return height;
+    },
+
+    /**
+     * Redraws the viewport.
+     */
+    redraw: function() {
+      if (this.batchCount_ != 0)
+        return;
+
+      var dataModel = this.dataModel;
+      if (!dataModel || !this.autoExpands_ && this.clientHeight == 0) {
+        this.cachedItems_ = {};
+        this.firstIndex_ = 0;
+        this.lastIndex_ = 0;
+        this.remainingSpace_ = true;
+        this.mergeItems(0, 0, {}, {});
+        return;
+      }
+
+      // Save the previous positions before any manipulation of elements.
+      var scrollTop = this.scrollTop;
+      var clientHeight = this.clientHeight;
+
+      // Store all the item sizes into the cache in advance, to prevent
+      // interleave measuring with mutating dom.
+      if (!this.fixedHeight_)
+        this.ensureAllItemSizesInCache();
+
+      // We cache the list items since creating the DOM nodes is the most
+      // expensive part of redrawing.
+      var cachedItems = this.cachedItems_ || {};
+      var newCachedItems = {};
+
+      var autoExpands = this.autoExpands_;
+
+      var itemsInViewPort = this.getItemsInViewPort(scrollTop, clientHeight);
+      // Draws the hidden rows just above/below the viewport to prevent
+      // flashing in scroll.
+      var firstIndex = Math.max(0, itemsInViewPort.first - 1);
+      var lastIndex = Math.min(itemsInViewPort.last + 1, dataModel.length);
+
+      var beforeFillerHeight =
+          this.autoExpands ? 0 : this.getItemTop(firstIndex);
+      var afterFillerHeight =
+          this.autoExpands ? 0 : this.getAfterFillerHeight(lastIndex);
+
+      this.beforeFiller_.style.height = beforeFillerHeight + 'px';
+
+      var sm = this.selectionModel;
+      var leadIndex = sm.leadIndex;
+
+      if (this.pinnedItem_ &&
+          this.pinnedItem_ != cachedItems[leadIndex]) {
+        if (this.pinnedItem_.hidden)
+          this.removeChild(this.pinnedItem_);
+        this.pinnedItem_ = undefined;
+      }
+
+      this.mergeItems(firstIndex, lastIndex, cachedItems, newCachedItems);
+
+      if (!this.pinnedItem_ && newCachedItems[leadIndex] &&
+          newCachedItems[leadIndex].parentNode == this) {
+        this.pinnedItem_ = newCachedItems[leadIndex];
+      }
+
+      this.afterFiller_.style.height = afterFillerHeight + 'px';
+
+      // We don't set the lead or selected properties until after adding all
+      // items, in case they force relayout in response to these events.
+      var listItem = null;
+      if (leadIndex != -1 && newCachedItems[leadIndex])
+        newCachedItems[leadIndex].lead = true;
+      for (var y = firstIndex; y < lastIndex; y++) {
+        if (sm.getIndexSelected(y))
+          newCachedItems[y].selected = true;
+        else if (y != leadIndex)
+          listItem = newCachedItems[y];
+      }
+
+      this.firstIndex_ = firstIndex;
+      this.lastIndex_ = lastIndex;
+
+      this.remainingSpace_ = itemsInViewPort.last > dataModel.length;
+      this.cachedItems_ = newCachedItems;
+
+      // Mesurings must be placed after adding all the elements, to prevent
+      // performance reducing.
+      if (!this.fixedHeight_) {
+        for (var y = firstIndex; y < lastIndex; y++)
+          this.cachedItemHeights_[y] =
+              this.measureItemHeight_(newCachedItems[y]);
+      }
+
+      // Measure again in case the item height has changed due to a page zoom.
+      //
+      // The measure above is only done the first time but this measure is done
+      // after every redraw. It is done in a timeout so it will not trigger
+      // a reflow (which made the redraw speed 3 times slower on my system).
+      // By using a timeout the measuring will happen later when there is no
+      // need for a reflow.
+      if (listItem && this.fixedHeight_) {
+        var list = this;
+        window.setTimeout(function() {
+          if (listItem.parentNode == list) {
+            list.measured_ = list.measureItem(listItem);
+          }
+        });
+      }
+    },
+
+    /**
+     * Restore the lead item that is present in the list but may be updated
+     * in the data model (supposed to be used inside a batch update). Usually
+     * such an item would be recreated in the redraw method. If reinsertion
+     * is undesirable (for instance to prevent losing focus) the item may be
+     * updated and restored. Assumed the listItem relates to the same data item
+     * as the lead item in the begin of the batch update.
+     *
+     * @param {ListItem} leadItem Already existing lead item.
+     */
+    restoreLeadItem: function(leadItem) {
+      delete this.cachedItems_[leadItem.listIndex];
+
+      leadItem.listIndex = this.selectionModel.leadIndex;
+      this.pinnedItem_ = this.cachedItems_[leadItem.listIndex] = leadItem;
+    },
+
+    /**
+     * Invalidates list by removing cached items.
+     */
+    invalidate: function() {
+      this.cachedItems_ = {};
+      this.cachedItemSized_ = {};
+    },
+
+    /**
+     * Redraws a single item.
+     * @param {number} index The row index to redraw.
+     */
+    redrawItem: function(index) {
+      if (index >= this.firstIndex_ &&
+          (index < this.lastIndex_ || this.remainingSpace_)) {
+        delete this.cachedItems_[index];
+        this.redraw();
+      }
+    },
+
+    /**
+     * Called when a list item is activated, currently only by a double click
+     * event.
+     * @param {number} index The index of the activated item.
+     */
+    activateItemAtIndex: function(index) {
+    },
+
+    /**
+     * Returns a ListItem for the leadIndex. If the item isn't present in the
+     * list creates it and inserts to the list (may be invisible if it's out of
+     * the visible range).
+     *
+     * Item returned from this method won't be removed until it remains a lead
+     * item or til the data model changes (unlike other items that could be
+     * removed when they go out of the visible range).
+     *
+     * @return {cr.ui.ListItem} The lead item for the list.
+     */
+    ensureLeadItemExists: function() {
+      var index = this.selectionModel.leadIndex;
+      if (index < 0)
+        return null;
+      var cachedItems = this.cachedItems_ || {};
+
+      var item = cachedItems[index] ||
+                 this.createItem(this.dataModel.item(index));
+      if (this.pinnedItem_ != item && this.pinnedItem_ &&
+          this.pinnedItem_.hidden) {
+        this.removeChild(this.pinnedItem_);
+      }
+      this.pinnedItem_ = item;
+      cachedItems[index] = item;
+      item.listIndex = index;
+      if (item.parentNode == this)
+        return item;
+
+      if (this.batchCount_ != 0)
+        item.hidden = true;
+
+      // Item will get to the right place in redraw. Choose place to insert
+      // reducing items reinsertion.
+      if (index <= this.firstIndex_)
+        this.insertBefore(item, this.beforeFiller_.nextSibling);
+      else
+        this.insertBefore(item, this.afterFiller_);
+      this.redraw();
+      return item;
+    },
+  };
+
+  cr.defineProperty(List, 'disabled', cr.PropertyKind.BOOL_ATTR);
+
+  /**
+   * Whether the list or one of its descendents has focus. This is necessary
+   * because list items can contain controls that can be focused, and for some
+   * purposes (e.g., styling), the list can still be conceptually focused at
+   * that point even though it doesn't actually have the page focus.
+   */
+  cr.defineProperty(List, 'hasElementFocus', cr.PropertyKind.BOOL_ATTR);
+
+  return {
+    List: List
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/list_item.js b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/list_item.js
new file mode 100644
index 0000000..a670a1f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/list_item.js
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 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.
+
+cr.define('cr.ui', function() {
+
+  /**
+   * Creates a new list item element.
+   * @param {string} opt_label The text label for the item.
+   * @constructor
+   * @extends {HTMLLIElement}
+   */
+  var ListItem = cr.ui.define('li');
+
+  ListItem.prototype = {
+    __proto__: HTMLLIElement.prototype,
+
+    /**
+     * Plain text label.
+     * @type {string}
+     */
+    get label() {
+      return this.textContent;
+    },
+    set label(label) {
+      this.textContent = label;
+    },
+
+    /**
+     * This item's index in the containing list.
+     * @type {number}
+     */
+    listIndex_: -1,
+
+    /**
+     * Called when an element is decorated as a list item.
+     */
+    decorate: function() {
+      this.setAttribute('role', 'listitem');
+    },
+
+    /**
+     * Called when the selection state of this element changes.
+     */
+    selectionChanged: function() {
+    },
+  };
+
+  /**
+   * Whether the item is selected. Setting this does not update the underlying
+   * selection model. This is only used for display purpose.
+   * @type {boolean}
+   */
+  cr.defineProperty(ListItem, 'selected', cr.PropertyKind.BOOL_ATTR,
+                    function() {
+                      this.selectionChanged();
+                    });
+
+  /**
+   * Whether the item is the lead in a selection. Setting this does not update
+   * the underlying selection model. This is only used for display purpose.
+   * @type {boolean}
+   */
+  cr.defineProperty(ListItem, 'lead', cr.PropertyKind.BOOL_ATTR);
+
+  /**
+   * This item's index in the containing list.
+   * @type {number}
+   */
+  cr.defineProperty(ListItem, 'listIndex');
+
+  return {
+    ListItem: ListItem
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/list_selection_controller.js b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/list_selection_controller.js
new file mode 100644
index 0000000..ecaa3e8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/list_selection_controller.js
@@ -0,0 +1,287 @@
+// Copyright (c) 2012 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.
+
+cr.define('cr.ui', function() {
+  /**
+   * Creates a selection controller that is to be used with lists. This is
+   * implemented for vertical lists but changing the behavior for horizontal
+   * lists or icon views is a matter of overriding {@code getIndexBefore},
+   * {@code getIndexAfter}, {@code getIndexAbove} as well as
+   * {@code getIndexBelow}.
+   *
+   * @param {cr.ui.ListSelectionModel} selectionModel The selection model to
+   *     interact with.
+   *
+   * @constructor
+   * @extends {!cr.EventTarget}
+   */
+  function ListSelectionController(selectionModel) {
+    this.selectionModel_ = selectionModel;
+  }
+
+  ListSelectionController.prototype = {
+
+    /**
+     * The selection model we are interacting with.
+     * @type {cr.ui.ListSelectionModel}
+     */
+    get selectionModel() {
+      return this.selectionModel_;
+    },
+
+    /**
+     * Returns the index below (y axis) the given element.
+     * @param {number} index The index to get the index below.
+     * @return {number} The index below or -1 if not found.
+     */
+    getIndexBelow: function(index) {
+      if (index == this.getLastIndex())
+        return -1;
+      return index + 1;
+    },
+
+    /**
+     * Returns the index above (y axis) the given element.
+     * @param {number} index The index to get the index above.
+     * @return {number} The index below or -1 if not found.
+     */
+    getIndexAbove: function(index) {
+      return index - 1;
+    },
+
+    /**
+     * Returns the index before (x axis) the given element. This returns -1
+     * by default but override this for icon view and horizontal selection
+     * models.
+     *
+     * @param {number} index The index to get the index before.
+     * @return {number} The index before or -1 if not found.
+     */
+    getIndexBefore: function(index) {
+      return -1;
+    },
+
+    /**
+     * Returns the index after (x axis) the given element. This returns -1
+     * by default but override this for icon view and horizontal selection
+     * models.
+     *
+     * @param {number} index The index to get the index after.
+     * @return {number} The index after or -1 if not found.
+     */
+    getIndexAfter: function(index) {
+      return -1;
+    },
+
+    /**
+     * Returns the next list index. This is the next logical and should not
+     * depend on any kind of layout of the list.
+     * @param {number} index The index to get the next index for.
+     * @return {number} The next index or -1 if not found.
+     */
+    getNextIndex: function(index) {
+      if (index == this.getLastIndex())
+        return -1;
+      return index + 1;
+    },
+
+    /**
+     * Returns the prevous list index. This is the previous logical and should
+     * not depend on any kind of layout of the list.
+     * @param {number} index The index to get the previous index for.
+     * @return {number} The previous index or -1 if not found.
+     */
+    getPreviousIndex: function(index) {
+      return index - 1;
+    },
+
+    /**
+     * @return {number} The first index.
+     */
+    getFirstIndex: function() {
+      return 0;
+    },
+
+    /**
+     * @return {number} The last index.
+     */
+    getLastIndex: function() {
+      return this.selectionModel.length - 1;
+    },
+
+    /**
+     * Called by the view when the user does a mousedown, mouseup, TOUCH_START
+     * and TOUCH_END on the list.
+     * @param {!Event} e The browser mouse or TouchHandler event.
+     * @param {number} index The index that was under the mouse pointer, -1 if
+     *     none.
+     */
+    handlePointerDownUp: function(e, index) {
+      var sm = this.selectionModel;
+      var anchorIndex = sm.anchorIndex;
+      var isDown = (e.type == 'mousedown' ||
+                    e.type == cr.ui.TouchHandler.EventType.TOUCH_START);
+
+      sm.beginChange();
+
+      if (index == -1) {
+        // On Mac we always clear the selection if the user clicks a blank area.
+        // On Windows, we only clear the selection if neither Shift nor Ctrl are
+        // pressed.
+        if (cr.isMac || cr.isChromeOS) {
+          sm.leadIndex = sm.anchorIndex = -1;
+          sm.unselectAll();
+        } else if (!isDown && !e.shiftKey && !e.ctrlKey)
+          // Keep anchor and lead indexes. Note that this is intentionally
+          // different than on the Mac.
+          if (sm.multiple)
+            sm.unselectAll();
+      } else {
+        if (sm.multiple && (cr.isMac ? e.metaKey :
+                                       (e.ctrlKey && !e.shiftKey))) {
+          // Selection is handled at mouseUp on windows/linux, mouseDown on mac.
+          if (cr.isMac ? isDown : !isDown) {
+            // Toggle the current one and make it anchor index.
+            sm.setIndexSelected(index, !sm.getIndexSelected(index));
+            sm.leadIndex = index;
+            sm.anchorIndex = index;
+          }
+        } else if (e.shiftKey && anchorIndex != -1 && anchorIndex != index) {
+          // Shift is done in mousedown.
+          if (isDown) {
+            sm.unselectAll();
+            sm.leadIndex = index;
+            if (sm.multiple)
+              sm.selectRange(anchorIndex, index);
+            else
+              sm.setIndexSelected(index, true);
+          }
+        } else {
+          // Right click for a context menu needs to not clear the selection.
+          var isRightClick = e.button == 2;
+
+          // If the index is selected this is handled in mouseup.
+          var indexSelected = sm.getIndexSelected(index);
+          if ((indexSelected && !isDown || !indexSelected && isDown) &&
+              !(indexSelected && isRightClick)) {
+            sm.selectedIndex = index;
+          }
+        }
+      }
+
+      sm.endChange();
+    },
+
+    /**
+     * Called by the view when it receives a keydown event.
+     * @param {Event} e The keydown event.
+     */
+    handleKeyDown: function(e) {
+      var SPACE_KEY_CODE = 32;
+      var tagName = e.target.tagName;
+      // If focus is in an input field of some kind, only handle navigation keys
+      // that aren't likely to conflict with input interaction (e.g., text
+      // editing, or changing the value of a checkbox or select).
+      if (tagName == 'INPUT') {
+        var inputType = e.target.type;
+        // Just protect space (for toggling) for checkbox and radio.
+        if (inputType == 'checkbox' || inputType == 'radio') {
+          if (e.keyCode == SPACE_KEY_CODE)
+            return;
+        // Protect all but the most basic navigation commands in anything else.
+        } else if (e.keyIdentifier != 'Up' && e.keyIdentifier != 'Down') {
+          return;
+        }
+      }
+      // Similarly, don't interfere with select element handling.
+      if (tagName == 'SELECT')
+        return;
+
+      var sm = this.selectionModel;
+      var newIndex = -1;
+      var leadIndex = sm.leadIndex;
+      var prevent = true;
+
+      // Ctrl/Meta+A
+      if (sm.multiple && e.keyCode == 65 &&
+          (cr.isMac && e.metaKey || !cr.isMac && e.ctrlKey)) {
+        sm.selectAll();
+        e.preventDefault();
+        return;
+      }
+
+      // Space
+      if (e.keyCode == SPACE_KEY_CODE) {
+        if (leadIndex != -1) {
+          var selected = sm.getIndexSelected(leadIndex);
+          if (e.ctrlKey || !selected) {
+            sm.setIndexSelected(leadIndex, !selected || !sm.multiple);
+            return;
+          }
+        }
+      }
+
+      switch (e.keyIdentifier) {
+        case 'Home':
+          newIndex = this.getFirstIndex();
+          break;
+        case 'End':
+          newIndex = this.getLastIndex();
+          break;
+        case 'Up':
+          newIndex = leadIndex == -1 ?
+              this.getLastIndex() : this.getIndexAbove(leadIndex);
+          break;
+        case 'Down':
+          newIndex = leadIndex == -1 ?
+              this.getFirstIndex() : this.getIndexBelow(leadIndex);
+          break;
+        case 'Left':
+          newIndex = leadIndex == -1 ?
+              this.getLastIndex() : this.getIndexBefore(leadIndex);
+          break;
+        case 'Right':
+          newIndex = leadIndex == -1 ?
+              this.getFirstIndex() : this.getIndexAfter(leadIndex);
+          break;
+        default:
+          prevent = false;
+      }
+
+      if (newIndex != -1) {
+        sm.beginChange();
+
+        sm.leadIndex = newIndex;
+        if (e.shiftKey) {
+          var anchorIndex = sm.anchorIndex;
+          if (sm.multiple)
+            sm.unselectAll();
+          if (anchorIndex == -1) {
+            sm.setIndexSelected(newIndex, true);
+            sm.anchorIndex = newIndex;
+          } else {
+            sm.selectRange(anchorIndex, newIndex);
+          }
+        } else if (e.ctrlKey && !cr.isMac && !cr.isChromeOS) {
+          // Setting the lead index is done above.
+          // Mac does not allow you to change the lead.
+        } else {
+          if (sm.multiple)
+            sm.unselectAll();
+          sm.setIndexSelected(newIndex, true);
+          sm.anchorIndex = newIndex;
+        }
+
+        sm.endChange();
+
+        if (prevent)
+          e.preventDefault();
+      }
+    }
+  };
+
+  return {
+    ListSelectionController: ListSelectionController
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/list_selection_model.js b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/list_selection_model.js
new file mode 100644
index 0000000..17b1c99
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/list_selection_model.js
@@ -0,0 +1,339 @@
+// Copyright (c) 2012 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.
+
+cr.define('cr.ui', function() {
+  /** @const */ var Event = cr.Event;
+  /** @const */ var EventTarget = cr.EventTarget;
+
+  /**
+   * Creates a new selection model that is to be used with lists.
+   *
+   * @param {number=} opt_length The number items in the selection.
+   *
+   * @constructor
+   * @extends {!cr.EventTarget}
+   */
+  function ListSelectionModel(opt_length) {
+    this.length_ = opt_length || 0;
+    // Even though selectedIndexes_ is really a map we use an array here to get
+    // iteration in the order of the indexes.
+    this.selectedIndexes_ = [];
+
+    // True if any item could be lead or anchor. False if only selected ones.
+    this.independentLeadItem_ = !cr.isMac && !cr.isChromeOS;
+  }
+
+  ListSelectionModel.prototype = {
+    __proto__: EventTarget.prototype,
+
+    /**
+     * The number of items in the model.
+     * @type {number}
+     */
+    get length() {
+      return this.length_;
+    },
+
+    /**
+     * The selected indexes.
+     * Setter also changes lead and anchor indexes if value list is nonempty.
+     * @type {!Array}
+     */
+    get selectedIndexes() {
+      return Object.keys(this.selectedIndexes_).map(Number);
+    },
+    set selectedIndexes(selectedIndexes) {
+      this.beginChange();
+      var unselected = {};
+      for (var index in this.selectedIndexes_) {
+        unselected[index] = true;
+      }
+
+      for (var i = 0; i < selectedIndexes.length; i++) {
+        var index = selectedIndexes[i];
+        if (index in this.selectedIndexes_) {
+          delete unselected[index];
+        } else {
+          this.selectedIndexes_[index] = true;
+          this.changedIndexes_[index] = true;
+        }
+      }
+
+      for (var index in unselected) {
+        delete this.selectedIndexes_[index];
+        this.changedIndexes_[index] = false;
+      }
+
+      if (selectedIndexes.length) {
+        this.leadIndex = this.anchorIndex = selectedIndexes[0];
+      } else {
+        this.leadIndex = this.anchorIndex = -1;
+      }
+      this.endChange();
+    },
+
+    /**
+     * Convenience getter which returns the first selected index.
+     * Setter also changes lead and anchor indexes if value is nonnegative.
+     * @type {number}
+     */
+    get selectedIndex() {
+      for (var i in this.selectedIndexes_) {
+        return Number(i);
+      }
+      return -1;
+    },
+    set selectedIndex(selectedIndex) {
+      this.selectedIndexes = selectedIndex != -1 ? [selectedIndex] : [];
+    },
+
+    /**
+     * Returns the last selected index or -1 if no item selected.
+     * @type {number}
+     */
+    get lastSelectedIndex() {
+      var result = -1;
+      for (var i in this.selectedIndexes_) {
+        result = Math.max(result, Number(i));
+      }
+      return result;
+    },
+
+    /**
+     * Selects a range of indexes, starting with {@code start} and ends with
+     * {@code end}.
+     * @param {number} start The first index to select.
+     * @param {number} end The last index to select.
+     */
+    selectRange: function(start, end) {
+      // Swap if starts comes after end.
+      if (start > end) {
+        var tmp = start;
+        start = end;
+        end = tmp;
+      }
+
+      this.beginChange();
+
+      for (var index = start; index != end; index++) {
+        this.setIndexSelected(index, true);
+      }
+      this.setIndexSelected(end, true);
+
+      this.endChange();
+    },
+
+    /**
+     * Selects all indexes.
+     */
+    selectAll: function() {
+      this.selectRange(0, this.length - 1);
+    },
+
+    /**
+     * Clears the selection
+     */
+    clear: function() {
+      this.beginChange();
+      this.length_ = 0;
+      this.anchorIndex = this.leadIndex = -1;
+      this.unselectAll();
+      this.endChange();
+    },
+
+    /**
+     * Unselects all selected items.
+     */
+    unselectAll: function() {
+      this.beginChange();
+      for (var i in this.selectedIndexes_) {
+        this.setIndexSelected(i, false);
+      }
+      this.endChange();
+    },
+
+    /**
+     * Sets the selected state for an index.
+     * @param {number} index The index to set the selected state for.
+     * @param {boolean} b Whether to select the index or not.
+     */
+    setIndexSelected: function(index, b) {
+      var oldSelected = index in this.selectedIndexes_;
+      if (oldSelected == b)
+        return;
+
+      if (b)
+        this.selectedIndexes_[index] = true;
+      else
+        delete this.selectedIndexes_[index];
+
+      this.beginChange();
+
+      this.changedIndexes_[index] = b;
+
+      // End change dispatches an event which in turn may update the view.
+      this.endChange();
+    },
+
+    /**
+     * Whether a given index is selected or not.
+     * @param {number} index The index to check.
+     * @return {boolean} Whether an index is selected.
+     */
+    getIndexSelected: function(index) {
+      return index in this.selectedIndexes_;
+    },
+
+    /**
+     * This is used to begin batching changes. Call {@code endChange} when you
+     * are done making changes.
+     */
+    beginChange: function() {
+      if (!this.changeCount_) {
+        this.changeCount_ = 0;
+        this.changedIndexes_ = {};
+        this.oldLeadIndex_ = this.leadIndex_;
+        this.oldAnchorIndex_ = this.anchorIndex_;
+      }
+      this.changeCount_++;
+    },
+
+    /**
+     * Call this after changes are done and it will dispatch a change event if
+     * any changes were actually done.
+     */
+    endChange: function() {
+      this.changeCount_--;
+      if (!this.changeCount_) {
+        // Calls delayed |dispatchPropertyChange|s, only when |leadIndex| or
+        // |anchorIndex| has been actually changed in the batch.
+        this.leadIndex_ = this.adjustIndex_(this.leadIndex_);
+        if (this.leadIndex_ != this.oldLeadIndex_) {
+          cr.dispatchPropertyChange(this, 'leadIndex',
+                                    this.leadIndex_, this.oldLeadIndex_);
+        }
+        this.oldLeadIndex_ = null;
+
+        this.anchorIndex_ = this.adjustIndex_(this.anchorIndex_);
+        if (this.anchorIndex_ != this.oldAnchorIndex_) {
+          cr.dispatchPropertyChange(this, 'anchorIndex',
+                                    this.anchorIndex_, this.oldAnchorIndex_);
+        }
+        this.oldAnchorIndex_ = null;
+
+        var indexes = Object.keys(this.changedIndexes_);
+        if (indexes.length) {
+          var e = new Event('change');
+          e.changes = indexes.map(function(index) {
+            return {
+              index: index,
+              selected: this.changedIndexes_[index]
+            };
+          }, this);
+          this.dispatchEvent(e);
+        }
+        this.changedIndexes_ = {};
+      }
+    },
+
+    leadIndex_: -1,
+    oldLeadIndex_: null,
+
+    /**
+     * The leadIndex is used with multiple selection and it is the index that
+     * the user is moving using the arrow keys.
+     * @type {number}
+     */
+    get leadIndex() {
+      return this.leadIndex_;
+    },
+    set leadIndex(leadIndex) {
+      var oldValue = this.leadIndex_;
+      var newValue = this.adjustIndex_(leadIndex);
+      this.leadIndex_ = newValue;
+      // Delays the call of dispatchPropertyChange if batch is running.
+      if (!this.changeCount_ && newValue != oldValue)
+        cr.dispatchPropertyChange(this, 'leadIndex', newValue, oldValue);
+    },
+
+    anchorIndex_: -1,
+    oldAnchorIndex_: null,
+
+    /**
+     * The anchorIndex is used with multiple selection.
+     * @type {number}
+     */
+    get anchorIndex() {
+      return this.anchorIndex_;
+    },
+    set anchorIndex(anchorIndex) {
+      var oldValue = this.anchorIndex_;
+      var newValue = this.adjustIndex_(anchorIndex);
+      this.anchorIndex_ = newValue;
+      // Delays the call of dispatchPropertyChange if batch is running.
+      if (!this.changeCount_ && newValue != oldValue)
+        cr.dispatchPropertyChange(this, 'anchorIndex', newValue, oldValue);
+    },
+
+    /**
+     * Helper method that adjustes a value before assiging it to leadIndex or
+     * anchorIndex.
+     * @param {number} index New value for leadIndex or anchorIndex.
+     * @return {number} Corrected value.
+     */
+    adjustIndex_: function(index) {
+      index = Math.max(-1, Math.min(this.length_ - 1, index));
+      // On Mac and ChromeOS lead and anchor items are forced to be among
+      // selected items. This rule is not enforces until end of batch update.
+      if (!this.changeCount_ && !this.independentLeadItem_ &&
+          !this.getIndexSelected(index)) {
+        index = this.lastSelectedIndex;
+      }
+      return index;
+    },
+
+    /**
+     * Whether the selection model supports multiple selected items.
+     * @type {boolean}
+     */
+    get multiple() {
+      return true;
+    },
+
+    /**
+     * Adjusts the selection after reordering of items in the table.
+     * @param {!Array.<number>} permutation The reordering permutation.
+     */
+    adjustToReordering: function(permutation) {
+      this.beginChange();
+      var oldLeadIndex = this.leadIndex;
+      var oldAnchorIndex = this.anchorIndex;
+
+      this.selectedIndexes = this.selectedIndexes.map(function(oldIndex) {
+        return permutation[oldIndex];
+      }).filter(function(index) {
+        return index != -1;
+      });
+
+      // Will be adjusted in endChange.
+      if (oldLeadIndex != -1)
+        this.leadIndex = permutation[oldLeadIndex];
+      if (oldAnchorIndex != -1)
+        this.anchorIndex = permutation[oldAnchorIndex];
+      this.endChange();
+    },
+
+    /**
+     * Adjusts selection model length.
+     * @param {number} length New selection model length.
+     */
+    adjustLength: function(length) {
+      this.length_ = length;
+    }
+  };
+
+  return {
+    ListSelectionModel: ListSelectionModel
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/list_single_selection_model.js b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/list_single_selection_model.js
new file mode 100644
index 0000000..7afd158
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/list_single_selection_model.js
@@ -0,0 +1,249 @@
+// Copyright (c) 2012 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.
+
+cr.define('cr.ui', function() {
+  /** @const */ var Event = cr.Event;
+  /** @const */ var EventTarget = cr.EventTarget;
+
+  /**
+   * Creates a new selection model that is to be used with lists. This only
+   * allows a single index to be selected.
+   *
+   * @param {number=} opt_length The number items in the selection.
+   *
+   * @constructor
+   * @extends {!cr.EventTarget}
+   */
+  function ListSingleSelectionModel(opt_length) {
+    this.length_ = opt_length || 0;
+    this.selectedIndex = -1;
+
+    // True if any item could be lead or anchor. False if only selected ones.
+    this.independentLeadItem_ = !cr.isMac && !cr.isChromeOS;
+  }
+
+  ListSingleSelectionModel.prototype = {
+    __proto__: EventTarget.prototype,
+
+    /**
+     * The number of items in the model.
+     * @type {number}
+     */
+    get length() {
+      return this.length_;
+    },
+
+    /**
+     * @type {!Array} The selected indexes.
+     */
+    get selectedIndexes() {
+      var i = this.selectedIndex;
+      return i != -1 ? [this.selectedIndex] : [];
+    },
+    set selectedIndexes(indexes) {
+      this.selectedIndex = indexes.length ? indexes[0] : -1;
+    },
+
+    /**
+     * Convenience getter which returns the first selected index.
+     * Setter also changes lead and anchor indexes if value is nonegative.
+     * @type {number}
+     */
+    get selectedIndex() {
+      return this.selectedIndex_;
+    },
+    set selectedIndex(selectedIndex) {
+      var oldSelectedIndex = this.selectedIndex;
+      var i = Math.max(-1, Math.min(this.length_ - 1, selectedIndex));
+
+      if (i != oldSelectedIndex) {
+        this.beginChange();
+        this.selectedIndex_ = i;
+        this.leadIndex = i >= 0 ? i : this.leadIndex;
+        this.endChange();
+      }
+    },
+
+    /**
+     * Selects a range of indexes, starting with {@code start} and ends with
+     * {@code end}.
+     * @param {number} start The first index to select.
+     * @param {number} end The last index to select.
+     */
+    selectRange: function(start, end) {
+      // Only select first index.
+      this.selectedIndex = Math.min(start, end);
+    },
+
+    /**
+     * Selects all indexes.
+     */
+    selectAll: function() {
+      // Select all is not allowed on a single selection model
+    },
+
+    /**
+     * Clears the selection
+     */
+    clear: function() {
+      this.beginChange();
+      this.length_ = 0;
+      this.selectedIndex = this.anchorIndex = this.leadIndex = -1;
+      this.endChange();
+    },
+
+    /**
+     * Unselects all selected items.
+     */
+    unselectAll: function() {
+      this.selectedIndex = -1;
+    },
+
+    /**
+     * Sets the selected state for an index.
+     * @param {number} index The index to set the selected state for.
+     * @param {boolean} b Whether to select the index or not.
+     */
+    setIndexSelected: function(index, b) {
+      // Only allow selection
+      var oldSelected = index == this.selectedIndex_;
+      if (oldSelected == b)
+        return;
+
+      if (b)
+        this.selectedIndex = index;
+      else if (index == this.selectedIndex_)
+        this.selectedIndex = -1;
+    },
+
+    /**
+     * Whether a given index is selected or not.
+     * @param {number} index The index to check.
+     * @return {boolean} Whether an index is selected.
+     */
+    getIndexSelected: function(index) {
+      return index == this.selectedIndex_;
+    },
+
+    /**
+     * This is used to begin batching changes. Call {@code endChange} when you
+     * are done making changes.
+     */
+    beginChange: function() {
+      if (!this.changeCount_) {
+        this.changeCount_ = 0;
+        this.selectedIndexBefore_ = this.selectedIndex_;
+      }
+      this.changeCount_++;
+    },
+
+    /**
+     * Call this after changes are done and it will dispatch a change event if
+     * any changes were actually done.
+     */
+    endChange: function() {
+      this.changeCount_--;
+      if (!this.changeCount_) {
+        if (this.selectedIndexBefore_ != this.selectedIndex_) {
+          var beforeChange = this.createChangeEvent('beforeChange');
+          if (this.dispatchEvent(beforeChange))
+            this.dispatchEvent(this.createChangeEvent('change'));
+          else
+            this.selectedIndex_ = this.selectedIndexBefore_;
+        }
+      }
+    },
+
+    /**
+     * Creates event with specified name and fills its {changes} property.
+     * @param {String} name Event name.
+     */
+    createChangeEvent: function(eventName) {
+      var e = new Event(eventName);
+      var indexes = [this.selectedIndexBefore_, this.selectedIndex_];
+      e.changes = indexes.filter(function(index) {
+        return index != -1;
+      }).map(function(index) {
+        return {
+          index: index,
+          selected: index == this.selectedIndex_
+        };
+      }, this);
+
+      return e;
+    },
+
+    leadIndex_: -1,
+
+    /**
+     * The leadIndex is used with multiple selection and it is the index that
+     * the user is moving using the arrow keys.
+     * @type {number}
+     */
+    get leadIndex() {
+      return this.leadIndex_;
+    },
+    set leadIndex(leadIndex) {
+      var li = this.adjustIndex_(leadIndex);
+      if (li != this.leadIndex_) {
+        var oldLeadIndex = this.leadIndex_;
+        this.leadIndex_ = li;
+        cr.dispatchPropertyChange(this, 'leadIndex', li, oldLeadIndex);
+        cr.dispatchPropertyChange(this, 'anchorIndex', li, oldLeadIndex);
+      }
+    },
+
+    adjustIndex_: function(index) {
+      index = Math.max(-1, Math.min(this.length_ - 1, index));
+      if (!this.independentLeadItem_)
+        index = this.selectedIndex;
+      return index;
+    },
+
+    /**
+     * The anchorIndex is used with multiple selection.
+     * @type {number}
+     */
+    get anchorIndex() {
+      return this.leadIndex;
+    },
+    set anchorIndex(anchorIndex) {
+      this.leadIndex = anchorIndex;
+    },
+
+    /**
+     * Whether the selection model supports multiple selected items.
+     * @type {boolean}
+     */
+    get multiple() {
+      return false;
+    },
+
+    /**
+     * Adjusts the selection after reordering of items in the table.
+     * @param {!Array.<number>} permutation The reordering permutation.
+     */
+    adjustToReordering: function(permutation) {
+      if (this.leadIndex != -1)
+        this.leadIndex = permutation[this.leadIndex];
+
+      var oldSelectedIndex = this.selectedIndex;
+      if (oldSelectedIndex != -1) {
+        this.selectedIndex = permutation[oldSelectedIndex];
+      }
+    },
+
+    /**
+     * Adjusts selection model length.
+     * @param {number} length New selection model length.
+     */
+    adjustLength: function(length) {
+      this.length_ = length;
+    }
+  };
+
+  return {
+    ListSingleSelectionModel: ListSingleSelectionModel
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/overlay.js b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/overlay.js
new file mode 100644
index 0000000..056bf1e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/overlay.js
@@ -0,0 +1,101 @@
+// Copyright (c) 2012 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 Provides dialog-like behaviors for the tracing UI.
+ */
+cr.define('cr.ui.overlay', function() {
+
+  /**
+   * Gets the top, visible overlay. It makes the assumption that if multiple
+   * overlays are visible, the last in the byte order is topmost.
+   * TODO(estade): rely on aria-visibility instead?
+   * @return {HTMLElement} The overlay.
+   */
+  function getTopOverlay() {
+    var overlays = document.querySelectorAll('.overlay:not([hidden])');
+    return overlays[overlays.length - 1];
+  }
+
+  /**
+   * Makes initializations which must hook at the document level.
+   */
+  function globalInitialization() {
+    // Close the overlay on escape.
+    document.addEventListener('keydown', function(e) {
+      if (e.keyCode == 27) {  // Escape
+        var overlay = getTopOverlay();
+        if (!overlay)
+          return;
+
+        cr.dispatchSimpleEvent(overlay, 'cancelOverlay');
+      }
+    });
+
+    window.addEventListener('resize', setMaxHeightAllPages);
+
+    setMaxHeightAllPages();
+  }
+
+  /**
+   * Sets the max-height of all pages in all overlays, based on the window
+   * height.
+   */
+  function setMaxHeightAllPages() {
+    var pages = document.querySelectorAll('.overlay .page');
+
+    var maxHeight = Math.min(0.9 * window.innerHeight, 640) + 'px';
+    for (var i = 0; i < pages.length; i++)
+      pages[i].style.maxHeight = maxHeight;
+  }
+
+  /**
+   * Adds behavioral hooks for the given overlay.
+   * @param {HTMLElement} overlay The .overlay.
+   */
+  function setupOverlay(overlay) {
+    // Close the overlay on clicking any of the pages' close buttons.
+    var closeButtons = overlay.querySelectorAll('.page > .close-button');
+    for (var i = 0; i < closeButtons.length; i++) {
+      closeButtons[i].addEventListener('click', function(e) {
+        cr.dispatchSimpleEvent(overlay, 'cancelOverlay');
+      });
+    }
+
+    // Remove the 'pulse' animation any time the overlay is hidden or shown.
+    overlay.__defineSetter__('hidden', function(value) {
+      this.classList.remove('pulse');
+      if (value)
+        this.setAttribute('hidden', true);
+      else
+        this.removeAttribute('hidden');
+    });
+    overlay.__defineGetter__('hidden', function() {
+      return this.hasAttribute('hidden');
+    });
+
+    // Shake when the user clicks away.
+    overlay.addEventListener('click', function(e) {
+      // Only pulse if the overlay was the target of the click.
+      if (this != e.target)
+        return;
+
+      // This may be null while the overlay is closing.
+      var overlayPage = this.querySelector('.page:not([hidden])');
+      if (overlayPage)
+        overlayPage.classList.add('pulse');
+    });
+    overlay.addEventListener('webkitAnimationEnd', function(e) {
+      e.target.classList.remove('pulse');
+    });
+  }
+
+  return {
+    globalInitialization: globalInitialization,
+    setupOverlay: setupOverlay,
+  };
+});
+
+document.addEventListener('DOMContentLoaded',
+                          cr.ui.overlay.globalInitialization);
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/touch_handler.js b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/touch_handler.js
new file mode 100644
index 0000000..8155a1f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/js/cr/ui/touch_handler.js
@@ -0,0 +1,875 @@
+// Copyright (c) 2012 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 Touch Handler. Class that handles all touch events and
+ * uses them to interpret higher level gestures and behaviors. TouchEvent is a
+ * built in mobile safari type:
+ * http://developer.apple.com/safari/library/documentation/UserExperience/Reference/TouchEventClassReference/TouchEvent/TouchEvent.html.
+ * This class is intended to work with all webkit browsers, tested on Chrome and
+ * iOS.
+ *
+ * The following types of gestures are currently supported.  See the definition
+ * of TouchHandler.EventType for details.
+ *
+ * Single Touch:
+ *      This provides simple single-touch events.  Any secondary touch is
+ *      ignored.
+ *
+ * Drag:
+ *      A single touch followed by some movement. This behavior will handle all
+ *      of the required events and report the properties of the drag to you
+ *      while the touch is happening and at the end of the drag sequence. This
+ *      behavior will NOT perform the actual dragging (redrawing the element)
+ *      for you, this responsibility is left to the client code.
+ *
+ * Long press:
+ *     When your element is touched and held without any drag occuring, the
+ *     LONG_PRESS event will fire.
+ */
+
+// Use an anonymous function to enable strict mode just for this file (which
+// will be concatenated with other files when embedded in Chrome)
+cr.define('cr.ui', function() {
+  'use strict';
+
+  /**
+   * A TouchHandler attaches to an Element, listents for low-level touch (or
+   * mouse) events and dispatching higher-level events on the element.
+   * @param {!Element} element The element to listen on and fire events
+   * for.
+   * @constructor
+   */
+  function TouchHandler(element) {
+    /**
+     * @type {!Element}
+     * @private
+     */
+    this.element_ = element;
+
+    /**
+     * The absolute sum of all touch y deltas.
+     * @type {number}
+     * @private
+     */
+    this.totalMoveY_ = 0;
+
+    /**
+     * The absolute sum of all touch x deltas.
+     * @type {number}
+     * @private
+     */
+    this.totalMoveX_ = 0;
+
+    /**
+     * An array of tuples where the first item is the horizontal component of a
+     * recent relevant touch and the second item is the touch's time stamp. Old
+     * touches are removed based on the max tracking time and when direction
+     * changes.
+      * @type {!Array.<number>}
+      * @private
+      */
+    this.recentTouchesX_ = [];
+
+    /**
+     * An array of tuples where the first item is the vertical component of a
+     * recent relevant touch and the second item is the touch's time stamp. Old
+     * touches are removed based on the max tracking time and when direction
+     * changes.
+     * @type {!Array.<number>}
+     * @private
+     */
+    this.recentTouchesY_ = [];
+
+    /**
+     * Used to keep track of all events we subscribe to so we can easily clean
+     * up
+     * @type {EventTracker}
+     * @private
+     */
+    this.events_ = new EventTracker();
+  }
+
+
+  /**
+   * DOM Events that may be fired by the TouchHandler at the element
+   */
+  TouchHandler.EventType = {
+    // Fired whenever the element is touched as the only touch to the device.
+    // enableDrag defaults to false, set to true to permit dragging.
+    TOUCH_START: 'touchHandler:touch_start',
+
+    // Fired when an element is held for a period of time.  Prevents dragging
+    // from occuring (even if enableDrag was set to true).
+    LONG_PRESS: 'touchHandler:long_press',
+
+    // If enableDrag was set to true at TOUCH_START, DRAG_START will fire when
+    // the touch first moves sufficient distance.  enableDrag is set to true but
+    // can be reset to false to cancel the drag.
+    DRAG_START: 'touchHandler:drag_start',
+
+    // If enableDrag was true after DRAG_START, DRAG_MOVE will fire whenever the
+    // touch is moved.
+    DRAG_MOVE: 'touchHandler:drag_move',
+
+    // Fired just before TOUCH_END when a drag is released.  Correlates 1:1 with
+    // a DRAG_START.
+    DRAG_END: 'touchHandler:drag_end',
+
+    // Fired whenever a touch that is being tracked has been released.
+    // Correlates 1:1 with a TOUCH_START.
+    TOUCH_END: 'touchHandler:touch_end',
+
+    // Fired whenever the element is tapped in a short time and no dragging is
+    // detected.
+    TAP: 'touchHandler:tap'
+  };
+
+
+  /**
+   * The type of event sent by TouchHandler
+   * @constructor
+   * @param {string} type The type of event (one of cr.ui.Grabber.EventType).
+   * @param {boolean} bubbles Whether or not the event should bubble.
+   * @param {number} clientX The X location of the touch.
+   * @param {number} clientY The Y location of the touch.
+   * @param {!Element} touchedElement The element at the current location of the
+   *        touch.
+   */
+  TouchHandler.Event = function(type, bubbles, clientX, clientY,
+      touchedElement) {
+    var event = document.createEvent('Event');
+    event.initEvent(type, bubbles, true);
+    event.__proto__ = TouchHandler.Event.prototype;
+
+    /**
+     * The X location of the touch affected
+     * @type {number}
+     */
+    event.clientX = clientX;
+
+    /**
+     * The Y location of the touch affected
+     * @type {number}
+     */
+    event.clientY = clientY;
+
+    /**
+     * The element at the current location of the touch.
+     * @type {!Element}
+     */
+    event.touchedElement = touchedElement;
+
+    return event;
+  };
+
+  TouchHandler.Event.prototype = {
+    __proto__: Event.prototype,
+
+    /**
+     * For TOUCH_START and DRAG START events, set to true to enable dragging or
+     * false to disable dragging.
+     * @type {boolean|undefined}
+     */
+    enableDrag: undefined,
+
+    /**
+     * For DRAG events, provides the horizontal component of the
+     * drag delta. Drag delta is defined as the delta of the start touch
+     * position and the current drag position.
+     * @type {number|undefined}
+     */
+    dragDeltaX: undefined,
+
+    /**
+     * For DRAG events, provides the vertical component of the
+     * drag delta.
+     * @type {number|undefined}
+     */
+    dragDeltaY: undefined
+  };
+
+  /**
+   * Maximum movement of touch required to be considered a tap.
+   * @type {number}
+   * @private
+   */
+  TouchHandler.MAX_TRACKING_FOR_TAP_ = 8;
+
+
+  /**
+   * The maximum number of ms to track a touch event. After an event is older
+   * than this value, it will be ignored in velocity calculations.
+   * @type {number}
+   * @private
+   */
+  TouchHandler.MAX_TRACKING_TIME_ = 250;
+
+
+  /**
+   * The maximum number of touches to track.
+   * @type {number}
+   * @private
+   */
+  TouchHandler.MAX_TRACKING_TOUCHES_ = 5;
+
+
+  /**
+   * The maximum velocity to return, in pixels per millisecond, that is used
+   * to guard against errors in calculating end velocity of a drag. This is a
+   * very fast drag velocity.
+   * @type {number}
+   * @private
+   */
+  TouchHandler.MAXIMUM_VELOCITY_ = 5;
+
+
+  /**
+   * The velocity to return, in pixel per millisecond, when the time stamps on
+   * the events are erroneous. The browser can return bad time stamps if the
+   * thread is blocked for the duration of the drag. This is a low velocity to
+   * prevent the content from moving quickly after a slow drag. It is less
+   * jarring if the content moves slowly after a fast drag.
+   * @type {number}
+   * @private
+   */
+  TouchHandler.VELOCITY_FOR_INCORRECT_EVENTS_ = 1;
+
+  /**
+   * The time, in milliseconds, that a touch must be held to be considered
+   * 'long'.
+   * @type {number}
+   * @private
+   */
+  TouchHandler.TIME_FOR_LONG_PRESS_ = 500;
+
+  TouchHandler.prototype = {
+    /**
+     * If defined, the identifer of the single touch that is active.  Note that
+     * 0 is a valid touch identifier - it should not be treated equivalently to
+     * undefined.
+     * @type {number|undefined}
+     * @private
+     */
+    activeTouch_: undefined,
+
+    /**
+     * @type {boolean|undefined}
+     * @private
+     */
+    tracking_: undefined,
+
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    startTouchX_: undefined,
+
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    startTouchY_: undefined,
+
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    endTouchX_: undefined,
+
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    endTouchY_: undefined,
+
+    /**
+     * Time of the touchstart event.
+     * @type {number|undefined}
+     * @private
+     */
+    startTime_: undefined,
+
+    /**
+     * The time of the touchend event.
+     * @type {number|undefined}
+     * @private
+     */
+    endTime_: undefined,
+
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    lastTouchX_: undefined,
+
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    lastTouchY_: undefined,
+
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    lastMoveX_: undefined,
+
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    lastMoveY_: undefined,
+
+    /**
+     * @type {number|undefined}
+     * @private
+     */
+    longPressTimeout_: undefined,
+
+    /**
+     * If defined and true, the next click event should be swallowed
+     * @type {boolean|undefined}
+     * @private
+     */
+    swallowNextClick_: undefined,
+
+    /**
+     * @type {boolean}
+     * @private
+     */
+    draggingEnabled_: false,
+
+    /**
+     * Start listenting for events.
+     * @param {boolean=} opt_capture True if the TouchHandler should listen to
+     *      during the capture phase.
+     * @param {boolean=} opt_mouse True if the TouchHandler should generate
+     *      events for mouse input (in addition to touch input).
+     */
+    enable: function(opt_capture, opt_mouse) {
+      var capture = !!opt_capture;
+
+      // Just listen to start events for now. When a touch is occuring we'll
+      // want to be subscribed to move and end events on the document, but we
+      // don't want to incur the cost of lots of no-op handlers on the document.
+      this.events_.add(this.element_, 'touchstart', this.onStart_.bind(this),
+                       capture);
+      if (opt_mouse) {
+        this.events_.add(this.element_, 'mousedown',
+                         this.mouseToTouchCallback_(this.onStart_.bind(this)),
+                         capture);
+      }
+
+      // If the element is long-pressed, we may need to swallow a click
+      this.events_.add(this.element_, 'click', this.onClick_.bind(this), true);
+    },
+
+    /**
+     * Stop listening to all events.
+     */
+    disable: function() {
+      this.stopTouching_();
+      this.events_.removeAll();
+    },
+
+    /**
+     * Wraps a callback with translations of mouse events to touch events.
+     * NOTE: These types really should be function(Event) but then we couldn't
+     * use this with bind (which operates on any type of function).  Doesn't
+     * JSDoc support some sort of polymorphic types?
+     * @param {Function} callback The event callback.
+     * @return {Function} The wrapping callback.
+     * @private
+     */
+    mouseToTouchCallback_: function(callback) {
+      return function(e) {
+        // Note that there may be synthesizes mouse events caused by touch
+        // events (a mouseDown after a touch-click).  We leave it up to the
+        // client to worry about this if it matters to them (typically a short
+        // mouseDown/mouseUp without a click is no big problem and it's not
+        // obvious how we identify such synthesized events in a general way).
+        var touch = {
+          // any fixed value will do for the identifier - there will only
+          // ever be a single active 'touch' when using the mouse.
+          identifier: 0,
+          clientX: e.clientX,
+          clientY: e.clientY,
+          target: e.target
+        };
+        e.touches = [];
+        e.targetTouches = [];
+        e.changedTouches = [touch];
+        if (e.type != 'mouseup') {
+          e.touches[0] = touch;
+          e.targetTouches[0] = touch;
+        }
+        callback(e);
+      };
+    },
+
+    /**
+     * Begin tracking the touchable element, it is eligible for dragging.
+     * @private
+     */
+    beginTracking_: function() {
+      this.tracking_ = true;
+    },
+
+    /**
+     * Stop tracking the touchable element, it is no longer dragging.
+     * @private
+     */
+    endTracking_: function() {
+      this.tracking_ = false;
+      this.dragging_ = false;
+      this.totalMoveY_ = 0;
+      this.totalMoveX_ = 0;
+    },
+
+    /**
+     * Reset the touchable element as if we never saw the touchStart
+     * Doesn't dispatch any end events - be careful of existing listeners.
+     */
+    cancelTouch: function() {
+      this.stopTouching_();
+      this.endTracking_();
+      // If clients needed to be aware of this, we could fire a cancel event
+      // here.
+    },
+
+    /**
+     * Record that touching has stopped
+     * @private
+     */
+    stopTouching_: function() {
+      // Mark as no longer being touched
+      this.activeTouch_ = undefined;
+
+      // If we're waiting for a long press, stop
+      window.clearTimeout(this.longPressTimeout_);
+
+      // Stop listening for move/end events until there's another touch.
+      // We don't want to leave handlers piled up on the document.
+      // Note that there's no harm in removing handlers that weren't added, so
+      // rather than track whether we're using mouse or touch we do both.
+      this.events_.remove(document, 'touchmove');
+      this.events_.remove(document, 'touchend');
+      this.events_.remove(document, 'touchcancel');
+      this.events_.remove(document, 'mousemove');
+      this.events_.remove(document, 'mouseup');
+    },
+
+    /**
+     * Touch start handler.
+     * @param {!TouchEvent} e The touchstart event.
+     * @private
+     */
+    onStart_: function(e) {
+      // Only process single touches.  If there is already a touch happening, or
+      // two simultaneous touches then just ignore them.
+      if (e.touches.length > 1)
+        // Note that we could cancel an active touch here.  That would make
+        // simultaneous touch behave similar to near-simultaneous. However, if
+        // the user is dragging something, an accidental second touch could be
+        // quite disruptive if it cancelled their drag.  Better to just ignore
+        // it.
+        return;
+
+      // It's still possible there could be an active "touch" if the user is
+      // simultaneously using a mouse and a touch input.
+      if (this.activeTouch_ !== undefined)
+        return;
+
+      var touch = e.targetTouches[0];
+      this.activeTouch_ = touch.identifier;
+
+      // We've just started touching so shouldn't swallow any upcoming click
+      if (this.swallowNextClick_)
+        this.swallowNextClick_ = false;
+
+      this.disableTap_ = false;
+
+      // Sign up for end/cancel notifications for this touch.
+      // Note that we do this on the document so that even if the user drags
+      // their finger off the element, we'll still know what they're doing.
+      if (e.type == 'mousedown') {
+        this.events_.add(document, 'mouseup',
+            this.mouseToTouchCallback_(this.onEnd_.bind(this)), false);
+      } else {
+        this.events_.add(document, 'touchend', this.onEnd_.bind(this), false);
+        this.events_.add(document, 'touchcancel', this.onEnd_.bind(this),
+            false);
+      }
+
+      // This timeout is cleared on touchEnd and onDrag
+      // If we invoke the function then we have a real long press
+      window.clearTimeout(this.longPressTimeout_);
+      this.longPressTimeout_ = window.setTimeout(
+          this.onLongPress_.bind(this),
+          TouchHandler.TIME_FOR_LONG_PRESS_);
+
+      // Dispatch the TOUCH_START event
+      this.draggingEnabled_ =
+          !!this.dispatchEvent_(TouchHandler.EventType.TOUCH_START, touch);
+
+      // We want dragging notifications
+      if (e.type == 'mousedown') {
+        this.events_.add(document, 'mousemove',
+            this.mouseToTouchCallback_(this.onMove_.bind(this)), false);
+      } else {
+        this.events_.add(document, 'touchmove', this.onMove_.bind(this), false);
+      }
+
+      this.startTouchX_ = this.lastTouchX_ = touch.clientX;
+      this.startTouchY_ = this.lastTouchY_ = touch.clientY;
+      this.startTime_ = e.timeStamp;
+
+      this.recentTouchesX_ = [];
+      this.recentTouchesY_ = [];
+      this.recentTouchesX_.push(touch.clientX, e.timeStamp);
+      this.recentTouchesY_.push(touch.clientY, e.timeStamp);
+
+      this.beginTracking_();
+    },
+
+    /**
+     * Given a list of Touches, find the one matching our activeTouch
+     * identifier. Note that Chrome currently always uses 0 as the identifier.
+     * In that case we'll end up always choosing the first element in the list.
+     * @param {TouchList} touches The list of Touch objects to search.
+     * @return {!Touch|undefined} The touch matching our active ID if any.
+     * @private
+     */
+    findActiveTouch_: function(touches) {
+      assert(this.activeTouch_ !== undefined, 'Expecting an active touch');
+      // A TouchList isn't actually an array, so we shouldn't use
+      // Array.prototype.filter/some, etc.
+      for (var i = 0; i < touches.length; i++) {
+        if (touches[i].identifier == this.activeTouch_)
+          return touches[i];
+      }
+      return undefined;
+    },
+
+    /**
+     * Touch move handler.
+     * @param {!TouchEvent} e The touchmove event.
+     * @private
+     */
+    onMove_: function(e) {
+      if (!this.tracking_)
+        return;
+
+      // Our active touch should always be in the list of touches still active
+      assert(this.findActiveTouch_(e.touches), 'Missing touchEnd');
+
+      var that = this;
+      var touch = this.findActiveTouch_(e.changedTouches);
+      if (!touch)
+        return;
+
+      var clientX = touch.clientX;
+      var clientY = touch.clientY;
+
+      var moveX = this.lastTouchX_ - clientX;
+      var moveY = this.lastTouchY_ - clientY;
+      this.totalMoveX_ += Math.abs(moveX);
+      this.totalMoveY_ += Math.abs(moveY);
+      this.lastTouchX_ = clientX;
+      this.lastTouchY_ = clientY;
+
+      var couldBeTap =
+          this.totalMoveY_ <= TouchHandler.MAX_TRACKING_FOR_TAP_ ||
+          this.totalMoveX_ <= TouchHandler.MAX_TRACKING_FOR_TAP_;
+
+      if (!couldBeTap)
+        this.disableTap_ = true;
+
+      if (this.draggingEnabled_ && !this.dragging_ && !couldBeTap) {
+        // If we're waiting for a long press, stop
+        window.clearTimeout(this.longPressTimeout_);
+
+        // Dispatch the DRAG_START event and record whether dragging should be
+        // allowed or not.  Note that this relies on the current value of
+        // startTouchX/Y - handlers may use the initial drag delta to determine
+        // if dragging should be permitted.
+        this.dragging_ = this.dispatchEvent_(
+            TouchHandler.EventType.DRAG_START, touch);
+
+        if (this.dragging_) {
+          // Update the start position here so that drag deltas have better
+          // values but don't touch the recent positions so that velocity
+          // calculations can still use touchstart position in the time and
+          // distance delta.
+          this.startTouchX_ = clientX;
+          this.startTouchY_ = clientY;
+          this.startTime_ = e.timeStamp;
+        } else {
+          this.endTracking_();
+        }
+      }
+
+      if (this.dragging_) {
+        this.dispatchEvent_(TouchHandler.EventType.DRAG_MOVE, touch);
+
+        this.removeTouchesInWrongDirection_(this.recentTouchesX_,
+            this.lastMoveX_, moveX);
+        this.removeTouchesInWrongDirection_(this.recentTouchesY_,
+            this.lastMoveY_, moveY);
+        this.removeOldTouches_(this.recentTouchesX_, e.timeStamp);
+        this.removeOldTouches_(this.recentTouchesY_, e.timeStamp);
+        this.recentTouchesX_.push(clientX, e.timeStamp);
+        this.recentTouchesY_.push(clientY, e.timeStamp);
+      }
+
+      this.lastMoveX_ = moveX;
+      this.lastMoveY_ = moveY;
+    },
+
+    /**
+     * Filters the provided recent touches array to remove all touches except
+     * the last if the move direction has changed.
+     * @param {!Array.<number>} recentTouches An array of tuples where the first
+     *     item is the x or y component of the recent touch and the second item
+     *     is the touch time stamp.
+     * @param {number|undefined} lastMove The x or y component of the previous
+     *     move.
+     * @param {number} recentMove The x or y component of the most recent move.
+     * @private
+     */
+    removeTouchesInWrongDirection_: function(recentTouches, lastMove,
+        recentMove) {
+      if (lastMove && recentMove && recentTouches.length > 2 &&
+          (lastMove > 0 ^ recentMove > 0)) {
+        recentTouches.splice(0, recentTouches.length - 2);
+      }
+    },
+
+    /**
+     * Filters the provided recent touches array to remove all touches older
+     * than the max tracking time or the 5th most recent touch.
+     * @param {!Array.<number>} recentTouches An array of tuples where the first
+     *     item is the x or y component of the recent touch and the second item
+     *     is the touch time stamp.
+     * @param {number} recentTime The time of the most recent event.
+     * @private
+     */
+    removeOldTouches_: function(recentTouches, recentTime) {
+      while (recentTouches.length && recentTime - recentTouches[1] >
+          TouchHandler.MAX_TRACKING_TIME_ ||
+          recentTouches.length >
+              TouchHandler.MAX_TRACKING_TOUCHES_ * 2) {
+        recentTouches.splice(0, 2);
+      }
+    },
+
+    /**
+     * Touch end handler.
+     * @param {!TouchEvent} e The touchend event.
+     * @private
+     */
+    onEnd_: function(e) {
+      var that = this;
+      assert(this.activeTouch_ !== undefined, 'Expect to already be touching');
+
+      // If the touch we're tracking isn't changing here, ignore this touch end.
+      var touch = this.findActiveTouch_(e.changedTouches);
+      if (!touch) {
+        // In most cases, our active touch will be in the 'touches' collection,
+        // but we can't assert that because occasionally two touchend events can
+        // occur at almost the same time with both having empty 'touches' lists.
+        // I.e., 'touches' seems like it can be a bit more up-to-date than the
+        // current event.
+        return;
+      }
+
+      // This is touchEnd for the touch we're monitoring
+      assert(!this.findActiveTouch_(e.touches),
+             'Touch ended also still active');
+
+      // Indicate that touching has finished
+      this.stopTouching_();
+
+      if (this.tracking_) {
+        var clientX = touch.clientX;
+        var clientY = touch.clientY;
+
+        if (this.dragging_) {
+          this.endTime_ = e.timeStamp;
+          this.endTouchX_ = clientX;
+          this.endTouchY_ = clientY;
+
+          this.removeOldTouches_(this.recentTouchesX_, e.timeStamp);
+          this.removeOldTouches_(this.recentTouchesY_, e.timeStamp);
+
+          this.dispatchEvent_(TouchHandler.EventType.DRAG_END, touch);
+
+          // Note that in some situations we can get a click event here as well.
+          // For now this isn't a problem, but we may want to consider having
+          // some logic that hides clicks that appear to be caused by a touchEnd
+          // used for dragging.
+        }
+
+        this.endTracking_();
+      }
+      this.draggingEnabled_ = false;
+
+      // Note that we dispatch the touchEnd event last so that events at
+      // different levels of semantics nest nicely (similar to how DOM
+      // drag-and-drop events are nested inside of the mouse events that trigger
+      // them).
+      this.dispatchEvent_(TouchHandler.EventType.TOUCH_END, touch);
+      if (!this.disableTap_)
+        this.dispatchEvent_(TouchHandler.EventType.TAP, touch);
+    },
+
+    /**
+     * Get end velocity of the drag. This method is specific to drag behavior,
+     * so if touch behavior and drag behavior is split then this should go with
+     * drag behavior. End velocity is defined as deltaXY / deltaTime where
+     * deltaXY is the difference between endPosition and the oldest recent
+     * position, and deltaTime is the difference between endTime and the oldest
+     * recent time stamp.
+     * @return {Object} The x and y velocity.
+     */
+    getEndVelocity: function() {
+      // Note that we could move velocity to just be an end-event parameter.
+      var velocityX = this.recentTouchesX_.length ?
+          (this.endTouchX_ - this.recentTouchesX_[0]) /
+          (this.endTime_ - this.recentTouchesX_[1]) : 0;
+      var velocityY = this.recentTouchesY_.length ?
+          (this.endTouchY_ - this.recentTouchesY_[0]) /
+          (this.endTime_ - this.recentTouchesY_[1]) : 0;
+
+      velocityX = this.correctVelocity_(velocityX);
+      velocityY = this.correctVelocity_(velocityY);
+
+      return {
+        x: velocityX,
+        y: velocityY
+      };
+    },
+
+    /**
+     * Correct erroneous velocities by capping the velocity if we think it's too
+     * high, or setting it to a default velocity if know that the event data is
+     * bad.
+     * @param {number} velocity The x or y velocity component.
+     * @return {number} The corrected velocity.
+     * @private
+     */
+    correctVelocity_: function(velocity) {
+      var absVelocity = Math.abs(velocity);
+
+      // We add to recent touches for each touchstart and touchmove. If we have
+      // fewer than 3 touches (6 entries), we assume that the thread was blocked
+      // for the duration of the drag and we received events in quick succession
+      // with the wrong time stamps.
+      if (absVelocity > TouchHandler.MAXIMUM_VELOCITY_) {
+        absVelocity = this.recentTouchesY_.length < 3 ?
+            TouchHandler.VELOCITY_FOR_INCORRECT_EVENTS_ :
+                TouchHandler.MAXIMUM_VELOCITY_;
+      }
+      return absVelocity * (velocity < 0 ? -1 : 1);
+    },
+
+    /**
+     * Handler when an element has been pressed for a long time
+     * @private
+     */
+    onLongPress_: function() {
+      // Swallow any click that occurs on this element without an intervening
+      // touch start event.  This simple click-busting technique should be
+      // sufficient here since a real click should have a touchstart first.
+      this.swallowNextClick_ = true;
+      this.disableTap_ = true;
+
+      // Dispatch to the LONG_PRESS
+      this.dispatchEventXY_(TouchHandler.EventType.LONG_PRESS, this.element_,
+          this.startTouchX_, this.startTouchY_);
+    },
+
+    /**
+     * Click handler - used to swallow clicks after a long-press
+     * @param {!Event} e The click event.
+     * @private
+     */
+    onClick_: function(e) {
+      if (this.swallowNextClick_) {
+        e.preventDefault();
+        e.stopPropagation();
+        this.swallowNextClick_ = false;
+      }
+    },
+
+    /**
+     * Dispatch a TouchHandler event to the element
+     * @param {string} eventType The event to dispatch.
+     * @param {Touch} touch The touch triggering this event.
+     * @return {boolean|undefined} The value of enableDrag after dispatching
+     *         the event.
+     * @private
+     */
+    dispatchEvent_: function(eventType, touch) {
+
+      // Determine which element was touched.  For mouse events, this is always
+      // the event/touch target.  But for touch events, the target is always the
+      // target of the touchstart (and it's unlikely we can change this
+      // since the common implementation of touch dragging relies on it). Since
+      // touch is our primary scenario (which we want to emulate with mouse),
+      // we'll treat both cases the same and not depend on the target.
+      var touchedElement;
+      if (eventType == TouchHandler.EventType.TOUCH_START) {
+        touchedElement = touch.target;
+      } else {
+        touchedElement = this.element_.ownerDocument.
+            elementFromPoint(touch.clientX, touch.clientY);
+      }
+
+      return this.dispatchEventXY_(eventType, touchedElement, touch.clientX,
+          touch.clientY);
+    },
+
+    /**
+     * Dispatch a TouchHandler event to the element
+     * @param {string} eventType The event to dispatch.
+       @param {number} clientX The X location for the event.
+       @param {number} clientY The Y location for the event.
+     * @return {boolean|undefined} The value of enableDrag after dispatching
+     *         the event.
+     * @private
+     */
+    dispatchEventXY_: function(eventType, touchedElement, clientX, clientY) {
+      var isDrag = (eventType == TouchHandler.EventType.DRAG_START ||
+          eventType == TouchHandler.EventType.DRAG_MOVE ||
+          eventType == TouchHandler.EventType.DRAG_END);
+
+      // Drag events don't bubble - we're really just dragging the element,
+      // not affecting its parent at all.
+      var bubbles = !isDrag;
+
+      var event = new TouchHandler.Event(eventType, bubbles, clientX, clientY,
+          touchedElement);
+
+      // Set enableDrag when it can be overridden
+      if (eventType == TouchHandler.EventType.TOUCH_START)
+        event.enableDrag = false;
+      else if (eventType == TouchHandler.EventType.DRAG_START)
+        event.enableDrag = true;
+
+      if (isDrag) {
+        event.dragDeltaX = clientX - this.startTouchX_;
+        event.dragDeltaY = clientY - this.startTouchY_;
+      }
+
+      this.element_.dispatchEvent(event);
+      return event.enableDrag;
+    }
+  };
+
+  return {
+    TouchHandler: TouchHandler
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/js/event_tracker.js b/chrome/common/extensions/docs/examples/api/fontSettings/js/event_tracker.js
new file mode 100644
index 0000000..12d5930
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/js/event_tracker.js
@@ -0,0 +1,97 @@
+// 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 EventTracker is a simple class that manages the addition and
+ *  removal of DOM event listeners. In particular, it keeps track of all
+ *  listeners that have been added and makes it easy to remove some or all of
+ *  them without requiring all the information again. This is particularly
+ *  handy when the listener is a generated function such as a lambda or the
+ *  result of calling Function.bind.
+ */
+
+// Use an anonymous function to enable strict mode just for this file (which
+// will be concatenated with other files when embedded in Chrome)
+var EventTracker = (function() {
+  'use strict';
+
+  /**
+   *  Create an EventTracker to track a set of events.
+   *  EventTracker instances are typically tied 1:1 with other objects or
+   *  DOM elements whose listeners should be removed when the object is disposed
+   *  or the corresponding elements are removed from the DOM.
+   *  @constructor
+   */
+  function EventTracker() {
+    /**
+     *  @type {Array.<EventTracker.Entry>}
+     *  @private
+     */
+    this.listeners_ = [];
+  }
+
+  /**
+   * The type of the internal tracking entry.
+   *  @typedef {{node: !Node,
+   *            eventType: string,
+   *            listener: Function,
+   *            capture: boolean}}
+   */
+  EventTracker.Entry;
+
+  EventTracker.prototype = {
+    /**
+     * Add an event listener - replacement for Node.addEventListener.
+     * @param {!Node} node The DOM node to add a listener to.
+     * @param {string} eventType The type of event to subscribe to.
+     * @param {Function} listener The listener to add.
+     * @param {boolean} capture Whether to invoke during the capture phase.
+     */
+    add: function(node, eventType, listener, capture) {
+      var h = {
+        node: node,
+        eventType: eventType,
+        listener: listener,
+        capture: capture
+      };
+      this.listeners_.push(h);
+      node.addEventListener(eventType, listener, capture);
+    },
+
+    /**
+     * Remove any specified event listeners added with this EventTracker.
+     * @param {!Node} node The DOM node to remove a listener from.
+     * @param {?string} eventType The type of event to remove.
+     */
+    remove: function(node, eventType) {
+      this.listeners_ = this.listeners_.filter(function(h) {
+        if (h.node == node && (!eventType || (h.eventType == eventType))) {
+          EventTracker.removeEventListener_(h);
+          return false;
+        }
+        return true;
+      });
+    },
+
+    /**
+     * Remove all event listeners added with this EventTracker.
+     */
+    removeAll: function() {
+      this.listeners_.forEach(EventTracker.removeEventListener_);
+      this.listeners_ = [];
+    }
+  };
+
+  /**
+   * Remove a single event listener given it's tracker entry.  It's up to the
+   * caller to ensure the entry is removed from listeners_.
+   * @param {EventTracker.Entry} h The entry describing the listener to remove.
+   * @private
+   */
+  EventTracker.removeEventListener_ = function(h) {
+    h.node.removeEventListener(h.eventType, h.listener, h.capture);
+  };
+
+  return EventTracker;
+})();
+
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/js/util.js b/chrome/common/extensions/docs/examples/api/fontSettings/js/util.js
new file mode 100644
index 0000000..2d75ea2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/js/util.js
@@ -0,0 +1,240 @@
+// Copyright (c) 2012 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.
+
+/**
+ * The global object.
+ * @type {!Object}
+ * @const
+ */
+var global = this;
+
+/**
+ * Alias for document.getElementById.
+ * @param {string} id The ID of the element to find.
+ * @return {HTMLElement} The found element or null if not found.
+ */
+function $(id) {
+  return document.getElementById(id);
+}
+
+/**
+ * Calls chrome.send with a callback and restores the original afterwards.
+ * @param {string} name The name of the message to send.
+ * @param {!Array} params The parameters to send.
+ * @param {string} callbackName The name of the function that the backend calls.
+ * @param {!Function} callback The function to call.
+ */
+function chromeSend(name, params, callbackName, callback) {
+  var old = global[callbackName];
+  global[callbackName] = function() {
+    // restore
+    global[callbackName] = old;
+
+    var args = Array.prototype.slice.call(arguments);
+    return callback.apply(global, args);
+  };
+  chrome.send(name, params);
+}
+
+/**
+ * Generates a CSS url string.
+ * @param {string} s The URL to generate the CSS url for.
+ * @return {string} The CSS url string.
+ */
+function url(s) {
+  // http://www.w3.org/TR/css3-values/#uris
+  // Parentheses, commas, whitespace characters, single quotes (') and double
+  // quotes (") appearing in a URI must be escaped with a backslash
+  var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1');
+  // WebKit has a bug when it comes to URLs that end with \
+  // https://bugs.webkit.org/show_bug.cgi?id=28885
+  if (/\\\\$/.test(s2)) {
+    // Add a space to work around the WebKit bug.
+    s2 += ' ';
+  }
+  return 'url("' + s2 + '")';
+}
+
+/**
+ * Parses query parameters from Location.
+ * @param {string} location The URL to generate the CSS url for.
+ * @return {object} Dictionary containing name value pairs for URL
+ */
+function parseQueryParams(location) {
+  var params = {};
+  var query = unescape(location.search.substring(1));
+  var vars = query.split('&');
+  for (var i = 0; i < vars.length; i++) {
+    var pair = vars[i].split('=');
+    params[pair[0]] = pair[1];
+  }
+  return params;
+}
+
+function findAncestorByClass(el, className) {
+  return findAncestor(el, function(el) {
+    if (el.classList)
+      return el.classList.contains(className);
+    return null;
+  });
+}
+
+/**
+ * Return the first ancestor for which the {@code predicate} returns true.
+ * @param {Node} node The node to check.
+ * @param {function(Node) : boolean} predicate The function that tests the
+ *     nodes.
+ * @return {Node} The found ancestor or null if not found.
+ */
+function findAncestor(node, predicate) {
+  var last = false;
+  while (node != null && !(last = predicate(node))) {
+    node = node.parentNode;
+  }
+  return last ? node : null;
+}
+
+function swapDomNodes(a, b) {
+  var afterA = a.nextSibling;
+  if (afterA == b) {
+    swapDomNodes(b, a);
+    return;
+  }
+  var aParent = a.parentNode;
+  b.parentNode.replaceChild(a, b);
+  aParent.insertBefore(b, afterA);
+}
+
+/**
+ * Disables text selection and dragging, with optional whitelist callbacks.
+ * @param {function(Event):boolean=} opt_allowSelectStart Unless this function
+ *    is defined and returns true, the onselectionstart event will be
+ *    surpressed.
+ * @param {function(Event):boolean=} opt_allowDragStart Unless this function
+ *    is defined and returns true, the ondragstart event will be surpressed.
+ */
+function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) {
+  // Disable text selection.
+  document.onselectstart = function(e) {
+    if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e)))
+      e.preventDefault();
+  };
+
+  // Disable dragging.
+  document.ondragstart = function(e) {
+    if (!(opt_allowDragStart && opt_allowDragStart.call(this, e)))
+      e.preventDefault();
+  };
+}
+
+/**
+ * Call this to stop clicks on <a href="#"> links from scrolling to the top of
+ * the page (and possibly showing a # in the link).
+ */
+function preventDefaultOnPoundLinkClicks() {
+  document.addEventListener('click', function(e) {
+    var anchor = findAncestor(e.target, function(el) {
+      return el.tagName == 'A';
+    });
+    // Use getAttribute() to prevent URL normalization.
+    if (anchor && anchor.getAttribute('href') == '#')
+      e.preventDefault();
+  });
+}
+
+/**
+ * Check the directionality of the page.
+ * @return {boolean} True if Chrome is running an RTL UI.
+ */
+function isRTL() {
+  return document.documentElement.dir == 'rtl';
+}
+
+/**
+ * Simple common assertion API
+ * @param {*} condition The condition to test.  Note that this may be used to
+ *     test whether a value is defined or not, and we don't want to force a
+ *     cast to Boolean.
+ * @param {string=} opt_message A message to use in any error.
+ */
+function assert(condition, opt_message) {
+  'use strict';
+  if (!condition) {
+    var msg = 'Assertion failed';
+    if (opt_message)
+      msg = msg + ': ' + opt_message;
+    throw new Error(msg);
+  }
+}
+
+/**
+ * Get an element that's known to exist by its ID. We use this instead of just
+ * calling getElementById and not checking the result because this lets us
+ * satisfy the JSCompiler type system.
+ * @param {string} id The identifier name.
+ * @return {!Element} the Element.
+ */
+function getRequiredElement(id) {
+  var element = $(id);
+  assert(element, 'Missing required element: ' + id);
+  return element;
+}
+
+// Handle click on a link. If the link points to a chrome: or file: url, then
+// call into the browser to do the navigation.
+document.addEventListener('click', function(e) {
+  // Allow preventDefault to work.
+  if (!e.returnValue)
+    return;
+
+  var el = e.target;
+  if (el.nodeType == Node.ELEMENT_NODE &&
+      el.webkitMatchesSelector('A, A *')) {
+    while (el.tagName != 'A') {
+      el = el.parentElement;
+    }
+
+    if ((el.protocol == 'file:' || el.protocol == 'about:') &&
+        (e.button == 0 || e.button == 1)) {
+      chrome.send('navigateToUrl', [
+        el.href,
+        el.target,
+        e.button,
+        e.altKey,
+        e.ctrlKey,
+        e.metaKey,
+        e.shiftKey
+      ]);
+      e.preventDefault();
+    }
+  }
+});
+
+/**
+ * Creates a new URL which is the old URL with a GET param of key=value.
+ * @param {string} url The base URL. There is not sanity checking on the URL so
+ *     it must be passed in a proper format.
+ * @param {string} key The key of the param.
+ * @param {string} value The value of the param.
+ * @return {string} The new URL.
+ */
+function appendParam(url, key, value) {
+  var param = encodeURIComponent(key) + '=' + encodeURIComponent(value);
+
+  if (url.indexOf('?') == -1)
+    return url + '?' + param;
+  return url + '&' + param;
+}
+
+/**
+ * Creates a new URL for a favicon request.
+ * @param {string} url The url for the favicon.
+ * @param {number=} opt_size Optional preferred size of the favicon.
+ * @return {string} Updated URL for the favicon.
+ */
+function getFaviconURL(url, opt_size) {
+  var size = opt_size || 16;
+  return 'chrome://favicon/size/' + size + '@' +
+      window.devicePixelRatio + 'x/' + url;
+}
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/manifest.json b/chrome/common/extensions/docs/examples/api/fontSettings/manifest.json
new file mode 100644
index 0000000..38c964c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/manifest.json
@@ -0,0 +1,12 @@
+{
+  "name": "Advanced Font Settings",
+  "version": "0.3",
+  "manifest_version": 2,
+  "description": "Customize per-script font settings.",
+  "options_page": "options.html",
+  "icons": {
+    "16": "fonts16.png",
+    "128": "fonts128.png"
+  },
+  "permissions": ["fontSettings"]
+}
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/options.html b/chrome/common/extensions/docs/examples/api/fontSettings/options.html
new file mode 100644
index 0000000..d5fcb7c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/options.html
@@ -0,0 +1,244 @@
+<!doctype html>
+<html>
+  <head>
+  <meta charset="utf-8">
+  <title>Advanced Font Settings</title>
+  <script src="js/cr.js"></script>
+  <script src="js/cr/event_target.js"></script>
+  <script src="js/cr/ui.js"></script>
+  <script src="js/cr/ui/overlay.js"></script>
+  <script src="js/cr/ui/array_data_model.js"></script>
+  <script src="js/cr/ui/list_item.js"></script>
+  <script src="js/cr/ui/list_selection_controller.js"></script>
+  <script src="js/cr/ui/list_selection_model.js"></script>
+  <script src="js/cr/ui/list_single_selection_model.js"></script>
+  <script src="js/event_tracker.js"></script>
+  <script src="js/cr/ui/touch_handler.js"></script>
+  <script src="js/cr/ui/list.js"></script>
+  <script src="js/util.js"></script>
+  <script src="options.js"></script>
+  <link rel="stylesheet" href="css/chrome_shared.css">
+  <link rel="stylesheet" href="css/overlay.css">
+  <link rel="stylesheet" href="css/widgets.css">
+  <link rel="stylesheet" href="css/uber_shared.css">
+  <link rel="stylesheet" href="css/list.css">
+  <style>
+    body.uber-frame {
+      font-family: sans-serif;
+      -webkit-margin-start: 30px;
+      -webkit-margin-end: 30px;
+      font-size: 14px;
+    }
+
+    body.uber-frame section {
+      max-width: none;
+    }
+
+    body.uber-frame footer {
+      max-width: none;
+      min-width: 0;
+      border-top: 1px solid #DDD;
+    }
+
+    .font-input-div {
+      -webkit-margin-end: 1em;
+      width: 16em;
+    }
+
+    .preview-box {
+      background-color: white;
+      margin-top: 10px;
+      padding: 15px;
+      text-align: center;
+    }
+
+    .bordered {
+      border: 1px solid #CCC;
+    }
+
+    input[type=range] {
+      width: 14em;
+    }
+
+    #right-pane {
+      -webkit-margin-start: 1em;
+      overflow-x: auto;
+      -webkit-box-flex: 1;
+    }
+
+    .font-setting-group {
+      display: -webkit-box;
+      -webkit-box-orient: horizontal;
+      margin: 2em;
+    }
+
+    .font-setting {
+      display: -webkit-box;
+      -webkit-box-orient: vertical;
+      -webkit-box-flex: 1;
+      -webkit-margin-start: 1em;
+    }
+
+    .font-family-and-size {
+      display: -webkit-box;
+      -webkit-box-orient: horizontal;
+    }
+
+    .font-setting-label {
+      font-weight: bold;
+      width: 6em;
+      text-align: end;
+    }
+
+    .font-size-slider {
+      -webkit-margin-end: 1em;
+    }
+
+    .overlay {
+      z-index: 100;
+    }
+  </style>
+</head>
+<body class="uber-frame" style="background-color: #f2f2f2">
+  <div id="overlay-container" class="overlay transparent" hidden>
+    <div id="reset-overlay" class="page">
+      <div class="close-button"></div>
+      <div id="reset-this-script-overlay-dialog" hidden>
+        <h1>Reset</h1>
+        <div id="reset-this-script-overlay-dialog-content" class="content-area">
+        </div>
+        <div class="action-area">
+          <div class="button-strip">
+            <button id="reset-this-script-cancel">Cancel</button>
+            <button id="reset-this-script-ok">Reset</button>
+          </div>
+        </div>
+      </div>
+      <div id="reset-all-scripts-overlay-dialog" hidden>
+        <h1>Reset</h1>
+        <div class="content-area">
+          Are you sure you want to reset all settings?
+        </div>
+        <div class="action-area">
+          <div class="button-strip">
+            <button id="reset-all-cancel">Cancel</button>
+            <button id="reset-all-ok">Reset</button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div style="display: -webkit-box; -webkit-box-orient: vertical;">
+    <h1>Advanced font settings</h1>
+    <div style="-webkit-box-flex: 1; display: -webkit-box; overflow: auto">
+      <div style="width: 20em; -webkit-box; -webkit-box-orient: vertical">
+        <h4>Script</h4>
+        <div class="bordered" style="-webkit-box-flex: 1;">
+          <list id="scriptList" style="background-color: white"></list>
+        </div>
+      </div>
+      <div id="right-pane">
+        <div class="font-setting-group">
+          <div class="font-setting-label">Standard</div>
+          <div class="font-setting">
+            <div class="font-family-and-size">
+              <div class="font-input-div">
+                <select id="standardFontList"></select>
+              </div>
+              <div class="font-size-slider">
+                <div>
+                  <span style="float: left">Tiny</span>
+                  <span style="float: right">Huge</span>
+                </div>
+                <input type="range" id="defaultFontSizeRange" min="0" max="36">
+              </div>
+              <input type="number" id="defaultFontSizeRocker" min="0" max="36">
+            </div>
+            <div id="standardFontSample" class="preview-box bordered"
+                style="font-family: standard;">
+            </div>
+          </div>
+        </div>
+        <div class="font-setting-group">
+          <div class="font-setting-label">Serif</div>
+          <div class="font-setting">
+            <div class="font-family-and-size">
+              <div class="font-input-div">
+                <select id="serifFontList"></select>
+              </div>
+            </div>
+            <div id="serifFontSample" class="preview-box bordered"
+                style="font-family: serif;">
+            </div>
+          </div>
+        </div>
+        <div class="font-setting-group">
+          <div class="font-setting-label">Sans-Serif</div>
+          <div class="font-setting">
+            <div class="font-family-and-size">
+              <div class="font-input-div">
+                <select id="sansSerifFontList"></select>
+              </div>
+            </div>
+            <div id="sansSerifFontSample" class="preview-box bordered"
+                 style="font-family: sans-serif;">
+            </div>
+          </div>
+        </div>
+        <div class="font-setting-group">
+          <div class="font-setting-label">Fixed</div>
+          <div class="font-setting">
+            <div class="font-family-and-size">
+              <div class="font-input-div">
+                <select id="fixedFontList"></select>
+              </div>
+              <div class="font-size-slider">
+                <div>
+                  <span style="float: left">Tiny</span>
+                  <span style="float: right">Huge</span>
+                </div>
+                <input type="range" id="defaultFixedFontSizeRange"
+                    min="0" max="36">
+              </div>
+              <input type="number" id="defaultFixedFontSizeRocker"
+                  min="0" max="36">
+            </div>
+            <div id="fixedFontSample" class="preview-box bordered"
+                style="font-family: monospace;">
+            </div>
+          </div>
+        </div>
+        <div class="font-setting-group">
+          <div class="font-setting-label"></div>
+          <div class="font-setting">
+            <div class="font-family-and-size">
+              <div class="font-input-div">Minimum font size</div>
+              <div class="font-size-slider">
+                <div>
+                  <span style="float: left">No minimum</span>
+                  <span style="float: right">Huge</span>
+                </div>
+                <input type="range" id="minFontSizeRange" min="0" max="36">
+              </div>
+              <input type="number" id="minFontSizeRocker" min="0" max="36">
+            </div>
+            <div id="minFontSample" class="preview-box bordered"
+                style="font-family: standard">
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <footer>
+      <section>
+        <button id="reset-this-script-button" style="font-size: smaller;">
+          Reset settings for this script
+        </button>
+        <button id="reset-all-button" style="font-size: smaller;">
+          Reset all settings
+        </button>
+      </section>
+    </footer>
+  </div>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/fontSettings/options.js b/chrome/common/extensions/docs/examples/api/fontSettings/options.js
new file mode 100644
index 0000000..dab7f65
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/fontSettings/options.js
@@ -0,0 +1,395 @@
+// Copyright (c) 2012 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.
+
+// The scripts supported by the Font Settings Extension API.
+var scripts = [
+  { scriptCode: 'Zyyy', scriptName: 'Default' },
+  { scriptCode: 'Arab', scriptName: 'Arabic' },
+  { scriptCode: 'Armn', scriptName: 'Armenian' },
+  { scriptCode: "Beng", scriptName: 'Bengali' },
+  { scriptCode: "Cher", scriptName: 'Cherokee' },
+  { scriptCode: "Cyrl", scriptName: 'Cyrillic' },
+  { scriptCode: "Deva", scriptName: 'Devanagari' },
+  { scriptCode: "Ethi", scriptName: 'Ethiopic' },
+  { scriptCode: "Geor", scriptName: 'Georgian' },
+  { scriptCode: "Grek", scriptName: 'Greek' },
+  { scriptCode: "Gujr", scriptName: 'Gujarati' },
+  { scriptCode: "Guru", scriptName: 'Gurmukhi' },
+  { scriptCode: "Hebr", scriptName: 'Hebrew' },
+  { scriptCode: "Jpan", scriptName: 'Japanese' },
+  { scriptCode: "Knda", scriptName: 'Kannada' },
+  { scriptCode: "Khmr", scriptName: 'Khmer' },
+  { scriptCode: "Hang", scriptName: 'Korean' },
+  { scriptCode: "Laoo", scriptName: 'Lao' },
+  { scriptCode: "Mlym", scriptName: 'Malayalam' },
+  { scriptCode: "Mong", scriptName: 'Mongolian' },
+  { scriptCode: "Mymr", scriptName: 'Myanmar' },
+  { scriptCode: "Orya", scriptName: 'Oriya' },
+  { scriptCode: "Hans", scriptName: 'Simplified Chinese' },
+  { scriptCode: "Sinh", scriptName: 'Sinhala' },
+  { scriptCode: "Taml", scriptName: 'Tamil' },
+  { scriptCode: "Telu", scriptName: 'Telugu' },
+  { scriptCode: "Thaa", scriptName: 'Thaana' },
+  { scriptCode: "Thai", scriptName: 'Thai' },
+  { scriptCode: "Tibt", scriptName: 'Tibetan' },
+  { scriptCode: "Hant", scriptName: 'Traditional Chinese' },
+  { scriptCode: "Cans", scriptName: 'Unified Canadian Aboriginal Syllabics' },
+  { scriptCode: "Yiii", scriptName: 'Yi' }
+];
+
+// The generic font families supported by the Font Settings Extension API.
+var families =
+    ["standard", "sansserif", "serif", "fixed", "cursive", "fantasy"];
+
+// Mapping between font list ids and the generic family setting they
+// represent.
+var fontPickers = [
+  { fontList: 'standardFontList', name: 'standard' },
+  { fontList: 'serifFontList', name: 'serif' },
+  { fontList: 'sansSerifFontList', name: 'sansserif' },
+  { fontList: 'fixedFontList', name: 'fixed' }
+];
+
+// Ids of elements to contain the sample text.
+var sampleTextDivIds = [
+  'standardFontSample',
+  'serifFontSample',
+  'sansSerifFontSample',
+  'fixedFontSample',
+  'minFontSample'
+];
+
+// Sample texts.
+var defaultSampleText = 'The quick brown fox jumps over the lazy dog.';
+var scriptSpecificSampleText = {
+  // "Cyrllic script".
+  'Cyrl': 'Кириллица',
+  'Hang': '정 참판 양반댁 규수 큰 교자 타고 혼례 치른 날.',
+  'Hans': '床前明月光,疑是地上霜。举头望明月,低头思故乡。',
+  'Hant': '床前明月光,疑是地上霜。舉頭望明月,低頭思故鄉。',
+  'Jpan': '吾輩は猫である。名前はまだ無い。',
+  // "Khmer language".
+  'Khmr': '\u1797\u17B6\u179F\u17B6\u1781\u17D2\u1798\u17C2\u179A',
+};
+
+// Definition for ScriptList.
+cr.define('fontSettings.ui', function() {
+  const List = cr.ui.List;
+  const ListItem = cr.ui.ListItem;
+  const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
+
+  function ScriptListItem(info) {
+    var el = cr.doc.createElement('li');
+    el.__proto__ = ScriptListItem.prototype;
+    el.info_ = info;
+    el.decorate();
+    return el;
+  };
+
+  ScriptListItem.prototype = {
+    __proto__: ListItem.prototype,
+
+    decorate: function() {
+      this.textContent = this.info_.scriptName;
+      if (this.info_.scriptCode == 'Zyyy') {
+        this.style.marginBottom = '1em';
+      }
+    }
+  };
+
+  var ScriptList = cr.ui.define('list');
+  ScriptList.prototype = {
+    __proto__: List.prototype,
+
+    decorate: function() {
+      List.prototype.decorate.call(this);
+      var sm = new ListSingleSelectionModel();
+      this.selectionModel = sm;
+      this.autoExpands = true;
+      this.dataModel = new cr.ui.ArrayDataModel(scripts);
+
+      // The list auto expands but is still just barely short enough to require
+      // a scroll bar. This is a hack to resize it to not require the scroll
+      // bar.
+      this.style.height = (this.clientHeight + 4) + 'px';
+    },
+
+    createItem: function(info) {
+      return new ScriptListItem(info);
+    }
+  };
+
+  return {
+    ScriptList: ScriptList,
+    ScriptListItem: ScriptListItem
+  };
+});
+
+function getSelectedScript() {
+  var scriptList = document.getElementById('scriptList');
+  return scriptList.selectedItem.scriptCode;
+}
+
+function getSelectedFont(fontList) {
+  return fontList.options[fontList.selectedIndex].value;
+}
+
+// Populates the font lists with the list of system fonts from |fonts|.
+function populateLists(fonts) {
+  for (var i = 0; i < fontPickers.length; i++) {
+    var list = document.getElementById(fontPickers[i].fontList);
+
+    // Add special item to indicate fallback to the non-per-script
+    // font setting. The Font Settings API uses the empty string to indicate
+    // fallback.
+    var defaultItem = document.createElement('option');
+    defaultItem.value = '';
+    defaultItem.text = '(Use default)';
+    list.add(defaultItem);
+
+    for (var j = 0; j < fonts.length; j++) {
+      var item = document.createElement('option');
+      item.value = fonts[j].fontId;
+      item.text = fonts[j].displayName;
+      list.add(item);
+    }
+  }
+
+  updateFontListsForScript();
+}
+
+// Returns a function that updates the font setting for |genericFamily|
+// to match the selected value in |fontList|. It can be used as an event
+// handler for selection changes in |fontList|.
+function getFontChangeHandler(fontList, genericFamily) {
+  return function() {
+    var script = getSelectedScript();
+    var font = getSelectedFont(fontList);
+
+    var details = {};
+    details.genericFamily = genericFamily;
+    details.fontId = font;
+    details.script = script;
+
+    chrome.fontSettings.setFont(details);
+  };
+}
+
+// Sets the selected value of |fontList| to |fontId|.
+function setSelectedFont(fontList, fontId) {
+  var script = getSelectedScript();
+  var i;
+  for (i = 0; i < fontList.length; i++) {
+    if (fontId == fontList.options[i].value) {
+      fontList.selectedIndex = i;
+      break;
+    }
+  }
+  if (i == fontList.length) {
+    console.warn("font '" + fontId + "' for " + fontList.id + ' for ' +
+        script + ' is not on the system');
+  }
+}
+
+// Returns a callback function that sets the selected value of |list| to the
+// font returned from |chrome.fontSettings.getFont|.
+function getFontHandler(list) {
+  return function(details) {
+    setSelectedFont(list, details.fontId);
+    list.disabled = !isControllableLevel(details.levelOfControl);
+  };
+}
+
+// Called when the script list selection changes. Sets the selected value of
+// each font list to the current font setting, and updates the samples' lang
+// so that they are shown in the current font setting.
+function updateFontListsForScript() {
+  var script = getSelectedScript();
+
+  for (var i = 0; i < fontPickers.length; i++) {
+    var list = document.getElementById(fontPickers[i].fontList);
+    var family = fontPickers[i].name;
+
+    var details = {};
+    details.genericFamily = family;
+    details.script = script;
+    chrome.fontSettings.getFont(details, getFontHandler(list));
+  }
+
+  if (typeof(scriptSpecificSampleText[script]) != 'undefined')
+    sample = scriptSpecificSampleText[script];
+  else
+    sample = defaultSampleText;
+  for (var i = 0; i < sampleTextDivIds.length; i++) {
+    var sampleTextDiv = document.getElementById(sampleTextDivIds[i]);
+    // For font selection it's the script code that matters, not language, so
+    // just use en for lang.
+    sampleTextDiv.lang = 'en-' + script;
+    sampleTextDiv.innerText = sample;
+  }
+}
+
+// Returns a function to be called when the user changes the font size
+// input element |elem|. The function calls the Font Settings Extension API
+// function |setter| to commit the change.
+function getFontSizeChangedFunc(elem, setter) {
+  return function() {
+    var pixelSize = parseInt(elem.value);
+    if (!isNaN(pixelSize)) {
+      setter({ pixelSize: pixelSize });
+    }
+  }
+}
+
+function isControllableLevel(levelOfControl) {
+  return levelOfControl == 'controllable_by_this_extension' ||
+      levelOfControl == 'controlled_by_this_extension';
+}
+
+// Returns a function to be used as a listener for font size setting changed
+// events from the Font Settings Extension API. The function updates the input
+// element |elem| and the elements in |sampleTexts| to reflect the change.
+function getFontSizeChangedOnBrowserFunc(elem, sampleTexts) {
+  return function(details) {
+    var size = details.pixelSize.toString();
+    elem.value = size;
+    elem.disabled = !isControllableLevel(details.levelOfControl);
+    for (var i = 0; i < sampleTexts.length; i++)
+      document.getElementById(sampleTexts[i]).style.fontSize = size + 'px';
+  }
+}
+
+// Maps the HTML <input> element with |id| to the extension API accessor
+// functions |getter| and |setter| for a setting and onChange event |apiEvent|
+// for the setting. Also, maps the element ids in |sampleTexts| to this setting.
+function initFontSizePref(id, sampleTexts, getter, setter, apiEvent) {
+  var elem = document.getElementById(id);
+  getter({}, function(details) {
+    var size = details.pixelSize.toString();
+    elem.value = size;
+    elem.disabled = !isControllableLevel(details.levelOfControl);
+    for (var i = 0; i < sampleTexts.length; i++)
+      document.getElementById(sampleTexts[i]).style.fontSize = size + 'px';
+  });
+  elem.addEventListener('change', getFontSizeChangedFunc(elem, setter));
+  apiEvent.addListener(getFontSizeChangedOnBrowserFunc(elem, sampleTexts));
+}
+
+function clearSettingsForScript(script) {
+  for (var i = 0; i < families.length; i++) {
+    chrome.fontSettings.clearFont({
+      script: script,
+      genericFamily: families[i]
+    });
+  }
+}
+
+function clearAllSettings() {
+  for (var i = 0; i < scripts.length; i++)
+    clearSettingsForScript(scripts[i].scriptCode);
+
+  chrome.fontSettings.clearDefaultFixedFontSize();
+  chrome.fontSettings.clearDefaultFontSize();
+  chrome.fontSettings.clearMinimumFontSize();
+}
+
+function closeOverlay() {
+  $('overlay-container').hidden = true;
+}
+
+function initResetButtons() {
+  var overlay = $('overlay-container');
+  cr.ui.overlay.globalInitialization();
+  cr.ui.overlay.setupOverlay(overlay);
+  overlay.addEventListener('cancelOverlay', closeOverlay);
+
+  $('reset-this-script-button').onclick = function(event) {
+    var scriptName = $('scriptList').selectedItem.scriptName;
+    $('reset-this-script-overlay-dialog-content').innerText =
+        'Are you sure you want to reset settings for ' + scriptName +
+        ' script?';
+
+    $('overlay-container').hidden = false;
+    $('reset-this-script-overlay-dialog').hidden = false;
+    $('reset-all-scripts-overlay-dialog').hidden = true;
+  }
+  $('reset-this-script-ok').onclick = function(event) {
+    clearSettingsForScript(getSelectedScript());
+    closeOverlay();
+  };
+  $('reset-this-script-cancel').onclick = closeOverlay;
+
+  $('reset-all-button').onclick = function(event) {
+    $('overlay-container').hidden = false;
+    $('reset-all-scripts-overlay-dialog').hidden = false;
+    $('reset-this-script-overlay-dialog').hidden = true;
+  }
+  $('reset-all-ok').onclick = function(event) {
+    clearAllSettings();
+    closeOverlay();
+  }
+  $('reset-all-cancel').onclick = closeOverlay;
+}
+
+function init() {
+  var scriptList = document.getElementById('scriptList');
+  fontSettings.ui.ScriptList.decorate(scriptList);
+  scriptList.selectionModel.selectedIndex = 0;
+  scriptList.selectionModel.addEventListener('change',
+                                             updateFontListsForScript);
+
+  // Populate the font lists.
+  chrome.fontSettings.getFontList(populateLists);
+
+  // Add change handlers to the font lists.
+  for (var i = 0; i < fontPickers.length; i++) {
+    var list = document.getElementById(fontPickers[i].fontList);
+    var handler = getFontChangeHandler(list, fontPickers[i].name);
+    list.addEventListener('change', handler);
+  }
+
+  chrome.fontSettings.onFontChanged.addListener(
+      updateFontListsForScript);
+
+  initFontSizePref(
+      'defaultFontSizeRocker',
+      ['standardFontSample', 'serifFontSample', 'sansSerifFontSample'],
+      chrome.fontSettings.getDefaultFontSize,
+      chrome.fontSettings.setDefaultFontSize,
+      chrome.fontSettings.onDefaultFontSizeChanged);
+  initFontSizePref(
+      'defaultFontSizeRange',
+      ['standardFontSample', 'serifFontSample', 'sansSerifFontSample'],
+      chrome.fontSettings.getDefaultFontSize,
+      chrome.fontSettings.setDefaultFontSize,
+      chrome.fontSettings.onDefaultFontSizeChanged);
+  initFontSizePref(
+      'defaultFixedFontSizeRocker',
+      ['fixedFontSample'],
+      chrome.fontSettings.getDefaultFixedFontSize,
+      chrome.fontSettings.setDefaultFixedFontSize,
+      chrome.fontSettings.onDefaultFixedFontSizeChanged);
+  initFontSizePref(
+      'defaultFixedFontSizeRange',
+      ['fixedFontSample'],
+      chrome.fontSettings.getDefaultFixedFontSize,
+      chrome.fontSettings.setDefaultFixedFontSize,
+      chrome.fontSettings.onDefaultFixedFontSizeChanged);
+  initFontSizePref(
+      'minFontSizeRocker',
+      ['minFontSample'],
+      chrome.fontSettings.getMinimumFontSize,
+      chrome.fontSettings.setMinimumFontSize,
+      chrome.fontSettings.onMinimumFontSizeChanged);
+  initFontSizePref(
+      'minFontSizeRange',
+      ['minFontSample'],
+      chrome.fontSettings.getMinimumFontSize,
+      chrome.fontSettings.setMinimumFontSize,
+      chrome.fontSettings.onMinimumFontSizeChanged);
+
+  initResetButtons();
+}
+
+document.addEventListener('DOMContentLoaded', init);
diff --git a/chrome/common/extensions/docs/examples/api/history/showHistory/clock.png b/chrome/common/extensions/docs/examples/api/history/showHistory/clock.png
new file mode 100644
index 0000000..08682cf
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/history/showHistory/clock.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/history/showHistory/manifest.json b/chrome/common/extensions/docs/examples/api/history/showHistory/manifest.json
new file mode 100644
index 0000000..7dd8bfe
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/history/showHistory/manifest.json
@@ -0,0 +1,11 @@
+{
+  "name": "Typed URL History",
+  "version": "1.1",
+  "description": "Reads your history, and shows the top ten pages you go to by typing the URL.",
+  "permissions": ["history", "tabs"],
+  "browser_action": {
+    "default_popup": "typedUrls.html",
+    "default_icon": "clock.png"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/history/showHistory/typedUrls.html b/chrome/common/extensions/docs/examples/api/history/showHistory/typedUrls.html
new file mode 100644
index 0000000..9b8bc71
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/history/showHistory/typedUrls.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <title>Recently Typed URLs</title>
+    <style>
+      body {min-width: 250px;}
+    </style>
+    <script src='typedUrls.js'></script>
+  </head>
+
+  <body>
+    <h2>Recently Typed URLs:</h2>
+    <div id="typedUrl_div"></div>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/history/showHistory/typedUrls.js b/chrome/common/extensions/docs/examples/api/history/showHistory/typedUrls.js
new file mode 100644
index 0000000..204d1ac
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/history/showHistory/typedUrls.js
@@ -0,0 +1,119 @@
+// Copyright (c) 2012 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.
+
+// Event listner for clicks on links in a browser action popup.
+// Open the link in a new tab of the current window.
+function onAnchorClick(event) {
+  chrome.tabs.create({
+    selected: true,
+    url: event.srcElement.href
+  });
+  return false;
+}
+
+// Given an array of URLs, build a DOM list of those URLs in the
+// browser action popup.
+function buildPopupDom(divName, data) {
+  var popupDiv = document.getElementById(divName);
+
+  var ul = document.createElement('ul');
+  popupDiv.appendChild(ul);
+
+  for (var i = 0, ie = data.length; i < ie; ++i) {
+    var a = document.createElement('a');
+    a.href = data[i];
+    a.appendChild(document.createTextNode(data[i]));
+    a.addEventListener('click', onAnchorClick);
+
+    var li = document.createElement('li');
+    li.appendChild(a);
+
+    ul.appendChild(li);
+  }
+}
+
+// Search history to find up to ten links that a user has typed in,
+// and show those links in a popup.
+function buildTypedUrlList(divName) {
+  // To look for history items visited in the last week,
+  // subtract a week of microseconds from the current time.
+  var microsecondsPerWeek = 1000 * 60 * 60 * 24 * 7;
+  var oneWeekAgo = (new Date).getTime() - microsecondsPerWeek;
+
+  // Track the number of callbacks from chrome.history.getVisits()
+  // that we expect to get.  When it reaches zero, we have all results.
+  var numRequestsOutstanding = 0;
+
+  chrome.history.search({
+      'text': '',              // Return every history item....
+      'startTime': oneWeekAgo  // that was accessed less than one week ago.
+    },
+    function(historyItems) {
+      // For each history item, get details on all visits.
+      for (var i = 0; i < historyItems.length; ++i) {
+        var url = historyItems[i].url;
+        var processVisitsWithUrl = function(url) {
+          // We need the url of the visited item to process the visit.
+          // Use a closure to bind the  url into the callback's args.
+          return function(visitItems) {
+            processVisits(url, visitItems);
+          };
+        };
+        chrome.history.getVisits({url: url}, processVisitsWithUrl(url));
+        numRequestsOutstanding++;
+      }
+      if (!numRequestsOutstanding) {
+        onAllVisitsProcessed();
+      }
+    });
+
+
+  // Maps URLs to a count of the number of times the user typed that URL into
+  // the omnibox.
+  var urlToCount = {};
+
+  // Callback for chrome.history.getVisits().  Counts the number of
+  // times a user visited a URL by typing the address.
+  var processVisits = function(url, visitItems) {
+    for (var i = 0, ie = visitItems.length; i < ie; ++i) {
+      // Ignore items unless the user typed the URL.
+      if (visitItems[i].transition != 'typed') {
+        continue;
+      }
+
+      if (!urlToCount[url]) {
+        urlToCount[url] = 0;
+      }
+
+      urlToCount[url]++;
+    }
+
+    // If this is the final outstanding call to processVisits(),
+    // then we have the final results.  Use them to build the list
+    // of URLs to show in the popup.
+    if (!--numRequestsOutstanding) {
+      onAllVisitsProcessed();
+    }
+  };
+
+  // This function is called when we have the final list of URls to display.
+  var onAllVisitsProcessed = function() {
+    // Get the top scorring urls.
+    urlArray = [];
+    for (var url in urlToCount) {
+      urlArray.push(url);
+    }
+
+    // Sort the URLs by the number of times the user typed them.
+    urlArray.sort(function(a, b) {
+      return urlToCount[b] - urlToCount[a];
+    });
+
+    buildPopupDom(divName, urlArray.slice(0, 10));
+  };
+}
+
+document.addEventListener('DOMContentLoaded', function () {
+  buildTypedUrlList("typedUrl_div");
+});
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/history/showHistoryFilter.zip b/chrome/common/extensions/docs/examples/api/history/showHistoryFilter.zip
new file mode 100644
index 0000000..08682cf
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/history/showHistoryFilter.zip
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/history/showHistoryFilter/clock.png b/chrome/common/extensions/docs/examples/api/history/showHistoryFilter/clock.png
new file mode 100644
index 0000000..91759ef
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/history/showHistoryFilter/clock.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/history/showHistoryFilter/historyFilterUrls.html b/chrome/common/extensions/docs/examples/api/history/showHistoryFilter/historyFilterUrls.html
new file mode 100644
index 0000000..2eb707e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/history/showHistoryFilter/historyFilterUrls.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+  <title>Most popular urls that you used during this hour in the last two months.</title>
+  <style>
+    body {min-width: 250px;}
+  </style>
+  <script src='historyFilterUrls.js'></script>
+  </head>
+  <body>
+    <h2>Most used urls:</h2>
+    <div id="filteredUrl_div"></div>
+  </body>
+</html>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/history/showHistoryFilter/historyFilterUrls.js b/chrome/common/extensions/docs/examples/api/history/showHistoryFilter/historyFilterUrls.js
new file mode 100644
index 0000000..1b48f5e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/history/showHistoryFilter/historyFilterUrls.js
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 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.
+
+// Event listner for clicks on links in a browser action popup.
+// Open the link in a new tab of the current window.
+function onAnchorClick(event) {
+  chrome.tabs.create({
+    selected: true,
+    url: event.srcElement.href
+  });
+  return false;
+}
+
+// Given an array of URLs, build a DOM list of those URLs in the
+// browser action popup.
+function buildPopupDom(divName, data) {
+  var popupDiv = document.getElementById(divName);
+
+  var ul = document.createElement('ul');
+  popupDiv.appendChild(ul);
+
+  for (var i = 0, ie = data.length; i < ie; ++i) {
+    var a = document.createElement('a');
+    a.href = data[i].url;
+    a.appendChild(document.createTextNode(data[i].title));
+    a.addEventListener('click', onAnchorClick);
+
+    var li = document.createElement('li');
+    li.appendChild(a);
+
+    ul.appendChild(li);
+  }
+}
+
+// Search history to find up to ten links that a user has visited during the
+// current time of the day +/- 1 hour.
+function buildTypedUrlList(divName) {
+  var millisecondsPerHour = 1000 * 60 * 60;
+  var maxResults = 10;
+
+  chrome.experimental.history.getMostVisited({
+      'filterWidth' : millisecondsPerHour,
+      'maxResults' : maxResults
+    },
+    function(historyItems) {
+      buildPopupDom(divName, historyItems);
+    }
+  );
+}
+
+document.addEventListener('DOMContentLoaded', function () {
+  buildTypedUrlList("filteredUrl_div");
+});
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/history/showHistoryFilter/manifest.json b/chrome/common/extensions/docs/examples/api/history/showHistoryFilter/manifest.json
new file mode 100644
index 0000000..5757516
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/history/showHistoryFilter/manifest.json
@@ -0,0 +1,11 @@
+{
+  "name": "Time filter for History",
+  "version": "1.1",
+  "description": "Reads your history, and shows top pages you visited in the time +/-1 hour in the last two months",
+  "permissions": ["experimental"],
+  "browser_action": {
+    "default_popup": "historyFilterUrls.html",
+    "default_icon": "clock.png"
+  },
+  "manifest_version": 2
+}
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/i18n/cld/background.js b/chrome/common/extensions/docs/examples/api/i18n/cld/background.js
new file mode 100644
index 0000000..1bd7039
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/i18n/cld/background.js
@@ -0,0 +1,28 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+var selectedId = -1;
+function refreshLanguage() {
+  chrome.tabs.detectLanguage(null, function(language) {
+    console.log(language);
+    if (language == " invalid_language_code")
+      language = "???";
+    chrome.browserAction.setBadgeText({"text": language, tabId: selectedId});
+  });
+}
+
+chrome.tabs.onUpdated.addListener(function(tabId, props) {
+  if (props.status == "complete" && tabId == selectedId)
+    refreshLanguage();
+});
+
+chrome.tabs.onSelectionChanged.addListener(function(tabId, props) {
+  selectedId = tabId;
+  refreshLanguage();
+});
+
+chrome.tabs.getSelected(null, function(tab) {
+  selectedId = tab.id;
+  refreshLanguage();
+});
diff --git a/chrome/common/extensions/docs/examples/api/i18n/cld/manifest.json b/chrome/common/extensions/docs/examples/api/i18n/cld/manifest.json
new file mode 100644
index 0000000..b7417de
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/i18n/cld/manifest.json
@@ -0,0 +1,15 @@
+{
+  "name": "CLD",
+  "description": "Displays the language of a tab",
+  "version": "0.2",
+  "background": {
+    "scripts": ["background.js"]
+  },
+  "permissions": [
+    "tabs"
+  ],
+  "browser_action": {
+      "default_name": "Page Language"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/en_US/messages.json b/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/en_US/messages.json
new file mode 100644
index 0000000..b33d2f0
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/en_US/messages.json
@@ -0,0 +1,27 @@
+{
+  "chrome_extension_name": {
+    "message": "AcceptLanguage"
+  },
+  "chrome_extension_description": {
+    "message": "Returns accept languages of the browser"
+  },
+  "click_here": {
+    "message": "Left click to list acceptLanguages."
+  },
+  "browser_action_title": {
+    "message": "Click Me"
+  },
+  "chrome_accept_languages": {
+    "message": "$CHROME$ accepts $languages$ languages",
+    "placeholders": {
+      "chrome": {
+        "content": "Chrome",
+        "example": "Chrome"
+      },
+      "languages": {
+        "content": "$1",
+        "example": "en-US,sr,de"
+      }
+    }
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/es/messages.json b/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/es/messages.json
new file mode 100644
index 0000000..0e1a7ef
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/es/messages.json
@@ -0,0 +1,27 @@
+{
+  "chrome_extension_name": {
+    "message": "AcceptLanguage"
+  },
+  "chrome_extension_description": {
+    "message": "Devuelve los idiomas aceptados por el navegador"
+  },
+  "click_here": {
+    "message": "Click con botón izquierdo para mostrar la lista de acceptLanguages."
+  },
+  "browser_action_title": {
+    "message": "Haz click aquí"
+  },
+  "chrome_accept_languages": {
+    "message": "$CHROME$ acepta los idiomas $languages$",
+    "placeholders": {
+      "chrome": {
+        "content": "Chrome",
+        "example": "Chrome"
+      },
+      "languages": {
+        "content": "$1",
+        "example": "en-US,sr,de"
+      }
+    }
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/sr/messages.json b/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/sr/messages.json
new file mode 100644
index 0000000..30bd958
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/i18n/getMessage/_locales/sr/messages.json
@@ -0,0 +1,24 @@
+{
+  "chrome_extension_name": {
+    "message": "Прихватљиви језици"
+  },
+  "chrome_extension_description": {
+    "message": "Језици које прегледач прихвата"
+  },
+  "click_here": {
+    "message": "Кликните да излистате дозвољене језике."
+  },
+  "chrome_accept_languages": {
+    "message": "$CHROME$ прихвата $languages$ језике.",
+    "placeholders": {
+      "chrome": {
+        "content": "Chrome",
+        "example": "Chrome"
+      },
+      "languages": {
+        "content": "$1",
+        "example": "en-US,sr,de"
+      }
+    }
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/api/i18n/getMessage/icon.png b/chrome/common/extensions/docs/examples/api/i18n/getMessage/icon.png
new file mode 100644
index 0000000..84c4be3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/i18n/getMessage/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/i18n/getMessage/manifest.json b/chrome/common/extensions/docs/examples/api/i18n/getMessage/manifest.json
new file mode 100644
index 0000000..52997f2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/i18n/getMessage/manifest.json
@@ -0,0 +1,12 @@
+{
+  "name": "__MSG_chrome_extension_name__",
+  "description": "__MSG_chrome_extension_description__",
+  "version": "0.2",
+  "default_locale": "en_US",
+  "browser_action": {
+      "default_title": "__MSG_browser_action_title__",
+      "default_icon": "icon.png",
+      "default_popup": "popup.html"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/i18n/getMessage/popup.html b/chrome/common/extensions/docs/examples/api/i18n/getMessage/popup.html
new file mode 100644
index 0000000..d765b9d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/i18n/getMessage/popup.html
@@ -0,0 +1,22 @@
+<!--
+Copyright (c) 2012 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.
+-->
+
+<html>
+  <head>
+    <style>
+      body {
+        color: black;
+        width: 300px;
+      }
+    </style>
+    <script src="popup.js"></script>
+  </head>
+  <body>
+    <div id="accept_lang">
+      <span id="languageSpan"></span>
+    </div>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/i18n/getMessage/popup.js b/chrome/common/extensions/docs/examples/api/i18n/getMessage/popup.js
new file mode 100644
index 0000000..fd70b61
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/i18n/getMessage/popup.js
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 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.
+
+function setChildTextNode(elementId, text) {
+  document.getElementById(elementId).innerText = text;
+}
+
+function init() {
+  setChildTextNode('languageSpan', chrome.i18n.getMessage("click_here"));
+}
+
+function getAcceptLanguages() {
+  chrome.i18n.getAcceptLanguages(function(languageList) {
+    var languages = languageList.join(",");
+    setChildTextNode('languageSpan',
+        chrome.i18n.getMessage("chrome_accept_languages", languages));
+  })
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+  document.querySelector('#accept_lang').addEventListener(
+      'click', getAcceptLanguages);
+  init();
+});
diff --git a/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/_locales/de/messages.json b/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/_locales/de/messages.json
new file mode 100644
index 0000000..0d8e2f7
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/_locales/de/messages.json
@@ -0,0 +1,8 @@
+{
+  "application_title": {
+    "message": "Eine lokalisierte gehostete Beispielanwendung"
+  },
+  "application_description": {
+    "message": "Hier steht eine Beschreibung der Applikation, die im Web Store auftauchen wird."
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/_locales/en/messages.json b/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/_locales/en/messages.json
new file mode 100644
index 0000000..ed87abf
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/_locales/en/messages.json
@@ -0,0 +1,10 @@
+{
+  "application_title": {
+    "message": "Minimal Localized Hosted App",
+    "description": "The title of the application, displayed in the web store."
+  },
+  "application_description": {
+    "message": "This is the minimal set of data required to upload a localized hosted application to the web store.",
+    "description": "The description of the application, displayed in the web store."
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/icon128.png b/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/icon128.png
new file mode 100644
index 0000000..0e4f441
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/icon128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/manifest.json b/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/manifest.json
new file mode 100644
index 0000000..9cb94fd
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/i18n/localizedHostedApp/manifest.json
@@ -0,0 +1,15 @@
+{
+  "name": "__MSG_application_title__",
+  "description": "__MSG_application_description__",
+  "version": "0.2",
+  "default_locale": "en",
+  "app": {
+    "launch": {
+      "web_url": "http://example.com/"
+    }
+  },
+  "icons": {
+    "128": "icon128.png"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/idle/idle_simple/background.js b/chrome/common/extensions/docs/examples/api/idle/idle_simple/background.js
new file mode 100644
index 0000000..c25e0ab
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/idle/idle_simple/background.js
@@ -0,0 +1,24 @@
+// Copyright (c) 2010 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.
+
+var history_log = [];
+
+/**
+* Stores a state every time an "active" event is sent, up to 20 items.
+*/
+chrome.idle.onStateChanged.addListener(function(newstate) {
+  var time = new Date();
+  if (history_log.length >= 20) {
+    history_log.pop();
+  }
+  history_log.unshift({'state':newstate, 'time':time});
+});
+
+/**
+* Opens history.html when the browser action is clicked.
+* Used window.open because I didn't want the tabs permission.
+*/
+chrome.browserAction.onClicked.addListener(function() {
+  window.open('history.html', 'testwindow', 'width=700,height=600');
+});
diff --git a/chrome/common/extensions/docs/examples/api/idle/idle_simple/history.html b/chrome/common/extensions/docs/examples/api/idle/idle_simple/history.html
new file mode 100644
index 0000000..c8061c5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/idle/idle_simple/history.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2010 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.
+-->
+<html>
+  <head>
+    <style>
+      body {
+        width: 100%;
+        font: 13px Arial;
+      }
+    </style>
+    <script src="history.js"></script>
+  </head>
+  <body>
+    <h1>Idle API Demonstration</h1>
+    <h2>Current state</h2>
+    <p>
+      Idle threshold:
+      <select id="idle-threshold">
+        <option selected value="15">15</option>
+        <option value="30">30</option>
+        <option value="60">60</option>
+      </select>
+    <p>
+      <code>chrome.idle.queryState(<strong id="idle-set-threshold"></strong>, ...);</code> - 
+      <span id="idle-state"></span>
+    </p>
+    <p>
+      Last state change: <span id="idle-laststate"></span>
+    </p>
+    
+    <h2>Idle changes:</h2>
+    <ul id='idle-history'></ul>
+  </body>
+</html>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/idle/idle_simple/history.js b/chrome/common/extensions/docs/examples/api/idle/idle_simple/history.js
new file mode 100644
index 0000000..814c7cb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/idle/idle_simple/history.js
@@ -0,0 +1,84 @@
+// Copyright (c) 2012 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.
+
+/**
+ * Convert a state and time into a nice styled chunk of HTML.
+ */
+function renderState(state, time) {
+  var now = new Date().getTime();
+  var diff = Math.round((time.getTime() - now) / 1000);
+  var str = (diff == 0) ?
+      "now" :
+      Math.abs(diff) + " seconds " + (diff > 0 ? "from now" : "ago");
+  var col = (state == "active") ?
+      "#009900" :
+      "#990000";
+  return "<b style='color: " + col + "'>" + state + "</b> " + str;
+};
+
+/**
+ * Creates DOM and injects a rendered state into the page.
+ */
+function renderItem(state, time, parent) {
+  var dom_item = document.createElement('li');
+  dom_item.innerHTML = renderState(state, time);
+  parent.appendChild(dom_item);
+};
+
+// Store previous state so we can show deltas.  This is important
+// because the API currently doesn't fire idle messages, and we'd
+// like to keep track of last time we went idle.
+var laststate = null;
+var laststatetime = null;
+
+/**
+ * Checks the current state of the browser.
+ */
+function checkState() {
+  threshold = parseInt(document.querySelector('#idle-threshold').value);
+  var dom_threshold = document.querySelector('#idle-set-threshold');
+  dom_threshold.innerText = threshold;
+
+  // Request the state based off of the user-supplied threshold.
+  chrome.idle.queryState(threshold, function(state) {
+    var time = new Date();
+    if (laststate != state) {
+      laststate = state;
+      laststatetime = time;
+    }
+
+    // Keep rendering results so we get a nice "seconds elapsed" view.
+    var dom_result = document.querySelector('#idle-state');
+    dom_result.innerHTML = renderState(state, time);
+    var dom_laststate = document.querySelector('#idle-laststate');
+    dom_laststate.innerHTML = renderState(laststate, laststatetime);
+  });
+};
+
+var dom_history = document.querySelector('#idle-history');
+
+/**
+ * Render the data gathered by the background page - should show a log
+ * of "active" states.  No events are fired upon idle.
+ */
+function renderHistory() {
+  dom_history.innerHTML = "";
+  var history_log = chrome.extension.getBackgroundPage().history_log;
+  for (var i = 0; i < history_log.length; i++) {
+    var data = history_log[i];
+    renderItem(data['state'], data['time'], dom_history);
+  }
+};
+
+
+document.addEventListener('DOMContentLoaded', function() {
+  // Check every second (even though this is overkill - minimum idle
+  // threshold is 15 seconds) so that the numbers appear to be counting up.
+  checkState();
+  window.setInterval(checkState, 1000);
+
+  // Check every second (see above).
+  renderHistory();
+  window.setInterval(renderHistory, 1000);
+});
diff --git a/chrome/common/extensions/docs/examples/api/idle/idle_simple/manifest.json b/chrome/common/extensions/docs/examples/api/idle/idle_simple/manifest.json
new file mode 100644
index 0000000..b6fba2a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/idle/idle_simple/manifest.json
@@ -0,0 +1,18 @@
+{
+  "name" : "Idle - Simple Example",
+  "version" : "1.0.1",
+  "description" : "Demonstrates the Idle API",
+  "background" : {
+    "scripts": ["background.js"]
+  },
+  "permissions" : [ "idle" ],
+  "browser_action" : {
+    "default_icon" : "sample-19.png"
+  },
+  "icons" : {
+    "16" : "sample-16.png",
+    "48" : "sample-48.png",
+    "128" : "sample-128.png"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/idle/idle_simple/sample-128.png b/chrome/common/extensions/docs/examples/api/idle/idle_simple/sample-128.png
new file mode 100644
index 0000000..d733b1e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/idle/idle_simple/sample-128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/idle/idle_simple/sample-16.png b/chrome/common/extensions/docs/examples/api/idle/idle_simple/sample-16.png
new file mode 100644
index 0000000..dcc5c14
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/idle/idle_simple/sample-16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/idle/idle_simple/sample-19.png b/chrome/common/extensions/docs/examples/api/idle/idle_simple/sample-19.png
new file mode 100644
index 0000000..01e0aa8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/idle/idle_simple/sample-19.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/idle/idle_simple/sample-48.png b/chrome/common/extensions/docs/examples/api/idle/idle_simple/sample-48.png
new file mode 100644
index 0000000..3af1eb8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/idle/idle_simple/sample-48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/background.js b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/background.js
new file mode 100644
index 0000000..9795d35
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/background.js
@@ -0,0 +1,20 @@
+/**
+ * Handles requests sent by the content script.  Shows an infobar.
+ */
+function onRequest(request, sender, sendResponse) {
+  // The number of matches is sent in the request - pass it to the
+  // infobar.
+  var url = "infobar.html#" + request.count;
+
+  // Show the infobar on the tab where the request was sent.
+  chrome.experimental.infobars.show({
+    tabId: sender.tab.id,
+    path: url
+  });
+
+  // Return nothing to let the connection be cleaned up.
+  sendResponse({});
+};
+
+// Listen for the content script to send a message to the background page.
+chrome.extension.onRequest.addListener(onRequest);
diff --git a/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/contentscript.js b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/contentscript.js
new file mode 100644
index 0000000..6fa03ec
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/contentscript.js
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2010 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.
+ */
+var regex = /sandwich/gi;
+matches = document.body.innerText.match(regex);
+if (matches) {
+  var payload = {
+    count: matches.length    // Pass the number of matches back.
+  };
+  chrome.extension.sendRequest(payload, function(response) {});
+}
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/infobar.html b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/infobar.html
new file mode 100644
index 0000000..82558cc
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/infobar.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2010 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.
+-->
+<html>
+  <head>
+    <style>
+      html {
+        height: 40px;
+      }
+      body {
+        background: #fffddd;
+        font: 16px Arial;
+        height: 100%;
+        display: -webkit-box;
+        -webkit-box-orient: vertical;
+        -webkit-box-pack: center;
+      }
+      em {
+        font-weight: bold;
+        font-style: normal;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="wrap">
+      The word <em>sandwich</em> appears <em id="count">X</em> times on this
+      page.
+    </div>
+    <script src="infobar.js"></script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/infobar.js b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/infobar.js
new file mode 100644
index 0000000..30ed797
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/infobar.js
@@ -0,0 +1,11 @@
+// Copyright (c) 2012 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.
+
+// Obtain the count of sandwiches from the page URL.
+var count = window.location.hash.substring(1);
+if (count) {
+  // Replace the placeholder text with the actual count.
+  var domcount = document.querySelector('#count');
+  domcount.innerText = count;
+}
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/manifest.json b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/manifest.json
new file mode 100644
index 0000000..71af880
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/manifest.json
@@ -0,0 +1,23 @@
+{
+  "name" : "SandwichBar",
+  "version" : "1.0.1",
+  "description" : "Shows an infobar on pages which contain the word 'sandwich'",
+  "background" : {
+    "scripts": ["background.js"]
+  },
+  "permissions" : [ "experimental" ],
+  "icons" : {
+    "16" : "sandwich-16.png",
+    "48" : "sandwich-48.png",
+    "128" : "sandwich-128.png"
+  },
+  "content_scripts" : [
+    {
+      "matches" : [ "http://*/*" ],
+      "js" : [ "contentscript.js" ],
+      "run_at" : "document_idle",
+      "all_frames" : false
+    }
+  ],
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/sandwich-128.png b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/sandwich-128.png
new file mode 100644
index 0000000..98f5b50
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/sandwich-128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/sandwich-16.png b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/sandwich-16.png
new file mode 100644
index 0000000..2258238
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/sandwich-16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/sandwich-19.png b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/sandwich-19.png
new file mode 100644
index 0000000..dfe3ae1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/sandwich-19.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/sandwich-48.png b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/sandwich-48.png
new file mode 100644
index 0000000..d61dea6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/infobars/sandwichbar/sandwich-48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/input.ime/basic.zip b/chrome/common/extensions/docs/examples/api/input.ime/basic.zip
new file mode 100644
index 0000000..84c4be3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/input.ime/basic.zip
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/input.ime/basic/background.html b/chrome/common/extensions/docs/examples/api/input.ime/basic/background.html
new file mode 100644
index 0000000..327b571
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/input.ime/basic/background.html
@@ -0,0 +1,37 @@
+<html>
+  <script>
+    var ime_api = chrome.input.ime;
+
+    var context_id = -1;
+
+    console.log("Initializing IME");
+
+    ime_api.onFocus.addListener(function(context) {
+      console.log('onFocus:' + context.contextID);
+      context_id = context.contextID;
+    });
+    ime_api.onBlur.addListener(function(contextID) {
+      console.log('onBlur:' + contextID);
+      context_id = -1;
+    });
+
+    ime_api.onActivate.addListener(function(engineID) {
+      console.log('onActivate:' + engineID);
+    });
+    ime_api.onDeactivated.addListener(function(engineID) {
+      console.log('onDeactivated:' + engineID);
+    });
+
+    ime_api.onKeyEvent.addListener(
+    function(engineID, keyData) {
+      console.log('onKeyEvent:' + keyData.key + " context: " + context_id);
+      if (keyData.type == "keydown" && keyData.key.match(/^[a-z]$/)) {
+        chrome.input.ime.commitText({"contextID": context_id,
+                                     "text": keyData.key.toUpperCase()});
+        return true;
+      }
+
+      return false
+    });
+  </script>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/input.ime/basic/icon.png b/chrome/common/extensions/docs/examples/api/input.ime/basic/icon.png
new file mode 100644
index 0000000..0421e4c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/input.ime/basic/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/input.ime/basic/manifest.json b/chrome/common/extensions/docs/examples/api/input.ime/basic/manifest.json
new file mode 100644
index 0000000..8459493
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/input.ime/basic/manifest.json
@@ -0,0 +1,19 @@
+{
+  "name": "Test IME",
+  "version": "1.0",
+  "description": "A simple IME that converts all keystrokes to upper case.",
+  "background_page": "background.html",
+  "permissions": [
+    "input"
+  ],
+  "input_components": [
+    {
+      "name": "Test IME",
+      "type": "ime",
+      "id": "test",
+      "description": "Test IME",  // A user visible description
+      "language": "en",  // The primary language this IME is used for
+      "layouts": ["us::eng"]  // The supported keyboard layouts for this IME
+    }
+  ]
+}
diff --git a/chrome/common/extensions/docs/examples/api/messaging/timer/clock.png b/chrome/common/extensions/docs/examples/api/messaging/timer/clock.png
new file mode 100644
index 0000000..107d6e7
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/messaging/timer/clock.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/messaging/timer/manifest.json b/chrome/common/extensions/docs/examples/api/messaging/timer/manifest.json
new file mode 100644
index 0000000..26fed01
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/messaging/timer/manifest.json
@@ -0,0 +1,18 @@
+{
+  "name": "Message Timer",
+  "version": "1.2",
+  "description": "Times how long it takes to send a message to a content script and back.",
+  "permissions": ["tabs"],
+  "content_scripts": [
+    {
+      "matches": ["http://*/*"],
+      "js": ["page.js"]
+    }
+  ],
+  "browser_action": {
+    "default_title": "Time to current page",
+    "default_icon": "clock.png",
+    "default_popup": "popup.html"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/messaging/timer/page.js b/chrome/common/extensions/docs/examples/api/messaging/timer/page.js
new file mode 100644
index 0000000..92f8df3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/messaging/timer/page.js
@@ -0,0 +1,10 @@
+chrome.extension.onConnect.addListener(function(port) {
+  port.onMessage.addListener(function(msg) {
+    port.postMessage({counter: msg.counter+1});
+  });
+});
+
+chrome.extension.onRequest.addListener(
+  function(request, sender, sendResponse) {
+    sendResponse({counter: request.counter+1});
+  });
diff --git a/chrome/common/extensions/docs/examples/api/messaging/timer/popup.html b/chrome/common/extensions/docs/examples/api/messaging/timer/popup.html
new file mode 100644
index 0000000..f3dbc06
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/messaging/timer/popup.html
@@ -0,0 +1,25 @@
+<head>
+<style>
+tr {
+  white-space: nowrap;
+}
+.results {
+  text-align: right;
+  min-width: 6em;
+  color: black;
+}
+</style>
+<script src="popup.js"></script>
+</head>
+<body>
+<table>
+  <tr>
+    <td><button id="testRequest">Measure sendRequest</button></td>
+    <td id="resultsRequest" class="results">(results)</td>
+  </tr>
+  <tr>
+    <td><button id="testConnect">Measure postMessage</button></td>
+    <td id="resultsConnect" class="results">(results)</td>
+  </tr>
+</table>
+</body>
diff --git a/chrome/common/extensions/docs/examples/api/messaging/timer/popup.js b/chrome/common/extensions/docs/examples/api/messaging/timer/popup.js
new file mode 100644
index 0000000..c213baf
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/messaging/timer/popup.js
@@ -0,0 +1,62 @@
+// Copyright (c) 2012 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.
+
+if (!chrome.benchmarking) {
+  alert("Warning:  Looks like you forgot to run chrome with " +
+        " --enable-benchmarking set.");
+  return;
+}
+
+function setChildTextNode(elementId, text) {
+  document.getElementById(elementId).innerText = text;
+}
+
+// Tests the roundtrip time of sendRequest().
+function testRequest() {
+  setChildTextNode("resultsRequest", "running...");
+
+  chrome.tabs.getSelected(null, function(tab) {
+    var timer = new chrome.Interval();
+    timer.start();
+
+    chrome.tabs.sendRequest(tab.id, {counter: 1}, function handler(response) {
+      if (response.counter < 1000) {
+        chrome.tabs.sendRequest(tab.id, {counter: response.counter}, handler);
+      } else {
+        timer.stop();
+        var usec = Math.round(timer.microseconds() / response.counter);
+        setChildTextNode("resultsRequest", usec + "usec");
+      }
+    });
+  });
+}
+
+// Tests the roundtrip time of Port.postMessage() after opening a channel.
+function testConnect() {
+  setChildTextNode("resultsConnect", "running...");
+
+  chrome.tabs.getSelected(null, function(tab) {
+    var timer = new chrome.Interval();
+    timer.start();
+
+    var port = chrome.tabs.connect(tab.id);
+    port.postMessage({counter: 1});
+    port.onMessage.addListener(function getResp(response) {
+      if (response.counter < 1000) {
+        port.postMessage({counter: response.counter});
+      } else {
+        timer.stop();
+        var usec = Math.round(timer.microseconds() / response.counter);
+        setChildTextNode("resultsConnect", usec + "usec");
+      }
+    });
+  });
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+  document.querySelector('#testRequest').addEventListener(
+      'click', testRequest);
+  document.querySelector('#testConnect').addEventListener(
+      'click', testConnect);
+});
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/notifications/128.png b/chrome/common/extensions/docs/examples/api/notifications/128.png
new file mode 100644
index 0000000..8cc7c37
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/notifications/128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/notifications/16.png b/chrome/common/extensions/docs/examples/api/notifications/16.png
new file mode 100644
index 0000000..4d38399
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/notifications/16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/notifications/48.png b/chrome/common/extensions/docs/examples/api/notifications/48.png
new file mode 100644
index 0000000..1e9eb41
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/notifications/48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/notifications/64.png b/chrome/common/extensions/docs/examples/api/notifications/64.png
new file mode 100644
index 0000000..d87bbbb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/notifications/64.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/notifications/background.js b/chrome/common/extensions/docs/examples/api/notifications/background.js
new file mode 100644
index 0000000..646063e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/notifications/background.js
@@ -0,0 +1,47 @@
+// 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.
+
+/*
+  Displays a notification with the current time. Requires "notifications"
+  permission in the manifest file (or calling
+  "webkitNotifications.requestPermission" beforehand).
+*/
+function show() {
+  var time = /(..)(:..)/.exec(new Date());     // The prettyprinted time.
+  var hour = time[1] % 12 || 12;               // The prettyprinted hour.
+  var period = time[1] < 12 ? 'a.m.' : 'p.m.'; // The period of the day.
+  var notification = window.webkitNotifications.createNotification(
+    '48.png',                      // The image.
+    hour + time[2] + ' ' + period, // The title.
+    'Time to make the toast.'      // The body.
+  );
+  notification.show();
+}
+
+// Conditionally initialize the options.
+if (!localStorage.isInitialized) {
+  localStorage.isActivated = true;   // The display activation.
+  localStorage.frequency = 1;        // The display frequency, in minutes.
+  localStorage.isInitialized = true; // The option initialization.
+}
+
+// Test for notification support.
+if (window.webkitNotifications) {
+  // While activated, show notifications at the display frequency.
+  if (JSON.parse(localStorage.isActivated)) { show(); }
+
+  var interval = 0; // The display interval, in minutes.
+
+  setInterval(function() {
+    interval++;
+
+    if (
+      JSON.parse(localStorage.isActivated) &&
+        localStorage.frequency <= interval
+    ) {
+      show();
+      interval = 0;
+    }
+  }, 60000);
+}
diff --git a/chrome/common/extensions/docs/examples/api/notifications/manifest.json b/chrome/common/extensions/docs/examples/api/notifications/manifest.json
new file mode 100644
index 0000000..1b6beaf
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/notifications/manifest.json
@@ -0,0 +1,16 @@
+{
+  "name": "Notification Demo",
+  "version": "1",
+  "description":
+    "Shows off desktop notifications, which are \"toast\" windows that pop up on the desktop.",
+  "icons": {"16": "16.png", "48": "48.png", "128": "128.png"},
+  "permissions": ["tabs", "notifications"],
+  "options_page": "options.html",
+  "background": { "scripts": ["background.js"] },
+  "manifest_version": 2,
+
+  // crbug.com/134315
+  "web_accessible_resources": [
+    "48.png"
+  ]
+}
diff --git a/chrome/common/extensions/docs/examples/api/notifications/options.html b/chrome/common/extensions/docs/examples/api/notifications/options.html
new file mode 100644
index 0000000..df61b37
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/notifications/options.html
@@ -0,0 +1,41 @@
+<!--
+  Copyright (c) 2012 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.
+
+  Brian Kennish <bkennish@chromium.org>
+
+  An option page for configuring notifications.
+-->
+<!doctype html>
+<html>
+  <head>
+    <title>Notification Demo</title>
+    <link href="style.css" rel="stylesheet" type="text/css">
+    <script src="options.js"></script>
+  </head>
+  <body>
+    <h1>
+      <img src="64.png" alt="Toast">
+      Notification Demo
+    </h1>
+    <h2>Options</h2>
+    <form id="options">
+      <input type="checkbox" name="isActivated" checked>
+      Display a notification every
+      <select name="frequency">
+        <option>1</option>
+        <option>2</option>
+        <option>3</option>
+        <option>4</option>
+        <option>5</option>
+        <option>10</option>
+        <option>15</option>
+        <option>20</option>
+        <option>25</option>
+        <option>30</option>
+      </select>
+      minute(s).
+    </form>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/notifications/options.js b/chrome/common/extensions/docs/examples/api/notifications/options.js
new file mode 100644
index 0000000..9f5e6a2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/notifications/options.js
@@ -0,0 +1,33 @@
+// 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.
+
+/*
+  Grays out or [whatever the opposite of graying out is called] the option
+  field.
+*/
+function ghost(isDeactivated) {
+  options.style.color = isDeactivated ? 'graytext' : 'black';
+                                              // The label color.
+  options.frequency.disabled = isDeactivated; // The control manipulability.
+}
+
+window.addEventListener('load', function() {
+  // Initialize the option controls.
+  options.isActivated.checked = JSON.parse(localStorage.isActivated);
+                                         // The display activation.
+  options.frequency.value = localStorage.frequency;
+                                         // The display frequency, in minutes.
+
+  if (!options.isActivated.checked) { ghost(true); }
+
+  // Set the display activation and frequency.
+  options.isActivated.onchange = function() {
+    localStorage.isActivated = options.isActivated.checked;
+    ghost(!options.isActivated.checked);
+  };
+
+  options.frequency.onchange = function() {
+    localStorage.frequency = options.frequency.value;
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/api/notifications/style.css b/chrome/common/extensions/docs/examples/api/notifications/style.css
new file mode 100644
index 0000000..7a5de73
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/notifications/style.css
@@ -0,0 +1,25 @@
+/**
+ * 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.
+ */
+
+/* Clone the look and feel of "chrome://" pages. */
+body {
+  margin: 10px;
+  font: 84% Arial, sans-serif
+}
+
+h1 { font-size: 156% }
+
+h1 img {
+  margin: 1px 5px 0 1px;
+  vertical-align: middle
+}
+
+h2 {
+  border-top: 1px solid #9cc2ef;
+  background-color: #ebeff9;
+  padding: 3px 5px;
+  font-size: 100%
+}
diff --git a/chrome/common/extensions/docs/examples/api/omnibox/simple-example/background.js b/chrome/common/extensions/docs/examples/api/omnibox/simple-example/background.js
new file mode 100644
index 0000000..73f3d7a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/omnibox/simple-example/background.js
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 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.
+
+// This event is fired each time the user updates the text in the omnibox,
+// as long as the extension's keyword mode is still active.
+chrome.omnibox.onInputChanged.addListener(
+  function(text, suggest) {
+    console.log('inputChanged: ' + text);
+    suggest([
+      {content: text + " one", description: "the first one"},
+      {content: text + " number two", description: "the second entry"}
+    ]);
+  });
+
+// This event is fired with the user accepts the input in the omnibox.
+chrome.omnibox.onInputEntered.addListener(
+  function(text) {
+    console.log('inputEntered: ' + text);
+    alert('You just typed "' + text + '"');
+  });
diff --git a/chrome/common/extensions/docs/examples/api/omnibox/simple-example/manifest.json b/chrome/common/extensions/docs/examples/api/omnibox/simple-example/manifest.json
new file mode 100644
index 0000000..dfdc4ca
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/omnibox/simple-example/manifest.json
@@ -0,0 +1,10 @@
+{
+  "name": "Omnibox Example",
+  "description" : "To use, type 'omnix' plus a search term into the Omnibox.",
+  "version": "1.1",
+  "background": {
+    "scripts": ["background.js"]
+  },
+  "omnibox": { "keyword" : "omnix" },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/override/blank_ntp/blank.html b/chrome/common/extensions/docs/examples/api/override/blank_ntp/blank.html
new file mode 100644
index 0000000..87cf3ed
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/override/blank_ntp/blank.html
@@ -0,0 +1,19 @@
+<html>
+ <head>
+  <title>Blank New Tab</title>
+  <style>
+  div {
+    color: #cccccc;
+    vertical-align: 50%;
+    text-align: center;
+    font-family: sans-serif;
+    font-size: 300%;
+  }
+  </style>
+ </head>
+ <body>
+  <div style="height:40%"></div>
+  <div>Blank New Tab&trade;</div>
+ </body>
+</html>
+
diff --git a/chrome/common/extensions/docs/examples/api/override/blank_ntp/manifest.json b/chrome/common/extensions/docs/examples/api/override/blank_ntp/manifest.json
new file mode 100644
index 0000000..c5ab29a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/override/blank_ntp/manifest.json
@@ -0,0 +1,9 @@
+{
+  "name": "Blank new tab page",
+  "version": "0.2",
+  "incognito": "split",
+  "chrome_url_overrides": {
+    "newtab": "blank.html"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/override/override_igoogle/manifest.json b/chrome/common/extensions/docs/examples/api/override/override_igoogle/manifest.json
new file mode 100644
index 0000000..a931c55
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/override/override_igoogle/manifest.json
@@ -0,0 +1,8 @@
+{
+  "name": "iGoogle new tab page",
+  "version": "0.2",
+  "chrome_url_overrides": {
+    "newtab": "redirect.html"
+  },
+  "manifest_version": 2
+}
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/override/override_igoogle/redirect.html b/chrome/common/extensions/docs/examples/api/override/override_igoogle/redirect.html
new file mode 100644
index 0000000..35117f4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/override/override_igoogle/redirect.html
@@ -0,0 +1,3 @@
+<head>
+<meta http-equiv="refresh"content="0;URL=http://www.google.com/ig">
+</head>
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/background.js b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/background.js
new file mode 100644
index 0000000..e6651c8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/background.js
@@ -0,0 +1,17 @@
+// Copyright (c) 2012 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.
+
+// Called when a message is passed.  We assume that the content script
+// wants to show the page action.
+function onRequest(request, sender, sendResponse) {
+  // Show the page action for the tab that the sender (content script)
+  // was on.
+  chrome.pageAction.show(sender.tab.id);
+
+  // Return nothing to let the connection be cleaned up.
+  sendResponse({});
+};
+
+// Listen for the content script to send a message to the background page.
+chrome.extension.onRequest.addListener(onRequest);
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/contentscript.js b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/contentscript.js
new file mode 100644
index 0000000..91037bb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/contentscript.js
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 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.
+ */
+var regex = /sandwich/;
+
+// Test the text of the body element against our regular expression.
+if (regex.test(document.body.innerText)) {
+  // The regular expression produced a match, so notify the background page.
+  chrome.extension.sendRequest({}, function(response) {});
+} else {
+  // No match was found.
+}
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/manifest.json b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/manifest.json
new file mode 100644
index 0000000..2b09d27
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/manifest.json
@@ -0,0 +1,29 @@
+{
+  "name" : "Page action by content",
+  "version" : "1.1",
+  "description" : "Shows a page action for HTML pages containing the word 'sandwich'",
+  "background" : {
+    "scripts": ["background.js"]
+  },
+  "page_action" :
+  {
+    "default_icon" : "sandwich-19.png",
+    "default_title" : "There's a 'sandwich' in this page!"
+  },
+  "content_scripts" : [
+    {
+      "matches" : [
+        "http://*/*",
+        "https://*/*"
+      ],
+      "js" : ["contentscript.js"],
+      "run_at" : "document_idle",
+      "all_frames" : false
+    }
+  ],
+  "icons" : {
+    "48" : "sandwich-48.png",
+    "128" : "sandwich-128.png"
+  },
+  "manifest_version": 2
+}
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/sandwich-128.png b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/sandwich-128.png
new file mode 100644
index 0000000..98f5b50
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/sandwich-128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/sandwich-19.png b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/sandwich-19.png
new file mode 100644
index 0000000..dfe3ae1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/sandwich-19.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/sandwich-48.png b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/sandwich-48.png
new file mode 100644
index 0000000..d61dea6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_content/sandwich-48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/background.js b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/background.js
new file mode 100644
index 0000000..ed1769d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/background.js
@@ -0,0 +1,15 @@
+// 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.
+
+// Called when the url of a tab changes.
+function checkForValidUrl(tabId, changeInfo, tab) {
+  // If the letter 'g' is found in the tab's URL...
+  if (tab.url.indexOf('g') > -1) {
+    // ... show the page action.
+    chrome.pageAction.show(tabId);
+  }
+};
+
+// Listen for any changes to the URL of any tab.
+chrome.tabs.onUpdated.addListener(checkForValidUrl);
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/icon-128.png b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/icon-128.png
new file mode 100644
index 0000000..0734990
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/icon-128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/icon-19.png b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/icon-19.png
new file mode 100644
index 0000000..9c3fa8f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/icon-19.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/icon-48.png b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/icon-48.png
new file mode 100644
index 0000000..0574e0e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/icon-48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/manifest.json b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/manifest.json
new file mode 100644
index 0000000..631ed9e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/pageaction_by_url/manifest.json
@@ -0,0 +1,19 @@
+{
+  "name": "Page action by URL",
+  "version": "1.0",
+  "description": "Shows a page action for urls which have the letter 'g' in them.",
+  "background": { "scripts": ["background.js"] },
+  "page_action" :
+  {
+    "default_icon" : "icon-19.png",
+    "default_title" : "There's a 'G' in this URL!"
+  },
+  "permissions" : [
+    "tabs"
+  ],
+  "icons" : {
+    "48" : "icon-48.png",
+    "128" : "icon-128.png"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/set_icon/background.html b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/background.html
new file mode 100644
index 0000000..9986850
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/background.html
@@ -0,0 +1,69 @@
+<html>
+<head>
+<script>
+  var lastTabId = 0;
+  var tab_clicks = {};
+
+  chrome.tabs.onSelectionChanged.addListener(function(tabId) {
+    lastTabId = tabId;
+    chrome.pageAction.show(lastTabId);
+  });
+
+  chrome.tabs.getSelected(null, function(tab) {
+    lastTabId = tab.id;
+    chrome.pageAction.show(lastTabId);
+  });
+
+  // Called when the user clicks on the page action.
+  chrome.pageAction.onClicked.addListener(function(tab) {
+    var clicks = tab_clicks[tab.id] || 0;
+    chrome.pageAction.setIcon({path: "icon" + (clicks + 1) + ".png",
+                               tabId: tab.id});
+    if (clicks % 2) {
+      chrome.pageAction.show(tab.id);
+    } else {
+      chrome.pageAction.hide(tab.id);
+      setTimeout(function() { chrome.pageAction.show(tab.id); }, 200);
+    }
+    chrome.pageAction.setTitle({title: "click:" + clicks, tabId: tab.id});
+
+    // We only have 2 icons, but cycle through 3 icons to test the
+    // out-of-bounds index bug.
+    clicks++;
+    if (clicks > 3)
+      clicks = 0;
+    tab_clicks[tab.id] = clicks;
+  });
+
+  var i = 0;
+  window.setInterval(function() {
+    var clicks = tab_clicks[lastTabId] || 0;
+
+    // Don't animate while in "click" mode.
+    if (clicks > 0) return;
+
+    // Don't do anything if we don't have a tab yet.
+    if (lastTabId == 0) return;
+
+    i++;
+    chrome.pageAction.setIcon({imageData: draw(i*2, i*4), tabId: lastTabId});
+  }, 50);
+
+  function draw(starty, startx) {
+    var canvas = document.getElementById('canvas');
+    var context = canvas.getContext('2d');
+    context.clearRect(0, 0, canvas.width, canvas.height);
+    context.fillStyle = "rgba(0,200,0,255)";
+    context.fillRect(startx % 19, starty % 19, 8, 8);
+    context.fillStyle = "rgba(0,0,200,255)";
+    context.fillRect((startx + 5) % 19, (starty + 5) % 19, 8, 8);
+    context.fillStyle = "rgba(200,0,0,255)";
+    context.fillRect((startx + 10) % 19, (starty + 10) % 19, 8, 8);
+    return context.getImageData(0, 0, 19, 19);
+  }
+</script>
+</head>
+<body>
+<canvas id="canvas" width="19" height="19"></canvas>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/set_icon/icon1.png b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/icon1.png
new file mode 100644
index 0000000..84c4be3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/icon1.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/set_icon/icon2.png b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/icon2.png
new file mode 100644
index 0000000..9bb6f33
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/icon2.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/pageAction/set_icon/manifest.json b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/manifest.json
new file mode 100644
index 0000000..6c9b23e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/pageAction/set_icon/manifest.json
@@ -0,0 +1,13 @@
+{
+  "name": "Animated Page Action",
+  "description": "This extension adds an animated browser action to the toolbar.",
+  "version": "1.1",
+  "permissions": ["tabs"],
+  "background": {
+    "page": "background.html"
+  },
+  "page_action": {
+    "default_title": "First icon"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/permissions/extension-questions.crx b/chrome/common/extensions/docs/examples/api/permissions/extension-questions.crx
new file mode 100644
index 0000000..9737b18
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/permissions/extension-questions.crx
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/permissions/extension-questions/images/icon.png b/chrome/common/extensions/docs/examples/api/permissions/extension-questions/images/icon.png
new file mode 100644
index 0000000..064f35b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/permissions/extension-questions/images/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/permissions/extension-questions/manifest.json b/chrome/common/extensions/docs/examples/api/permissions/extension-questions/manifest.json
new file mode 100644
index 0000000..4b0c8b3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/permissions/extension-questions/manifest.json
@@ -0,0 +1,18 @@
+{
+  "name": "Top Chrome Extension Questions",
+  "version": "0.2",
+  "description": "Sample demonstration of the optional permissions API.",
+  "icons": {
+    "128": "images/icon.png",
+    "48": "images/icon.png",
+    "16": "images/icon.png"
+  },
+  "browser_action": {
+    "default_icon": "images/icon.png",
+    "default_popup": "popup.html"
+  },
+  "options_page": "options.html",
+  "permissions": ["experimental"],
+  "optional_permissions": ["http://api.stackoverflow.com/"],
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/permissions/extension-questions/options.html b/chrome/common/extensions/docs/examples/api/permissions/extension-questions/options.html
new file mode 100644
index 0000000..7fa0aae
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/permissions/extension-questions/options.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<head>
+  <style>
+    #status { font-weight: bold; }
+  </style>
+</head>
+<body>
+  <button id="enable">Grant Permission</button>
+  <button id="disable">Revoke Permission</button>
+  <p>
+  Stack Overflow permission status: <span id="status"></span>
+  <script src="options.js"></script>
+  </p>
+</body>
diff --git a/chrome/common/extensions/docs/examples/api/permissions/extension-questions/options.js b/chrome/common/extensions/docs/examples/api/permissions/extension-questions/options.js
new file mode 100644
index 0000000..9ff8f7e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/permissions/extension-questions/options.js
@@ -0,0 +1,46 @@
+// 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.
+
+var PERMISSIONS = {origins: ['http://api.stackoverflow.com/']};
+var YES = 'ENABLED';
+var NO = 'DISABLED';
+
+var $status = document.querySelector('#status');
+chrome.permissions.onAdded.addListener(function(permissions) {
+  $status.innerText = YES;
+});
+chrome.permissions.onRemoved.addListener(function(permissions) {
+  $status.innerText = NO;
+});
+chrome.permissions.contains(PERMISSIONS, function(contains) {
+  $status.innerText = contains ? YES : NO;
+});
+
+document.querySelector('button#enable').addEventListener('click', function() {
+  chrome.permissions.contains(PERMISSIONS, function(allowed) {
+    if (allowed) {
+      alert('You already have SO host permission!');
+    } else {
+      chrome.permissions.request(PERMISSIONS, function(result) {
+        if (result) {
+          console.log('SO host permission granted!' +
+                      'Open the browser action again.');
+        }
+      });
+    }
+  });
+});
+
+document.querySelector('button#disable').addEventListener('click', function() {
+  chrome.permissions.contains(PERMISSIONS, function(allowed) {
+    if (allowed) {
+      chrome.permissions.remove(PERMISSIONS, function(result) {
+        console.log('Revoked SO host permission.');
+      });
+    } else {
+      alert('No SO host permission found.');
+    }
+  });
+});
+
diff --git a/chrome/common/extensions/docs/examples/api/permissions/extension-questions/popup.html b/chrome/common/extensions/docs/examples/api/permissions/extension-questions/popup.html
new file mode 100644
index 0000000..43fc6b0
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/permissions/extension-questions/popup.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<head>
+  <style>
+    body { width: 300px; }
+  </style>
+</head>
+<body>
+  <h3 id="title"></h3>
+  <ul id="results"></ul>
+  <script src="popup.js"></script>
+</body>
diff --git a/chrome/common/extensions/docs/examples/api/permissions/extension-questions/popup.js b/chrome/common/extensions/docs/examples/api/permissions/extension-questions/popup.js
new file mode 100644
index 0000000..46c74d9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/permissions/extension-questions/popup.js
@@ -0,0 +1,47 @@
+// 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.
+
+var PERMISSIONS = {origins: ['http://api.stackoverflow.com/']};
+var URL = 'http://api.stackoverflow.com/1.1/questions?max=10&sort=votes&tagged=google-chrome-extension';
+var ROOT = 'http://stackoverflow.com';
+
+chrome.permissions.contains(PERMISSIONS, function(result) {
+  if (!result) {
+    // Open options page to request permissions.
+    document.querySelector('#title').innerText =
+        'Requires Stack Overflow permission';
+    chrome.tabs.create({url: 'options.html'});
+  } else {
+    // Make the request to SO.
+    makeRequest(function(data) {
+      // Render the results.
+      renderQuestions(JSON.parse(data));
+    });
+  }
+});
+
+function makeRequest(callback) {
+  var xhr = new XMLHttpRequest();
+  xhr.open('GET', URL);
+  xhr.addEventListener('load', function(e) {
+    var result = xhr.responseText;
+    callback(result);
+  });
+  xhr.send();
+}
+
+function renderQuestions(data) {
+  var $results = document.querySelector('#results');
+  var questions = data.questions;
+  for (var i = 0; i < Math.min(10, questions.length); i++) {
+    var question = questions[i];
+    var $question = document.createElement('li');
+    var url = ROOT + question.question_answers_url;
+    $question.innerHTML = '<a href="' + url + '" target="_blank">' +
+        question.title + '</a>';
+    results.appendChild($question);
+  }
+  // Update title too.
+  document.querySelector('#title').innerText = 'Top Chrome Extension Questions';
+}
diff --git a/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/advicedog.jpg b/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/advicedog.jpg
new file mode 100644
index 0000000..9274fbd
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/advicedog.jpg
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/manifest.json b/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/manifest.json
new file mode 100644
index 0000000..9b0acc1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/manifest.json
@@ -0,0 +1,11 @@
+{

+  "name" : "Block/allow third-party cookies API example extension",

+  "version" : "0.1",

+  "description" : "Sample extension which demonstrates how to access a preference.",

+  "permissions": [ "privacy" ],

+  "browser_action": {

+     "default_icon": "advicedog.jpg",

+     "default_popup": "popup.html"

+  },

+  "manifest_version": 2

+}

diff --git a/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/popup.css b/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/popup.css
new file mode 100644
index 0000000..3e715ba
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/popup.css
@@ -0,0 +1,13 @@
+/**
+ * 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.
+ */
+
+#container {
+  width: 300px;
+}
+
+#incognito {
+  display: none;
+}
diff --git a/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/popup.html b/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/popup.html
new file mode 100644
index 0000000..37dccec
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/popup.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <link href="popup.css" rel="stylesheet" type="text/css">
+  <script src="popup.js"></script>
+</head>
+<body>
+
+<div id="container">
+  <input type="checkbox" id="regularValue" />
+  Allow third-party sites to set cookies
+  <div id="incognito">
+    <input type="checkbox" id="useSeparateIncognitoSettings" />
+    Use separate setting for incognito mode:
+    <br>
+    <input type="checkbox" id="incognitoValue" disabled="disabled"/>
+    Allow third-party sites to set cookies in incognito sessions
+  </div>
+  <div id="incognito-forbidden">
+    Select "Allow in incognito" to access incognito preferences
+  </div>
+</div>
+
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/popup.js b/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/popup.js
new file mode 100644
index 0000000..ce25b11
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/preferences/allowThirdPartyCookies/popup.js
@@ -0,0 +1,127 @@
+// Copyright (c) 2012 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.
+
+
+var pref = chrome.privacy.websites.thirdPartyCookiesAllowed;
+
+function $(id) {
+  return document.getElementById(id);
+}
+
+/**
+ * Returns whether the |levelOfControl| means that the extension can change the
+ * preference value.
+ *
+ * @param levelOfControl{string}
+ */
+function settingIsControllable(levelOfControl) {
+  return (levelOfControl == 'controllable_by_this_extension' ||
+          levelOfControl == 'controlled_by_this_extension');
+}
+
+/**
+ * Updates the UI to reflect the state of the preference.
+ *
+ * @param settings{object} A settings object, as returned from |get()| or the
+ * |onchange| event.
+ */
+function updateUI(settings) {
+  var disableUI = !settingIsControllable(settings.levelOfControl);
+  document.getElementById('regularValue').disabled = disableUI;
+  document.getElementById('useSeparateIncognitoSettings').disabled = disableUI;
+  if (settings.hasOwnProperty('incognitoSpecific')) {
+    var hasIncognitoValue = settings.incognitoSpecific;
+    document.getElementById('useSeparateIncognitoSettings').checked =
+        hasIncognitoValue;
+    document.getElementById('incognitoValue').disabled =
+        disableUI || !hasIncognitoValue;
+    document.getElementById('incognitoValue').checked = settings.value;
+  } else {
+    document.getElementById('regularValue').checked = settings.value;
+  }
+}
+
+/**
+ * Wrapper for |updateUI| which is used as callback for the |get()| method and
+ * which logs the result.
+ * If there was an error getting the preference, does nothing.
+ *
+ * @param settings{object} A settings object, as returned from |get()|.
+ */
+function updateUIFromGet(settings) {
+  if (settings) {
+    console.log('pref.get result:' + JSON.stringify(settings));
+    updateUI(settings);
+  }
+}
+
+/**
+ * Wrapper for |updateUI| which is used as handler for the |onchange| event
+ * and which logs the result.
+ *
+ * @param settings{object} A settings object, as returned from the |onchange|
+ * event.
+ */
+function updateUIFromOnChange(settings) {
+  console.log('pref.onChange event:' + JSON.stringify(settings));
+  updateUI(settings);
+}
+
+/*
+ * Initializes the UI.
+ */
+function init() {
+  chrome.extension.isAllowedIncognitoAccess(function(allowed) {
+    if (allowed) {
+      pref.get({'incognito': true}, updateUIFromGet);
+      $('incognito').style.display = 'block';
+      $('incognito-forbidden').style.display = 'none';
+    }
+  });
+  pref.get({}, updateUIFromGet);
+  pref.onChange.addListener(updateUIFromOnChange);
+
+  $('regularValue').addEventListener('click', function () {
+    setPrefValue(this.checked, false);
+  });
+  $('useSeparateIncognitoSettings').addEventListener('click', function () {
+     setUseSeparateIncognitoSettings(this.checked);
+  });
+  $('incognitoValue').addEventListener('click', function () {
+    setPrefValue(this.checked, true);
+  });
+}
+
+/**
+ * Called from the UI to change the preference value.
+ *
+ * @param enabled{boolean} The new preference value.
+ * @param incognito{boolean} Whether the value is specific to incognito mode.
+ */
+function setPrefValue(enabled, incognito) {
+  var scope = incognito ? 'incognito_session_only' : 'regular';
+  pref.set({'value': enabled, 'scope': scope});
+}
+
+/**
+ * Called from the UI to change whether to use separate settings for
+ * incognito mode.
+ *
+ * @param value{boolean} whether to use separate settings for
+ * incognito mode.
+ */
+function setUseSeparateIncognitoSettings(value) {
+  if (!value) {
+    pref.clear({'incognito': true});
+  } else {
+    // Explicitly set the value for incognito mode.
+    pref.get({'incognito': true}, function(settings) {
+      pref.set({'incognito': true, 'value': settings.value});
+    });
+  }
+  document.getElementById('incognitoValue').disabled = !value;
+}
+
+// Call `init` to kick things off.
+document.addEventListener('DOMContentLoaded', init);
diff --git a/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/advicedog.jpg b/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/advicedog.jpg
new file mode 100644
index 0000000..9274fbd
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/advicedog.jpg
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/manifest.json b/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/manifest.json
new file mode 100644
index 0000000..57013fd
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/manifest.json
@@ -0,0 +1,11 @@
+{

+  "name" : "Block/allow referrer API example extension",

+  "version" : "0.1",

+  "description" : "Sample extension which demonstrates how to access a preference.",

+  "permissions": [ "privacy" ],

+  "browser_action": {

+     "default_icon": "advicedog.jpg",

+     "default_popup": "popup.html"

+  },

+  "manifest_version": 2

+}

diff --git a/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/popup.css b/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/popup.css
new file mode 100644
index 0000000..3e715ba
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/popup.css
@@ -0,0 +1,13 @@
+/**
+ * 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.
+ */
+
+#container {
+  width: 300px;
+}
+
+#incognito {
+  display: none;
+}
diff --git a/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/popup.html b/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/popup.html
new file mode 100644
index 0000000..7f94699
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/popup.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <link href="popup.css" rel="stylesheet" type="text/css">
+  <script src="popup.js"></script>
+</head>
+<body>
+
+<div id="container">
+  <input type="checkbox" id="regularValue" />
+  Enable referrers
+  <div id="incognito">
+    <input type="checkbox" id="useSeparateIncognitoSettings" />
+    Use separate setting for incognito mode:
+    <br>
+    <input type="checkbox" id="incognitoValue" disabled="disabled"/>
+    Enable referrers in incognito sessions
+  </div>
+  <div id="incognito-forbidden">
+    Select "Allow in incognito" to access incognito preferences
+  </div>
+</div>
+
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/popup.js b/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/popup.js
new file mode 100644
index 0000000..72bb9e4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/preferences/enableReferrer/popup.js
@@ -0,0 +1,127 @@
+// Copyright (c) 2012 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.
+
+
+var pref = chrome.privacy.websites.referrersEnabled;
+
+function $(id) {
+  return document.getElementById(id);
+}
+
+/**
+ * Returns whether the |levelOfControl| means that the extension can change the
+ * preference value.
+ *
+ * @param levelOfControl{string}
+ */
+function settingIsControllable(levelOfControl) {
+  return (levelOfControl == 'controllable_by_this_extension' ||
+          levelOfControl == 'controlled_by_this_extension');
+}
+
+/**
+ * Updates the UI to reflect the state of the preference.
+ *
+ * @param settings{object} A settings object, as returned from |get()| or the
+ * |onchange| event.
+ */
+function updateUI(settings) {
+  var disableUI = !settingIsControllable(settings.levelOfControl);
+  document.getElementById('regularValue').disabled = disableUI;
+  document.getElementById('useSeparateIncognitoSettings').disabled = disableUI;
+  if (settings.hasOwnProperty('incognitoSpecific')) {
+    var hasIncognitoValue = settings.incognitoSpecific;
+    document.getElementById('useSeparateIncognitoSettings').checked =
+        hasIncognitoValue;
+    document.getElementById('incognitoValue').disabled =
+        disableUI || !hasIncognitoValue;
+    document.getElementById('incognitoValue').checked = settings.value;
+  } else {
+    document.getElementById('regularValue').checked = settings.value;
+  }
+}
+
+/**
+ * Wrapper for |updateUI| which is used as callback for the |get()| method and
+ * which logs the result.
+ * If there was an error getting the preference, does nothing.
+ *
+ * @param settings{object} A settings object, as returned from |get()|.
+ */
+function updateUIFromGet(settings) {
+  if (settings) {
+    console.log('pref.get result:' + JSON.stringify(settings));
+    updateUI(settings);
+  }
+}
+
+/**
+ * Wrapper for |updateUI| which is used as handler for the |onchange| event
+ * and which logs the result.
+ *
+ * @param settings{object} A settings object, as returned from the |onchange|
+ * event.
+ */
+function updateUIFromOnChange(settings) {
+  console.log('pref.onChange event:' + JSON.stringify(settings));
+  updateUI(settings);
+}
+
+/*
+ * Initializes the UI.
+ */
+function init() {
+  chrome.extension.isAllowedIncognitoAccess(function(allowed) {
+    if (allowed) {
+      pref.get({'incognito': true}, updateUIFromGet);
+      $('incognito').style.display = 'block';
+      $('incognito-forbidden').style.display = 'none';
+    }
+  });
+  pref.get({}, updateUIFromGet);
+  pref.onChange.addListener(updateUIFromOnChange);
+
+  $('regularValue').addEventListener('click', function () {
+    setPrefValue(this.checked, false);
+  });
+  $('useSeparateIncognitoSettings').addEventListener('click', function () {
+     setUseSeparateIncognitoSettings(this.checked);
+  });
+  $('incognitoValue').addEventListener('click', function () {
+    setPrefValue(this.checked, true);
+  });
+}
+
+/**
+ * Called from the UI to change the preference value.
+ *
+ * @param enabled{boolean} The new preference value.
+ * @param incognito{boolean} Whether the value is specific to incognito mode.
+ */
+function setPrefValue(enabled, incognito) {
+  var scope = incognito ? 'incognito_session_only' : 'regular';
+  pref.set({'value': enabled, 'scope': scope});
+}
+
+/**
+ * Called from the UI to change whether to use separate settings for
+ * incognito mode.
+ *
+ * @param value{boolean} whether to use separate settings for
+ * incognito mode.
+ */
+function setUseSeparateIncognitoSettings(value) {
+  if (!value) {
+    pref.clear({'incognito': true});
+  } else {
+    // Explicitly set the value for incognito mode.
+    pref.get({'incognito': true}, function(settings) {
+      pref.set({'incognito': true, 'value': settings.value});
+    });
+  }
+  document.getElementById('incognitoValue').disabled = !value;
+}
+
+// Call `init` to kick things off.
+document.addEventListener('DOMContentLoaded', init);
diff --git a/chrome/common/extensions/docs/examples/api/processes/process_monitor/icon.png b/chrome/common/extensions/docs/examples/api/processes/process_monitor/icon.png
new file mode 100644
index 0000000..84c4be3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/processes/process_monitor/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/processes/process_monitor/manifest.json b/chrome/common/extensions/docs/examples/api/processes/process_monitor/manifest.json
new file mode 100644
index 0000000..a3c972d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/processes/process_monitor/manifest.json
@@ -0,0 +1,14 @@
+{
+  "name": "Process Monitor",
+  "version": "1.1",
+  "description": "Adds a browser action that monitors resource usage of all browser processes.",
+  "permissions": [
+    "experimental", "tabs"
+  ],
+  "browser_action": {
+      "default_title": "Process Monitor",
+      "default_icon": "icon.png",
+      "default_popup": "popup.html"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/processes/process_monitor/popup.html b/chrome/common/extensions/docs/examples/api/processes/process_monitor/popup.html
new file mode 100644
index 0000000..9e7eac5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/processes/process_monitor/popup.html
@@ -0,0 +1,32 @@
+<html>
+<head>
+<script src="popup.js"></script>
+<style>
+body {
+  overflow: hidden;
+  margin: 0px;
+  padding: 0px;
+  background: white;
+}
+
+div:first-child {
+  margin-top: 0px;
+}
+
+div, td {
+  padding: 1px 3px;
+  font-family: sans-serif;
+  font-size: 10pt;
+  margin-top: 1px;
+}
+</style>
+</head>
+<body>
+<div id="title"><b>Process Monitor</b></div>
+<div id="process-list"><i>Loading...</i></div>
+<div id="buttons">
+  <input type="button" value="End Process" id="killProcess" />
+</div>
+</div>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/processes/process_monitor/popup.js b/chrome/common/extensions/docs/examples/api/processes/process_monitor/popup.js
new file mode 100644
index 0000000..b0ee288
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/processes/process_monitor/popup.js
@@ -0,0 +1,80 @@
+// Copyright (c) 2012 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.
+
+// Shows an updating list of process statistics.
+function init() {
+  chrome.experimental.processes.onUpdatedWithMemory.addListener(
+    function(processes) {
+      var table = "<table>\n" +
+        "<tr><td><b>Process</b></td>" +
+        "<td>OS ID</td>" +
+        "<td>Type</td>" +
+        "<td>Tabs</td>" +
+        "<td>CPU</td>" +
+        "<td>Network</td>" +
+        "<td>Private Memory</td>" +
+        "<td>FPS</td>" +
+        "<td>JS Memory</td>" +
+        "<td></td>" +
+        "</tr>\n";
+      for (pid in processes) {
+        table = displayProcessInfo(processes[pid], table);
+      }
+      table += "</table>\n";
+      var div = document.getElementById("process-list");
+      div.innerHTML = table;
+    });
+
+  document.getElementById("killProcess").onclick = function () {
+    var procId = parseInt(prompt("Enter process ID"));
+    chrome.experimental.processes.terminate(procId);
+  }
+}
+
+function displayProcessInfo(process, table) {
+  // Format network string like task manager
+  var network = process.network;
+  if (network > 1024) {
+    network = (network / 1024).toFixed(1) + " kB/s";
+  } else if (network > 0) {
+    network += " B/s";
+  } else if (network == -1) {
+    network = "N/A";
+  }
+
+  table +=
+    "<tr><td>" + process.id + "</td>" +
+    "<td>" + process.osProcessId + "</td>" +
+    "<td>" + process.type + "</td>" +
+    "<td>" + process.tabs + "</td>" +
+    "<td>" + process.cpu + "</td>" +
+    "<td>" + network + "</td>";
+
+  if ("privateMemory" in process) {
+    table += "<td>" + (process.privateMemory / 1024) + "K</td>";
+  } else {
+    table += "<td>N/A</td>";
+  }
+  if ("fps" in process) {
+    table += "<td>" + process.fps.toFixed(2) + "</td>";
+  } else {
+    table += "<td>N/A</td>";
+  }
+
+  if ("jsMemoryAllocated" in process) {
+    var allocated = process.jsMemoryAllocated / 1024;
+    var used = process.jsMemoryUsed / 1024;
+    table += "<td>" + allocated.toFixed(2) + "K (" + used.toFixed(2) +
+        "K live)</td>";
+  } else {
+    table += "<td>N/A</td>";
+  }
+
+  table +=
+    "<td></td>" +
+    "</tr>\n";
+  return table;
+}
+
+document.addEventListener('DOMContentLoaded', init);
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/processes/show_tabs/icon.png b/chrome/common/extensions/docs/examples/api/processes/show_tabs/icon.png
new file mode 100644
index 0000000..84c4be3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/processes/show_tabs/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/processes/show_tabs/manifest.json b/chrome/common/extensions/docs/examples/api/processes/show_tabs/manifest.json
new file mode 100644
index 0000000..e321acf
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/processes/show_tabs/manifest.json
@@ -0,0 +1,14 @@
+{
+  "name": "Show Tabs in Process",
+  "version": "1.0",
+  "description": "Adds a browser action showing which tabs share the current tab's process.",
+  "permissions": [
+    "experimental", "tabs", "chrome://favicon/*"
+  ],
+  "browser_action": {
+      "default_title": "Show Tabs in this Process",
+      "default_icon": "icon.png",
+      "default_popup": "popup.html"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/processes/show_tabs/popup.css b/chrome/common/extensions/docs/examples/api/processes/show_tabs/popup.css
new file mode 100644
index 0000000..31e15e1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/processes/show_tabs/popup.css
@@ -0,0 +1,25 @@
+/**
+ * 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.
+ */
+
+body {
+  overflow: hidden;
+  margin: 0px;
+  padding: 0px;
+  background: white;
+  width: 400px;
+}
+
+div:first-child {
+  margin-top: 0px;
+}
+
+div {
+  padding: 1px 3px;
+  font-family: sans-serif;
+  font-size: 10pt;
+  width: 400px;
+  margin-top: 1px;
+}
diff --git a/chrome/common/extensions/docs/examples/api/processes/show_tabs/popup.html b/chrome/common/extensions/docs/examples/api/processes/show_tabs/popup.html
new file mode 100644
index 0000000..d813345
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/processes/show_tabs/popup.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Popup</title>
+    <link href="popup.css" rel="stylesheet" type="text/css">
+    <script src="popup.js"></script>
+  </head>
+  <body>
+    <div id="title"></div>
+    <div id="tab-list"></div>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/processes/show_tabs/popup.js b/chrome/common/extensions/docs/examples/api/processes/show_tabs/popup.js
new file mode 100644
index 0000000..a92829c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/processes/show_tabs/popup.js
@@ -0,0 +1,69 @@
+// 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.
+
+// Show a list of all tabs in the same process as this one.
+function init() {
+  chrome.windows.getCurrent(function(currentWindow) {
+    chrome.tabs.getSelected(currentWindow.id, function(selectedTab) {
+      chrome.experimental.processes.getProcessIdForTab(selectedTab.id,
+        function(pid) {
+          var outputDiv = document.getElementById("tab-list");
+          var titleDiv = document.getElementById("title");
+          titleDiv.innerHTML = "<b>Tabs in Process " + pid + ":</b>";
+          displayTabInfo(currentWindow.id, selectedTab, outputDiv);
+          displaySameProcessTabs(selectedTab, pid, outputDiv);
+        }
+      );
+
+    });
+  });
+}
+
+function displaySameProcessTabs(selectedTab, processId, outputDiv) {
+  // Loop over all windows and their tabs
+  var tabs = [];
+  chrome.windows.getAll({ populate: true }, function(windowList) {
+    for (var i = 0; i < windowList.length; i++) {
+      for (var j = 0; j < windowList[i].tabs.length; j++) {
+        var tab = windowList[i].tabs[j];
+        if (tab.id != selectedTab.id) {
+          tabs.push(tab);
+        }
+      }
+    }
+
+    // Display tab in list if it is in the same process
+    tabs.forEach(function(tab) {
+      chrome.experimental.processes.getProcessIdForTab(tab.id,
+        function(pid) {
+          if (pid == processId) {
+            displayTabInfo(tab.windowId, tab, outputDiv);
+          }
+        }
+      );
+    });
+  });
+}
+
+// Print a link to a given tab
+function displayTabInfo(windowId, tab, outputDiv) {
+  if (tab.favIconUrl != undefined) {
+    outputDiv.innerHTML += "<img src='chrome://favicon/" + tab.url + "'>\n";
+  }
+  outputDiv.innerHTML +=
+    "<b><a href='#' onclick='showTab(window, " + windowId + ", " + tab.id +
+    ")'>" + tab.title + "</a></b><br>\n" +
+    "<i>" + tab.url + "</i><br>\n";
+}
+
+// Bring the selected tab to the front
+function showTab(origWindow, windowId, tabId) {
+  // TODO: Bring the window to the front.  (See http://crbug.com/31434)
+  //chrome.windows.update(windowId, {focused: true});
+  chrome.tabs.update(tabId, { selected: true });
+  origWindow.close();
+}
+
+// Kick things off.
+document.addEventListener('DOMContentLoaded', init);
diff --git a/chrome/common/extensions/docs/examples/api/record/page_cycler/manifest.json b/chrome/common/extensions/docs/examples/api/record/page_cycler/manifest.json
new file mode 100644
index 0000000..f675894
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/record/page_cycler/manifest.json
@@ -0,0 +1,17 @@
+{
+  "name": "Page Cycler",
+  "version": "0.0",
+  "description": "Page Cycler UI",
+  "app": {
+    "launch": {
+      "local_path": "page_cycler.html"
+    }
+  },
+  "icons": {
+    "128": "page_cycler_icon.png",
+    "16": "page_cycler_icon_16.png"
+  },
+  "permissions": [
+    "experimental"
+  ]
+}
diff --git a/chrome/common/extensions/docs/examples/api/record/page_cycler/page_cycler.css b/chrome/common/extensions/docs/examples/api/record/page_cycler/page_cycler.css
new file mode 100644
index 0000000..27630fa
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/record/page_cycler/page_cycler.css
@@ -0,0 +1,102 @@
+/* Copyright (c) 2012 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. */
+
+body {
+   font-family:sans-serif;
+   font-size:85%;
+}
+
+.control-label {
+  margin-right:1em;
+  font-style:bold;
+}
+
+.url-list {
+  width:100%;
+  height:20em;
+  border: 1px solid;
+}
+
+.entry-field {
+   margin:10px;
+}
+
+.error-list-show {
+  color:red;
+}
+
+.error-list-hide {
+  display:none;
+}
+
+button,input {
+}
+
+.command-button {
+  position:relative;
+  top:2em;
+  text-align:center;
+}
+
+h1 {
+  text-align:center;
+}
+
+.dir-choice {
+  display:inline-block;
+  border: 1px solid;
+  width:40em;
+}
+
+.tab-menu {
+  position:absolute;
+  left:0;
+  width:15em;
+}
+
+.tab-label {
+  margin:1px;
+  padding:1px;
+}
+
+.enabled-tab-label {
+  border: 1px solid;
+  border-color:#888;
+  border-radius:2px;
+  background:#EFF;
+}
+
+.disabled-tab-label {
+  background-color:#FFF;
+}
+
+body, html, #page {
+  height:100%;
+}
+
+div#tab-navigator {
+  position:relative;
+  float:left;
+  width:19%;
+}
+
+div#tab-wrapper {
+  position:relative;
+  float:right;
+  width:79%;
+}
+
+.splitter {
+  position:relative;
+  float:left;
+  width:5px;
+  height:100%;
+  background-color:#CCC;
+}
+
+.tab {
+  position:absolute;
+  top:0px;
+  left:0px;
+}
diff --git a/chrome/common/extensions/docs/examples/api/record/page_cycler/page_cycler.html b/chrome/common/extensions/docs/examples/api/record/page_cycler/page_cycler.html
new file mode 100644
index 0000000..6e2131a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/record/page_cycler/page_cycler.html
@@ -0,0 +1,90 @@
+<!doctype html>
+
+<!-- Copyright (c) 2012 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. -->
+
+<html>
+<head>
+<title>Page Cycler</title>
+<link rel="stylesheet" type="text/css" href="page_cycler.css"/>
+</head>
+<body>
+
+<div id="page">
+  <div id="tab-navigator">
+    <div class="tab-label" id="capture-tab-label"><a>Prepare Test</a></div>
+    <div class="tab-label" id="replay-tab-label"><a>Run Test</a></div>
+  </div>
+  
+  <div class="splitter"></div>
+
+  <div id="tab-wrapper">
+    <div id="capture-tab" class="tab">
+      <h1>Set Up Page Cycling Test</h1>
+      <p>
+        <b>Set up the list of URLs, and a cache directory.  "Prepare Test" will
+        do a preliminary visit of all the URLs, caching them in the indicated
+        directory.</b>
+      </p>
+      <div id="capture-errors-display" class="error-list-hide">
+        <h2>Errors Occured during Capture:</h2>
+        <p id="capture-errors"></p>
+      </div>
+      <div>
+        <p class="control-label">List of URLs to cycle</p>
+        <textarea id="capture-urls" class="url-list">&lt;Add URLS&gt;
+        </textarea>
+      </div>
+      <div class="entry-field">
+        <span class="control-label">Cache directory:</span><br/>
+        <input id="capture-cache-dir" type="text" class="dir-choice"/>
+      </div>
+  
+      <div class="command-button">
+        <button id="capture-test">
+          Prepare Test
+        </button>
+      </div>
+  
+    </div>
+  
+    <div id="replay-tab" class="tab">
+      <h1>Replay Prepared Test</h1>
+      <div id="replay-errors-display" class="error-list-hide">
+         <h2>Errors Occured during Replay:</h2>
+         <p id="replay-errors"></p>
+      </div>
+      <div>
+        <p class="control-label">List of URLs to replay</p>
+        <p id="replay-urls" class="url-list"></p>
+      </div>
+      <div class="entry-field">
+        <span class="control-label">Cache directory: </span>
+        <span id="replay-cache-dir"></span>
+      </div>
+      <div class="entry-field">
+        <span class="control-label">Repeat count: </span>
+        <input id="repeat-count" type="text" value="1"/>
+      </div>
+      <div class="entry-field">
+        <span class="control-label">Extension directory: </span>
+        <input id="extension-dir" type="text" class="dir-choice"/>
+      </div>
+      <div>
+         <p class="control-label">Result of Replay</p>
+         <textarea id="replay-result" class="url-list">
+         </textarea>
+      </div>
+      <div class="command-button">
+        <button id="replay-test" disabled=true>
+          Replay Test
+        </button>
+      </div>
+    </div>
+  </div>
+</div>
+  
+<script type="text/javascript" src="page_cycler.js"></script>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/record/page_cycler/page_cycler.js b/chrome/common/extensions/docs/examples/api/record/page_cycler/page_cycler.js
new file mode 100644
index 0000000..c43b864
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/record/page_cycler/page_cycler.js
@@ -0,0 +1,143 @@
+// Copyright (c) 2012 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.
+
+function $(criterion) {
+  return document.querySelector(criterion);
+}
+
+var pageCyclerUI = new (function () {
+  var noTestMessage = "N/A -- do Prepare Test";
+
+  this.urlList = [];
+  this.cacheDir = "";
+
+  this.captureTab = $("#capture-tab");
+  this.captureTabLabel = $("#capture-tab-label");
+  this.captureButton = $("#capture-test");
+  this.captureErrorDiv = $("#capture-errors-display");
+  this.captureErrorList = $("#capture-errors");
+
+  this.replayTab = $("#replay-tab");
+  this.replayTabLabel = $("#replay-tab-label");
+  this.replayURLs = $("#replay-urls");
+  this.replayCache = $("#replay-cache-dir");
+  this.replayButton = $("#replay-test");
+  this.replayErrorDiv = $("#replay-errors-display");
+  this.replayErrorList = $("#replay-errors");
+
+  this.replayURLs.innerText = this.replayCache.innerText = noTestMessage;
+
+  this.enableTab = function(tabLabel, tab) {
+    var tabList = document.querySelectorAll(".tab");
+    var tabLabelList = document.querySelectorAll(".tab-label");
+
+    for (var i = 0; i < tabList.length; i++)
+      if (tabList[i] == tab)
+        tabList[i].style.visibility = "visible";
+      else
+        tabList[i].style.visibility = "hidden";
+
+    for (var i = 0; i < tabLabelList.length; i++)
+      if (tabLabelList[i] == tabLabel) {
+         tabLabelList[i].classList.add("enabled-tab-label");
+         tabLabelList[i].classList.remove("disabled-tab-label");
+      } else {
+         tabLabelList[i].classList.remove("enabled-tab-label");
+         tabLabelList[i].classList.add("disabled-tab-label");
+      }
+  }
+
+  this.chooseCapture = function() {
+    this.enableTab(this.captureTabLabel, this.captureTab);
+  }
+
+  this.chooseReplay = function() {
+    this.enableTab(this.replayTabLabel, this.replayTab);
+  }
+
+  this.captureTest = function() {
+    var errorList = $("#capture-errors");
+    var errors = [];
+
+    this.cacheDir = $("#capture-cache-dir").value;
+    this.urlList = $("#capture-urls").value.split("\n");
+
+    if (errors.length > 0) {
+      this.captureErrorList.innerText = errors.join("\n");
+      this.captureErrorDiv.className = "error-list-show";
+    }
+    else {
+      this.captureErrorDiv.className = "error-list-hide";
+      this.captureButton.disabled = true;
+      chrome.experimental.record.captureURLs(this.urlList, this.cacheDir,
+          this.onCaptureDone.bind(this));
+    }
+  }
+
+  this.onCaptureDone = function(errors) {
+
+    this.captureButton.disabled = false;
+    if (errors.length > 0) {
+      this.captureErrorList.innerText = errors.join("\n");
+      this.captureErrorDiv.className = "error-list-show";
+      this.replayButton.disabled = true;
+      this.replayCache.innerText = this.replayURLs.innerText = noTestMessage;
+    }
+    else {
+      this.captureErrorDiv.className = "error-list-hide";
+      this.replayButton.disabled = false;
+      this.replayURLs.innerText = this.urlList.join("\n");
+      this.replayCache.innerText = this.cacheDir;
+    }
+  }
+
+  this.replayTest = function() {
+    var extensionPath = $("#extension-dir").value;
+    var repeatCount = parseInt($('#repeat-count').value);
+    var errors = [];
+
+    // Check local errors
+    if (isNaN(repeatCount))
+      errors.push("Enter a number for repeat count");
+    else if (repeatCount < 1 || repeatCount > 100)
+      errors.push("Repeat count must be between 1 and 100");
+
+    if (errors.length > 0) {
+      this.replayErrorList.innerText = errors.join("\n");
+      this.replayErrorDiv.className = "error-list-show";
+    } else {
+      this.replayErrorDiv.className = "error-list-hide";
+      this.replayButton.disabled = true;
+      chrome.experimental.record.replayURLs(
+          this.urlList,
+          this.cacheDir,
+          repeatCount,
+          {"extensionPath": extensionPath},
+          this.onReplayDone.bind(this));
+    }
+  }
+
+  this.onReplayDone = function(result) {
+    var replayResult = $("#replay-result");
+
+    this.replayButton.disabled = false;
+
+    if (result.errors.length > 0) {
+      this.replayErrorList.innerText = result.errors.join("<br>");
+      this.replayErrorDiv.className = "error-list-show";
+    }
+    else {
+      this.replayErrorDiv.className = "error-list-hide";
+      replayResult.innerText = "Test took " + result.runTime + "mS :\n" +
+        result.stats;
+    }
+  }
+
+  this.captureButton.addEventListener("click", this.captureTest.bind(this));
+  this.replayButton.addEventListener("click", this.replayTest.bind(this));
+  this.captureTabLabel.addEventListener("click", this.chooseCapture.bind(this));
+  this.replayTabLabel.addEventListener("click", this.chooseReplay.bind(this));
+  this.enableTab(this.captureTabLabel, this.captureTab);
+})();
+
diff --git a/chrome/common/extensions/docs/examples/api/record/page_cycler/page_cycler_icon.png b/chrome/common/extensions/docs/examples/api/record/page_cycler/page_cycler_icon.png
new file mode 100644
index 0000000..9bf904c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/record/page_cycler/page_cycler_icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/record/page_cycler/page_cycler_icon_16.png b/chrome/common/extensions/docs/examples/api/record/page_cycler/page_cycler_icon_16.png
new file mode 100644
index 0000000..7ccc7df
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/record/page_cycler/page_cycler_icon_16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/speechInput/basic/background.js b/chrome/common/extensions/docs/examples/api/speechInput/basic/background.js
new file mode 100644
index 0000000..3093b6c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/speechInput/basic/background.js
@@ -0,0 +1,41 @@
+// 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.
+
+function setStartIcon() {
+  chrome.browserAction.setIcon({ path: "start.png" });
+}
+
+function setStopIcon() {
+  chrome.browserAction.setIcon({ path: "stop.png" });
+}
+
+chrome.browserAction.onClicked.addListener(function(tab) {
+  chrome.experimental.speechInput.isRecording(function(recording) {
+    if (!recording) {
+      chrome.experimental.speechInput.start({}, function() {
+        if (chrome.extension.lastError) {
+          alert("Couldn't start speech input: " +
+              chrome.extension.lastError.message);
+          setStartIcon();
+        } else {
+          setStopIcon();
+        }
+      });
+    } else {
+      chrome.experimental.speechInput.stop(function() {
+        setStartIcon();
+      });
+    }
+  });
+});
+
+chrome.experimental.speechInput.onError.addListener(function(error) {
+  alert("Speech input failed: " + error.code);
+  setStartIcon();
+});
+
+chrome.experimental.speechInput.onResult.addListener(function(result) {
+  alert(result.hypotheses[0].utterance);
+  setStartIcon();
+});
diff --git a/chrome/common/extensions/docs/examples/api/speechInput/basic/manifest.json b/chrome/common/extensions/docs/examples/api/speechInput/basic/manifest.json
new file mode 100644
index 0000000..f7e8f03
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/speechInput/basic/manifest.json
@@ -0,0 +1,20 @@
+{
+  "name": "Speech Recognizer",
+  "version": "1.1",
+  "description": "Recognizes your speech and tells you the most likely result.",
+
+  "browser_action": {
+    "default_title": "Speech Recognizer",
+    "default_icon": "start.png"
+  },
+
+  "background": {
+    "persistent": false,
+    "scripts": ["background.js"]
+  },
+
+  "permissions": [
+    "experimental"
+  ],
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/speechInput/basic/start.png b/chrome/common/extensions/docs/examples/api/speechInput/basic/start.png
new file mode 100644
index 0000000..9efb1e2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/speechInput/basic/start.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/speechInput/basic/stop.png b/chrome/common/extensions/docs/examples/api/speechInput/basic/stop.png
new file mode 100644
index 0000000..5318c1c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/speechInput/basic/stop.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/storage/stylizr/icon.png b/chrome/common/extensions/docs/examples/api/storage/stylizr/icon.png
new file mode 100644
index 0000000..85ea072
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/storage/stylizr/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/storage/stylizr/manifest.json b/chrome/common/extensions/docs/examples/api/storage/stylizr/manifest.json
new file mode 100644
index 0000000..69c0bec
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/storage/stylizr/manifest.json
@@ -0,0 +1,21 @@
+{
+  "name": "Stylizr",
+  "description": "Spruce up your pages with custom CSS.",
+  "version": "1.0",
+
+  "permissions": [
+    "storage",
+    "tabs",
+    "<all_urls>"
+  ],
+
+  "options_page": "options.html",
+
+  "browser_action": {
+    "default_icon": "icon.png",
+    "default_title": "Stylize!",
+    "default_popup": "popup.html"
+  },
+
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/storage/stylizr/options.html b/chrome/common/extensions/docs/examples/api/storage/stylizr/options.html
new file mode 100644
index 0000000..108e661
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/storage/stylizr/options.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Stylizr</title>
+    <style>
+      body {
+        font-family: sans-serif;
+      }
+      label {
+        display: block;
+      }
+      textarea {
+        font-family: monospace;
+      }
+      .message {
+        height: 20px;
+        background: #eee;
+        padding: 5px;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="message"></div>
+    <h3>Stylizr Instructions</h3>
+
+    <ol>
+      <li>Write CSS in this textarea and save</li>
+      <li>Navigate to some page</li>
+      <li>Click the browser action icon <img src="icon.png" /></li>
+      <li>Hey presto! CSS is injected!</li>
+    </ol>
+
+    <textarea name="style_url" id="style_url" cols=80 rows=24
+        placeholder="eg: * { font-size: 110%; }"></textarea>
+
+    <br/>
+    <button class="submit">Save</button>
+    <button class="reset">Reset</button>
+
+    <script src="options.js"></script>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/storage/stylizr/options.js b/chrome/common/extensions/docs/examples/api/storage/stylizr/options.js
new file mode 100644
index 0000000..a687c5f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/storage/stylizr/options.js
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 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.
+
+// Store CSS data in the "local" storage area.
+//
+// Usually we try to store settings in the "sync" area since a lot of the time
+// it will be a better user experience for settings to automatically sync
+// between browsers.
+//
+// However, "sync" is expensive with a strict quota (both in storage space and
+// bandwidth) so data that may be as large and updated as frequently as the CSS
+// may not be suitable.
+var storage = chrome.storage.local;
+
+// Get at the DOM controls used in the sample.
+var resetButton = document.querySelector('button.reset');
+var submitButton = document.querySelector('button.submit');
+var textarea = document.querySelector('textarea');
+
+// Load any CSS that may have previously been saved.
+loadChanges();
+
+submitButton.addEventListener('click', saveChanges);
+resetButton.addEventListener('click', reset);
+
+function saveChanges() {
+  // Get the current CSS snippet from the form.
+  var cssCode = textarea.value;
+  // Check that there's some code there.
+  if (!cssCode) {
+    message('Error: No CSS specified');
+    return;
+  }
+  // Save it using the Chrome extension storage API.
+  storage.set({'css': cssCode}, function() {
+    // Notify that we saved.
+    message('Settings saved');
+  });
+}
+
+function loadChanges() {
+  storage.get('css', function(items) {
+    // To avoid checking items.css we could specify storage.get({css: ''}) to
+    // return a default value of '' if there is no css value yet.
+    if (items.css) {
+      textarea.value = items.css;
+      message('Loaded saved CSS.');
+    }
+  });
+}
+
+function reset() {
+  // Remove the saved value from storage. storage.clear would achieve the same
+  // thing.
+  storage.remove('css', function(items) {
+    message('Reset stored CSS');
+  });
+  // Refresh the text area.
+  textarea.value = '';
+}
+
+function message(msg) {
+  var message = document.querySelector('.message');
+  message.innerText = msg;
+  setTimeout(function() {
+    message.innerText = '';
+  }, 3000);
+}
diff --git a/chrome/common/extensions/docs/examples/api/storage/stylizr/popup.html b/chrome/common/extensions/docs/examples/api/storage/stylizr/popup.html
new file mode 100644
index 0000000..02b597d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/storage/stylizr/popup.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Stylizr</title>
+    <style>
+      body {
+        font-family: sans-serif;
+        width: 200px;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="message"></div>
+    <script src="popup.js"></script>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/storage/stylizr/popup.js b/chrome/common/extensions/docs/examples/api/storage/stylizr/popup.js
new file mode 100644
index 0000000..c19b5c1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/storage/stylizr/popup.js
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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.
+
+// Store CSS data in the "local" storage area.
+//
+// See note in options.js for rationale on why not to use "sync".
+var storage = chrome.storage.local;
+
+var message = document.querySelector('#message');
+
+// Check if there is CSS specified.
+storage.get('css', function(items) {
+  console.log(items);
+  // If there is CSS specified, inject it into the page.
+  if (items.css) {
+    chrome.tabs.insertCSS(null, {code: items.css}, function() {
+      if (chrome.extension.lastError) {
+        message.innerText = 'Not allowed to inject CSS into special page.';
+      } else {
+        message.innerText = 'Injected style!';
+      }
+    });
+  } else {
+    var optionsUrl = chrome.extension.getURL('options.html');
+    message.innerHTML = 'Set a style in the <a target="_blank" href="' +
+        optionsUrl + '">options page</a> first.';
+  }
+});
+
diff --git a/chrome/common/extensions/docs/examples/api/systemInfo/index.html b/chrome/common/extensions/docs/examples/api/systemInfo/index.html
new file mode 100644
index 0000000..aa6d7c6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/systemInfo/index.html
@@ -0,0 +1,17 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title> System Information </title>
+<script src="main.js"></script>
+</head>
+<body>
+<div id="title"><b>Storage Information</b></div>
+<div id="storage-list">
+  <i>Loading ...</i>
+</div>
+<div>
+   <i>Monitor free space change? </i>
+   <input type="button" value="Start" id="start-btn"></input>
+   <input type="button" value="Stop" id="stop-btn"></input>
+</div>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/systemInfo/main.js b/chrome/common/extensions/docs/examples/api/systemInfo/main.js
new file mode 100644
index 0000000..2e3bc7b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/systemInfo/main.js
@@ -0,0 +1,62 @@
+// Copyright (c) 2012 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.
+
+var systemInfo = chrome.experimental.systemInfo;
+
+var indicator = {}
+var isStarted = false;
+
+function onStorageChanged(info) {
+  var elem = document.getElementById(info.id);
+  if (indicator[info.id]++ % 2)
+    elem.bgColor = "green";
+  else
+    elem.bgColor = "white";
+  elem.innerHTML = info.availableCapacity;
+}
+
+function startMonitor() {
+  if (isStarted) return;
+  systemInfo.storage.onAvailableCapacityChanged.addListener(onStorageChanged);
+  isStarted = true;
+}
+
+function stopMonitor() {
+  if (!isStarted) return;
+  systemInfo.storage.onAvailableCapacityChanged.removeListener(
+      onStorageChanged);
+  isStarted = false;
+}
+
+function init() {
+  document.getElementById("start-btn").onclick = startMonitor;
+  document.getElementById("stop-btn").onclick = stopMonitor;
+
+  chrome.experimental.systemInfo.storage.get(function(units) {
+    var table = "<table width=65% border=\"1\">\n" +
+      "<tr><td><b>ID</b></td>" +
+      "<td>Type</td>" +
+      "<td>Capacity (bytes)</td>" +
+      "<td>Available (bytes)</td>" +
+      "</tr>\n";
+    for (var i = 0; i < units.length; i++) {
+      indicator[units[i].id] = 0;
+      table += showStorageInfo(units[i]);
+    }
+    table += "</table>\n";
+    var div = document.getElementById("storage-list");
+    div.innerHTML = table;
+  });
+}
+
+function showStorageInfo(unit) {
+  table = "<tr><td>" + unit.id + "</td>" +
+    "<td>" + unit.type + "</td>" +
+    "<td>" + unit.capacity + "</td>" +
+    "<td id=" + "\"" + unit.id + "\">" + unit.availableCapacity + "</td>" +
+    "</tr>\n";
+  return table;
+}
+
+document.addEventListener('DOMContentLoaded', init);
diff --git a/chrome/common/extensions/docs/examples/api/systemInfo/manifest.json b/chrome/common/extensions/docs/examples/api/systemInfo/manifest.json
new file mode 100644
index 0000000..57e3070
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/systemInfo/manifest.json
@@ -0,0 +1,12 @@
+{
+ "version": "0.1",
+ "name": "SystemInfo APIs",
+ "permissions": ["experimental"],
+ "manifest_version": 2,
+ "description": "Show disk capacity via SystemInfo API",
+ "app": {
+   "launch": {
+     "local_path": "index.html"
+   }
+ }
+}
diff --git a/chrome/common/extensions/docs/examples/api/tabs/inspector/background.js b/chrome/common/extensions/docs/examples/api/tabs/inspector/background.js
new file mode 100644
index 0000000..a58a003
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/inspector/background.js
@@ -0,0 +1,7 @@
+// Copyright (c) 2012 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.
+
+chrome.browserAction.onClicked.addListener(function(tab) {
+  chrome.tabs.create({url:chrome.extension.getURL("tabs_api.html")});
+});
diff --git a/chrome/common/extensions/docs/examples/api/tabs/inspector/jstemplate_compiled.js b/chrome/common/extensions/docs/examples/api/tabs/inspector/jstemplate_compiled.js
new file mode 100644
index 0000000..2f62b31
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/inspector/jstemplate_compiled.js
@@ -0,0 +1,1182 @@
+/**
+ * @fileoverview This file contains miscellaneous basic functionality.
+ *
+ */
+
+/**
+ * Creates a DOM element with the given tag name in the document of the
+ * owner element.
+ *
+ * @param {String} tagName  The name of the tag to create.
+ * @param {Element} owner The intended owner (i.e., parent element) of
+ * the created element.
+ * @param {Point} opt_position  The top-left corner of the created element.
+ * @param {Size} opt_size  The size of the created element.
+ * @param {Boolean} opt_noAppend Do not append the new element to the owner.
+ * @return {Element}  The newly created element node.
+ */
+function createElement(tagName, owner, opt_position, opt_size, opt_noAppend) {
+  var element = ownerDocument(owner).createElement(tagName);
+  if (opt_position) {
+    setPosition(element, opt_position);
+  }
+  if (opt_size) {
+    setSize(element, opt_size);
+  }
+  if (owner && !opt_noAppend) {
+    appendChild(owner, element);
+  }
+
+  return element;
+}
+
+/**
+ * Creates a text node with the given value.
+ *
+ * @param {String} value  The text to place in the new node.
+ * @param {Element} owner The owner (i.e., parent element) of the new
+ * text node.
+ * @return {Text}  The newly created text node.
+ */
+function createTextNode(value, owner) {
+  var element = ownerDocument(owner).createTextNode(value);
+  if (owner) {
+    appendChild(owner, element);
+  }
+  return element;
+}
+
+/**
+ * Returns the document owner of the given element. In particular,
+ * returns window.document if node is null or the browser does not
+ * support ownerDocument.
+ *
+ * @param {Node} node  The node whose ownerDocument is required.
+ * @returns {Document|Null}  The owner document or null if unsupported.
+ */
+function ownerDocument(node) {
+  return (node ? node.ownerDocument : null) || document;
+}
+
+/**
+ * Wrapper function to create CSS units (pixels) string
+ *
+ * @param {Number} numPixels  Number of pixels, may be floating point.
+ * @returns {String}  Corresponding CSS units string.
+ */
+function px(numPixels) {
+  return round(numPixels) + "px";
+}
+
+/**
+ * Sets the left and top of the given element to the given point.
+ *
+ * @param {Element} element  The dom element to manipulate.
+ * @param {Point} point  The desired position.
+ */
+function setPosition(element, point) {
+  var style = element.style;
+  style.position = "absolute";
+  style.left = px(point.x);
+  style.top = px(point.y);
+}
+
+/**
+ * Sets the width and height style attributes to the given size.
+ *
+ * @param {Element} element  The dom element to manipulate.
+ * @param {Size} size  The desired size.
+ */
+function setSize(element, size) {
+  var style = element.style;
+  style.width = px(size.width);
+  style.height = px(size.height);
+}
+
+/**
+ * Sets display to none. Doing this as a function saves a few bytes for
+ * the 'style.display' property and the 'none' literal.
+ *
+ * @param {Element} node  The dom element to manipulate.
+ */
+function displayNone(node) {
+  node.style.display = 'none';
+}
+
+/**
+ * Sets display to default.
+ *
+ * @param {Element} node  The dom element to manipulate.
+ */
+function displayDefault(node) {
+  node.style.display = '';
+}
+
+/**
+ * Appends the given child to the given parent in the DOM
+ *
+ * @param {Element} parent  The parent dom element.
+ * @param {Node} child  The new child dom node.
+ */
+function appendChild(parent, child) {
+  parent.appendChild(child);
+}
+
+
+/**
+ * Wrapper for the eval() builtin function to evaluate expressions and
+ * obtain their value. It wraps the expression in parentheses such
+ * that object literals are really evaluated to objects. Without the
+ * wrapping, they are evaluated as block, and create syntax
+ * errors. Also protects against other syntax errors in the eval()ed
+ * code and returns null if the eval throws an exception.
+ *
+ * @param {String} expr
+ * @return {Object|Null}
+ */
+function jsEval(expr) {
+  try {
+    return eval('[' + expr + '][0]');
+  } catch (e) {
+    return null;
+  }
+}
+
+
+/**
+ * Wrapper for the eval() builtin function to execute statements. This
+ * guards against exceptions thrown, but doesn't return a
+ * value. Still, mostly for testability, it returns a boolean to
+ * indicate whether execution was successful. NOTE:
+ * javascript's eval semantics is murky in that it confounds
+ * expression evaluation and statement execution into a single
+ * construct. Cf. jsEval().
+ *
+ * @param {String} stmt
+ * @return {Boolean}
+ */
+function jsExec(stmt) {
+  try {
+    eval(stmt);
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+
+/**
+ * Wrapper for eval with a context. NOTE: The style guide
+ * deprecates eval, so this is the exception that proves the
+ * rule. Notice also that since the value of the expression is
+ * returned rather than assigned to a local variable, one major
+ * objection aganist the use of the with() statement, namely that
+ * properties of the with() target override local variables of the
+ * same name, is void here.
+ *
+ * @param {String} expr
+ * @param {Object} context
+ * @return {Object|Null}
+ */
+function jsEvalWith(expr, context) {
+  try {
+    with (context) {
+      return eval('[' + expr + '][0]');
+    }
+  } catch (e) {
+    return null;
+  }
+}
+
+
+var DOM_ELEMENT_NODE = 1;
+var DOM_ATTRIBUTE_NODE = 2;
+var DOM_TEXT_NODE = 3;
+var DOM_CDATA_SECTION_NODE = 4;
+var DOM_ENTITY_REFERENCE_NODE = 5;
+var DOM_ENTITY_NODE = 6;
+var DOM_PROCESSING_INSTRUCTION_NODE = 7;
+var DOM_COMMENT_NODE = 8;
+var DOM_DOCUMENT_NODE = 9;
+var DOM_DOCUMENT_TYPE_NODE = 10;
+var DOM_DOCUMENT_FRAGMENT_NODE = 11;
+var DOM_NOTATION_NODE = 12;
+
+/**
+ * Traverses the element nodes in the DOM tree underneath the given
+ * node and finds the first node with elemId, or null if there is no such
+ * element.  Traversal is in depth-first order.
+ *
+ * NOTE: The reason this is not combined with the elem() function is
+ * that the implementations are different.
+ * elem() is a wrapper for the built-in document.getElementById() function,
+ * whereas this function performs the traversal itself.
+ * Modifying elem() to take an optional root node is a possibility,
+ * but the in-built function would perform better than using our own traversal.
+ *
+ * @param {Element} node Root element of subtree to traverse.
+ * @param {String} elemId The id of the element to search for.
+ * @return {Element|Null} The corresponding element, or null if not found.
+ */
+function nodeGetElementById(node, elemId) {
+  for (var c = node.firstChild; c; c = c.nextSibling) {
+    if (c.id == elemId) {
+      return c;
+    }
+    if (c.nodeType == DOM_ELEMENT_NODE) {
+      var n = arguments.callee.call(this, c, elemId);
+      if (n) {
+        return n;
+      }
+    }
+  }
+  return null;
+}
+
+
+/**
+ * Get an attribute from the DOM.  Simple redirect, exists to compress code.
+ *
+ * @param {Element} node  Element to interrogate.
+ * @param {String} name  Name of parameter to extract.
+ * @return {String}  Resulting attribute.
+ */
+function domGetAttribute(node, name) {
+  return node.getAttribute(name);
+}
+
+/**
+ * Set an attribute in the DOM.  Simple redirect to compress code.
+ *
+ * @param {Element} node  Element to interrogate.
+ * @param {String} name  Name of parameter to set.
+ * @param {String} value  Set attribute to this value.
+ */
+function domSetAttribute(node, name, value) {
+  node.setAttribute(name, value);
+}
+
+/**
+ * Remove an attribute from the DOM.  Simple redirect to compress code.
+ *
+ * @param {Element} node  Element to interrogate.
+ * @param {String} name  Name of parameter to remove.
+ */
+function domRemoveAttribute(node, name) {
+  node.removeAttribute(name);
+}
+
+/**
+ * Clone a node in the DOM.
+ *
+ * @param {Node} node  Node to clone.
+ * @return {Node}  Cloned node.
+ */
+function domCloneNode(node) {
+  return node.cloneNode(true);
+}
+
+
+/**
+ * Return a safe string for the className of a node.
+ * If className is not a string, returns "".
+ *
+ * @param {Element} node  DOM element to query.
+ * @return {String}
+ */
+function domClassName(node) {
+  return node.className ? "" + node.className : "";
+}
+
+/**
+ * Adds a class name to the class attribute of the given node.
+ *
+ * @param {Element} node  DOM element to modify.
+ * @param {String} className  Class name to add.
+ */
+function domAddClass(node, className) {
+  var name = domClassName(node);
+  if (name) {
+    var cn = name.split(/\s+/);
+    var found = false;
+    for (var i = 0; i < jsLength(cn); ++i) {
+      if (cn[i] == className) {
+        found = true;
+        break;
+      }
+    }
+
+    if (!found) {
+      cn.push(className);
+    }
+
+    node.className = cn.join(' ');
+  } else {
+    node.className = className;
+  }
+}
+
+/**
+ * Removes a class name from the class attribute of the given node.
+ *
+ * @param {Element} node  DOM element to modify.
+ * @param {String} className  Class name to remove.
+ */
+function domRemoveClass(node, className) {
+  var c = domClassName(node);
+  if (!c || c.indexOf(className) == -1) {
+    return;
+  }
+  var cn = c.split(/\s+/);
+  for (var i = 0; i < jsLength(cn); ++i) {
+    if (cn[i] == className) {
+      cn.splice(i--, 1);
+    }
+  }
+  node.className = cn.join(' ');
+}
+
+/**
+ * Checks if a node belongs to a style class.
+ *
+ * @param {Element} node  DOM element to test.
+ * @param {String} className  Class name to check for.
+ * @return {Boolean}  Node belongs to style class.
+ */
+function domTestClass(node, className) {
+  var cn = domClassName(node).split(/\s+/);
+  for (var i = 0; i < jsLength(cn); ++i) {
+    if (cn[i] == className) {
+      return true;
+    }
+  }
+  return false;
+}
+
+/**
+ * Inserts a new child before a given sibling.
+ *
+ * @param {Node} newChild  Node to insert.
+ * @param {Node} oldChild  Sibling node.
+ * @return {Node}  Reference to new child.
+ */
+function domInsertBefore(newChild, oldChild) {
+  return oldChild.parentNode.insertBefore(newChild, oldChild);
+}
+
+/**
+ * Appends a new child to the specified (parent) node.
+ *
+ * @param {Element} node  Parent element.
+ * @param {Node} child  Child node to append.
+ * @return {Node}  Newly appended node.
+ */
+function domAppendChild(node, child) {
+  return node.appendChild(child);
+}
+
+/**
+ * Remove a new child from the specified (parent) node.
+ *
+ * @param {Element} node  Parent element.
+ * @param {Node} child  Child node to remove.
+ * @return {Node}  Removed node.
+ */
+function domRemoveChild(node, child) {
+  return node.removeChild(child);
+}
+
+/**
+ * Replaces an old child node with a new child node.
+ *
+ * @param {Node} newChild  New child to append.
+ * @param {Node} oldChild  Old child to remove.
+ * @return {Node}  Replaced node.
+ */
+function domReplaceChild(newChild, oldChild) {
+  return oldChild.parentNode.replaceChild(newChild, oldChild);
+}
+
+/**
+ * Removes a node from the DOM.
+ *
+ * @param {Node} node  The node to remove.
+ * @return {Node}  The removed node.
+ */
+function domRemoveNode(node) {
+  return domRemoveChild(node.parentNode, node);
+}
+
+/**
+ * Creates a new text node in the given document.
+ *
+ * @param {Document} doc  Target document.
+ * @param {String} text  Text composing new text node.
+ * @return {Text}  Newly constructed text node.
+ */
+function domCreateTextNode(doc, text) {
+  return doc.createTextNode(text);
+}
+
+/**
+ * Creates a new node in the given document
+ *
+ * @param {Document} doc  Target document.
+ * @param {String} name  Name of new element (i.e. the tag name)..
+ * @return {Element}  Newly constructed element.
+ */
+function domCreateElement(doc, name) {
+  return doc.createElement(name);
+}
+
+/**
+ * Creates a new attribute in the given document.
+ *
+ * @param {Document} doc  Target document.
+ * @param {String} name  Name of new attribute.
+ * @return {Attr}  Newly constructed attribute.
+ */
+function domCreateAttribute(doc, name) {
+  return doc.createAttribute(name);
+}
+
+/**
+ * Creates a new comment in the given document.
+ *
+ * @param {Document} doc  Target document.
+ * @param {String} text  Comment text.
+ * @return {Comment}  Newly constructed comment.
+ */
+function domCreateComment(doc, text) {
+  return doc.createComment(text);
+}
+
+/**
+ * Creates a document fragment.
+ *
+ * @param {Document} doc  Target document.
+ * @return {DocumentFragment}  Resulting document fragment node.
+ */
+function domCreateDocumentFragment(doc) {
+  return doc.createDocumentFragment();
+}
+
+/**
+ * Redirect to document.getElementById
+ *
+ * @param {Document} doc  Target document.
+ * @param {String} id  Id of requested node.
+ * @return {Element|Null}  Resulting element.
+ */
+function domGetElementById(doc, id) {
+  return doc.getElementById(id);
+}
+
+/**
+ * Redirect to window.setInterval
+ *
+ * @param {Window} win  Target window.
+ * @param {Function} fun  Callback function.
+ * @param {Number} time  Time in milliseconds.
+ * @return {Object}  Contract id.
+ */
+function windowSetInterval(win, fun, time) {
+  return win.setInterval(fun, time);
+}
+
+/**
+ * Redirect to window.clearInterval
+ *
+ * @param {Window} win  Target window.
+ * @param {object} id  Contract id.
+ * @return {any}  NOTE: Return type unknown?
+ */
+function windowClearInterval(win, id) {
+  return win.clearInterval(id);
+}
+
+/**
+ * Determines whether one node is recursively contained in another.
+ * @param parent The parent node.
+ * @param child The node to look for in parent.
+ * @return parent recursively contains child
+ */
+function containsNode(parent, child) {
+  while (parent != child && child.parentNode) {
+    child = child.parentNode;
+  }
+  return parent == child;
+};
+/**
+ * @fileoverview This file contains javascript utility functions that
+ * do not depend on anything defined elsewhere.
+ *
+ */
+
+/**
+ * Returns the value of the length property of the given object. Used
+ * to reduce compiled code size.
+ *
+ * @param {Array | String} a  The string or array to interrogate.
+ * @return {Number}  The value of the length property.
+ */
+function jsLength(a) {
+  return a.length;
+}
+
+var min = Math.min;
+var max = Math.max;
+var ceil = Math.ceil;
+var floor = Math.floor;
+var round = Math.round;
+var abs = Math.abs;
+
+/**
+ * Copies all properties from second object to the first.  Modifies to.
+ *
+ * @param {Object} to  The target object.
+ * @param {Object} from  The source object.
+ */
+function copyProperties(to, from) {
+  foreachin(from, function(p) {
+    to[p] = from[p];
+  });
+}
+
+/**
+ * Iterates over the array, calling the given function for each
+ * element.
+ *
+ * @param {Array} array
+ * @param {Function} fn
+ */
+function foreach(array, fn) {
+  var I = jsLength(array);
+  for (var i = 0; i < I; ++i) {
+    fn(array[i], i);
+  }
+}
+
+/**
+ * Safely iterates over all properties of the given object, calling
+ * the given function for each property. If opt_all isn't true, uses
+ * hasOwnProperty() to assure the property is on the object, not on
+ * its prototype.
+ *
+ * @param {Object} object
+ * @param {Function} fn
+ * @param {Boolean} opt_all  If true, also iterates over inherited properties.
+ */
+function foreachin(object, fn, opt_all) {
+  for (var i in object) {
+    if (opt_all || !object.hasOwnProperty || object.hasOwnProperty(i)) {
+      fn(i, object[i]);
+    }
+  }
+}
+
+/**
+ * Appends the second array to the first, copying its elements.
+ * Optionally only a slice of the second array is copied.
+ *
+ * @param {Array} a1  Target array (modified).
+ * @param {Array} a2  Source array.
+ * @param {Number} opt_begin  Begin of slice of second array (optional).
+ * @param {Number} opt_end  End (exclusive) of slice of second array (optional).
+ */
+function arrayAppend(a1, a2, opt_begin, opt_end) {
+  var i0 = opt_begin || 0;
+  var i1 = opt_end || jsLength(a2);
+  for (var i = i0; i < i1; ++i) {
+    a1.push(a2[i]);
+  }
+}
+
+/**
+ * Trim whitespace from begin and end of string.
+ *
+ * @see testStringTrim();
+ *
+ * @param {String} str  Input string.
+ * @return {String}  Trimmed string.
+ */
+function stringTrim(str) {
+  return stringTrimRight(stringTrimLeft(str));
+}
+
+/**
+ * Trim whitespace from beginning of string.
+ *
+ * @see testStringTrimLeft();
+ *
+ * @param {String} str  Input string.
+ * @return {String}  Trimmed string.
+ */
+function stringTrimLeft(str) {
+  return str.replace(/^\s+/, "");
+}
+
+/**
+ * Trim whitespace from end of string.
+ *
+ * @see testStringTrimRight();
+ *
+ * @param {String} str  Input string.
+ * @return {String}  Trimmed string.
+ */
+function stringTrimRight(str) {
+  return str.replace(/\s+$/, "");
+}
+
+/**
+ * Jscompiler wrapper for parseInt() with base 10.
+ *
+ * @param {String} s String repersentation of a number.
+ *
+ * @return {Number} The integer contained in s, converted on base 10.
+ */
+function parseInt10(s) {
+  return parseInt(s, 10);
+}
+/**
+ * @fileoverview A simple formatter to project JavaScript data into
+ * HTML templates. The template is edited in place. I.e. in order to
+ * instantiate a template, clone it from the DOM first, and then
+ * process the cloned template. This allows for updating of templates:
+ * If the templates is processed again, changed values are merely
+ * updated.
+ *
+ * NOTE: IE DOM doesn't have importNode().
+ *
+ * NOTE: The property name "length" must not be used in input
+ * data, see comment in jstSelect_().
+ */
+
+
+/**
+ * Names of jstemplate attributes. These attributes are attached to
+ * normal HTML elements and bind expression context data to the HTML
+ * fragment that is used as template.
+ */
+var ATT_select = 'jsselect';
+var ATT_instance = 'jsinstance';
+var ATT_display = 'jsdisplay';
+var ATT_values = 'jsvalues';
+var ATT_eval = 'jseval';
+var ATT_transclude = 'transclude';
+var ATT_content = 'jscontent';
+
+
+/**
+ * Names of special variables defined by the jstemplate evaluation
+ * context. These can be used in js expression in jstemplate
+ * attributes.
+ */
+var VAR_index = '$index';
+var VAR_this = '$this';
+
+
+/**
+ * Context for processing a jstemplate. The context contains a context
+ * object, whose properties can be referred to in jstemplate
+ * expressions, and it holds the locally defined variables.
+ *
+ * @param {Object} opt_data The context object. Null if no context.
+ *
+ * @param {Object} opt_parent The parent context, from which local
+ * variables are inherited. Normally the context object of the parent
+ * context is the object whose property the parent object is. Null for the
+ * context of the root object.
+ *
+ * @constructor
+ */
+function JsExprContext(opt_data, opt_parent) {
+  var me = this;
+
+  /**
+   * The local context of the input data in which the jstemplate
+   * expressions are evaluated. Notice that this is usually an Object,
+   * but it can also be a scalar value (and then still the expression
+   * $this can be used to refer to it). Notice this can be a scalar
+   * value, including undefined.
+   *
+   * @type {Object}
+   */
+  me.data_ = opt_data;
+
+  /**
+   * The context for variable definitions in which the jstemplate
+   * expressions are evaluated. Other than for the local context,
+   * which replaces the parent context, variable definitions of the
+   * parent are inherited. The special variable $this points to data_.
+   *
+   * @type {Object}
+   */
+  me.vars_ = {};
+  if (opt_parent) {
+    copyProperties(me.vars_, opt_parent.vars_);
+  }
+  this.vars_[VAR_this] = me.data_;
+}
+
+
+/**
+ * Evaluates the given expression in the context of the current
+ * context object and the current local variables.
+ *
+ * @param {String} expr A javascript expression.
+ *
+ * @param {Element} template DOM node of the template.
+ *
+ * @return The value of that expression.
+ */
+JsExprContext.prototype.jseval = function(expr, template) {
+  with (this.vars_) {
+    with (this.data_) {
+      try {
+        return (function() {
+          return eval('[' + expr + '][0]');
+        }).call(template);
+      } catch (e) {
+        return null;
+      }
+    }
+  }
+}
+
+
+/**
+ * Clones the current context for a new context object. The cloned
+ * context has the data object as its context object and the current
+ * context as its parent context. It also sets the $index variable to
+ * the given value. This value usually is the position of the data
+ * object in a list for which a template is instantiated multiply.
+ *
+ * @param {Object} data The new context object.
+ *
+ * @param {Number} index Position of the new context when multiply
+ * instantiated. (See implementation of jstSelect().)
+ *
+ * @return {JsExprContext}
+ */
+JsExprContext.prototype.clone = function(data, index) {
+  var ret = new JsExprContext(data, this);
+  ret.setVariable(VAR_index, index);
+  if (this.resolver_) {
+    ret.setSubTemplateResolver(this.resolver_);
+  }
+  return ret;
+}
+
+
+/**
+ * Binds a local variable to the given value. If set from jstemplate
+ * jsvalue expressions, variable names must start with $, but in the
+ * API they only have to be valid javascript identifier.
+ *
+ * @param {String} name
+ *
+ * @param {Object} value
+ */
+JsExprContext.prototype.setVariable = function(name, value) {
+  this.vars_[name] = value;
+}
+
+
+/**
+ * Sets the function used to resolve the values of the transclude
+ * attribute into DOM nodes. By default, this is jstGetTemplate(). The
+ * value set here is inherited by clones of this context.
+ *
+ * @param {Function} resolver The function used to resolve transclude
+ * ids into a DOM node of a subtemplate. The DOM node returned by this
+ * function will be inserted into the template instance being
+ * processed. Thus, the resolver function must instantiate the
+ * subtemplate as necessary.
+ */
+JsExprContext.prototype.setSubTemplateResolver = function(resolver) {
+  this.resolver_ = resolver;
+}
+
+
+/**
+ * Resolves a sub template from an id. Used to process the transclude
+ * attribute. If a resolver function was set using
+ * setSubTemplateResolver(), it will be used, otherwise
+ * jstGetTemplate().
+ *
+ * @param {String} id The id of the sub template.
+ *
+ * @return {Node} The root DOM node of the sub template, for direct
+ * insertion into the currently processed template instance.
+ */
+JsExprContext.prototype.getSubTemplate = function(id) {
+  return (this.resolver_ || jstGetTemplate).call(this, id);
+}
+
+
+/**
+ * HTML template processor. Data values are bound to HTML templates
+ * using the attributes transclude, jsselect, jsdisplay, jscontent,
+ * jsvalues. The template is modifed in place. The values of those
+ * attributes are JavaScript expressions that are evaluated in the
+ * context of the data object fragment.
+ *
+ * @param {JsExprContext} context Context created from the input data
+ * object.
+ *
+ * @param {Element} template DOM node of the template. This will be
+ * processed in place. After processing, it will still be a valid
+ * template that, if processed again with the same data, will remain
+ * unchanged.
+ */
+function jstProcess(context, template) {
+  var processor = new JstProcessor();
+  processor.run_([ processor, processor.jstProcess_, context, template ]);
+}
+
+
+/**
+ * Internal class used by jstemplates to maintain context.
+ * NOTE: This is necessary to process deep templates in Safari
+ * which has a relatively shallow stack.
+ * @class
+ */
+function JstProcessor() {
+}
+
+
+/**
+ * Runs the state machine, beginning with function "start".
+ *
+ * @param {Array} start The first function to run, in the form
+ * [object, method, args ...]
+ */
+JstProcessor.prototype.run_ = function(start) {
+  var me = this;
+
+  me.queue_ = [ start ];
+  while (jsLength(me.queue_)) {
+    var f = me.queue_.shift();
+    f[1].apply(f[0], f.slice(2));
+  }
+}
+
+
+/**
+ * Appends a function to be called later.
+ * Analogous to calling that function on a subsequent line, or a subsequent
+ * iteration of a loop.
+ *
+ * @param {Array} f  A function in the form [object, method, args ...]
+ */
+JstProcessor.prototype.enqueue_ = function(f) {
+  this.queue_.push(f);
+}
+
+
+/**
+ * Implements internals of jstProcess.
+ *
+ * @param {JsExprContext} context
+ *
+ * @param {Element} template
+ */
+JstProcessor.prototype.jstProcess_ = function(context, template) {
+  var me = this;
+
+  var transclude = domGetAttribute(template, ATT_transclude);
+  if (transclude) {
+    var tr = context.getSubTemplate(transclude);
+    if (tr) {
+      domReplaceChild(tr, template);
+      me.enqueue_([ me, me.jstProcess_, context, tr ]);
+    } else {
+      domRemoveNode(template);
+    }
+    return;
+  }
+
+  var select = domGetAttribute(template, ATT_select);
+  if (select) {
+    me.jstSelect_(context, template, select);
+    return;
+  }
+
+  var display = domGetAttribute(template, ATT_display);
+  if (display) {
+    if (!context.jseval(display, template)) {
+      displayNone(template);
+      return;
+    }
+
+    displayDefault(template);
+  }
+
+
+  var values = domGetAttribute(template, ATT_values);
+  if (values) {
+    me.jstValues_(context, template, values);
+  }
+
+  var expressions = domGetAttribute(template, ATT_eval);
+  if (expressions) {
+    foreach(expressions.split(/\s*;\s*/), function(expression) {
+      expression = stringTrim(expression);
+      if (jsLength(expression)) {
+        context.jseval(expression, template);
+      }
+    });
+  }
+
+  var content = domGetAttribute(template, ATT_content);
+  if (content) {
+    me.jstContent_(context, template, content);
+
+  } else {
+    var childnodes = [];
+    for (var i = 0; i < jsLength(template.childNodes); ++i) {
+      if (template.childNodes[i].nodeType == DOM_ELEMENT_NODE) {
+      me.enqueue_(
+          [ me, me.jstProcess_, context, template.childNodes[i] ]);
+      }
+    }
+  }
+}
+
+
+/**
+ * Implements the jsselect attribute: evalutes the value of the
+ * jsselect attribute in the current context, with the current
+ * variable bindings (see JsExprContext.jseval()). If the value is an
+ * array, the current template node is multiplied once for every
+ * element in the array, with the array element being the context
+ * object. If the array is empty, or the value is undefined, then the
+ * current template node is dropped. If the value is not an array,
+ * then it is just made the context object.
+ *
+ * @param {JsExprContext} context The current evaluation context.
+ *
+ * @param {Element} template The currently processed node of the template.
+ *
+ * @param {String} select The javascript expression to evaluate.
+ *
+ * @param {Function} process The function to continue processing with.
+ */
+JstProcessor.prototype.jstSelect_ = function(context, template, select) {
+  var me = this;
+
+  var value = context.jseval(select, template);
+  domRemoveAttribute(template, ATT_select);
+
+  var instance = domGetAttribute(template, ATT_instance);
+  var instance_last = false;
+  if (instance) {
+    if (instance.charAt(0) == '*') {
+      instance = parseInt10(instance.substr(1));
+      instance_last = true;
+    } else {
+      instance = parseInt10(instance);
+    }
+  }
+
+  var multiple = (value !== null &&
+                  typeof value == 'object' &&
+                  typeof value.length == 'number');
+  var multiple_empty = (multiple && value.length == 0);
+
+  if (multiple) {
+    if (multiple_empty) {
+      if (!instance) {
+        domSetAttribute(template, ATT_select, select);
+        domSetAttribute(template, ATT_instance, '*0');
+        displayNone(template);
+      } else {
+        domRemoveNode(template);
+      }
+
+    } else {
+      displayDefault(template);
+      if (instance === null || instance === "" || instance === undefined ||
+          (instance_last && instance < jsLength(value) - 1)) {
+        var templatenodes = [];
+        var instances_start = instance || 0;
+        for (var i = instances_start + 1; i < jsLength(value); ++i) {
+          var node = domCloneNode(template);
+          templatenodes.push(node);
+          domInsertBefore(node, template);
+        }
+        templatenodes.push(template);
+
+        for (var i = 0; i < jsLength(templatenodes); ++i) {
+          var ii = i + instances_start;
+          var v = value[ii];
+          var t = templatenodes[i];
+
+          me.enqueue_([ me, me.jstProcess_, context.clone(v, ii), t ]);
+          var instanceStr = (ii == jsLength(value) - 1 ? '*' : '') + ii;
+          me.enqueue_(
+              [ null, postProcessMultiple_, t, select, instanceStr ]);
+        }
+
+      } else if (instance < jsLength(value)) {
+        var v = value[instance];
+
+        me.enqueue_(
+            [me, me.jstProcess_, context.clone(v, instance), template]);
+        var instanceStr = (instance == jsLength(value) - 1 ? '*' : '')
+                          + instance;
+        me.enqueue_(
+            [ null, postProcessMultiple_, template, select, instanceStr ]);
+      } else {
+        domRemoveNode(template);
+      }
+    }
+  } else {
+    if (value == null) {
+      domSetAttribute(template, ATT_select, select);
+      displayNone(template);
+    } else {
+      me.enqueue_(
+          [ me, me.jstProcess_, context.clone(value, 0), template ]);
+      me.enqueue_(
+          [ null, postProcessSingle_, template, select ]);
+    }
+  }
+}
+
+
+/**
+ * Sets ATT_select and ATT_instance following recursion to jstProcess.
+ *
+ * @param {Element} template  The template
+ *
+ * @param {String} select  The jsselect string
+ *
+ * @param {String} instanceStr  The new value for the jsinstance attribute
+ */
+function postProcessMultiple_(template, select, instanceStr) {
+  domSetAttribute(template, ATT_select, select);
+  domSetAttribute(template, ATT_instance, instanceStr);
+}
+
+
+/**
+ * Sets ATT_select and makes the element visible following recursion to
+ * jstProcess.
+ *
+ * @param {Element} template  The template
+ *
+ * @param {String} select  The jsselect string
+ */
+function postProcessSingle_(template, select) {
+  domSetAttribute(template, ATT_select, select);
+  displayDefault(template);
+}
+
+
+/**
+ * Implements the jsvalues attribute: evaluates each of the values and
+ * assigns them to variables in the current context (if the name
+ * starts with '$', javascript properties of the current template node
+ * (if the name starts with '.'), or DOM attributes of the current
+ * template node (otherwise). Since DOM attribute values are always
+ * strings, the value is coerced to string in the latter case,
+ * otherwise it's the uncoerced javascript value.
+ *
+ * @param {JsExprContext} context Current evaluation context.
+ *
+ * @param {Element} template Currently processed template node.
+ *
+ * @param {String} valuesStr Value of the jsvalues attribute to be
+ * processed.
+ */
+JstProcessor.prototype.jstValues_ = function(context, template, valuesStr) {
+  var values = valuesStr.split(/\s*;\s*/);
+  for (var i = 0; i < jsLength(values); ++i) {
+    var colon = values[i].indexOf(':');
+    if (colon < 0) {
+      continue;
+    }
+    var label = stringTrim(values[i].substr(0, colon));
+    var value = context.jseval(values[i].substr(colon + 1), template);
+
+    if (label.charAt(0) == '$') {
+      context.setVariable(label, value);
+
+    } else if (label.charAt(0) == '.') {
+      var nameSpaceLabel = label.substr(1).split('.');
+      var nameSpaceObject = template;
+      var nameSpaceDepth = jsLength(nameSpaceLabel);
+      for (var j = 0, J = nameSpaceDepth - 1; j < J; ++j) {
+        var jLabel = nameSpaceLabel[j];
+        if (!nameSpaceObject[jLabel]) {
+          nameSpaceObject[jLabel] = {};
+        }
+        nameSpaceObject = nameSpaceObject[jLabel];
+      }
+      nameSpaceObject[nameSpaceLabel[nameSpaceDepth - 1]] = value;
+    } else if (label) {
+      if (typeof value == 'boolean') {
+        if (value) {
+          domSetAttribute(template, label, label);
+        } else {
+          domRemoveAttribute(template, label);
+        }
+      } else {
+        domSetAttribute(template, label, '' + value);
+      }
+    }
+  }
+}
+
+
+/**
+ * Implements the jscontent attribute. Evalutes the expression in
+ * jscontent in the current context and with the current variables,
+ * and assigns its string value to the content of the current template
+ * node.
+ *
+ * @param {JsExprContext} context Current evaluation context.
+ *
+ * @param {Element} template Currently processed template node.
+ *
+ * @param {String} content Value of the jscontent attribute to be
+ * processed.
+ */
+JstProcessor.prototype.jstContent_ = function(context, template, content) {
+  var value = '' + context.jseval(content, template);
+  if (template.innerHTML == value) {
+    return;
+  }
+  while (template.firstChild) {
+    domRemoveNode(template.firstChild);
+  }
+  var t = domCreateTextNode(ownerDocument(template), value);
+  domAppendChild(template, t);
+}
+
+
+/**
+ * Helps to implement the transclude attribute, and is the initial
+ * call to get hold of a template from its ID.
+ *
+ * @param {String} name The ID of the HTML element used as template.
+ *
+ * @returns {Element} The DOM node of the template. (Only element
+ * nodes can be found by ID, hence it's a Element.)
+ */
+function jstGetTemplate(name) {
+  var section = domGetElementById(document, name);
+  if (section) {
+    var ret = domCloneNode(section);
+    domRemoveAttribute(ret, 'id');
+    return ret;
+  } else {
+    return null;
+  }
+}
+
+window['jstGetTemplate'] = jstGetTemplate;
+window['jstProcess'] = jstProcess;
+window['JsExprContext'] = JsExprContext;
diff --git a/chrome/common/extensions/docs/examples/api/tabs/inspector/manifest.json b/chrome/common/extensions/docs/examples/api/tabs/inspector/manifest.json
new file mode 100644
index 0000000..68b7728
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/inspector/manifest.json
@@ -0,0 +1,14 @@
+{
+  "name": "Tab Inspector",
+  "description": "Utility for working with the extension tabs api",
+  "version": "0.3",
+  "permissions": ["tabs"],
+  "background": {
+    "persistent": false,
+    "scripts": ["background.js"]
+  },
+  "browser_action": {
+    "default_title": "show tab inspector"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/tabs/inspector/tabs_api.html b/chrome/common/extensions/docs/examples/api/tabs/inspector/tabs_api.html
new file mode 100644
index 0000000..d58f8d8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/inspector/tabs_api.html
@@ -0,0 +1,105 @@
+<html>
+<head>
+<script src="jstemplate_compiled.js" type="text/javascript"></script>
+<script src="tabs_api.js"></script>
+</head>
+  <body>
+    <div id="windowList">
+      <div style="background-color: #AAEEEE; margin: 4px; padding: 8px; margin: 20px" jsselect="$this"
+          jsvalues="id:'window_' + id">
+        <div style="font-style: italic; width: 80px; display: inline-block">
+          Window: <span jscontent="id"></span>
+        </div>
+        <div style="display: inline-block">
+          left: <input style="width: 60px" type="text" jsvalues="value:$this.left;id:'left_' + id" />
+          top: <input style="width: 60px" type="text" jsvalues="value:$this.top;id:'top_' + id" />
+          width: <input style="width: 60px" type="text" jsvalues="value:$this.width;id:'width_' + id" />
+          height: <input style="width: 60px" type="text" jsvalues="value:$this.height;id:'height_' + id" />
+          <input type="checkbox" jsvalues="checked:focused; id:'focused_' + id" /> Focused
+          <input type="checkbox" jsvalues="checked:current; id:'current_' + id" /> Current
+          <button onclick="refreshWindow(this.jstdata);" jsvalues=".jstdata:id">Refresh</button>
+        </div>
+        <div id="tabList">
+          <div jsselect="tabs">
+            <div style="background-color: #EEEEEE; margin: 8px; padding: 4px"  jsvalues="id:'tab_' + id">
+              <div style="margin: 8px">
+                <div style="font-style: italic; width: 80px; display: inline-block" jscontent="'TabId: ' + id"></div>
+                <div style="width: 300px; display: inline-block">
+                  index: <input style="width: 20px" type="text" jsvalues="value:$this.index;id:'index_' + id" />
+                  windowId: <input style="width: 20px" type="text" jsvalues="value:windowId;id:'windowId_' + id" />
+                  <button onclick="moveTab(this.jstdata);" jsvalues=".jstdata:id">Move</button>
+                  <button onclick="refreshTab(this.jstdata);" jsvalues=".jstdata:id">Refresh</button>
+                </div>
+              </div>
+              <div style="margin: 8px">
+                <div>
+                  <div style="width: 40px; display:inline-block">title:</div>
+                  <input style="width: 90%" type="text" jsvalues="value:title;id:'title_' + id" />
+                </div>
+                <div>
+                  <div style="width: 40px; display:inline-block">url:</div>
+                  <input style="width: 90%" type="text" jsvalues="value:url;id:'url_' + id" />
+                </div>
+                <div><input type="checkbox" jsvalues="checked:selected; id:'selected_' + id" /> Selected</div>
+              </div>
+              <button onclick="updateTab(this.jstdata)" jsvalues=".jstdata:id">Update Tab</button>
+              <button onclick="removeTab(this.jstdata);" jsvalues=".jstdata:id">Close Tab</button>
+            </div>
+          </div>
+        </div>
+        <button onclick="updateWindow(this.jstdata);" jsvalues=".jstdata:id">Update Window</button>
+        <button onclick="removeWindow(this.jstdata);" jsvalues=".jstdata:id">Close Window</button>
+        <button onclick="refreshSelectedTab(this.jstdata);" jsvalues=".jstdata:id">Refresh Selected Tab</button>
+      </div>
+    </div>
+    <div style="background-color: #EEEEBB; margin: 20px; padding: 8px">
+      <h3 style="text-align: center; margin: 8px"> Create Window</h3>
+      <div style="margin: 8px">
+        <div style="width: 300px; display: inline-block">
+          left: <input style="width: 20px" type="text" id="new_window_left" />
+          top: <input style="width: 20px" type="text" id="new_window_top" />
+          width: <input style="width: 20px" type="text" id="new_window_width" />
+          height: <input style="width: 20px" type="text" id="new_window_height" />
+        </div>
+      </div>
+      <div style="margin: 8px">
+        <div>
+          <div style="width: 40px; display:inline-block">url:</div>
+          <input style="width: 90%" type="text" id="new_window_url" />
+        </div>
+      </div>
+      <button onclick="createWindow();">Create</button>
+    </div>
+    <div style="background-color: #EEEEAA; margin: 20px; padding: 8px">
+      <h3 style="text-align: center; margin: 8px"> Create Tab</h3>
+      <div style="margin: 8px">
+        <div style="width: 300px; display: inline-block">
+          index: <input style="width: 20px" type="text" id="index_new" />
+          windowId: <input style="width: 20px" type="text" id="windowId_new" />
+          <button onclick="moveTab(this.jstdata);" jsvalues=".jstdata:id">Move</button>
+        </div>
+      </div>
+      <div style="margin: 8px">
+        <div>
+          <div style="width: 40px; display:inline-block">title:</div>
+          <input style="width: 90%" type="text" id="title_new" />
+        </div>
+        <div>
+          <div style="width: 40px; display:inline-block">url:</div>
+          <input style="width: 90%" type="text" id="url_new" />
+        </div>
+        <div><input type="checkbox" id="selected_new" /> Selected</div>
+      </div>
+      <button onclick="createTab();">Create</button>
+    </div>
+    <div style="margin: 20px;">
+      <button onclick="loadWindowList();">Refresh</button>
+      <button onclick="updateAll();">Update All</button>
+      <button onclick="moveAll();">Move All</button>
+      <button onclick="clearLog();">-->Clear Log</button>
+      <button onclick="chrome.windows.create();">New Window</button>
+    </div>
+    <div id="log" style="background-color: #EEAAEE; margin: 20px; padding: 8px">
+    </div>
+  </body>
+</html>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/tabs/inspector/tabs_api.js b/chrome/common/extensions/docs/examples/api/tabs/inspector/tabs_api.js
new file mode 100644
index 0000000..1cd6e7a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/inspector/tabs_api.js
@@ -0,0 +1,304 @@
+// Copyright (c) 2012 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.
+
+tabs = {};
+tabIds = [];
+
+focusedWindowId = undefined;
+currentWindowId = undefined;
+
+function bootStrap() {
+  chrome.windows.getCurrent(function(currentWindow) {
+    currentWindowId = currentWindow.id;
+    chrome.windows.getLastFocused(function(focusedWindow) {
+      focusedWindowId = focusedWindow.id;
+      loadWindowList();
+    });
+  });
+}
+
+function isInt(i) {
+  return (typeof i == "number") && !(i % 1) && !isNaN(i);
+}
+
+function loadWindowList() {
+  chrome.windows.getAll({ populate: true }, function(windowList) {
+    tabs = {};
+    tabIds = [];
+    for (var i = 0; i < windowList.length; i++) {
+      windowList[i].current = (windowList[i].id == currentWindowId);
+      windowList[i].focused = (windowList[i].id == focusedWindowId);
+
+      for (var j = 0; j < windowList[i].tabs.length; j++) {
+        tabIds[tabIds.length] = windowList[i].tabs[j].id;
+        tabs[windowList[i].tabs[j].id] = windowList[i].tabs[j];
+      }
+    }
+
+    var input = new JsExprContext(windowList);
+    var output = document.getElementById('windowList');
+    jstProcess(input, output);
+  });
+}
+
+function updateTabData(id) {
+  var retval = {
+    url: document.getElementById('url_' + id).value,
+    selected: document.getElementById('selected_' + id).value ? true : false
+  }
+
+  return retval;
+}
+
+function updateTab(id){
+  try {
+    chrome.tabs.update(id, updateTabData(id));
+  } catch (e) {
+    alert(e);
+  }
+}
+
+function moveTabData(id) {
+  return {
+    'index': parseInt(document.getElementById('index_' + id).value),
+    'windowId': parseInt(document.getElementById('windowId_' + id).value)
+  }
+}
+function moveTab(id) {
+  try {
+    chrome.tabs.move(id, moveTabData(id));
+  } catch (e) {
+    alert(e);
+  }
+}
+
+function createTabData(id) {
+  return {
+    'index': parseInt(document.getElementById('index_' + id).value),
+    'windowId': parseInt(document.getElementById('windowId_' + id).value),
+    'index': parseInt(document.getElementById('index_' + id).value),
+    'url': document.getElementById('url_' + id).value,
+    'selected': document.getElementById('selected_' + id).value ? true : false
+  }
+}
+
+function createTab() {
+  var args = createTabData('new')
+
+  if (!isInt(args.windowId))
+    delete args.windowId;
+  if (!isInt(args.index))
+    delete args.index;
+
+  try {
+    chrome.tabs.create(args);
+  } catch (e) {
+    alert(e);
+  }
+}
+
+function updateAll() {
+  try {
+    for (var i = 0; i < tabIds.length; i++) {
+      chrome.tabs.update(tabIds[i], updateTabData(tabIds[i]));
+    }
+  } catch(e) {
+    alert(e);
+  }
+}
+
+function moveAll() {
+  appendToLog('moving all');
+  try {
+    for (var i = 0; i < tabIds.length; i++) {
+      chrome.tabs.move(tabIds[i], moveTabData(tabIds[i]));
+    }
+  } catch(e) {
+    alert(e);
+  }
+}
+
+function removeTab(tabId) {
+  try {
+    chrome.tabs.remove(tabId, function() {
+      appendToLog('tab: ' + tabId + ' removed.');
+    });
+  } catch (e) {
+    alert(e);
+  }
+}
+
+function appendToLog(logLine) {
+  document.getElementById('log')
+      .appendChild(document.createElement('div'))
+      .innerText = "> " + logLine;
+}
+
+function clearLog() {
+  document.getElementById('log').innerText = '';
+}
+
+chrome.windows.onCreated.addListener(function(createInfo) {
+  appendToLog('windows.onCreated -- window: ' + createInfo.id);
+  loadWindowList();
+});
+
+chrome.windows.onFocusChanged.addListener(function(windowId) {
+  focusedWindowId = windowId;
+  appendToLog('windows.onFocusChanged -- window: ' + windowId);
+  loadWindowList();
+});
+
+chrome.windows.onRemoved.addListener(function(windowId) {
+  appendToLog('windows.onRemoved -- window: ' + windowId);
+  loadWindowList();
+});
+
+chrome.tabs.onCreated.addListener(function(tab) {
+  appendToLog(
+      'tabs.onCreated -- window: ' + tab.windowId + ' tab: ' + tab.id +
+      ' title: ' + tab.title + ' index ' + tab.index + ' url ' + tab.url);
+  loadWindowList();
+});
+
+chrome.tabs.onAttached.addListener(function(tabId, props) {
+  appendToLog(
+      'tabs.onAttached -- window: ' + props.newWindowId + ' tab: ' + tabId +
+      ' index ' + props.newPosition);
+  loadWindowList();
+});
+
+chrome.tabs.onMoved.addListener(function(tabId, props) {
+  appendToLog(
+      'tabs.onMoved -- window: ' + props.windowId + ' tab: ' + tabId +
+      ' from ' + props.fromIndex + ' to ' +  props.toIndex);
+  loadWindowList();
+});
+
+function refreshTab(tabId) {
+  chrome.tabs.get(tabId, function(tab) {
+    var input = new JsExprContext(tab);
+    var output = document.getElementById('tab_' + tab.id);
+    jstProcess(input, output);
+    appendToLog('tab refreshed -- tabId: ' + tab.id + ' url: ' + tab.url);
+  });
+}
+
+chrome.tabs.onUpdated.addListener(function(tabId, props) {
+  appendToLog(
+      'tabs.onUpdated -- tab: ' + tabId + ' status ' + props.status +
+      ' url ' + props.url);
+  refreshTab(tabId);
+});
+
+chrome.tabs.onDetached.addListener(function(tabId, props) {
+  appendToLog(
+      'tabs.onDetached -- window: ' + props.oldWindowId + ' tab: ' + tabId +
+      ' index ' + props.oldPosition);
+  loadWindowList();
+});
+
+chrome.tabs.onSelectionChanged.addListener(function(tabId, props) {
+  appendToLog(
+      'tabs.onSelectionChanged -- window: ' + props.windowId + ' tab: ' +
+      tabId);
+  loadWindowList();
+});
+
+chrome.tabs.onRemoved.addListener(function(tabId) {
+  appendToLog('tabs.onRemoved -- tab: ' + tabId);
+  loadWindowList();
+});
+
+function createWindow() {
+  var args = {
+    'left': parseInt(document.getElementById('new_window_left').value),
+    'top': parseInt(document.getElementById('new_window_top').value),
+    'width': parseInt(document.getElementById('new_window_width').value),
+    'height': parseInt(document.getElementById('new_window_height').value),
+    'url': document.getElementById('new_window_url').value
+  }
+
+  if (!isInt(args.left))
+    delete args.left;
+  if (!isInt(args.top))
+    delete args.top;
+  if (!isInt(args.width))
+    delete args.width;
+  if (!isInt(args.height))
+    delete args.height;
+  if (!args.url)
+    delete args.url;
+
+  try {
+    chrome.windows.create(args);
+  } catch(e) {
+    alert(e);
+  }
+}
+
+function refreshWindow(windowId) {
+  chrome.windows.get(windowId, function(window) {
+    chrome.tabs.getAllInWindow(window.id, function(tabList) {
+      window.tabs = tabList;
+      var input = new JsExprContext(window);
+      var output = document.getElementById('window_' + window.id);
+      jstProcess(input, output);
+      appendToLog(
+          'window refreshed -- windowId: ' + window.id + ' tab count:' +
+          window.tabs.length);
+    });
+  });
+}
+
+function updateWindowData(id) {
+  var retval = {
+    left: parseInt(document.getElementById('left_' + id).value),
+    top: parseInt(document.getElementById('top_' + id).value),
+    width: parseInt(document.getElementById('width_' + id).value),
+    height: parseInt(document.getElementById('height_' + id).value)
+  }
+  if (!isInt(retval.left))
+    delete retval.left;
+  if (!isInt(retval.top))
+    delete retval.top;
+  if (!isInt(retval.width))
+    delete retval.width;
+  if (!isInt(retval.height))
+    delete retval.height;
+
+  return retval;
+}
+
+function updateWindow(id){
+  try {
+    chrome.windows.update(id, updateWindowData(id));
+  } catch (e) {
+    alert(e);
+  }
+}
+
+function removeWindow(windowId) {
+  try {
+    chrome.windows.remove(windowId, function() {
+      appendToLog('window: ' + windowId + ' removed.');
+    });
+  } catch (e) {
+    alert(e);
+  }
+}
+
+function refreshSelectedTab(windowId) {
+  chrome.tabs.getSelected(windowId, function(tab) {
+    var input = new JsExprContext(tab);
+    var output = document.getElementById('tab_' + tab.id);
+    jstProcess(input, output);
+    appendToLog(
+        'selected tab refreshed -- tabId: ' + tab.id + ' url:' + tab.url);
+  });
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+  bootStrap();
+});
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/tabs/pin/README b/chrome/common/extensions/docs/examples/api/tabs/pin/README
new file mode 100644
index 0000000..d9d5c65
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/pin/README
@@ -0,0 +1,2 @@
+Demo Chrome Extension that uses the Tab Pinning API. Enables a new keyboard
+shortcut (Ctrl + Shift + P) to toggle pinning and unpinning of the current tab.
diff --git a/chrome/common/extensions/docs/examples/api/tabs/pin/background.js b/chrome/common/extensions/docs/examples/api/tabs/pin/background.js
new file mode 100644
index 0000000..cf54e3b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/pin/background.js
@@ -0,0 +1,13 @@
+// Copyright (c) 2012 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.
+
+chrome.commands.onCommand.addListener(function(command) {
+  if (command == "toggle-pin") {
+    // Get the currently selected tab
+    chrome.tabs.getSelected(null, function(tab) {
+      // Toggle the pinned status
+      chrome.tabs.update(tab.id, {'pinned': !tab.pinned});
+    });
+  }
+});
diff --git a/chrome/common/extensions/docs/examples/api/tabs/pin/manifest.json b/chrome/common/extensions/docs/examples/api/tabs/pin/manifest.json
new file mode 100644
index 0000000..e3e56a3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/pin/manifest.json
@@ -0,0 +1,19 @@
+{
+  "name": "Keyboard Pin",
+  "version": "0.2",
+  "description": "Creates a keyboard shortcut (Alt + Shift + P) to toggle the pinned state of the currently selected tab",
+  "permissions": [
+    "tabs"
+  ],
+  "background": {
+    "persistent": false,
+    "scripts": ["background.js"]
+  },
+  "commands": {
+    "toggle-pin": {
+      "suggested_key": { "default": "Alt+Shift+P" },
+      "description": "Toggle tab pin"
+    }
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/tabs/screenshot/background.js b/chrome/common/extensions/docs/examples/api/tabs/screenshot/background.js
new file mode 100644
index 0000000..3670b02
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/screenshot/background.js
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 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.
+
+// To make sure we can uniquely identify each screenshot tab, add an id as a
+// query param to the url that displays the screenshot.
+// Note: It's OK that this is a global variable (and not in localStorage),
+// because the event page will stay open as long as any screenshot tabs are
+// open.
+var id = 100;
+
+function takeScreenshot() {
+  chrome.tabs.captureVisibleTab(null, function(img) {
+    var screenshotUrl = img;
+    var viewTabUrl = chrome.extension.getURL('screenshot.html?id=' + id++)
+
+    chrome.tabs.create({url: viewTabUrl}, function(tab) {
+      var targetId = tab.id;
+
+      var addSnapshotImageToTab = function(tabId, changedProps) {
+        // We are waiting for the tab we opened to finish loading.
+        // Check that the the tab's id matches the tab we opened,
+        // and that the tab is done loading.
+        if (tabId != targetId || changedProps.status != "complete")
+          return;
+
+        // Passing the above test means this is the event we were waiting for.
+        // There is nothing we need to do for future onUpdated events, so we
+        // use removeListner to stop geting called when onUpdated events fire.
+        chrome.tabs.onUpdated.removeListener(addSnapshotImageToTab);
+
+        // Look through all views to find the window which will display
+        // the screenshot.  The url of the tab which will display the
+        // screenshot includes a query parameter with a unique id, which
+        // ensures that exactly one view will have the matching URL.
+        var views = chrome.extension.getViews();
+        for (var i = 0; i < views.length; i++) {
+          var view = views[i];
+          if (view.location.href == viewTabUrl) {
+            view.setScreenshotUrl(screenshotUrl);
+            break;
+          }
+        }
+      };
+      chrome.tabs.onUpdated.addListener(addSnapshotImageToTab);
+    });
+  });
+}
+
+// Listen for a click on the camera icon.  On that click, take a screenshot.
+chrome.browserAction.onClicked.addListener(function(tab) {
+  if (tab.url.match(/code.google.com/)) {
+    takeScreenshot();
+  } else {
+    alert('This sample can only take screenshots of code.google.com pages');
+  }
+});
diff --git a/chrome/common/extensions/docs/examples/api/tabs/screenshot/camera.png b/chrome/common/extensions/docs/examples/api/tabs/screenshot/camera.png
new file mode 100644
index 0000000..687525f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/screenshot/camera.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/tabs/screenshot/manifest.json b/chrome/common/extensions/docs/examples/api/tabs/screenshot/manifest.json
new file mode 100644
index 0000000..4376aa0
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/screenshot/manifest.json
@@ -0,0 +1,15 @@
+{
+  "name": "Test Screenshot Extension",
+  "version": "1.1",
+  "description": "Demonstrate screenshot functionality in the chrome.tabs api. Note: only works for code.google.com",
+  "background": {
+    "persistent": false,
+    "scripts": ["background.js"]
+  },
+  "browser_action": {
+     "default_icon": "camera.png",
+     "default_title": "Take a screen shot!"
+  },
+  "permissions": ["tabs", "http://code.google.com/"],
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/tabs/screenshot/screenshot.html b/chrome/common/extensions/docs/examples/api/tabs/screenshot/screenshot.html
new file mode 100644
index 0000000..8739b46
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/screenshot/screenshot.html
@@ -0,0 +1,10 @@
+<html>
+<script src="screenshot.js"></script>
+<body>
+  Image here:
+  <p>
+    <img id="target" src="white.png" width="640" height="480">
+  <p>
+  End image
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/tabs/screenshot/screenshot.js b/chrome/common/extensions/docs/examples/api/tabs/screenshot/screenshot.js
new file mode 100644
index 0000000..cf83d33
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/screenshot/screenshot.js
@@ -0,0 +1,7 @@
+// 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.
+
+function setScreenshotUrl(url) {
+  document.getElementById('target').src = url;
+}
diff --git a/chrome/common/extensions/docs/examples/api/tabs/screenshot/white.png b/chrome/common/extensions/docs/examples/api/tabs/screenshot/white.png
new file mode 100644
index 0000000..06d8aca
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/tabs/screenshot/white.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/topsites/magic8ball/manifest.json b/chrome/common/extensions/docs/examples/api/topsites/magic8ball/manifest.json
new file mode 100644
index 0000000..84f12ac
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/topsites/magic8ball/manifest.json
@@ -0,0 +1,13 @@
+{
+  "name": "NTP prototyping extension",
+  "version": "1.1",
+  "description": "extension to prototype new NTP designs",
+  "chrome_url_overrides" : {
+    "newtab": "newTab.html"
+  },
+  "permissions": [
+    "topSites",
+    "chrome://favicon/"
+  ],
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/topsites/magic8ball/newTab.css b/chrome/common/extensions/docs/examples/api/topsites/magic8ball/newTab.css
new file mode 100644
index 0000000..6c070fb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/topsites/magic8ball/newTab.css
@@ -0,0 +1,28 @@
+/* Copyright (c) 2012 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.
+ */
+
+html {
+  background-color: #ddd;
+}
+
+#spacer {
+  height: 200px;
+}
+
+#title {
+  color: #555;
+  font-weight: bold;
+  height: 200px;
+  vertical-align: middle;
+}
+
+#mostVisitedThumb {
+  background-repeat: no-repeat;
+  height: 200px;
+  margin-left: 20px;
+  padding-left: 20px;
+  vertical-align: middle;
+  width: 212px;
+}
diff --git a/chrome/common/extensions/docs/examples/api/topsites/magic8ball/newTab.html b/chrome/common/extensions/docs/examples/api/topsites/magic8ball/newTab.html
new file mode 100644
index 0000000..4539728
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/topsites/magic8ball/newTab.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2010 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.
+-->
+
+<html>
+<meta charset="utf-8">
+<script src="newTab.js"></script>
+<link rel="stylesheet" href="newTab.css">
+
+<title>New 8ball</title>
+
+<body>
+  <center>
+    <div id="spacer"></div>
+    <span id='title'>Magic 8 ball says to visit</span>
+    <a id='mostVisitedThumb'>
+      <span></span>
+    </a>
+  </center>
+</body>
+
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/topsites/magic8ball/newTab.js b/chrome/common/extensions/docs/examples/api/topsites/magic8ball/newTab.js
new file mode 100644
index 0000000..69ffad6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/topsites/magic8ball/newTab.js
@@ -0,0 +1,20 @@
+// Copyright (c) 2012 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.
+
+function $(id) {
+  return document.getElementById(id);
+}
+
+function thumbnailsGotten(data) {
+  var eightBallWindow = $('mostVisitedThumb');
+  var rand = Math.floor(Math.random() * data.length);
+  eightBallWindow.href = data[rand].url;
+  eightBallWindow.textContent = data[rand].title;
+  eightBallWindow.style.backgroundImage = 'url(chrome://favicon/' +
+      data[rand].url + ')';
+}
+
+window.onload = function() {
+  chrome.topSites.get(thumbnailsGotten);
+}
diff --git a/chrome/common/extensions/docs/examples/api/ttsEngine/console_tts_engine/console_tts_engine.html b/chrome/common/extensions/docs/examples/api/ttsEngine/console_tts_engine/console_tts_engine.html
new file mode 100644
index 0000000..62fd68d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/ttsEngine/console_tts_engine/console_tts_engine.html
@@ -0,0 +1,49 @@
+<html>
+<head>
+  <title>Console TTS Engine</title>
+  <style>
+    body {
+      font-family: arial, helvetica, sans-serif;
+    }
+    table {
+      text-align: center;
+      padding: 10px;
+    }
+    #text {
+      text-align: left;
+      padding: 4px;
+      border: 1px solid #aaa;
+      width: 99%;
+      min-height: 100px;
+      overflow: auto;
+    }
+  </style>
+  <script type="text/javascript">
+    function clearText() {
+      document.getElementById("text").innerHTML = "";
+    }
+  </script>
+</head>
+<body>
+  <table>
+    <tr>
+      <th>Voice Name</th>
+      <th>Language</th>
+      <th>Gender</th>
+      <th>Rate</th>
+      <th>Pitch</th>
+      <th>Volume</th>
+    </tr>
+    <tr>
+      <td id="voiceName"></td>
+      <td id="lang"></td>
+      <td id="gender"></td>
+      <td id="rate"></td>
+      <td id="pitch"></td>
+      <td id="volume"></td>
+    </tr>
+  </table>
+  <button onclick="clearText()">Clear</button>
+  <p id="text"></p>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/ttsEngine/console_tts_engine/console_tts_engine.js b/chrome/common/extensions/docs/examples/api/ttsEngine/console_tts_engine/console_tts_engine.js
new file mode 100644
index 0000000..3f69094
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/ttsEngine/console_tts_engine/console_tts_engine.js
@@ -0,0 +1,115 @@
+// Copyright (c) 2012 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.
+
+var timeoutId;
+var ttsId = -1;
+var ttsWindow;
+var milliseconds;
+var curOptions;
+
+function areNewOptions(options) {
+  var properties = ['voiceName', 'lang', 'gender', 'rate', 'pitch', 'volume'];
+
+  for (var i = 0; i < properties.length; ++i) {
+    if (options[properties[i]] != curOptions[properties[i]]) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+function getTtsElement(element) {
+  return ttsWindow.document.getElementById(element);
+}
+
+function appendText(text) {
+  getTtsElement("text").innerHTML += text;
+}
+
+function logOptions() {
+  getTtsElement("voiceName").innerHTML = curOptions.voiceName;
+  getTtsElement("lang").innerHTML = curOptions.lang;
+  getTtsElement("gender").innerHTML = curOptions.gender;
+  getTtsElement("rate").innerHTML = curOptions.rate;
+  getTtsElement("pitch").innerHTML = curOptions.pitch;
+  getTtsElement("volume").innerHTML = curOptions.volume;
+}
+
+function logUtterance(utterance, index, sendTtsEvent) {
+  if (index == utterance.length) {
+    sendTtsEvent({'type': 'end', 'charIndex': utterance.length});
+    return;
+  }
+
+  appendText(utterance[index]);
+
+  if (utterance[index] == ' ') {
+    sendTtsEvent({'type': 'word', 'charIndex': index});
+  }
+  else if (utterance[index] == '.' ||
+      utterance[index] == '?' ||
+      utterance[index] == '!') {
+    sendTtsEvent({'type': 'sentence', 'charIndex': index});
+  }
+
+  timeoutId = setTimeout(function() {
+    logUtterance(utterance, ++index, sendTtsEvent)
+  }, milliseconds);
+}
+
+var speakListener = function(utterance, options, sendTtsEvent) {
+  clearTimeout(timeoutId);
+
+  sendTtsEvent({'type': 'start', 'charIndex': 0});
+
+  if (ttsId == -1) {
+    // Create a new window that overlaps the bottom 40% of the current window
+    chrome.windows.getCurrent(function(curWindow) {
+      chrome.windows.create(
+          {"url": "console_tts_engine.html",
+           "focused": false,
+           "top": Math.round(curWindow.top + 6/10 * curWindow.height),
+           "left": curWindow.left,
+           "width": curWindow.width,
+           "height": Math.round(4/10 * curWindow.height)},
+          function(newWindow) {
+            ttsId = newWindow.id;
+            ttsWindow = chrome.extension.getViews({"windowId": ttsId})[0];
+
+            curOptions = options;
+            logOptions();
+
+            // Fastest timeout == 1 ms (@ options.rate = 10.0)
+            milliseconds = 10 / curOptions.rate;
+            logUtterance(utterance, 0, sendTtsEvent);
+          }
+      );
+    });
+  } else {
+    if (areNewOptions(options)) {
+      curOptions = options;
+      logOptions();
+
+      milliseconds = 10 / curOptions.rate;
+    }
+
+    logUtterance(utterance, 0, sendTtsEvent);
+  }
+
+};
+
+var stopListener = function() {
+  clearTimeout(timeoutId);
+};
+
+var removedListener = function(windowId, removeInfo) {
+  if (ttsId == windowId) {
+    ttsId = -1;
+  }
+}
+
+chrome.ttsEngine.onSpeak.addListener(speakListener);
+chrome.ttsEngine.onStop.addListener(stopListener);
+chrome.windows.onRemoved.addListener(removedListener);
diff --git a/chrome/common/extensions/docs/examples/api/ttsEngine/console_tts_engine/manifest.json b/chrome/common/extensions/docs/examples/api/ttsEngine/console_tts_engine/manifest.json
new file mode 100644
index 0000000..35be832
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/ttsEngine/console_tts_engine/manifest.json
@@ -0,0 +1,19 @@
+{
+  "name": "Console TTS Engine",
+  "manifest_version": 2,
+  "version": "2.1",
+  "description": "A \"silent\" TTS engine that prints text to a small window rather than synthesizing speech.",
+  "permissions": ["ttsEngine", "tabs"],
+  "background": {
+    "persistent": false,
+    "scripts": ["console_tts_engine.js"]
+  },
+  "tts_engine": {
+    "voices": [
+      {
+        "voice_name": "Console",
+        "event_types": ["start", "word", "sentence", "end"]
+      }
+    ]
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/api/webNavigation/basic/_locales/en/messages.json b/chrome/common/extensions/docs/examples/api/webNavigation/basic/_locales/en/messages.json
new file mode 100644
index 0000000..e639880
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/webNavigation/basic/_locales/en/messages.json
@@ -0,0 +1,52 @@
+{
+  "extName":  {
+    "message": "WebNavigation Tech Demo",
+    "description": "The extension name."
+  },
+  "extDescription": {
+    "message": "Demonstration of the WebNavigation extension API.",
+    "description": "The extension description."
+  },
+
+  "navigationDescription": {
+    "message": ", requested $NUM$ times.  Loaded in an average of $LOAD$ miliseconds.",
+    "description": "The message posted in the popup for each stored navigation.",
+    "placeholders": {
+      "NUM": {
+        "content": "$1",
+        "example": "4 (The number of times this URL was accessed.)"
+      },
+      "LOAD": {
+        "content": "$2",
+        "example": "12.345 (The average load time in miliseconds.)"
+      }
+    }
+  },
+
+  "inHandler": {
+    "message": "In webNavigation[`%s`] handler: %o",
+    "description": "Notification displayed for each webNavigation event."
+  },
+
+  "inHandlerError": {
+    "message": "In webNavigation[`%s`] handler: No data!",
+    "description": "Notification displayed in a webNavigation event handler without data!"
+  },
+
+  "errorCommittedWithoutPending": {
+    "message": "Wha?  `onCommitted` for `%s` called, though it's not pending: %o",
+    "description": "Error logged when `onCommitted` is triggered on a non-pending request."
+  },
+  "errorCompletedWithoutPending": {
+    "message": "Wha?  `onCompleted` for `%s` called, though it's not pending: %o",
+    "description": "Error logged when `onCompleted` is triggered on a non-pending request."
+  },
+  "errorErrorOccurredWithoutPending": {
+    "message": "Wha?  `onErrorOccurred` for `%s` called, though it's not pending: %o",
+    "description": "Error logged when `onErrorOccurred` is triggered on a non-pending request."
+  },
+  "errorCommittedWithoutPending": {
+    "message": "Wha?  `onCompleted` for `%s` called, though it's not pending: %o",
+    "description": "Error logged when `onCompleted` is triggered on a non-pending request."
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/api/webNavigation/basic/background.js b/chrome/common/extensions/docs/examples/api/webNavigation/basic/background.js
new file mode 100644
index 0000000..a056515
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/webNavigation/basic/background.js
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 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.
+
+/**
+ * @filedescription Initializes the extension's background page.
+ */
+
+var nav = new NavigationCollector();
+
+var eventList = ['onBeforeNavigate', 'onCreatedNavigationTarget',
+    'onCommitted', 'onCompleted', 'onDOMContentLoaded',
+    'onErrorOccurred', 'onReferenceFragmentUpdated', 'onTabReplaced',
+    'onHistoryStateUpdated'];
+
+eventList.forEach(function(e) {
+  chrome.webNavigation[e].addListener(function(data) {
+    if (typeof data)
+      console.log(chrome.i18n.getMessage('inHandler'), e, data);
+    else
+      console.error(chrome.i18n.getMessage('inHandlerError'), e);
+  });
+});
+
+// Reset the navigation state on startup. We only want to collect data within a
+// session.
+chrome.runtime.onStartup.addListener(function() {
+  nav.resetDataStorage();
+});
diff --git a/chrome/common/extensions/docs/examples/api/webNavigation/basic/icon.png b/chrome/common/extensions/docs/examples/api/webNavigation/basic/icon.png
new file mode 100644
index 0000000..103ff36
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/webNavigation/basic/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/webNavigation/basic/manifest.json b/chrome/common/extensions/docs/examples/api/webNavigation/basic/manifest.json
new file mode 100644
index 0000000..2c0d435
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/webNavigation/basic/manifest.json
@@ -0,0 +1,18 @@
+{
+  "name":           "__MSG_extName__",
+  "version":        "0.2",
+  "description":    "__MSG_extDescription__",
+  "default_locale": "en",
+  "background": {
+    "persistent": false,
+    "scripts": ["navigation_collector.js", "background.js"]
+  },
+  "browser_action": {
+    "default_icon": "icon.png",
+    "default_popup": "popup.html"
+  },
+  "permissions":    [
+    "webNavigation", "storage"
+  ],
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/webNavigation/basic/navigation_collector.js b/chrome/common/extensions/docs/examples/api/webNavigation/basic/navigation_collector.js
new file mode 100644
index 0000000..a166d04
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/webNavigation/basic/navigation_collector.js
@@ -0,0 +1,500 @@
+// Copyright (c) 2012 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.
+
+/**
+ * Implements the NavigationCollector object that powers the extension.
+ *
+ * @author mkwst@google.com (Mike West)
+ */
+
+/**
+ * Collects navigation events, and provides a list of successful requests
+ * that you can do interesting things with. Calling the constructor will
+ * automatically bind handlers to the relevant webnavigation API events,
+ * and to a `getMostRequestedUrls` extension message for internal
+ * communication between background pages and popups.
+ *
+ * @constructor
+ */
+function NavigationCollector() {
+  /**
+   * A list of currently pending requests, implemented as a hash of each
+   * request's tab ID, frame ID, and URL in order to ensure uniqueness.
+   *
+   * @type {Object.<string, {start: number}>}
+   * @private
+   */
+  this.pending_ = {};
+
+  /**
+   * A list of completed requests, implemented as a hash of each
+   * request's tab ID, frame ID, and URL in order to ensure uniqueness.
+   *
+   * @type {Object.<string, Array.<NavigationCollector.Request>>}
+   * @private
+   */
+  this.completed_ = {};
+
+  /**
+   * A list of requests that errored off, implemented as a hash of each
+   * request's tab ID, frame ID, and URL in order to ensure uniqueness.
+   *
+   * @type {Object.<string, Array.<NavigationCollector.Request>>}
+   * @private
+   */
+  this.errored_ = {};
+
+  // Bind handlers to the 'webNavigation' events that we're interested
+  // in handling in order to build up a complete picture of the whole
+  // navigation event.
+  chrome.webNavigation.onCreatedNavigationTarget.addListener(
+      this.onCreatedNavigationTargetListener_.bind(this));
+  chrome.webNavigation.onBeforeNavigate.addListener(
+      this.onBeforeNavigateListener_.bind(this));
+  chrome.webNavigation.onCompleted.addListener(
+      this.onCompletedListener_.bind(this));
+  chrome.webNavigation.onCommitted.addListener(
+      this.onCommittedListener_.bind(this));
+  chrome.webNavigation.onErrorOccurred.addListener(
+      this.onErrorOccurredListener_.bind(this));
+  chrome.webNavigation.onReferenceFragmentUpdated.addListener(
+      this.onReferenceFragmentUpdatedListener_.bind(this));
+  chrome.webNavigation.onHistoryStateUpdated.addListener(
+      this.onHistoryStateUpdatedListener_.bind(this));
+
+  // Bind handler to extension messages for communication from popup.
+  chrome.extension.onRequest.addListener(this.onRequestListener_.bind(this));
+
+  this.loadDataStorage_();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The possible transition types that explain how the navigation event
+ * was generated (i.e. "The user clicked on a link." or "The user submitted
+ * a form").
+ *
+ * @see http://code.google.com/chrome/extensions/trunk/history.html
+ * @enum {string}
+ */
+NavigationCollector.NavigationType = {
+  AUTO_BOOKMARK: 'auto_bookmark',
+  AUTO_SUBFRAME: 'auto_subframe',
+  FORM_SUBMIT: 'form_submit',
+  GENERATED: 'generated',
+  KEYWORD: 'keyword',
+  KEYWORD_GENERATED: 'keyword_generated',
+  LINK: 'link',
+  MANUAL_SUBFRAME: 'manual_subframe',
+  RELOAD: 'reload',
+  START_PAGE: 'start_page',
+  TYPED: 'typed'
+};
+
+/**
+ * The possible transition qualifiers:
+ *
+ * * CLIENT_REDIRECT: Redirects caused by JavaScript, or a refresh meta tag
+ *   on a page.
+ *
+ * * SERVER_REDIRECT: Redirected by the server via a 301/302 response.
+ *
+ * * FORWARD_BACK: User used the forward or back buttons to navigate through
+ *   her browsing history.
+ *
+ * @enum {string}
+ */
+NavigationCollector.NavigationQualifier = {
+  CLIENT_REDIRECT: 'client_redirect',
+  FORWARD_BACK: 'forward_back',
+  SERVER_REDIRECT: 'server_redirect'
+};
+
+/**
+ * @typedef {{url: string, transitionType: NavigationCollector.NavigationType,
+ *     transitionQualifier: Array.<NavigationCollector.NavigationQualifier>,
+ *     openedInNewTab: boolean, source: {frameId: ?number, tabId: ?number},
+ *     duration: number}}
+ */
+NavigationCollector.Request;
+
+///////////////////////////////////////////////////////////////////////////////
+
+NavigationCollector.prototype = {
+  /**
+   * Returns a somewhat unique ID for a given WebNavigation request.
+   *
+   * @param {!{tabId: ?number, frameId: ?number}} data Information
+   *     about the navigation event we'd like an ID for.
+   * @return {!string} ID created by combining the source tab ID and frame ID
+   *     (or target tab/frame IDs if there's no source), as the API ensures
+   *     that these will be unique across a single navigation event.
+   * @private
+   */
+  parseId_: function(data) {
+    return data.tabId + '-' + (data.frameId ? data.frameId : 0);
+  },
+
+
+  /**
+   * Creates an empty entry in the pending array if one doesn't already exist,
+   * and prepopulates the errored and completed arrays for ease of insertion
+   * later.
+   *
+   * @param {!string} id The request's ID, as produced by parseId_.
+   * @param {!string} url The request's URL.
+   */
+  prepareDataStorage_: function(id, url) {
+    this.pending_[id] = this.pending_[id] || {
+      openedInNewTab: false,
+      source: {
+        frameId: null,
+        tabId: null
+      },
+      start: null,
+      transitionQualifiers: [],
+      transitionType: null
+    };
+    this.completed_[url] = this.completed_[url] || [];
+    this.errored_[url] = this.errored_[url] || [];
+  },
+
+
+  /**
+   * Retrieves our saved data from storage.
+   * @private
+   */
+  loadDataStorage_: function() {
+    chrome.storage.local.get({
+      "completed": {},
+      "errored": {},
+    }, function(storage) {
+      this.completed_ = storage.completed;
+      this.errored_ = storage.errored;
+    }.bind(this));
+  },
+
+
+  /**
+   * Persists our state to the storage API.
+   * @private
+   */
+  saveDataStorage_: function() {
+    chrome.storage.local.set({
+      "completed": this.completed_,
+      "errored": this.errored_,
+    });
+  },
+
+
+  /**
+   * Resets our saved state to empty.
+   */
+  resetDataStorage: function() {
+    this.completed_ = {};
+    this.errored_ = {};
+    this.saveDataStorage_();
+    // Load again, in case there is an outstanding storage.get request. This
+    // one will reload the newly-cleared data.
+    this.loadDataStorage_();
+  },
+
+
+  /**
+   * Handler for the 'onCreatedNavigationTarget' event. Updates the
+   * pending request with a source frame/tab, and notes that it was opened in a
+   * new tab.
+   *
+   * Pushes the request onto the
+   * 'pending_' object, and stores it for later use.
+   *
+   * @param {!Object} data The event data generated for this request.
+   * @private
+   */
+  onCreatedNavigationTargetListener_: function(data) {
+    var id = this.parseId_(data);
+    this.prepareDataStorage_(id, data.url);
+    this.pending_[id].openedInNewTab = data.tabId;
+    this.pending_[id].source = {
+      tabId: data.sourceTabId,
+      frameId: data.sourceFrameId
+    };
+    this.pending_[id].start = data.timeStamp;
+  },
+
+
+  /**
+   * Handler for the 'onBeforeNavigate' event. Pushes the request onto the
+   * 'pending_' object, and stores it for later use.
+   *
+   * @param {!Object} data The event data generated for this request.
+   * @private
+   */
+  onBeforeNavigateListener_: function(data) {
+    var id = this.parseId_(data);
+    this.prepareDataStorage_(id, data.url);
+    this.pending_[id].start = this.pending_[id].start || data.timeStamp;
+  },
+
+
+  /**
+   * Handler for the 'onCommitted' event. Updates the pending request with
+   * transition information.
+   *
+   * Pushes the request onto the
+   * 'pending_' object, and stores it for later use.
+   *
+   * @param {!Object} data The event data generated for this request.
+   * @private
+   */
+  onCommittedListener_: function(data) {
+    var id = this.parseId_(data);
+    if (!this.pending_[id]) {
+      console.warn(
+          chrome.i18n.getMessage('errorCommittedWithoutPending'),
+          data.url,
+          data);
+    } else {
+      this.prepareDataStorage_(id, data.url);
+      this.pending_[id].transitionType = data.transitionType;
+      this.pending_[id].transitionQualifiers =
+          data.transitionQualifiers;
+    }
+  },
+
+
+  /**
+   * Handler for the 'onReferenceFragmentUpdated' event. Updates the pending
+   * request with transition information.
+   *
+   * Pushes the request onto the
+   * 'pending_' object, and stores it for later use.
+   *
+   * @param {!Object} data The event data generated for this request.
+   * @private
+   */
+  onReferenceFragmentUpdatedListener_: function(data) {
+    var id = this.parseId_(data);
+    if (!this.pending_[id]) {
+      this.completed_[data.url] = this.completed_[data.url] || [];
+      this.completed_[data.url].push({
+        duration: 0,
+        openedInNewWindow: false,
+        source: {
+          frameId: null,
+          tabId: null
+        },
+        transitionQualifiers: data.transitionQualifiers,
+        transitionType: data.transitionType,
+        url: data.url
+      });
+      this.saveDataStorage_();
+    } else {
+      this.prepareDataStorage_(id, data.url);
+      this.pending_[id].transitionType = data.transitionType;
+      this.pending_[id].transitionQualifiers =
+          data.transitionQualifiers;
+    }
+  },
+
+
+  /**
+   * Handler for the 'onHistoryStateUpdated' event. Updates the pending
+   * request with transition information.
+   *
+   * Pushes the request onto the
+   * 'pending_' object, and stores it for later use.
+   *
+   * @param {!Object} data The event data generated for this request.
+   * @private
+   */
+  onHistoryStateUpdatedListener_: function(data) {
+    var id = this.parseId_(data);
+    if (!this.pending_[id]) {
+      this.completed_[data.url] = this.completed_[data.url] || [];
+      this.completed_[data.url].push({
+        duration: 0,
+        openedInNewWindow: false,
+        source: {
+          frameId: null,
+          tabId: null
+        },
+        transitionQualifiers: data.transitionQualifiers,
+        transitionType: data.transitionType,
+        url: data.url
+      });
+      this.saveDataStorage_();
+    } else {
+      this.prepareDataStorage_(id, data.url);
+      this.pending_[id].transitionType = data.transitionType;
+      this.pending_[id].transitionQualifiers =
+          data.transitionQualifiers;
+    }
+  },
+
+
+  /**
+   * Handler for the 'onCompleted` event. Pulls the request's data from the
+   * 'pending_' object, combines it with the completed event's data, and pushes
+   * a new NavigationCollector.Request object onto 'completed_'.
+   *
+   * @param {!Object} data The event data generated for this request.
+   * @private
+   */
+  onCompletedListener_: function(data) {
+    var id = this.parseId_(data);
+    if (!this.pending_[id]) {
+      console.warn(
+          chrome.i18n.getMessage('errorCompletedWithoutPending'),
+          data.url,
+          data);
+    } else {
+      this.completed_[data.url].push({
+        duration: (data.timeStamp - this.pending_[id].start),
+        openedInNewWindow: this.pending_[id].openedInNewWindow,
+        source: this.pending_[id].source,
+        transitionQualifiers: this.pending_[id].transitionQualifiers,
+        transitionType: this.pending_[id].transitionType,
+        url: data.url
+      });
+      delete this.pending_[id];
+      this.saveDataStorage_();
+    }
+  },
+
+
+  /**
+   * Handler for the 'onErrorOccurred` event. Pulls the request's data from the
+   * 'pending_' object, combines it with the completed event's data, and pushes
+   * a new NavigationCollector.Request object onto 'errored_'.
+   *
+   * @param {!Object} data The event data generated for this request.
+   * @private
+   */
+  onErrorOccurredListener_: function(data) {
+    var id = this.parseId_(data);
+    if (!this.pending_[id]) {
+      console.error(
+          chrome.i18n.getMessage('errorErrorOccurredWithoutPending'),
+          data.url,
+          data);
+    } else {
+      this.prepareDataStorage_(id, data.url);
+      this.errored_[data.url].push({
+        duration: (data.timeStamp - this.pending_[id].start),
+        openedInNewWindow: this.pending_[id].openedInNewWindow,
+        source: this.pending_[id].source,
+        transitionQualifiers: this.pending_[id].transitionQualifiers,
+        transitionType: this.pending_[id].transitionType,
+        url: data.url
+      });
+      delete this.pending_[id];
+      this.saveDataStorage_();
+    }
+  },
+
+  /**
+   * 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
+   */
+  onRequestListener_: function(request, sender, sendResponse) {
+    if (request.type === 'getMostRequestedUrls')
+      sendResponse({result: this.getMostRequestedUrls(request.num)});
+    else
+      sendResponse({});
+  },
+
+///////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * @return {Object.<string, NavigationCollector.Request>} The complete list of
+   *     successful navigation requests.
+   */
+  get completed() {
+    return this.completed_;
+  },
+
+
+  /**
+   * @return {Object.<string, Navigationcollector.Request>} The complete list of
+   *     unsuccessful navigation requests.
+   */
+  get errored() {
+    return this.errored_;
+  },
+
+
+  /**
+   * Get a list of the X most requested URLs.
+   *
+   * @param {number=} num The number of successful navigation requests to
+   *     return. If 0 is passed in, or the argument left off entirely, all
+   *     successful requests are returned.
+   * @return {Object.<string, NavigationCollector.Request>} The list of
+   *     successful navigation requests, sorted in decending order of frequency.
+   */
+  getMostRequestedUrls: function(num) {
+    return this.getMostFrequentUrls_(this.completed, num);
+  },
+
+
+  /**
+   * Get a list of the X most errored URLs.
+   *
+   * @param {number=} num The number of unsuccessful navigation requests to
+   *     return. If 0 is passed in, or the argument left off entirely, all
+   *     successful requests are returned.
+   * @return {Object.<string, NavigationCollector.Request>} The list of
+   *     unsuccessful navigation requests, sorted in decending order
+   *     of frequency.
+   */
+  getMostErroredUrls: function(num) {
+    return this.getMostErroredUrls_(this.errored, num);
+  },
+
+
+  /**
+   * Get a list of the most frequent URLs in a list.
+   *
+   * @param {NavigationCollector.Request} list A list of URLs to parse.
+   * @param {number=} num The number of navigation requests to return. If
+   *     0 is passed in, or the argument left off entirely, all requests
+   *     are returned.
+   * @return {Object.<string, NavigationCollector.Request>} The list of
+   *     navigation requests, sorted in decending order of frequency.
+   * @private
+   */
+  getMostFrequentUrls_: function(list, num) {
+    var result = [];
+    var avg;
+    // Convert the 'completed_' object to an array.
+    for (var x in list) {
+      avg = 0;
+      if (list.hasOwnProperty(x) && list[x].length) {
+        list[x].forEach(function(o) {
+          avg += o.duration;
+        });
+        avg = avg / list[x].length;
+        result.push({
+          url: x,
+          numRequests: list[x].length,
+          requestList: list[x],
+          average: avg
+        });
+      }
+    }
+    // Sort the array.
+    result.sort(function(a, b) {
+      return b.numRequests - a.numRequests;
+    });
+    // Return the requested number of results.
+    return num ? result.slice(0, num) : result;
+  }
+};
diff --git a/chrome/common/extensions/docs/examples/api/webNavigation/basic/popup.css b/chrome/common/extensions/docs/examples/api/webNavigation/basic/popup.css
new file mode 100644
index 0000000..e551226
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/webNavigation/basic/popup.css
@@ -0,0 +1,63 @@
+/**
+ * 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.
+ */
+
+
+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;
+}
+
+body > section {
+  border-radius: 5px;
+  background: -webkit-linear-gradient(rgba(234, 238, 243, 0.2), #EAEEF3),
+  -webkit-linear-gradient(
+  left, #EAEEF3, #EAEEF3 97%, #D3D7DB);
+  font: 14px/1 Arial,Sans Serif;
+  padding: 10px;
+  width:  563px;
+  max-height: 400px;
+  overflow-y: auto;
+  box-shadow: inset 0px 2px 5px rgba(0,0,0,0.5);
+}
+
+body > section > ol {
+  padding: 0;
+  margin: 0;
+  list-style: none inside;
+}
+
+body > section > ol > li {
+  position: relative;
+  margin: 0.5em 0 0.5em 40px;
+}
+
+code {
+  word-wrap: break-word;
+  background: rgba(255,255,0, 0.5);
+}
+
+em {
+  position: absolute;
+  top: 0px;
+  left: -40px;
+  width: 30px;
+  text-align: right;
+  font: 30px/1 Helvetica, sans-serif;
+  font-weight: 700;
+}
+
+p {
+  min-height: 30px;
+  line-height: 1.2;
+}
diff --git a/chrome/common/extensions/docs/examples/api/webNavigation/basic/popup.html b/chrome/common/extensions/docs/examples/api/webNavigation/basic/popup.html
new file mode 100644
index 0000000..1f51802
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/webNavigation/basic/popup.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<!--
+ * 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.
+-->
+<html>
+  <head>
+    <title>WebNavigation Tech Demo Popup</title>
+    <link href="popup.css" rel="stylesheet" type="text/css">
+  </head>
+  <body>
+    <h1>Most Requested URLs</h1>
+    <section></section>
+    <script src="popup.js"></script>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/api/webNavigation/basic/popup.js b/chrome/common/extensions/docs/examples/api/webNavigation/basic/popup.js
new file mode 100644
index 0000000..35f6e00
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/webNavigation/basic/popup.js
@@ -0,0 +1,36 @@
+// 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.
+
+/**
+ * @filedescription Initializes the extension's popup page.
+ */
+
+chrome.extension.sendRequest(
+    {'type': 'getMostRequestedUrls'},
+    function generateList(response) {
+      var section = document.querySelector('body>section');
+      var results = response.result;
+      var ol = document.createElement('ol');
+      var li, p, em, code, text;
+      var i;
+      for (i = 0; i < results.length; i++ ) {
+        li = document.createElement('li');
+        p = document.createElement('p');
+        em = document.createElement('em');
+        em.textContent = i + 1;
+        code = document.createElement('code');
+        code.textContent = results[i].url;
+        text = document.createTextNode(
+          chrome.i18n.getMessage('navigationDescription',
+            [results[i].numRequests,
+            results[i].average]));
+        p.appendChild(em);
+        p.appendChild(code);
+        p.appendChild(text);
+        li.appendChild(p);
+        ol.appendChild(li);
+      }
+      section.innerHTML = '';
+      section.appendChild(ol);
+    });
diff --git a/chrome/common/extensions/docs/examples/api/windows/merge_windows/NOTICE b/chrome/common/extensions/docs/examples/api/windows/merge_windows/NOTICE
new file mode 100644
index 0000000..d86114b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/windows/merge_windows/NOTICE
@@ -0,0 +1,2 @@
+This extension uses icons based on the famfamfam silk series.
+http://www.famfamfam.com/lab/icons/silk/
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/api/windows/merge_windows/arrow_in.png b/chrome/common/extensions/docs/examples/api/windows/merge_windows/arrow_in.png
new file mode 100644
index 0000000..745c651
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/windows/merge_windows/arrow_in.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/windows/merge_windows/background.js b/chrome/common/extensions/docs/examples/api/windows/merge_windows/background.js
new file mode 100644
index 0000000..e978921
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/windows/merge_windows/background.js
@@ -0,0 +1,45 @@
+// 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.
+
+var targetWindow = null;
+var tabCount = 0;
+
+function start(tab) {
+  chrome.windows.getCurrent(getWindows);
+}
+
+function getWindows(win) {
+  targetWindow = win;
+  chrome.tabs.getAllInWindow(targetWindow.id, getTabs);
+}
+
+function getTabs(tabs) {
+  tabCount = tabs.length;
+  // We require all the tab information to be populated.
+  chrome.windows.getAll({"populate" : true}, moveTabs);
+}
+
+function moveTabs(windows) {
+  var numWindows = windows.length;
+  var tabPosition = tabCount;
+
+  for (var i = 0; i < numWindows; i++) {
+    var win = windows[i];
+
+    if (targetWindow.id != win.id) {
+      var numTabs = win.tabs.length;
+
+      for (var j = 0; j < numTabs; j++) {
+        var tab = win.tabs[j];
+        // Move the tab into the window that triggered the browser action.
+        chrome.tabs.move(tab.id,
+            {"windowId": targetWindow.id, "index": tabPosition});
+        tabPosition++;
+      }
+    }
+  }
+}
+
+// Set up a click handler so that we can merge all the windows.
+chrome.browserAction.onClicked.addListener(start);
diff --git a/chrome/common/extensions/docs/examples/api/windows/merge_windows/manifest.json b/chrome/common/extensions/docs/examples/api/windows/merge_windows/manifest.json
new file mode 100644
index 0000000..38ea796
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/windows/merge_windows/manifest.json
@@ -0,0 +1,19 @@
+{
+  "name": "Merge Windows",
+  "version": "1.0.1.0",
+  "description": "Merges all of the browser's windows into the current window",
+  "icons": {
+    "48": "merge_windows_48.png",
+    "128": "merge_windows_128.png"
+  },
+  "background": {
+    "persistent": false,
+    "scripts": ["background.js"]
+  },
+  "browser_action": {
+    "default_icon": "arrow_in.png",
+    "default_title": "Merge Windows"
+  },
+  "permissions": ["tabs"],
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/api/windows/merge_windows/merge_windows_128.png b/chrome/common/extensions/docs/examples/api/windows/merge_windows/merge_windows_128.png
new file mode 100644
index 0000000..a083b58
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/windows/merge_windows/merge_windows_128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/api/windows/merge_windows/merge_windows_48.png b/chrome/common/extensions/docs/examples/api/windows/merge_windows/merge_windows_48.png
new file mode 100644
index 0000000..7defec6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/api/windows/merge_windows/merge_windows_48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/apps/background-simple/README b/chrome/common/extensions/docs/examples/apps/background-simple/README
new file mode 100644
index 0000000..dc74c5a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/background-simple/README
@@ -0,0 +1,19 @@
+This example demonstrates background window functionality in a hosted app.
+To run the app, you first need to edit it and install it:
+
+1. Put index.html and background.html in a directory where the HTTP server
+   can find them.
+
+2. Edit manifest.json. Search for SOME_, replacing the text with URLs
+   pointing to the launch page (index.html) and to the directory where
+   index.html and background.html live.
+
+3. Install the app from manifest.json. You can use the Load unpacked extension
+   button on the chrome://extensions page.
+
+Once the app is installed, you can launch it by clicking its icon on the
+New Tab page.
+
+For more information, see the documentation:
+
+   http://code.google.com/chrome/apps/docs/developers_guide.html
diff --git a/chrome/common/extensions/docs/examples/apps/background-simple/background.html b/chrome/common/extensions/docs/examples/apps/background-simple/background.html
new file mode 100644
index 0000000..12d906e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/background-simple/background.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2009 The Chromium Authors. All rights reserved.  Use of this
+ * source code is governed by a BSD-style license that can be found in the
+ * LICENSE file.
+-->
+<html>
+  <script>
+    var notification = webkitNotifications.createNotification("",
+        "Simple Background App",
+        "A background window has been created");
+    notification.show();
+  </script>
+</html>
diff --git a/chrome/common/extensions/docs/examples/apps/background-simple/index.html b/chrome/common/extensions/docs/examples/apps/background-simple/index.html
new file mode 100644
index 0000000..5a10603
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/background-simple/index.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2009 The Chromium Authors. All rights reserved.  Use of this
+ * source code is governed by a BSD-style license that can be found in the
+ * LICENSE file.
+-->
+<html>
+  <head>
+    <title>Simple Background App</title>
+    <style>
+      .hidden { display: none; }
+      #unsupported { color: #d00; }
+    </style>
+  </head>
+  <body>
+    <h1>Simple Background App</h1>
+    <p id="supported" class="hidden">
+      <button id="openBackgroundWindow">Open background window</button>
+      <button id="closeBackgroundWindow">Close background window</button>
+    </p>
+    <p id="unsupported" class="hidden">
+      You are not using Chrome or have not installed the application for this
+      page.
+    </p>
+    <script src="index.js"></script>
+    <p>
+      This app displays a notification
+      whenever its background window is created.
+      Background windows and this app are described in the
+      <a href="http://code.google.com/chrome/apps/docs/developers_guide.html">apps documentation</a>.
+    </p>
+    <p>
+      The generic source code is available for
+      <a href="http://code.google.com/chrome/extensions/trunk/examples/apps/background-simple.zip">download</a>.
+      The
+      <a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/apps/background-simple/README?content-type=text/plain">README</a>
+      tells you how to modify the code.
+    </p>
+    <p>
+      If you just want to run a version of this app that's already on the web,
+      here's how:
+    </p>
+    <ol>
+      <li>
+        <a href="http://background-simple.appspot.com/app.crx">Install the app</a>
+        from background-simple.appspot.com.
+      </li>
+      <li>
+        Launch Simple Background App from the New Tab page.
+      </li>
+    </ol>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/apps/background-simple/index.js b/chrome/common/extensions/docs/examples/apps/background-simple/index.js
new file mode 100644
index 0000000..970810c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/background-simple/index.js
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 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.
+
+// Check for support
+if (window.chrome && window.chrome.app && window.chrome.app.isInstalled) {
+  document.getElementById('supported').className = '';
+} else {
+  document.getElementById('unsupported').className = '';
+}
+var bgWinUrl = "background.html#yay";
+var bgWinName = "bgNotifier";
+
+function openBackgroundWindow() {
+  window.open(bgWinUrl, bgWinName, "background");
+}
+
+function closeBackgroundWindow() {
+  var w = window.open(bgWinUrl, bgWinName, "background");
+  w.close();
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+  document.querySelector('#openBackgroundWindow').addEventListener(
+    'click', openBackgroundWindow);
+  document.querySelector('#closeBackgroundWindow').addEventListener(
+    'click', closeBackgroundWindow);
+});
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/apps/background-simple/manifest.json b/chrome/common/extensions/docs/examples/apps/background-simple/manifest.json
new file mode 100644
index 0000000..fb9ba8f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/background-simple/manifest.json
@@ -0,0 +1,12 @@
+{

+  "name": "Simple Background App",

+  "version": "0.2",

+  "app": {

+    "urls": [ "http://SOME_SITE_WITHOUT_PORT_NUMBERS/SOME_PATH/" ],

+    "launch": {

+      "web_url": "http://SOME_SITE/SOME_PATH/index.html"

+    }

+  },

+  "permissions": ["background", "notifications"],

+  "manifest_version": 2

+}

diff --git a/chrome/common/extensions/docs/examples/apps/calculator/LICENSE b/chrome/common/extensions/docs/examples/apps/calculator/LICENSE
new file mode 100644
index 0000000..e6c0d72
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/LICENSE
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/README.md b/chrome/common/extensions/docs/examples/apps/calculator/README.md
new file mode 100644
index 0000000..f0b0380
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/README.md
@@ -0,0 +1,11 @@
+# Calculator
+
+A sample application that provides a simple calculator. Supports basic
+operations such as addition, multiplication, subtraction and division.
+
+This sample also incorporates an MVC-style structure.
+
+## APIs
+
+* [Runtime](http://developer.chrome.com/trunk/apps/app.runtime.html)
+* [Window](http://developer.chrome.com/trunk/apps/app.window.html)
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/app/LICENSE b/chrome/common/extensions/docs/examples/apps/calculator/app/LICENSE
new file mode 100644
index 0000000..e6c0d72
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/app/LICENSE
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/app/calculator.html b/chrome/common/extensions/docs/examples/apps/calculator/app/calculator.html
new file mode 100644
index 0000000..c242f4b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/app/calculator.html
@@ -0,0 +1,58 @@
+<html>
+  <head>
+    <title>Calculator</title>
+    <link rel="stylesheet" type="text/css" href="style.css" />
+  </head>
+  <body>
+    <div id="calculator">
+      <div id="calculator-fade">
+        <div id="calculator-fade-edge"></div>
+        <div id="calculator-fade-gradient"></div>
+      </div>
+      <div id="calculator-display" role="log" aria-live="polite">
+        <div class="equation">
+          <span class="accumulator" aria-hidden="true"></span>
+          <span class="operation">
+            <span class="operator">
+              <div class="spacer"></div>
+              <div class="value"></div>
+            </span>
+            <span class="operand">0</span>
+          </span>
+        </div>
+      </div>
+      <div id="calculator-buttons">
+        <div>
+          <button class="clear" title="clear" data-button="clear"></button>
+          <button class="negate" title="negate" data-button="negate"></button>
+          <button class="divide" title="divide" data-button="divide"></button>
+          <button class="multiply" title="multiply" data-button="multiply">
+          </button>
+        </div>
+        <div>
+          <button class="seven" title="seven" data-button="seven"></button>
+          <button class="eight" title="eight" data-button="eight"></button>
+          <button class="nine" title="nine" data-button="nine"></button>
+          <button class="subtract" title="subtract" data-button="subtract">
+          </button>
+        </div>
+        <div>
+          <button class="four" title="four" data-button="four"></button>
+          <button class="five" title="five" data-button="five"></button>
+          <button class="six" title="six" data-button="six"></button>
+          <button class="add" title="add" data-button="add"></button>
+        </div>
+        <div>
+          <button class="one" title="one" data-button="one"></button>
+          <button class="two" title="two" data-button="two"></button>
+          <button class="three" title="three" data-button="three"></button>
+          <button class="equals" title="equals" data-button="equals"></button>
+        </div>
+        <div>
+          <button class="zero" title="zero" data-button="zero"></button>
+          <button class="point" title="point" data-button="point"></button>
+        </div>
+      </div>
+    </div>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/app/controller.js b/chrome/common/extensions/docs/examples/apps/calculator/app/controller.js
new file mode 100644
index 0000000..52d6458
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/app/controller.js
@@ -0,0 +1,102 @@
+/**
+ * Copyright (c) 2012 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.
+ **/
+
+// Checking for "chrome.app.runtime" availability allows this Chrome app code to
+// be tested in a regular web page (like tests/manual.html). Checking for
+// "chrome" and "chrome.app" availability further allows this code to be tested
+// in non-Chrome browsers, which is useful for example to test touch support
+// with a non-Chrome touch device.
+if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) {
+  chrome.app.runtime.onLaunched.addListener(function() {
+    chrome.app.window.create('calculator.html', {
+      defaultWidth: 243, minWidth: 243, maxWidth: 243,
+      defaultHeight: 380, minHeight: 380, maxHeight: 380,
+      id: 'calculator'
+    }, function(appWindow) {
+      appWindow.contentWindow.onload = function() {
+        new Controller(new Model(9), new View(appWindow.contentWindow));
+      };
+    });
+  });
+}
+
+function Controller(model, view) {
+  this.inputs = this.defineInputs_();
+  this.model = model;
+  this.view = view;
+  this.view.onButton = function(button) {
+    this.handleInput_(this.inputs.byButton[button]);
+  }.bind(this);
+  this.view.onKey = function(key) {
+    this.handleInput_(this.inputs.byKey[key]);
+  }.bind(this);
+}
+
+/** @private */
+Controller.prototype.defineInputs_ = function() {
+  var inputs = {byButton: {}, byKey: {}};
+  inputs.byButton['zero'] = inputs.byKey['48'] = '0';
+  inputs.byButton['one'] = inputs.byKey['49'] = '1';
+  inputs.byButton['two'] = inputs.byKey['50'] = '2';
+  inputs.byButton['three'] = inputs.byKey['51'] = '3';
+  inputs.byButton['four'] = inputs.byKey['52'] = '4';
+  inputs.byButton['five'] = inputs.byKey['53'] = '5';
+  inputs.byButton['six'] = inputs.byKey['54'] = '6';
+  inputs.byButton['seven'] = inputs.byKey['55'] = '7';
+  inputs.byButton['eight'] = inputs.byKey['56'] = '8';
+  inputs.byButton['nine'] = inputs.byKey['57'] = '9';
+  inputs.byButton['point'] = inputs.byKey['190'] = '.';
+  inputs.byButton['add'] = inputs.byKey['^187'] = '+';
+  inputs.byButton['subtract'] = inputs.byKey['189'] = '-';
+  inputs.byButton['multiply'] = inputs.byKey['^56'] = '*';
+  inputs.byButton['divide'] = inputs.byKey['191'] = '/';
+  inputs.byButton['equals'] = inputs.byKey['187'] = inputs.byKey['13'] = '=';
+  inputs.byButton['negate'] = inputs.byKey['32'] = '+ / -';
+  inputs.byButton['clear'] = inputs.byKey['67'] = 'AC';
+  inputs.byButton['back'] = inputs.byKey['8'] = 'back';
+  return inputs;
+};
+
+/** @private */
+Controller.prototype.handleInput_ = function(input) {
+  var values, accumulator, operator, operand;
+  if (input) {
+    values = this.model.handle(input);
+    accumulator = values.accumulator;
+    operator = values.operator;
+    operand = values.operand;
+    if (input === 'AC') {
+      this.view.clearDisplay({operand: '0'});
+    } else if (input === '=') {
+      this.view.addResults({accumulator: accumulator, operand: accumulator});
+    } else if (input.match(/^[+*/-]$/)) {
+      this.updateValues_({accumulator: accumulator});
+      this.view.addValues({operator: values.operator});
+    } else if (!this.updateValues_({operator: operator, operand: operand})) {
+      this.view.addValues({operator: operator, operand: operand});
+    }
+  }
+};
+
+/** @private */
+Controller.prototype.updateValues_ = function(values) {
+  // Values which are "finalized" (which have an accumulator value) shouldn't
+  // and won't be updated, and this method will return false for them.
+  var before = this.view.getValues();
+  var after = !before.accumulator ? values : {};
+  this.view.setValues({
+    accumulator: this.getUpdatedValue_(before, after, 'accumulator'),
+    operator: this.getUpdatedValue_(before, after, 'operator'),
+    operand: this.getUpdatedValue_(before, after, 'operand', !before.operator)
+  });
+  return !before.accumulator;
+}
+
+/** @private */
+Controller.prototype.getUpdatedValue_ = function(before, after, key, zero) {
+  var value = (typeof after[key] !== 'undefined') ? after[key] : before[key];
+  return zero ? (value || '0') : value;
+}
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/app/images/buttons_1x.png b/chrome/common/extensions/docs/examples/apps/calculator/app/images/buttons_1x.png
new file mode 100644
index 0000000..0963079
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/app/images/buttons_1x.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/app/images/buttons_2x.png b/chrome/common/extensions/docs/examples/apps/calculator/app/images/buttons_2x.png
new file mode 100644
index 0000000..d7d58c4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/app/images/buttons_2x.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/app/images/icon-128x128.png b/chrome/common/extensions/docs/examples/apps/calculator/app/images/icon-128x128.png
new file mode 100644
index 0000000..e9cdf63
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/app/images/icon-128x128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/app/images/icon-16x16.png b/chrome/common/extensions/docs/examples/apps/calculator/app/images/icon-16x16.png
new file mode 100644
index 0000000..c82fc5a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/app/images/icon-16x16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/app/manifest.json b/chrome/common/extensions/docs/examples/apps/calculator/app/manifest.json
new file mode 100644
index 0000000..4799a6c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/app/manifest.json
@@ -0,0 +1,12 @@
+{
+  "name": "Calculator",
+  "description": "A simple calculator.",
+  "manifest_version": 2,
+  "minimum_chrome_version": "23",
+  "version": "1.3.1",
+  "app": {"background": {"scripts": ["model.js", "view.js", "controller.js"]}},
+  "icons": {
+    "16": "images/icon-16x16.png",
+    "128": "images/icon-128x128.png"
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/app/model.js b/chrome/common/extensions/docs/examples/apps/calculator/app/model.js
new file mode 100644
index 0000000..ba7a258
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/app/model.js
@@ -0,0 +1,142 @@
+/**
+ * Copyright (c) 2012 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.
+ **/
+
+function Model(precision) {
+  this.reset_({precision: precision});
+}
+
+/**
+ * Handles a calculator key input, updating the calculator state accordingly and
+ * returning an object with 'accumulator', 'operator', and 'operand' properties
+ * representing that state.
+ *
+ * @private
+ */
+Model.prototype.handle = function(input) {
+  switch (input) {
+    case '+':
+    case '-':
+    case '/':
+    case '*':
+      // For operations, ignore the last operator if no operand was entered,
+      // otherwise perform the current calculation before setting the new
+      // operator. In either case, clear the operand and the defaults.
+      var operator = this.operand && this.operator;
+      var result = this.calculate_(operator, this.operand);
+      return this.reset_({accumulator: result, operator: input});
+    case '=':
+      // For the equal sign, perform the current calculation and save the
+      // operator and operands used as defaults, or if there is no current
+      // operator, use the default operators and operands instead. In any case,
+      // clear the operator and operand and return a transient state with a '='
+      // operator.
+      var operator = this.operator || this.defaults.operator;
+      var operand = this.operator ? this.operand : this.defaults.operand;
+      var result = this.calculate_(operator, operand);
+      var defaults = {operator: operator, operand: this.operand};
+      return this.reset_({accumulator: result, defaults: defaults});
+    case 'AC':
+      return this.reset_({});
+    case 'C':
+      return this.operand  ? this.set_({operand: null}) :
+             this.operator ? this.set_({operator: null}) :
+                             this.handle('AC');
+    case 'back':
+      var length = (this.operand || '').length;
+      return (length > 1)  ? this.set_({operand: this.operand.slice(0, -1)}) :
+             this.operand  ? this.set_({operand: null}) :
+                             this.set_({operator: null});
+    case '+ / -':
+      var initial = (this.operand || '0')[0];
+      return (initial === '-') ? this.set_({operand: this.operand.slice(1)}) :
+             (initial !== '0') ? this.set_({operand: '-' + this.operand}) :
+                                 this.set_({});
+    default:
+      var operand = (this.operand || '0') + input;
+      var duplicate = (operand.replace(/[^.]/g, '').length > 1);
+      var overflow = (operand.replace(/[^0-9]/g, '').length > this.precision);
+      return operand.match(/^0[0-9]/)  ? this.set_({operand: operand[1]}) :
+             (!duplicate && !overflow) ? this.set_({operand: operand}) :
+                                         this.set_({});
+  }
+}
+
+/**
+ * Reset the model's state to the passed in state.
+ *
+ * @private
+ */
+Model.prototype.reset_ = function(state) {
+  this.accumulator = this.operand = this.operator = null;
+  this.defaults = {operator: null, operand: null};
+  return this.set_(state);
+}
+
+/**
+ * Selectively replace the model's state with the passed in state.
+ *
+ * @private
+ */
+Model.prototype.set_ = function(state) {
+  var ifDefined = function(x, y) { return (x !== undefined) ? x : y; };
+  var precision = (state && state.precision) || this.precision || 9;
+  this.precision = Math.min(Math.max(precision, 1), 9);
+  this.accumulator = ifDefined(state && state.accumulator, this.accumulator);
+  this.operator = ifDefined(state && state.operator, this.operator);
+  this.operand = ifDefined(state && state.operand, this.operand);
+  this.defaults = ifDefined(state && state.defaults, this.defaults);
+  return this;
+}
+
+/**
+ * Performs a calculation based on the passed in operator and operand, updating
+ * the model's state with the operator and operand used but returning the result
+ * of the calculation instead of updating the model's state with it.
+ *
+ * @private
+ */
+Model.prototype.calculate_ = function(operator, operand) {
+  var x = Number(this.accumulator) || 0;
+  var y = operand ? Number(operand) : x;
+  this.set_({accumulator: String(x), operator: operator, operand: String(y)});
+  return (this.operator == '+') ? this.round_(x + y) :
+         (this.operator == '-') ? this.round_(x - y) :
+         (this.operator == '*') ? this.round_(x * y) :
+         (this.operator == '/') ? this.round_(x / y) :
+                                  this.round_(y);
+}
+
+/**
+ * Returns the string representation of the passed in value rounded to the
+ * model's precision, or "E" on overflow.
+ *
+ * @private
+ */
+Model.prototype.round_ = function(x) {
+  var exponent = Number(x.toExponential(this.precision - 1).split('e')[1]);
+  var digits = this.digits_(exponent);
+  var exponential = x.toExponential(digits).replace(/\.?0+e/, 'e');
+  var fixed = (Math.abs(exponent) < this.precision && exponent > -7);
+  return !digits ? 'E' : fixed ? String(Number(exponential)) : exponential;
+}
+
+/**
+ * Returns the appropriate number of digits to include of a number based on
+ * its size.
+ *
+ * @private
+ */
+Model.prototype.digits_ = function(exponent) {
+  return (isNaN(exponent) || exponent < -199 || exponent > 199) ? 0 :
+         (exponent < -99) ? (this.precision - 1 - 5) :
+         (exponent < -9) ? (this.precision - 1 - 4) :
+         (exponent < -6) ? (this.precision - 1 - 3) :
+         (exponent < 0) ? (this.precision - 1 + exponent) :
+         (exponent < this.precision) ? (this.precision - 1) :
+         (exponent < 10) ? (this.precision - 1 - 3) :
+         (exponent < 100) ? (this.precision - 1 - 4) :
+                            (this.precision - 1 - 5);
+}
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/app/style.css b/chrome/common/extensions/docs/examples/apps/calculator/app/style.css
new file mode 100644
index 0000000..e499166
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/app/style.css
@@ -0,0 +1,308 @@
+/**
+ * Copyright (c) 2012 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.
+ **/
+
+body {
+  font: bold 16px
+        'Open Sans', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
+  margin: 0;
+  overflow: hidden;
+}
+
+#calculator {
+  height: 100%;
+  width: 100%;
+}
+
+#calculator-buttons {
+  background: gray;
+  bottom: 0;
+  height: 225px;
+  left: 0;
+  position: absolute;
+  right: 0;
+}
+
+#calculator-buttons div {
+  font-size: 0;
+  margin: 0 auto;
+  position: relative;
+  width: 243px;
+}
+
+#calculator-buttons button {
+  border: none;
+  height: 45px;
+  width: 61px;
+}
+
+@media all and (-webkit-max-device-pixel-ratio: 1.5) {
+  #calculator-buttons button {
+    background: url('images/buttons_1x.png');
+    background-size: 486px 225px;  /* Chrome requires this be defined _with_ */
+  }                                /* or _after_ the image, Safari requires */
+}                                  /* _before_ or _after_, so using _after_. */
+
+@media all and (-webkit-min-device-pixel-ratio: 1.5) {
+  #calculator-buttons button {
+    background: url('images/buttons_2x.png');
+    background-size: 486px 225px;  /* Chrome requires this be defined _with_ */
+  }                                /* or _after_ the image, Safari requires */
+}                                  /* _before_ or _after_, so using _after_. */
+
+#calculator-buttons button.add {
+  background-position: -183px -90px;
+  width: 60px;
+}
+
+#calculator-buttons button.add[data-active="touch"],
+#calculator-buttons button.add[data-active="mouse"]:active {
+  background-position: -426px -90px;
+}
+
+#calculator-buttons button.clear {
+  /* The default background-position: 0 0; is fine */
+}
+
+#calculator-buttons button.clear[data-active="touch"],
+#calculator-buttons button.clear[data-active="mouse"]:active {
+  background-position: -243px 0;
+}
+
+#calculator-buttons button.divide {
+  background-position: -122px 0;
+}
+
+#calculator-buttons button.divide[data-active="touch"],
+#calculator-buttons button.divide[data-active="mouse"]:active {
+  background-position: -365px 0;
+}
+
+#calculator-buttons button.eight {
+  background-position: -61px -45px;
+}
+
+#calculator-buttons button.eight[data-active="touch"],
+#calculator-buttons button.eight[data-active="mouse"]:active {
+  background-position: -304px -45px;
+}
+
+#calculator-buttons button.equals {
+  background-position: -183px -135px;
+  height: 90px;
+  margin: 0 0 -45px;
+  width: 60px;
+}
+
+#calculator-buttons button.equals[data-active="touch"],
+#calculator-buttons button.equals[data-active="mouse"]:active {
+  background-position: -426px -135px;
+}
+
+#calculator-buttons button.five {
+  background-position: -61px -90px;
+}
+
+#calculator-buttons button.five[data-active="touch"],
+#calculator-buttons button.five[data-active="mouse"]:active {
+  background-position: -304px -90px;
+}
+
+#calculator-buttons button.four {
+  background-position: 0 -90px;
+}
+
+#calculator-buttons button.four[data-active="touch"],
+#calculator-buttons button.four[data-active="mouse"]:active {
+  background-position: -243px -90px;
+}
+
+#calculator-buttons button.multiply {
+  background-position: -183px 0;
+  width: 60px;
+}
+
+#calculator-buttons button.multiply[data-active="touch"],
+#calculator-buttons button.multiply[data-active="mouse"]:active {
+  background-position: -426px 0;
+}
+
+#calculator-buttons button.negate {
+  background-position: -61px 0;
+}
+
+#calculator-buttons button.negate[data-active="touch"],
+#calculator-buttons button.negate[data-active="mouse"]:active {
+  background-position: -304px 0;
+}
+
+#calculator-buttons button.nine {
+  background-position: -122px -45px;
+}
+
+#calculator-buttons button.nine[data-active="touch"],
+#calculator-buttons button.nine[data-active="mouse"]:active {
+  background-position: -365px -45px;
+}
+
+#calculator-buttons button.one {
+  background-position: 0 -135px;
+}
+
+#calculator-buttons button.one[data-active="touch"],
+#calculator-buttons button.one[data-active="mouse"]:active {
+  background-position: -243px -135px;
+}
+
+#calculator-buttons button.point {
+  background-position: -122px -180px;
+}
+
+#calculator-buttons button.point[data-active="touch"],
+#calculator-buttons button.point[data-active="mouse"]:active {
+  background-position: -365px -180px;
+}
+
+#calculator-buttons button.seven {
+  background-position: 0 -45px;
+}
+
+#calculator-buttons button.seven[data-active="touch"],
+#calculator-buttons button.seven[data-active="mouse"]:active {
+  background-position: -243px -45px;
+}
+
+#calculator-buttons button.six {
+  background-position: -122px -90px;
+}
+
+#calculator-buttons button.six[data-active="touch"],
+#calculator-buttons button.six[data-active="mouse"]:active {
+  background-position: -365px -90px;
+}
+
+#calculator-buttons button.subtract {
+  background-position: -183px -45px;
+  width: 60px;
+}
+
+#calculator-buttons button.subtract[data-active="touch"],
+#calculator-buttons button.subtract[data-active="mouse"]:active {
+  background-position: -426px -45px;
+}
+
+#calculator-buttons button.three {
+  background-position: -122px -135px;
+}
+
+#calculator-buttons button.three[data-active="touch"],
+#calculator-buttons button.three[data-active="mouse"]:active {
+  background-position: -365px -135px;
+}
+
+#calculator-buttons button.two {
+  background-position: -61px -135px;
+}
+
+#calculator-buttons button.two[data-active="touch"],
+#calculator-buttons button.two[data-active="mouse"]:active {
+  background-position: -304px -135px;
+}
+
+#calculator-buttons button.zero {
+  background-position: 0 -180px;
+  width: 122px;
+}
+
+#calculator-buttons button.zero[data-active="touch"],
+#calculator-buttons button.zero[data-active="mouse"]:active {
+  background-position: -243px -180px;
+}
+
+#calculator-display,
+#calculator-display:focus {
+  background: white;
+  bottom: 225px;
+  left: auto;
+  letter-spacing: 1px;
+  overflow: scroll;
+  padding: 20px;
+  position: absolute;
+  right: auto;
+  top: 0;
+  width: 203px;
+}
+
+#calculator-display .equation {
+  display: table;  /* Table display is required to allow baseline vertical */
+                   /* alignment of accumulator, operator, and operand text */
+                   /* with different font sizes whose actual pixel heights */
+                   /* will be different on different platforms. */
+  height: 22px;
+  padding: 0 0 6px;
+  width: 100%;
+}
+
+#calculator-display .equation * {
+  display: table-cell;
+  text-align: left;
+  vertical-align: baseline;
+}
+
+#calculator-display .equation .accumulator {
+  color: rgb(136, 136, 136);
+  font-size: 13px;
+  width: 100%;
+}
+
+#calculator-display .equation .operation {
+  color: rgb(44, 44, 44);
+  display: table;
+}
+
+#calculator-display .equation .operation .operator .spacer {
+  display: block;  /* Keeps operator 15px wide even though it's a table-cell. */
+  height: 0px;
+  width: 15px;
+}
+
+#calculator-display .equation .operation .operator .value {
+  display: block;
+}
+
+#calculator-display .hr {
+  border-top: 1px solid rgb(217, 217, 217);
+  height: 0;
+  position: relative;
+  top: -6px;
+  width: 100%;
+}
+
+#calculator-display .hr + .equation {
+  margin: -1px 0 0;  /* Keeps spacing between lines uniform even with .hr. */
+}
+
+#calculator-fade {
+  left: 0;
+  position: absolute;
+  right: 0;
+  top: 0;
+  z-index: 99;
+}
+
+#calculator-fade-edge {
+  background: white;
+  height: 5px;
+}
+
+#calculator-fade-gradient {
+  background: -webkit-linear-gradient(rgba(255, 255, 255, 1),
+                                      rgba(255, 255, 255, 0));
+  height: 20px;
+}
+
+::-webkit-scrollbar {
+  display: none;
+}
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/app/view.js b/chrome/common/extensions/docs/examples/apps/calculator/app/view.js
new file mode 100644
index 0000000..e24b588
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/app/view.js
@@ -0,0 +1,153 @@
+/**
+ * Copyright (c) 2012 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.
+ **/
+
+function View(window) {
+  this.display = window.document.querySelector('#calculator-display');
+  this.buttons = window.document.querySelectorAll('#calculator-buttons button');
+  window.addEventListener('keydown', this.handleKey_.bind(this));
+  Array.prototype.forEach.call(this.buttons, function(button) {
+    button.addEventListener('click', this.handleClick_.bind(this));
+    button.addEventListener('mousedown', this.handleMouse_.bind(this));
+    button.addEventListener('touchstart', this.handleTouch_.bind(this));
+    button.addEventListener('touchmove', this.handleTouch_.bind(this));
+    button.addEventListener('touchend', this.handleTouchEnd_.bind(this));
+    button.addEventListener('touchcancel', this.handleTouchEnd_.bind(this));
+  }, this);
+}
+
+View.prototype.clearDisplay = function(values) {
+  this.display.innerHTML = '';
+  this.addValues(values);
+};
+
+View.prototype.addResults = function(values) {
+  this.appendChild_(this.display, null, 'div', 'hr');
+  this.addValues(values);
+};
+
+View.prototype.addValues = function(values) {
+  var equation = this.makeElement_('div', 'equation');
+  this.appendChild_(equation, null, 'span', 'accumulator', values.accumulator);
+  this.appendChild_(equation, null, 'span', 'operation');
+  this.appendChild_(equation, '.operation', 'span', 'operator');
+  this.appendChild_(equation, '.operation', 'span', 'operand', values.operand);
+  this.appendChild_(equation, '.operator', 'div', 'spacer');
+  this.appendChild_(equation, '.operator', 'div', 'value', values.operator);
+  this.setAttribute_(equation, '.accumulator', 'aria-hidden', 'true');
+  this.display.appendChild(equation).scrollIntoView();
+};
+
+View.prototype.setValues = function(values) {
+  var equation = this.display.lastElementChild;
+  this.setContent_(equation, '.accumulator', values.accumulator || '');
+  this.setContent_(equation, '.operator .value', values.operator || '');
+  this.setContent_(equation, '.operand', values.operand || '');
+};
+
+View.prototype.getValues = function() {
+  var equation = this.display.lastElementChild;
+  return {
+    accumulator: this.getContent_(equation, '.accumulator') || null,
+    operator: this.getContent_(equation, '.operator .value') || null,
+    operand: this.getContent_(equation, '.operand') || null,
+  };
+};
+
+/** @private */
+View.prototype.handleKey_ = function(event) {
+  this.onKey.call(this, event.shiftKey ? ('^' + event.which) : event.which);
+}
+
+/** @private */
+View.prototype.handleClick_ = function(event) {
+  this.onButton.call(this, event.target.dataset.button)
+}
+
+/** @private */
+View.prototype.handleMouse_ = function(event) {
+  event.target.setAttribute('data-active', 'mouse');
+}
+
+/** @private */
+View.prototype.handleTouch_ = function(event) {
+  event.preventDefault();
+  this.handleTouchChange_(event.touches[0]);
+}
+
+/** @private */
+View.prototype.handleTouchEnd_ = function(event) {
+  this.handleTouchChange_(null);
+}
+
+/** @private */
+View.prototype.handleTouchChange_ = function(location) {
+  var previous = this.touched;
+  if (!this.isInButton_(previous, location)) {
+    this.touched = this.findButtonContaining_(location);
+    if (previous)
+      previous.removeAttribute('data-active');
+    if (this.touched) {
+      this.touched.setAttribute('data-active', 'touch');
+      this.onButton.call(this, this.touched.dataset.button);
+    }
+  }
+}
+
+/** @private */
+View.prototype.findButtonContaining_ = function(location) {
+  var found;
+  for (var i = 0; location && i < this.buttons.length && !found; ++i) {
+    if (this.isInButton_(this.buttons[i], location))
+      found = this.buttons[i];
+  }
+  return found;
+}
+
+/** @private */
+View.prototype.isInButton_ = function(button, location) {
+  var bounds = location && button && button.getClientRects()[0];
+  var x = bounds && location.clientX;
+  var y = bounds && location.clientY;
+  var x1 = bounds && bounds.left;
+  var x2 = bounds && bounds.right;
+  var y1 = bounds && bounds.top;
+  var y2 = bounds && bounds.bottom;
+  return (bounds && x >= x1 && x < x2 && y >= y1 && y < y2);
+}
+
+/** @private */
+View.prototype.makeElement_ = function(tag, classes, content) {
+  var element = this.display.ownerDocument.createElement(tag);
+  element.setAttribute('class', classes);
+  element.textContent = content || '';
+  return element;
+};
+
+/** @private */
+View.prototype.appendChild_ = function(root, selector, tag, classes, content) {
+  var parent = (root && selector) ? root.querySelector(selector) : root;
+  parent.appendChild(this.makeElement_(tag, classes, content));
+};
+
+/** @private */
+View.prototype.setAttribute_ = function(root, selector, name, value) {
+  var element = root && root.querySelector(selector);
+  if (element)
+    element.setAttribute(name, value);
+};
+
+/** @private */
+View.prototype.setContent_ = function(root, selector, content) {
+  var element = root && root.querySelector(selector);
+  if (element)
+    element.textContent = content || '';
+};
+
+/** @private */
+View.prototype.getContent_ = function(root, selector) {
+  var element = root && root.querySelector(selector);
+  return element ? element.textContent : null;
+};
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/assets/440-280-tile.png b/chrome/common/extensions/docs/examples/apps/calculator/assets/440-280-tile.png
new file mode 100644
index 0000000..de78fd5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/assets/440-280-tile.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/assets/calculator-1x.psd b/chrome/common/extensions/docs/examples/apps/calculator/assets/calculator-1x.psd
new file mode 100644
index 0000000..77622aa
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/assets/calculator-1x.psd
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/assets/calculator-2x.psd b/chrome/common/extensions/docs/examples/apps/calculator/assets/calculator-2x.psd
new file mode 100644
index 0000000..0e11c14
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/assets/calculator-2x.psd
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/assets/promo.png b/chrome/common/extensions/docs/examples/apps/calculator/assets/promo.png
new file mode 100644
index 0000000..ca0fee9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/assets/promo.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/tests/automatic.html b/chrome/common/extensions/docs/examples/apps/calculator/tests/automatic.html
new file mode 100644
index 0000000..ba439ef
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/tests/automatic.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <script type="text/javascript" src="../app/model.js"></script>
+    <script type="text/javascript" src="../app/view.js"></script>
+    <script type="text/javascript" src="../app/controller.js"></script>
+    <script type="text/javascript" src="utilities.js"></script>
+    <script type="text/javascript" src="tests.js"></script>
+  </head>
+  <body>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/tests/manual.css b/chrome/common/extensions/docs/examples/apps/calculator/tests/manual.css
new file mode 100644
index 0000000..fba83cb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/tests/manual.css
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2012 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.
+ **/
+
+body {
+  font-family: "Open Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
+  margin: 8px;
+}
+
+.bottom {
+  background: LightSteelBlue;
+  height: 1em;
+}
+
+.browser {
+  background: SteelBlue;
+  font-size: 80%;
+  font-weight: normal;
+  text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+  padding: 0.5em 0 0.5em 1.25em;
+}
+
+.details {
+  color: black;
+}
+
+.failure, .failure .success {
+  background: LightCoral;
+}
+
+.header {
+  color: GhostWhite;
+  padding: 0;
+}
+
+li {
+  margin: 0.5em 0;
+  padding: 0;
+  font-weight: bold;
+}
+
+li.failure .difference {
+  background: IndianRed;
+}
+
+li li {
+  margin: 0;
+  padding: 0;
+}
+
+li li p {
+  font-weight: normal;
+}
+
+li p {
+  margin: 0;
+}
+
+ol {
+  list-style: decimal inside;
+  margin: 0;
+  padding: 0;
+}
+
+ol p {
+  font-style: italic;
+  padding: 1em 1em 0 1em;
+}
+
+ol ol {
+  list-style: lower-alpha;
+  padding: 0.5em 0.5em 0.5em 4em;
+}
+
+ol ol p {
+  font-family: "Courier", "Courier New", monospace;
+  font-style: normal;
+  padding: 0;
+  margin: 0 0 0 0.5em;
+  white-space: pre;
+}
+
+.status {
+  height: 0.4em;
+  padding: 0;
+}
+
+.success {
+  background: LightGreen;
+}
+
+.summary {
+  background: WhiteSmoke;
+  color: Black;
+  padding: 0.5em 0 0.5em 1em;
+  position: static;
+}
+
+.summary .results .failed {
+  color: LightCoral;
+  text-shadow: rgba(0, 0, 0, 1) 1px 1px 1px;
+}
+
+.summary .results .failed.none {
+  color: Black;
+  text-shadow: none;
+}
+
+.summary .results .passed {
+  color: LightGreen;
+  text-shadow: rgba(0, 0, 0, 1) 1px 1px 1px;
+}
+
+.summary .results .passed.none {
+  color: Black;
+  text-shadow: none;
+}
+
+.title {
+  background: rgb(34, 34, 85);
+  border-radius: 8px 8px 0 0;
+  cursor: default;
+  font-size: 125%;
+  line-height: 1em;
+  margin: 0;
+  -webkit-user-select: none;
+  font-weight: lighter;
+  padding: 0.5em 0 0.5em 0.8em;
+}
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/tests/manual.html b/chrome/common/extensions/docs/examples/apps/calculator/tests/manual.html
new file mode 100644
index 0000000..eb0cbcb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/tests/manual.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>Calculator Chrome App</title>
+    <link rel="stylesheet" type="text/css" href="manual.css">
+    <script type="text/javascript" src="../app/model.js"></script>
+    <script type="text/javascript" src="../app/view.js"></script>
+    <script type="text/javascript" src="../app/controller.js"></script>
+    <script type="text/javascript" src="utilities.js"></script>
+    <script type="text/javascript" src="tests.js"></script>
+    <script type="text/javascript" src="manual.js"></script>
+  </head>
+  <body>
+    <div class="header">
+      <div class="title">Tests</div>
+      <div class="status"></div>
+      <div class="summary">
+        <div class="execution">
+          <span class="count"></span>&nbsp;tests run in&nbsp;
+          <span class="duration"></span>&nbsp;ms
+        </div>
+        <div class="results">
+          <span class="passed"></span>&nbsp;passed
+          <span class="failed"></span>&nbsp;failed
+        </div>
+      </div>
+      <div class="browser"></div>
+      <div class="bottom"></div>
+    </div>
+    <div class="details">
+      <ol></ol>
+    </div>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/tests/manual.js b/chrome/common/extensions/docs/examples/apps/calculator/tests/manual.js
new file mode 100644
index 0000000..61bb4e5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/tests/manual.js
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2012 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.
+ **/
+
+window.onload = function() {
+  var classes = {true: 'success', false: 'failure'};
+  var status = document.querySelector('.status');
+  var summary = document.querySelector('.summary');
+  var execution = summary.querySelector('.execution');
+  var count = execution.querySelector('.count');
+  var duration = execution.querySelector('.duration');
+  var results = summary.querySelector('.results');
+  var passed = results.querySelector('.passed');
+  var failed = results.querySelector('.failed');
+  var browser = document.querySelector('.browser');
+  var details = document.querySelector('.details ol');
+  var start = Date.now();
+  var run = window.runTests(false);
+  var end = Date.now();
+  var counts = {passed: 0, failed: 0};
+  var tests = [];
+  var step;
+  for (var i = 0; i < run.tests.length; ++i) {
+    tests[i] = document.createElement('li');
+    tests[i].setAttribute('class', classes[run.tests[i].success]);
+    tests[i].appendChild(document.createElement('p'));
+    tests[i].children[0].textContent = run.tests[i].name;
+    tests[i].appendChild(document.createElement('ol'));
+    counts.passed += run.tests[i].success ? 1 : 0;
+    counts.failed += run.tests[i].success ? 0 : 1;
+    for (var j = 0; j < run.tests[i].steps.length; ++j) {
+      step = document.createElement('li');
+      tests[i].children[1].appendChild(step);
+      step.setAttribute('class', classes[run.tests[i].steps[j].success]);
+      step.appendChild(document.createElement('p'));
+      step.children[0].textContent = run.tests[i].steps[j].messages[0];
+      for (var k = 1; k < run.tests[i].steps[j].messages.length; ++k) {
+        step.appendChild(document.createElement('p'));
+        step.children[k].textContent = run.tests[i].steps[j].messages[k];
+        step.children[k].setAttribute('class', 'difference');
+      }
+    }
+  }
+  status.setAttribute('class', 'status ' + classes[run.success]);
+  count.textContent = run.tests.length;
+  duration.textContent = end - start;
+  passed.textContent = counts.passed;
+  passed.setAttribute('class', counts.passed ? 'passed' : 'passed none');
+  failed.textContent = counts.failed;
+  failed.setAttribute('class', counts.failed ? 'failed' : 'failed none');
+  browser.textContent = window.navigator.userAgent;
+  tests.forEach(function(test) { details.appendChild(test); });
+}
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/tests/tests.js b/chrome/common/extensions/docs/examples/apps/calculator/tests/tests.js
new file mode 100644
index 0000000..1439601
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/tests/tests.js
@@ -0,0 +1,258 @@
+/**
+ * Copyright (c) 2012 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.
+ **/
+
+window.runTests = function(log) {
+  var run = window.calculatorTestRun.create();
+/*
+  // ---------------------------------------------------------------------------
+  // Test fixes for <http://crbug.com/156448>:
+
+  run.test('Twenty eight can be divided by three', '28 / 3 = [9.3333333]');
+  run.test('Twenty nine can be divided by three', '29 / 3 = [9.6666667]');
+  run.test('Thirty can be divided by three', '30 / 3 = [10]');
+  run.test('Thirty one can be divided by three', '31 / 3 = [10.333333]');
+  run.test('Thirty two can be divided by three', '32 / 3 = [10.666667]');
+  run.test('Thirty three can be divided by three', '33 / 3 = [11]');
+
+  // ---------------------------------------------------------------------------
+  // Test fixes for <http://crbug.com/156449>:
+
+  // run.test('Equals without operator results in operand value',
+  //          '123 = [123]');
+
+  // run.test('Operations without operands uses default operands',
+  //          '2 + = [[2 _ 2][_ + 2][][4 _ 4]]');
+
+  // TODO(dharcourt): Test the display for the expected output.
+  // run.test('Successive operators replace each other.',
+  //          '123 + - * / [123 / _] and no previous [* + *]');
+
+  // ---------------------------------------------------------------------------
+  // Test fixes for <http://crbug.com/156450>:
+
+  // run.test('Operand can be erased and replaced',
+  //          '123 + 456 < < < < 789 = [789]');
+
+  // TODO(dharcourt): Test the display for the expected output.
+  // run.test('Operators can be erased and replaced.',
+  //          '123 + 456 < < < < < * 2 = [246]');
+
+  // TODO(dharcourt): Test the display for the expected output.
+  // run.test('Erase is ignored after equals.', '123 + 456 = < [579]');
+  // run.test('Erase is ignored after zero result.', '123 - 123 = < [9]');
+
+  // TODO(dharcourt): Test the display for the expected output.
+  // run.test('Erasing an operand makes it blank.',
+  //          '123 + 456 < < < [_ + _]');
+
+  // ---------------------------------------------------------------------------
+  // Test fixes for <http://crbug.com/156451>:
+
+  // run.test('Negation applies to zero', '~ [[-0]]');
+  // run.test('Negation applies before input', '~ 123 = [[-123]]');
+  // run.test('Negation applies after input is erased',
+  //          '123 < < < ~ 456 [[-456]]');
+  // run.test('Negation is preserved when input is erased',
+  //          '123 ~ < < < 456 [[-456]]');
+  // run.test('Negation supports small values',
+  //          '0.0000001 ~ [[-0.0000001]]');
+
+  // run.test('Addition resets negated zeros', '~ + [_ + [0]]');
+  // run.test('Subtraction resets negated zeros', '~ - [_ - [0]]');
+  // run.test('Multiplication resets negated zeros', '~ * [_ * [0]]');
+  // run.test('Division resets negated zeros', '~ / [_ / [0]]');
+  // run.test('Equals resets negated zeros', '~ = [0 _ [0]]');
+
+  // ---------------------------------------------------------------------------
+  // Test fixes for <http://crbug.com/156452>:
+
+  // TODO(dharcourt): Test the display for the expected output.
+  // TODO(dharcourt): Make the test utilities support 'error'.
+  // run.test('Errors results are spelled out', '1 / 0 = [[error]]');
+  // run.test('Addition treats errors as zero',
+  //          '1 / 0 = [error] + [0 + [0]] 123 = [123]');
+  // run.test('Subtraction treats errors as zero',
+  //          '1 / 0 = [error] - [0 - [0]] 123 = [-123]');
+  // run.test('Multiplication treats errors as zero',
+  //          '1 / 0 = [error] * [0 * [0]] 123 = [0]');
+  // run.test('Division treats errors as zero',
+  //          '1 / 0 = [error] / [0 / [0]] 123 = [0]');
+  // run.test('Equals treats errors as zero',
+  //         '1 / 0 = [error] = [0]');
+
+  // ---------------------------------------------------------------------------
+  // Test fixes for <http://crbug.com/156453>:
+
+  // run.test('Common operations are reversible',
+  //          '1 / 3 * 3 = [[1]]');
+
+  // run.test('Large numbers are displayed as exponentials',
+  //          '12345678 * 10 = [[1.23456e8]]');
+  // run.test('Small numbers are displayed as exponentials',
+  //          '0.0000001 / 10 = [[1e-8]]');
+
+  // ---------------------------------------------------------------------------
+  // Other tests.
+  // TODO(dharcourt): Organize and beef up these tests.
+  // TODO(dharcourt): Test {nega,posi}tive {under,over}flows.
+
+  run.test("Initialization", function(controller) {
+    run.verify(null, controller.model.accumulator, 'Accumulator');
+    run.verify(null, controller.model.operator, 'Operator');
+    run.verify(null, controller.model.operand, 'Operand');
+    run.verify(null, controller.model.defaults.operator, 'Default Operator');
+    run.verify(null, controller.model.defaults.operand, 'Defaults Operand');
+  });
+
+  run.test("AC", '1 + 2 = [3] 4 A [[_ _ 0]]');
+
+  run.test("back", '1 + 2 < [_ + _] < [_ _ _] < [_ _ 0]');
+  // TODO(dharcourt@chromium.org): The previous lines should be:
+  //   '1 + 2 < [_ + _] < [_ _ 0] < [_ _ 0]'
+  // TODO(dharcourt@chromium.org): Test more AC, C, back
+
+  run.test("Miscellaneous Test A",
+           '2 [_ _ 2] + [[2 _ 2][_ + _]] = [[2 _ 2][_ + _][][4 _ 4]]' +
+           '          + [[2 _ 2][_ + _][][4 _ 4][_ + _]]' +
+           '          = [[2 _ 2][_ + _][][4 _ 4][_ + _][][8 _ 8]]' +
+           '          = [[2 _ 2][_ + _][][4 _ 4][_ + _][][8 _ 8][][12 _ 12]]');
+  // TODO(dharcourt@chromium.org): The previous lines should be:
+  //     '2 [_ _ 2] + [[2 _ 2][_ + _]] = [[2 _ 2][_ + 2][][4 _ 4]]' +
+  //     '          + [[2 _ 2][_ + 2][][4 _ 4][_ + _]]' +
+  //     '          = [[2 _ 2][_ + 2][][4 _ 4][_ + 4][][8 _ 8]]' +
+  //     '          = [[2 _ 2][_ + 2][][4 _ 4][_ + 4][][8 _ 8][][12 _ 12]]');
+*/
+  run.test("Miscellaneous Test B", '2 * = [4] * [_ * _] = [16] = [64]');
+
+  run.test("Miscellaneous Test C", '0.020202020 [_ _ 0.0202020==]');
+
+  run.test("Miscellaneous Test D", '.2 [_ _ 0 .2]');
+
+  run.test("Miscellaneous Test E", '0.00000014 [_ _ 0.0000001=]');
+
+  run.test("Miscellaneous Test F", '0.10000004 [_ _ 0.1000000=]');
+
+  run.test("Miscellaneous Test G", '0.12312312 [_ _ 0.1231231=]');
+
+  run.test("Miscellaneous Test H", '1.231231234 [_ _ 1.2312312==]');
+
+  run.test("Miscellaneous Test I", '123.1231234 [_ _ 123.12312==]');
+
+  run.test("Miscellaneous Test J", '123123.1234 [_ _ 123123.12==]');
+
+  run.test("Miscellaneous Test K", '12312312.34 [_ _ 12312312.==]');
+
+  run.test("Miscellaneous Test L", '12312312.04 [_ _ 12312312.==]');
+
+  run.test("Miscellaneous Test M", '1231231234 [_ _ 12312312==]');
+
+  run.test("Miscellaneous Test N", '1 + 1 + [[1 _ 1][2 + 1][_ + _]] = [4]');
+
+  run.test("Miscellaneous Test O", '1 + 1 [_ + 1] 2 [_ + 1 2]');
+
+  run.test("Positive + Positive", '82959 + 4 = [82963]');
+
+  run.test("Positive + Negative", '123 + 456~ = [-333]');
+
+  run.test("Negative + Positive", '502~ + 385 = [-117]');
+
+  run.test("Negative + Negative", '4296~ + 32~ = [-4328]');
+
+  run.test("Positive + Zero", '23650 + 0 = [23650]');
+
+  run.test("Negative + Zero", '489719~ + 0 = [-489719]');
+
+  run.test("Zero + Positive", '0 + 4296 = [4296]');
+
+  run.test("Zero + Negative", '0 + 322~ = [-322]');
+
+  run.test("Zero + Zero", '0 + 0 = [0]');
+
+  run.test("Addition Chain",
+           '+ 5 + 3~ + [2] 2~ + [0] 1~ + [-1] 3 + [2] 0 = [2]');
+
+  run.test("Positive - Positive", '4534 - 327 = [4207]');
+
+  run.test("Subtraction Chain",
+           '- 5 - [-5] 3~ - [-2] 2~ - [0] 1~ - [1] 3 - [-2] 0 = [-2]');
+
+  run.test("Positive * Positive", '7459 * 660 = [4922940]');
+
+  run.test("Multiplication Chain",
+           '* 5 = [0] 1 * [1] 5 * [5] 2~ ' +
+           '* [-10] 1.5 * [-15] 1~ * [15] 0 = [0]');
+
+  run.test("Positive / Positive", '181 / 778 = [0.2326478]');
+
+  run.test("Division Chain",
+           '/ 5 = [0] 1 / [1] 5 / [0.2] 2~ ' +
+           '/ [-0.1] 0.1 / [-1] 1~ / [1] 0 = [E]');
+
+  run.test("Positive Decimal Plus Positive Decimal",
+           '7.48 + 8.2 = [15.68]');
+
+  run.test("Decimals with Leading Zeros",
+           '0.0023 + 0.082 = [0.0843]');
+
+  run.test("Addition and Subtraction Chain",
+           '4 + [4] 1055226 - [1055230] 198067 = [857163]');
+
+  run.test("Multiplication and Division Chain",
+           '580 * [580] 544 / [315520] 64 = [4930]');
+
+  run.test("Addition After Equals",
+           '5138 + 3351301 = [3356439] 550  + 62338 = [62888]');
+
+  run.test("Missing First Operand in Addition", '+ 9701389 = [9701389]');
+
+  run.test("Missing First Operand in Subtraction", '- 1770 = [-1770]');
+
+  run.test("Missing First Operand in Multiplication", '* 65146 = [0]');
+
+  run.test("Missing First Operand in Division", '/ 8 = [0]');
+
+  run.test("Missing Second Operand in Addition",
+           '28637 + = [[28637 _ 28637][_ + _][][57274 _ 57274]]');
+           // TODO(dharcourt): Previous line should be:
+           //        '28637 + = [[28637 _ 28637][_ + 28637][][57274 _ 57274]');
+
+  run.test("Missing Second Operand in Subtraction",
+           '52288 - = [[52288 _ 52288][_ - _][][0 _ 0]]');
+           // TODO(dharcourt): Previous line should be:
+           //        '52288 - = [[52288 _ 52288][_ - 52288][][0 _ 0]]');
+
+  run.test("Missing Second Operand in Multiplication",
+           '17 * = [17 _ 17][_ * _][][289]');
+           // TODO(dharcourt): Previous line should be:
+           //        '17 * = [17 _ 17][_ * 17][][289]');
+
+  run.test("Missing Second Operand in Division",
+           '47 / = [47 _ 47][_ / _][][1]');
+           // TODO(dharcourt): Previous line should be:
+           //        '47 / = [47 _ 47][_ / 47][][1]');
+
+  run.test("Missing Last Operand in Addition and Subtraction Chain",
+           '8449 + 4205138 - [4213587] = [0]');
+
+/* TODO(dharcourt): Fix & reactivate these:
+
+  run.test("Leading zeros should be collapsed",
+           '0 [_ _ [0]] 0 [_ _ [0]] 0 [_ _ [0]] ' +
+           '2 [_ _ [2]] 0 [_ _ [2 0]] + [20 + _] ' +
+           '0 [20 + [0]] 0 [20 + [0]] 0 [20 + [0]] ' +
+           '2 [20 + [2]] 0 [20 + [2 0]] = [40 _ _]');
+
+  run.test("Test utilities should correctly predict zeros collapse",
+           '000 [[0==]] 20 [[20]] + 000 [[0==]] 20 [[20]] = [40]' +
+           '00020 + 00020 = [40]');
+
+*/
+
+  if (log || typeof log === 'undefined')
+    run.log();
+
+  return run;
+};
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/tests/utilities.js b/chrome/common/extensions/docs/examples/apps/calculator/tests/utilities.js
new file mode 100644
index 0000000..53f3e8b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/tests/utilities.js
@@ -0,0 +1,501 @@
+/**
+ * Copyright (c) 2012 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.
+ *
+ * The utility class defined in this file allow calculator tests to be written
+ * more succinctly.
+ *
+ * Tests that would be written with QUnit like this:
+ *
+ *   test('Two Plus Two', function() {
+ *     var mock = window.mockView.create();
+ *     var controller = new Controller(new Model(8), mock);
+ *     deepEqual(mock.testButton('2'), [null, null, '2'], '2');
+ *     deepEqual(mock.testButton('+'), ['2', '+', null], '+');
+ *     deepEqual(mock.testButton('2'), ['2', '+', '2'], '2');
+ *     deepEqual(mock.testButton('='), ['4', '=', null], '=');
+ *   });
+ *
+ * can instead be written as:
+ *
+ *   var run = calculatorTestRun.create();
+ *   run.test('Two Plus Two', '2 + 2 = [4]');
+ */
+
+'use strict';
+
+window.mockView = {
+
+  create: function() {
+    var view = Object.create(this);
+    view.display = [];
+    return view;
+  },
+
+  clearDisplay: function(values) {
+    this.display = [];
+    this.addValues(values);
+  },
+
+  addResults: function(values) {
+    this.display.push([]);
+    this.addValues(values);
+  },
+
+  addValues: function(values) {
+    this.display.push([
+      values.accumulator || '',
+      values.operator || '',
+      values.operand || ''
+    ]);
+  },
+
+  setValues: function(values) {
+    this.display.pop();
+    this.addValues(values);
+  },
+
+  getValues: function() {
+    var last = this.display[this.display.length - 1];
+    return {
+      accumulator: last && last[0] || null,
+      operator: last && last[1] || null,
+      operand: last && last[2] || null
+    };
+  },
+
+  testButton: function(button) {
+    this.onButton.call(this, button);
+    return this.display;
+  }
+
+};
+
+window.calculatorTestRun = {
+
+  BUTTONS: {
+    '0': 'zero',
+    '1': 'one',
+    '2': 'two',
+    '3': 'three',
+    '4': 'four',
+    '5': 'five',
+    '6': 'six',
+    '7': 'seven',
+    '8': 'eight',
+    '9': 'nine',
+    '.': 'point',
+    '+': 'add',
+    '-': 'subtract',
+    '*': 'multiply',
+    '/': 'divide',
+    '=': 'equals',
+    '~': 'negate',
+    'A': 'clear',
+    '<': 'back'
+  },
+
+  NAMES: {
+    '~': '+ / -',
+    'A': 'AC',
+    '<': 'back',
+  },
+
+  /**
+   * Returns an object representing a run of calculator tests.
+   */
+  create: function() {
+    var run = Object.create(this);
+    run.tests = [];
+    run.success = true;
+    return run;
+  },
+
+  /**
+   * Runs a test defined as either a sequence or a function.
+   */
+  test: function(name, test) {
+    this.tests.push({name: name, steps: [], success: true});
+    if (typeof test === 'string')
+      this.testSequence_(name, test);
+    else if (typeof test === 'function')
+      test.call(this, new Controller(new Model(8), window.mockView.create()));
+    else
+      this.fail(this.getDescription_('invalid test: ', test));
+  },
+
+  /**
+   * Log test failures to the console.
+   */
+  log: function() {
+    var parts = ['\n\n', 0, ' tests passed, ', 0, ' failed.\n\n'];
+    if (!this.success) {
+      this.tests.forEach(function(test, index) {
+        var number = this.formatNumber_(index + 1, 2);
+        var prefix = test.success ? 'PASS: ' : 'FAIL: ';
+        parts[test.success ? 1 : 3] += 1;
+        parts.push(number, ') ', prefix, test.name, '\n');
+        test.steps.forEach(function(step) {
+          var prefix = step.success ? 'PASS: ' : 'FAIL: ';
+          step.messages.forEach(function(message) {
+            parts.push('    ', prefix, message, '\n');
+            prefix = '      ';
+          });
+        });
+        parts.push('\n');
+      }.bind(this));
+      console.log(parts.join(''));
+    }
+  },
+
+  /**
+   * Verify that actual values after a test step match expectations.
+   */
+  verify: function(expected, actual, message) {
+    if (this.areEqual_(expected, actual))
+      this.succeed(message);
+    else
+      this.fail(message, expected, actual);
+  },
+
+  /**
+   * Record a successful test step.
+   */
+  succeed: function(message) {
+    var test = this.tests[this.tests.length - 1];
+    test.steps.push({success: true, messages: [message]});
+  },
+
+  /**
+   * Fail the current test step. Expected and actual values are optional.
+   */
+  fail: function(message, expected, actual) {
+    var test = this.tests[this.tests.length - 1];
+    var failure = {success: false, messages: [message]};
+    if (expected !== undefined) {
+      failure.messages.push(this.getDescription_('expected: ', expected));
+      failure.messages.push(this.getDescription_('actual:   ', actual));
+    }
+    test.steps.push(failure);
+    test.success = false;
+    this.success = false;
+  },
+
+  /**
+   * @private
+   * Tests how a new calculator controller handles a sequence of numbers,
+   * operations, and commands, verifying that the controller's view has expected
+   * values displayed after each input handled by the controller.
+   *
+   * Within the sequence string, expected values must be specified as arrays of
+   * the form described below. The strings '~', '<', and 'A' is interpreted as
+   * the commands '+ / -', 'back', and 'AC' respectively, and other strings are
+   * interpreted as the digits, periods, operations, and commands represented
+   * by those strings.
+   *
+   * Expected values are sequences of arrays of the following forms:
+   *
+   *   []
+   *   [accumulator]
+   *   [accumulator operator operand]
+   *   [accumulator operator prefix suffix]
+   *
+   * where |accumulator|, |operand|, |prefix|, and |suffix| are numbers or
+   * underscores and |operator| is one of the operator characters or an
+   * underscore. The |operand|, |prefix|, and |suffix| numbers may contain
+   * leading zeros and embedded '=' characters which will be interpreted as
+   * described in the comments for the |testNumber_()| method above. Underscores
+   * represent values that are expected to be blank. '[]' arrays represent
+   * horizontal separators expected in the display. '[accumulator]' arrays
+   * adjust the last expected value array by setting only its accumulator value.
+   * If that value is already set they behave like '[accumulator _ accumulator]'
+   * arrays.
+   *
+   * Expected value array must be specified just after the sequence element
+   * which they are meant to test, like this:
+   *
+   *   run.testSequence_(controller, '12 - 34 = [][-22 _ -22]')
+   *
+   * When expected values are not specified for an element, the following rules
+   * are applied to obtain best guesses for the expected values for that
+   * element's tests:
+   *
+   *   - The initial expected values arrays are:
+   *
+   *       [['', '', '']]
+   *
+   *   - If the element being tested is a number, the expected operand value
+   *     of the last expected value array is set and changed as described in the
+   *     comments for the |testNumber_()| method above.
+   *
+   *   - If the element being tested is the '+ / -' operation the expected
+   *     values arrays will be changed as follows:
+   *
+   *       [*, [x, y, '']]     -> [*, [x, y, '']]
+   *       [*, [x, y, z]]      -> [*, [x, y, -z]
+   *       [*, [x, y, z1, z2]] -> [*, [x, y, -z1z2]
+   *
+   *   - If the element |e| being tested is the '+', '-', '*', or '/' operation
+   *     the expected values will be changed as follows:
+   *
+   *       [*, [x, y, '']]     -> [*, ['', e, '']]
+   *       [*, [x, y, z]]      -> [*, [z, y, z], ['', e, '']]
+   *       [*, [x, y, z1, z2]] -> [*, [z1z2, y, z1z2], ['', e, '']]
+   *
+   *   - If the element being tested is the '=' command, the expected values
+   *     will be changed as follows:
+   *
+   *       [*, ['', '', '']]   -> [*, [], ['0', '', '0']]
+   *       [*, [x, y, '']]     -> [*, [x, y, z], [], ['0', '', '0']]
+   *       [*, [x, y, z]]      -> [*, [x, y, z], [], [z, '', z]]
+   *       [*, [x, y, z1, z2]] -> [*, [x, y, z], [], [z1z2, '', z1z2]]
+   *
+   * So for example this call:
+   *
+   *   run.testSequence_('My Test', '12 + 34 - 56 = [][-10]')
+   *
+   * would yield the following tests:
+   *
+   *   run.testInput_(controller, '1', [['', '', '1']]);
+   *   run.testInput_(controller, '2', [['', '', '12']]);
+   *   run.testInput_(controller, '+', [['12', '', '12'], ['', '+', '']]);
+   *   run.testInput_(controller, '3', [['12', '', '12'], ['', '+', '3']]);
+   *   run.testInput_(controller, '4', [..., ['', '+', '34']]);
+   *   run.testInput_(controller, '-', [..., ['34', '', '34'], ['', '-', '']]);
+   *   run.testInput_(controller, '2', [..., ['34', '', '34'], ['', '-', '2']]);
+   *   run.testInput_(controller, '1', [..., ..., ['', '-', '21']]);
+   *   run.testInput_(controller, '=', [[], [-10, '', -10]]);
+   */
+  testSequence_: function(name, sequence) {
+    var controller = new Controller(new Model(8), window.mockView.create());
+    var expected = [['', '', '']];
+    var elements = this.parseSequence_(sequence);
+    for (var i = 0; i < elements.length; ++i) {
+      if (!Array.isArray(elements[i])) {  // Skip over expected value arrays.
+        // Update and ajust expectations.
+        this.updatedExpectations_(expected, elements[i]);
+        if (Array.isArray(elements[i + 1] && elements[i + 1][0]))
+          expected = this.adjustExpectations_([], elements[i + 1], 0);
+        else
+          expected = this.adjustExpectations_(expected, elements, i + 1);
+        // Test.
+        if (elements[i].match(/^-?[\d.][\d.=]*$/))
+          this.testNumber_(controller, elements[i], expected);
+        else
+          this.testInput_(controller, elements[i], expected);
+      };
+    }
+  },
+
+  /** @private */
+  parseSequence_: function(sequence) {
+    // Define the patterns used below.
+    var ATOMS = /(-?[\d.][\d.=]*)|([+*/=~<CAE_-])/g;  // number || command
+    var VALUES = /(\[[^\[\]]*\])/g;                   // expected values
+    // Massage the sequence into a JSON array string, so '2 + 2 = [4]' becomes:
+    sequence = sequence.replace(ATOMS, ',$1$2,');     // ',2, ,+, ,2, ,=, [,4,]'
+    sequence = sequence.replace(/\s+/g, '');          // ',2,,+,,2,,=,[,4,]'
+    sequence = sequence.replace(VALUES, ',$1,');      // ',2,,+,,2,,=,,[,4,],'
+    sequence = sequence.replace(/,,+/g, ',');         // ',2,+,2,=,[,4,],'
+    sequence = sequence.replace(/\[,/g, '[');         // ',2,+,2,=,[4,],'
+    sequence = sequence.replace(/,\]/g, ']');         // ',2,+,2,=,[4],'
+    sequence = sequence.replace(/(^,)|(,$)/g, '');    // '2,+,2,=,[4]'
+    sequence = sequence.replace(ATOMS, '"$1$2"');     // '"2","+","2","=",["4"]'
+    sequence = sequence.replace(/"_"/g, '""');        // '"2","+","2","=",["4"]'
+    // Fix some cases handled incorrectly by the massaging above, like the
+    // original sequences '[_ _ 0=]' and '[-1]', which would have become
+    // '["","","0","="]]' and '["-","1"]' respectively and would need to be
+    // fixed to '["","","0="]]' and '["-1"]'respectively.
+    sequence.replace(VALUES, function(match) {
+      return match.replace(/(","=)|(=",")/g, '=').replace(/-","/g, '-');
+    });
+    // Return an array created from the resulting JSON string.
+    return JSON.parse('[' + sequence + ']');
+  },
+
+  /** @private */
+  updatedExpectations_: function(expected, element) {
+    var last = expected[expected.length - 1];
+    var empty = (last && !last[0] && !last[1] && !last[2] && !last[3]);
+    var operand = last && last.slice(2).join('');
+    var operation = element.match(/[+*/-]/);
+    var equals = (element === '=');
+    var negate = (element === '~');
+    if (operation && !operand)
+      expected.splice(-1, 1, ['', element, '']);
+    else if (operation)
+      expected.splice(-1, 1, [operand, last[1], operand], ['', element, '']);
+    else if (equals && empty)
+      expected.splice(-1, 1, [], [operand || '0', '', operand || '0']);
+    else if (equals)
+      expected.push([], [operand || '0', '', operand || '0']);
+    else if (negate && operand)
+      expected[expected.length - 1].splice(2, 2, '-' + operand);
+  },
+
+  /** @private */
+  adjustExpectations_: function(expectations, adjustments, start) {
+    var replace = !expectations.length;
+    var adjustment, expectation;
+    for (var i = 0; Array.isArray(adjustments[start + i]); ++i) {
+      adjustment = adjustments[start + i];
+      expectation = expectations[expectations.length - 1];
+      if (adjustments[start + i].length != 1) {
+        expectations.splice(-i - 1, replace ? 0 : 1);
+        expectations.push(adjustments[start + i]);
+      } else if (i || !expectation || !expectation.length || expectation[0]) {
+        expectations.splice(-i - 1, replace ? 0 : 1);
+        expectations.push([adjustment[0], '', adjustment[0]]);
+      } else {
+        expectations[expectations.length - i - 2][0] = adjustment[0];
+      }
+    }
+    return expectations;
+  },
+
+  /**
+   * @private
+   * Tests how a calculator controller handles a sequence of digits and periods
+   * representing a number. During the test, the expected operand values are
+   * updated before each digit and period of the input according to these rules:
+   *
+   *   - If the last of the passed in expected values arrays has an expected
+   *     accumulator value, add an empty expected values array and proceed
+   *     according to the rules below with this new array.
+   *
+   *   - If the last of the passed in expected values arrays has no expected
+   *     operand value and no expected operand prefix and suffix values, the
+   *     expected operand used for the tests will start with the first digit or
+   *     period of the numeric sequence and the following digits and periods of
+   *     that sequence will be appended to that expected operand before each of
+   *     the following digits and periods in the test.
+   *
+   *   - If the last of the passed in expected values arrays has a single
+   *     expected operand value instead of operand prefix and suffix values, the
+   *     expected operand used for the tests will start with the first character
+   *     of that operand value and one additional character of that value will
+   *     be added to the expected operand before each of the following digits
+   *     and periods in the tests.
+   *
+   *   - If the last of the passed in expected values arrays has operand prefix
+   *     and suffix values instead of an operand value, the expected operand
+   *     used for the tests will start with the prefix value and the first
+   *     character of the suffix value, and one character of that suffix value
+   *     will be added to the expected operand before each of the following
+   *     digits and periods in the tests.
+   *
+   *   - In all of these cases, leading zeros and occurrences of the '='
+   *     character in the expected operand value will be ignored.
+   *
+   * For example the sequence of calls:
+   *
+   *   run.testNumber_(controller, '00', [[x, y, '0=']])
+   *   run.testNumber_(controller, '1.2.3', [[x, y, '1.2=3']])
+   *   run.testNumber_(controller, '45', [[x, y, '1.23', '45']])
+   *
+   * would yield the following tests:
+   *
+   *   run.testInput_(controller, '0', [[x, y, '0']]);
+   *   run.testInput_(controller, '0', [[x, y, '0']]);
+   *   run.testInput_(controller, '1', [[x, y, '1']]);
+   *   run.testInput_(controller, '.', [[x, y, '1.']]);
+   *   run.testInput_(controller, '2', [[x, y, '1.2']]);
+   *   run.testInput_(controller, '.', [[x, y, '1.2']]);
+   *   run.testInput_(controller, '3', [[x, y, '1.23']]);
+   *   run.testInput_(controller, '4', [[x, y, '1.234']]);
+   *   run.testInput_(controller, '5', [[x, y, '1.2345']]);
+   *
+   * It would also changes the expected value arrays to the following:
+   *
+   *   [[x, y, '1.2345']]
+   */
+  testNumber_: function(controller, number, expected) {
+    var last = expected[expected.length - 1];
+    var prefix = (last && !last[0] && last.length > 3 && last[2]) || '';
+    var suffix = (last && !last[0] && last[last.length - 1]) || number;
+    var append = (last && !last[0]) ? ['', last[1], ''] : ['', '', ''];
+    var start = (last && !last[0]) ? -1 : expected.length;
+    var count = (last && !last[0]) ? 1 : 0;
+    expected.splice(start, count, append);
+    for (var i = 0; i < number.length; ++i) {
+      append[2] = prefix + suffix.slice(0, i + 1);
+      append[2] = append[2].replace(/^0+([0-9])/, '$1').replace(/=/g, '');
+      this.testInput_(controller, number[i], expected);
+    }
+  },
+
+  /**
+   * @private
+   * Tests how a calculator controller handles a single element of input,
+   * logging the state of the controller before and after the test.
+   */
+  testInput_: function(controller, input, expected) {
+    var prefix = ['"', this.NAMES[input] || input, '": '];
+    var before = this.addDescription_(prefix, controller, ' => ');
+    var display = controller.view.testButton(this.BUTTONS[input]);
+    var actual = display.slice(-expected.length);
+    this.verify(expected, actual, this.getDescription_(before, controller));
+  },
+
+  /** @private */
+  areEqual_: function(x, y) {
+    return Array.isArray(x) ? this.areArraysEqual_(x, y) : (x == y);
+  },
+
+  /** @private */
+  areArraysEqual_: function(a, b) {
+    return Array.isArray(a) &&
+           Array.isArray(b) &&
+           a.length === b.length &&
+           a.every(function(element, i) {
+             return this.areEqual_(a[i], b[i]);
+           }, this);
+  },
+
+  /** @private */
+  getDescription_: function(prefix, object, suffix) {
+    var strings = Array.isArray(prefix) ? prefix : prefix ? [prefix] : [];
+    return this.addDescription_(strings, object, suffix).join('');
+  },
+
+  /** @private */
+  addDescription_: function(prefix, object, suffix) {
+    var strings = Array.isArray(prefix) ? prefix : prefix ? [prefix] : [];
+    if (Array.isArray(object)) {
+      strings.push('[', '');
+      object.forEach(function(element) {
+        this.addDescription_(strings, element, ', ');
+      }, this);
+      strings.pop();  // Pops the last ', ', or pops '' for empty arrays.
+      strings.push(']');
+    } else if (typeof object === 'number') {
+      strings.push('#');
+      strings.push(String(object));
+    } else if (typeof object === 'string') {
+      strings.push('"');
+      strings.push(object);
+      strings.push('"');
+    } else if (object instanceof Controller) {
+      strings.push('(');
+      this.addDescription_(strings, object.model.accumulator, ' ');
+      this.addDescription_(strings, object.model.operator, ' ');
+      this.addDescription_(strings, object.model.operand, ' | ');
+      this.addDescription_(strings, object.model.defaults.operator, ' ');
+      this.addDescription_(strings, object.model.defaults.operand, ')');
+    } else {
+      strings.push(String(object));
+    }
+    strings.push(suffix || '');
+    return strings;
+  },
+
+  /** @private */
+  formatNumber_: function(number, digits) {
+    var string = String(number);
+    var array = Array(Math.max(digits - string.length, 0) + 1);
+    array[array.length - 1] = string;
+    return array.join('0');
+  }
+
+};
diff --git a/chrome/common/extensions/docs/examples/apps/cycler/capture_tab.js b/chrome/common/extensions/docs/examples/apps/cycler/capture_tab.js
new file mode 100644
index 0000000..dba1ad1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/cycler/capture_tab.js
@@ -0,0 +1,92 @@
+// Copyright (c) 2012 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.
+
+/**
+ * Constructor for the tab UI governing setup and initial running of capture
+ * baselines. All HTML controls under tag #capture-tab, plus the tab label
+ * #capture-tab-label are controlled by this class.
+ * @param {!Object} cyclerUI  The master UI class, needed for global state
+ *     such as current capture name.
+ * @param {!Object} cyclerData  The local FileSystem-based database of
+ *     available captures to play back.
+ * @param {!Object} playbackTab  The class governing playback selections.
+ *     We need this in order to update its choices when we get asynchronous
+ *     callbacks for successfully completed capture baselines.
+ */
+var CaptureTab = function (cyclerUI, cyclerData, playbackTab) {
+  // Members for all UI elements subject to programmatic adjustment.
+  this.tabLabel_ = $('#capture-tab-label');
+  this.captureTab_ = $('#capture-tab');
+  this.captureName_ = $('#capture-name');
+  this.captureURLs_ = $('#capture-urls');
+  this.doCaptureButton_ = $('#do-capture');
+
+  // References to other major components of the extension.
+  this.cyclerUI_ = cyclerUI;
+  this.cyclerData_ = cyclerData;
+  this.playbackTab_ = playbackTab;
+
+  /*
+   * Enable the capture tab and its label.
+   */
+  this.enable = function() {
+    this.captureTab_.hidden = false;
+    this.tabLabel_.classList.add('selected');
+  };
+
+  /*
+   * Disable the capture tab and its label.
+   */
+  this.disable = function() {
+    this.captureTab_.hidden = true;
+    this.tabLabel_.classList.remove('selected');
+  };
+
+  /**
+   * Do a capture using current data from the capture tab. Post an error
+   * dialog if said data is incorrect or incomplete. Otherwise pass
+   * control to the browser side.
+   * @private
+   */
+  this.doCapture_ = function() {
+    var errors = [];
+    var captureName = this.captureName_.value.trim();
+    var urlList;
+
+    urlList = this.captureURLs_.value.split('\n');
+    if (captureName.length == 0)
+      errors.push('Must give a capture name');
+    if (urlList.length == 0)
+      errors.push('Must give at least one URL');
+
+    if (errors.length > 0) {
+      this.cyclerUI_.showMessage(errors.join('\n'), 'Ok');
+    } else {
+      this.doCaptureButton_.disabled = true;
+      chrome.experimental.record.captureURLs(captureName, urlList,
+          this.onCaptureDone.bind(this));
+    }
+  }
+
+  /**
+   * Callback for completed (or possibly failed) capture. Post a message
+   * box, either with errors or "Success!" message.
+   * @param {!Array.<string>} errors List of errors that occured
+   *     during capture, if any.
+   */
+  this.onCaptureDone = function(errors) {
+    this.doCaptureButton_.disabled = false;
+
+    if (errors.length > 0) {
+      this.cyclerUI_.showMessage(errors.join('\n'), 'Ok');
+    } else {
+      this.cyclerUI_.showMessage('Success!', 'Ok');
+      this.cyclerUI_.currentCaptureName = this.captureName_.value.trim();
+      this.cyclerData_.saveCapture(this.cyclerUI_.currentCaptureName);
+    }
+  }
+
+  // Set up listener for capture button.
+  this.doCaptureButton_.addEventListener('click', this.doCapture_.bind(this));
+};
diff --git a/chrome/common/extensions/docs/examples/apps/cycler/cycler.css b/chrome/common/extensions/docs/examples/apps/cycler/cycler.css
new file mode 100644
index 0000000..78da44a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/cycler/cycler.css
@@ -0,0 +1,149 @@
+/* Copyright (c) 2012 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. */
+
+/* Mimic sans-serif, dark bluish-gray font from Chrome|Settings page.
+ * This is approximate, as Chrome|Settings can adjust CSS per-platform and
+ * for instance uses Ubuntu font on Unix. It's at least quite close. */
+body {
+   font-family: Arial, sans-serif;
+   font-size: 12px;
+   color: #303942;
+}
+
+/* Mimic button from Settings page */
+button {
+  -webkit-padding-end: 10px;
+  -webkit-padding-start: 10px;
+  background-color: buttonface;
+  background-image: -webkit-linear-gradient(#EDEDED, #EDEDED 38%, #DEDEDE);
+  border: 1px solid rgba(0, 0, 0, 0.25);
+  border-radius: 2px;
+  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08),
+      inset 0 1px 2px rgba(255, 255, 255, 0.75);
+  color: #444;
+  font: inherit;
+  margin: 0 1px 0 0;
+  min-height: 2em;
+  min-width: 4em;
+  text-shadow: #F0F0F0 0px 1px 0px;
+}
+
+button:active {
+  background-image: -webkit-linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7);
+  box-shadow: none;
+  text-shadow: none;
+}
+
+input, select {
+  width: 15em;
+}
+
+/* Top left header and tab headings, respectively */
+#header, h1 {
+  font-size: 18px;
+  font-weight: normal;
+  margin-top: 10px;
+}
+
+/*  h2 for major subheadings has pure black */
+h2 {
+  color: black;
+  font-size: 14px;
+  font-weight: normal;
+  margin-top: 20px;
+}
+
+.divider {
+  background-color: #EEE;
+  height: 1px;
+  min-width: 600px;
+}
+
+.tab-label {
+  color: #999;
+  cursor: pointer;
+  font-size: 13px;
+  margin-top: 15px;
+}
+
+.selected {
+  color: #464E5A;
+}
+
+.indent {
+  margin-left: 18px;
+}
+
+.sub-label {
+  min-width: 10em;
+}
+
+.column {
+  -webkit-box-orient: vertical;
+  display: -webkit-box;
+}
+
+.row {
+  -webkit-box-orient: horizontal;
+  display: -webkit-box;
+}
+
+.gapped {
+  margin-top: 1em;
+}
+
+.url-list {
+  height: 8em;
+  width: 20em;
+}
+
+/* Cycler header is a little lighter blue-gray */
+#header {
+  color: #5C6166;
+  margin-bottom: 1.5em;
+  margin-left: 1em;
+}
+
+#header-icon {
+  float:left;
+  position:relative;
+}
+
+#tab-navigator {
+  float:left;
+  position:relative;
+  width:100px;
+}
+
+#tab-wrapper {
+  margin-left: 100px;
+}
+
+#playback-repeats {
+  width: 3em;
+}
+
+#popup {
+  background-color: rgba(255, 255, 255, .6);
+  height:100%;
+  left: 0;
+  position: absolute;
+  top: 0;
+  width:100%;
+  z-index: 1;
+}
+
+#popup-box {
+  background-color: #fff;
+  border: 1px solid;
+  left: 30em;
+  padding: 1em;
+  position: absolute;
+  top: 12em;
+  z-index: 2;
+}
+
+#no-captures {
+  padding-top: 20px;
+}
diff --git a/chrome/common/extensions/docs/examples/apps/cycler/cycler.html b/chrome/common/extensions/docs/examples/apps/cycler/cycler.html
new file mode 100644
index 0000000..db3551d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/cycler/cycler.html
@@ -0,0 +1,107 @@
+<!doctype html>
+
+<!-- Copyright (c) 2012 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. -->
+
+<html>
+
+<head>
+  <title>Page Cycler</title>
+  <link rel='stylesheet' type='text/css' href='cycler.css'/>
+</head>
+
+<body>
+
+<div id='page'>
+  <div class='row'>
+    <div id='tab-navigator column'>
+      <div class='row'>
+        <img id='header-icon' src='cycler_icon_128.png' height='30'
+          height='30'/>
+        <h1 id='header'>Cycler</h1>
+      </div>
+      <div id='capture-tab-label' class='tab-label selected'>
+        <a>Capture</a>
+      </div>
+      <div id='playback-tab-label' class='tab-label'>
+        <a>Playback</a>
+      </div>
+    </div>
+
+    <div id='tab-wrapper'>
+      <div id='capture-tab'>
+        <div class='column'>
+          <h1>Capture</h1>
+          <div class='divider'></div>
+          <h2>Name:</h2>
+          <input class='indent name-combo' id='capture-name' type='text'/>
+          <h2>URLs:</h2>
+          <div class='indent'>
+            Enter the URLs to capture, one URL per line.<br/>
+            <textarea id='capture-urls' class='url-list'></textarea>
+          </div>
+          <button id='do-capture' class='gapped'>Capture!</button>
+        </div>
+      </div>
+
+      <div id='playback-tab' hidden>
+        <div class='column'>
+          <h1>Playback</h1>
+          <div class='divider'> </div>
+            <div id="no-captures" hidden>
+              <h2>No captures available.  Create new ones via Capture tab.</h2>
+            </div>
+            <div id='have-captures'>
+              <div class='column'>
+              <h2>Capture:</h2>
+              <select id='playback-name' class='indent name-combo'>
+                <option value='' disabled selected>Choose a capture</option>
+              </select>
+              <div id='playback-details' class='gapped'>
+                <div class='column gapped'>
+                  <h2>URLs:</h2>
+                  <textarea id='playback-urls' class='indent url-list'>
+                  </textarea>
+                </div>
+                <h2>Playback&nbsp;Options:</h2>
+                <div class='row gapped'>
+                  <div class='indent sub-label'>Repetitions:</div>
+                  <input id='playback-repeats' type='number' min='1' max='100'
+                      value='1'/>
+                </div>
+                <div class='row gapped'>
+                  <div class='indent sub-label'>Install&nbsp;Extension:</div>
+                  <input id='playback-extension' type='text' />
+                  <button id='playback-browse'>Browse..</button>
+                </div>
+                <div class='row gapped'>
+                  <button id='do-playback'>Playback</button>
+                  <button id='do-delete'>Delete</button>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <div id='popup' hidden>
+    <div id='popup-box' style='column'>
+      <div id='popup-content'>
+        When in the course of human events it<br/>
+        becomes necessary for one people<br/>
+        to dissolve...
+      </div>
+      <button id='do-popup-dismiss' class='gapped'>Dismiss</button>
+    </div>
+  </div>
+</div>
+
+<script type='text/javascript' src='cycler_data.js'></script>
+<script type='text/javascript' src='capture_tab.js'></script>
+<script type='text/javascript' src='playback_tab.js'></script>
+<script type='text/javascript' src='cycler_ui.js'></script>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/apps/cycler/cycler.js b/chrome/common/extensions/docs/examples/apps/cycler/cycler.js
new file mode 100644
index 0000000..1913288
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/cycler/cycler.js
@@ -0,0 +1,142 @@
+// Copyright (c) 2012 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.
+
+function $(criterion) {
+  return document.querySelector(criterion);
+}
+
+var cyclerUI = new (function () {
+  this.urlList = [];
+  this.cacheDir = "";
+
+  this.captureTab = $("#capture-tab");
+  this.captureTabLabel = $("#capture-tab-label");
+  this.captureButton = $("#capture-test");
+  this.captureErrorDiv = $("#capture-errors-display");
+  this.captureErrorList = $("#capture-errors");
+
+  this.playbackTab = $("#playback-tab");
+  this.playbackTabLabel = $("#playback-tab-label");
+  this.playbackURLs = $("#playback-urls");
+  this.playbackCache = $("#playback-cache-dir");
+  this.playbackButton = $("#playback-test");
+  this.playbackErrorDiv = $("#playback-errors-display");
+  this.playbackErrorList = $("#playback-errors");
+
+  this.playbackURLs.innerText = this.playbackCache.innerText = noTestMessage;
+
+  this.enableTab = function(tabLabel, tab) {
+    var tabList = document.querySelectorAll(".tab");
+    var tabLabelList = document.querySelectorAll(".tab-label");
+
+    for (var i = 0; i < tabList.length; i++)
+      if (tabList[i] == tab)
+        tabList[i].style.visibility = "visible";
+      else
+        tabList[i].style.visibility = "hidden";
+
+    for (var i = 0; i < tabLabelList.length; i++)
+      if (tabLabelList[i] == tabLabel) {
+         tabLabelList[i].classList.add("enabled-tab-label");
+         tabLabelList[i].classList.remove("disabled-tab-label");
+      } else {
+         tabLabelList[i].classList.remove("enabled-tab-label");
+         tabLabelList[i].classList.add("disabled-tab-label");
+      }
+  }
+
+  this.chooseCapture = function() {
+    this.enableTab(this.captureTabLabel, this.captureTab);
+  }
+
+  this.chooseReplay = function() {
+    this.enableTab(this.playbackTabLabel, this.playbackTab);
+  }
+
+  this.captureTest = function() {
+    var errorList = $("#capture-errors");
+    var errors = [];
+
+    this.cacheDir = $("#capture-cache-dir").value;
+    this.urlList = $("#capture-urls").value.split("\n");
+
+    if (errors.length > 0) {
+      this.captureErrorList.innerText = errors.join("\n");
+      this.captureErrorDiv.className = "error-list-show";
+    }
+    else {
+      this.captureErrorDiv.className = "error-list-hide";
+      this.captureButton.disabled = true;
+      chrome.experimental.record.captureURLs(this.urlList, this.cacheDir,
+          this.onCaptureDone.bind(this));
+    }
+  }
+
+  this.onCaptureDone = function(errors) {
+
+    this.captureButton.disabled = false;
+    if (errors.length > 0) {
+      this.captureErrorList.innerText = errors.join("\n");
+      this.captureErrorDiv.className = "error-list-show";
+      this.playbackButton.disabled = true;
+      this.playbackCache.innerText = this.playbackURLs.innerText =
+          noTestMessage;
+    }
+    else {
+      this.captureErrorDiv.className = "error-list-hide";
+      this.playbackButton.disabled = false;
+      this.playbackURLs.innerText = this.urlList.join("\n");
+      this.playbackCache.innerText = this.cacheDir;
+    }
+  }
+
+  this.playbackTest = function() {
+    var extensionPath = $("#extension-dir").value;
+    var repeatCount = parseInt($('#repeat-count').value);
+    var errors = [];
+
+    // Check local errors
+    if (isNaN(repeatCount))
+      errors.push("Enter a number for repeat count");
+    else if (repeatCount < 1 || repeatCount > 100)
+      errors.push("Repeat count must be between 1 and 100");
+
+    if (errors.length > 0) {
+      this.playbackErrorList.innerText = errors.join("\n");
+      this.playbackErrorDiv.className = "error-list-show";
+    } else {
+      this.playbackErrorDiv.className = "error-list-hide";
+      this.playbackButton.disabled = true;
+      chrome.experimental.record.playbackURLs(
+          this.urlList,
+          this.cacheDir,
+          repeatCount,
+          {"extensionPath": extensionPath},
+          this.onReplayDone.bind(this));
+    }
+  }
+
+  this.onReplayDone = function(result) {
+    var playbackResult = $("#playback-result");
+
+    this.playbackButton.disabled = false;
+
+    if (result.errors.length > 0) {
+      this.playbackErrorList.innerText = result.errors.join("<br>");
+      this.playbackErrorDiv.className = "error-list-show";
+    }
+    else {
+      this.playbackErrorDiv.className = "error-list-hide";
+      playbackResult.innerText = "Test took " + result.runTime + "mS :\n" +
+        result.stats;
+    }
+  }
+
+  this.captureButton.addEventListener("click", this.captureTest.bind(this));
+  this.playbackButton.addEventListener("click", this.playbackTest.bind(this));
+  this.captureTabLabel.addEventListener("click", this.chooseCapture.bind(this));
+  this.playbackTabLabel.addEventListener("click", this.chooseReplay.bind(this));
+  this.enableTab(this.captureTabLabel, this.captureTab);
+})();
+
diff --git a/chrome/common/extensions/docs/examples/apps/cycler/cycler_data.js b/chrome/common/extensions/docs/examples/apps/cycler/cycler_data.js
new file mode 100644
index 0000000..2feda45
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/cycler/cycler_data.js
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 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.
+
+var CyclerData = function () {
+  this.currentCaptures_ = ['alpha', 'beta', 'gamma'];
+
+  /**
+   * Mark a capture as saved successfully. Actual writing of the cache
+   * directory and URL list into the FileSystem is done from the C++ side.
+   * JS side just updates the capture choices.
+   * @param {!string} name The name of the capture.
+   * TODO(cstaley): Implement actual addition of new capture data
+   */
+  this.saveCapture = function(name) {
+    console.log('Saving capture ' + name);
+    this.currentCaptures_.push(name);
+  }
+
+  /**
+   * Return a list of currently stored captures in the local FileSystem.
+   * @return {Array.<!string>} Names of all the current captures.
+   * TODO(cstaley): Implement actual generation of current capture list via
+   * ls-like traversal of the extension's FileSystem.
+   */
+  this.getCaptures = function() {
+    return this.currentCaptures_;
+  }
+
+  /**
+   * Delete capture |name| from the local FileSystem, and update the
+   * capture choices HTML select element.
+   * @param {!string} name The name of the capture to delete.
+   * TODO(cstaley): Implement actual deletion
+   */
+  this.deleteCapture = function(name) {
+    this.currentCaptures_.splice(this.currentCaptures_.indexOf(name), 1);
+  }
+};
diff --git a/chrome/common/extensions/docs/examples/apps/cycler/cycler_icon.png b/chrome/common/extensions/docs/examples/apps/cycler/cycler_icon.png
new file mode 100644
index 0000000..9bf904c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/cycler/cycler_icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/apps/cycler/cycler_icon_128.png b/chrome/common/extensions/docs/examples/apps/cycler/cycler_icon_128.png
new file mode 100644
index 0000000..9bf904c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/cycler/cycler_icon_128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/apps/cycler/cycler_icon_16.png b/chrome/common/extensions/docs/examples/apps/cycler/cycler_icon_16.png
new file mode 100644
index 0000000..7ccc7df
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/cycler/cycler_icon_16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/apps/cycler/cycler_ui.js b/chrome/common/extensions/docs/examples/apps/cycler/cycler_ui.js
new file mode 100644
index 0000000..c841b39
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/cycler/cycler_ui.js
@@ -0,0 +1,107 @@
+// Copyright (c) 2012 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.
+
+function $(criterion) {
+  return document.querySelector(criterion);
+}
+
+var cyclerUI = new (function () {
+
+  /**
+   * Enum for different UI states.
+   * @enum {number}
+   * @private
+   */
+  var EnableState_ = {capture: 0, playback: 1};
+
+  this.cyclerData_ = new CyclerData();
+
+  // Members for all UI elements subject to programmatic adjustment.
+  this.captureTabLabel_ = $('#capture-tab-label');
+  this.playbackTabLabel_ = $('#playback-tab-label');
+
+  this.playbackTab_ = new PlaybackTab(this, this.cyclerData_);
+  this.captureTab_ = new CaptureTab(this, this.cyclerData_, this.playbackTab_);
+
+  this.popupDialog_ = $('#popup');
+  this.popupContent_ = $('#popup-content');
+  this.doPopupDismiss_ = $('#do-popup-dismiss');
+
+  /**
+   * Name of the most recent capture made, or the one most recently chosen
+   * for playback.
+   * @type {!string}
+   */
+  this.currentCaptureName = null;
+
+  /**
+   * One of the EnableState_ values, showing which tab is presently
+   * enabled.
+   * @type {number}
+   */
+  this.enableState = null;
+
+  /*
+   * Enable the capture tab, changing tab labels approproiately.
+   * @private
+   */
+  this.enableCapture_ = function() {
+    if (this.enableState != EnableState_.capture) {
+      this.enableState = EnableState_.capture;
+
+      this.captureTab_.enable();
+      this.playbackTab_.disable();
+    }
+  };
+
+  /*
+   * Enable the playback tab, changing tab labels approproiately.
+   * @private
+   */
+  this.enablePlayback_ = function() {
+    if (this.enableState != EnableState_.playback) {
+      this.enableState = EnableState_.playback;
+
+      this.captureTab_.disable();
+      this.playbackTab_.enable();
+    }
+  };
+
+  /**
+   * Show an overlay with a message, a dismiss button with configurable
+   * label, and an action to call upon dismissal.
+   * @param {!string} content The message to display.
+   * @param {!string} dismissLabel The label on the dismiss button.
+   * @param {function()} action Additional action to take, if any, upon
+   *     dismissal.
+   */
+  this.showMessage = function(content, dismissLabel, action) {
+    this.popupContent_.innerText = content;
+    this.doPopupDismiss_.innerText = dismissLabel;
+    this.popupDialog_.hidden = false;
+    if (action != null)
+      doPopupDismiss_.addEventListener('click', action);
+  }
+
+  /**
+   * Default action for popup dismissal button, performed in addition to
+   * any other actions that may be specified in showMessage_ call.
+   * @private
+   */
+  this.clearMessage_ = function() {
+    this.popupDialog_.hidden = true;
+  }
+
+  // Set up listeners on all buttons.
+  this.doPopupDismiss_.addEventListener('click', this.clearMessage_.bind(this));
+
+  // Set up listeners on tab labels.
+  this.captureTabLabel_.addEventListener('click',
+      this.enableCapture_.bind(this));
+  this.playbackTabLabel_.addEventListener('click',
+      this.enablePlayback_.bind(this));
+
+  // Start with capture tab displayed.
+  this.enableCapture_();
+})();
diff --git a/chrome/common/extensions/docs/examples/apps/cycler/manifest.json b/chrome/common/extensions/docs/examples/apps/cycler/manifest.json
new file mode 100644
index 0000000..ba9624c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/cycler/manifest.json
@@ -0,0 +1,18 @@
+{
+  "manifest_version": 2,
+  "name": "Cycler",
+  "version": "0.1",
+  "description": "Cycler UI",
+  "app": {
+    "launch": {
+      "local_path": "cycler.html"
+    }
+  },
+  "icons": {
+    "128": "cycler_icon_128.png",
+    "16": "cycler_icon_16.png"
+  },
+  "permissions": [
+    "experimental"
+  ]
+}
diff --git a/chrome/common/extensions/docs/examples/apps/cycler/playback_tab.js b/chrome/common/extensions/docs/examples/apps/cycler/playback_tab.js
new file mode 100644
index 0000000..a2ff0da
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/cycler/playback_tab.js
@@ -0,0 +1,234 @@
+// Copyright (c) 2012 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.
+
+/**
+ * Constructor for the tab UI governing playback selection and running.
+ * All HTML controls under tag #plaback-tab, plus the tab label
+ * #playback-tab-label are controlled by this class.
+ * @param {!Object} cyclerUI  The master UI class, needed for global state
+ *     such as current capture name.
+ * @param {!Object} cyclerData  The local FileSystem-based database of
+ *     available captures to play back.
+ */
+var PlaybackTab = function (cyclerUI, cyclerData) {
+  /**
+   * Enum for different playback tab states.
+   * @enum {number}
+   * @private
+   */
+  var EnableState_ = {
+    choosePlayback: 1,  // Choose a playback, if none already chosen.
+    showPlayback: 2,  // Show currently chosen playback, and offer options.
+    showNoCaptures: 3  // Show message indicating no captures are available.
+  };
+
+  this.cyclerUI_ = cyclerUI;
+  this.cyclerData_ = cyclerData;
+
+  /**
+   * Create members for all UI elements with which we'll interact.
+   */
+  this.tabLabel_ = $('#playback-tab-label');
+  this.playbackTab_ = $('#playback-tab');
+  this.noCaptures_ = $('#no-captures');
+  this.yesCaptures_ = $('#have-captures');
+
+  this.playbackName_ = $('#playback-name');
+  this.playbackDetails_ = $('#playback-details');
+  this.playbackURLs_ = $('#playback-urls');
+  this.playbackRepeats_ = $('#playback-repeats');
+  this.playbackExtension_ = $('#playback-extension');
+  this.doPlaybackButton_ = $('#do-playback');
+  this.doDeleteButton_ = $('#do-delete');
+
+  /*
+   * Enable the playback tab, showing no current playback choice, by
+   * hiding the playbackDetails_ div.
+   * @private
+   */
+  this.enableChoosePlayback_ = function() {
+    if (this.enableState != EnableState_.choosePlayback) {
+      this.enableState = EnableState_.choosePlayback;
+      this.yesCaptures_.hidden = false;
+      this.noCaptures_.hidden = true;
+      this.playbackDetails_.hidden = true;
+    }
+  };
+
+  /*
+   * Enable the playback tab, showing a current playback choice by showing
+   * the playbackDetails_ div.
+   * @private
+   */
+  this.enableShowPlayback_ = function() {
+    if (this.enableState != EnableState_.showPlayback) {
+      this.enableState = EnableState_.showPlayback;
+      this.yesCaptures_.hidden = false;
+      this.noCaptures_.hidden = true;
+      this.playbackDetails_.hidden = false;
+    }
+  };
+
+  /*
+   * Enable the playback tab and adjust tab labels appropriately. Show
+   * no available captures by hiding the yesCaptures_ div and showing the
+   * noCaptures_ div instead.
+   * @private
+   */
+  this.enableShowNoCaptures_ = function() {
+    if (this.enableState != EnableState_.showNoCaptures) {
+      this.enableState = EnableState_.showNoCaptures;
+      this.noCaptures_.hidden = false;
+      this.yesCaptures_.hidden = true;
+    }
+  };
+
+  /**
+   * Enable the playback tab, showing either its "no captures", "choose
+   * a capture" or "display chosen capture" form, depending on the state
+   * of existing captures and |currentCaptureName_|.
+   */
+  this.enable = function() {
+    this.tabLabel_.classList.add('selected');
+    this.updatePlaybackChoices_();
+
+    this.playbackTab_.hidden = false;
+    if (this.cyclerData_.getCaptures().length == 0) {
+      this.enableShowNoCaptures_();
+    } else if (this.cyclerUI_.currentCaptureName == null) {
+      this.enableChoosePlayback_();
+    } else {
+      this.enableShowPlayback_();
+    }
+  }
+
+  /**
+   * Disable the playback tab altogether, presumably in favor of some
+   * other tab.
+   */
+  this.disable = function() {
+    this.tabLabel_.classList.remove('selected');
+    this.playbackTab_.hidden = true;
+  }
+
+  /**
+   * Utility function to refresh the selection list of captures that may
+   * be chosen for playback. Show all current captures, and also a
+   * "Choose a capture" default text if no capture is currently selected.
+   * @private
+   */
+  this.updatePlaybackChoices_ = function() {
+    var captureNames = this.cyclerData_.getCaptures();
+    var options = this.playbackName_.options;
+    var nextIndex = 0;
+    var chooseOption;
+
+    options.length = 0;
+
+    if (this.cyclerUI_.currentCaptureName == null) {
+      chooseOption = new Option('Choose a capture', null);
+      chooseOption.disabled = true;
+      options.add(chooseOption);
+      options.selectedIndex = nextIndex++;
+    }
+    for (var i = 0; i < captureNames.length; i++) {
+      options.add(new Option(captureNames[i], captureNames[i]));
+      if (captureNames[i] == this.cyclerUI_.currentCaptureName) {
+        options.selectedIndex = nextIndex;
+      }
+      nextIndex++;
+    }
+  }
+
+  /**
+   * Event callback for selection of a capture to play back. Save the
+   * choice in |currentCaptureName_|. Update the selection list because the
+   * default reminder message is no longer needed when a capture is chosen.
+   * Change playback tab to show-chosen-capture mode.
+   */
+  this.selectPlaybackName = function() {
+    this.cyclerUI_.currentCaptureName = this.playbackName_.value;
+    this.updatePlaybackChoices_();
+    this.enableShowPlayback_();
+  }
+
+  /**
+   * Event callback for pressing the playback button, which button is only
+   * enabled if a capture has been chosen for playback. Check for errors
+   * on playback page, and either display a message with such errors, or
+   * call the extenion api to initiate a playback.
+   * @private
+   */
+  this.doPlayback_ = function() {
+    var extensionPath = this.playbackExtension_.value;
+    var repeatCount = parseInt(this.playbackRepeats_.value);
+    var errors = [];
+
+    // Check local errors
+    if (isNaN(repeatCount)) {
+      errors.push('Enter a number for repeat count');
+    } else if (repeatCount < 1 || repeatCount > 100) {
+      errors.push('Repeat count must be between 1 and 100');
+    }
+
+    if (errors.length > 0) {
+      this.cyclerUI_.showMessage(errors.join('\n'), 'Ok');
+    } else {
+      this.doPlaybackButton_.disabled = true;
+      chrome.experimental.record.replayURLs(
+          this.cyclerUI_.currentCaptureName,
+          repeatCount,
+          {'extensionPath': extensionPath},
+          this.onPlaybackDone.bind(this));
+    }
+  }
+
+  /**
+   * Extension API calls this back when a playback is done.
+   * @param {!{
+   *   runTime: number,
+   *   stats: string,
+   *   errors: !Array.<string>
+   * }} results  The results of the playback, including running time in ms,
+   *     a string of statistics information, and a string array of errors.
+   */
+  this.onPlaybackDone = function(results) {
+    this.doPlaybackButton_.disabled = false;
+
+    if (results.errors.length > 0) {
+      this.cyclerUI_.showMessage(results.errors.join('\n'), 'Ok');
+    } else {
+      this.cyclerUI_.showMessage('Test took ' + results.runTime + 'mS :\n' +
+          results.stats, 'Ok');
+    }
+  }
+
+  /**
+   * Delete the capture with name |currentCaptureName_|. For this method
+   * to be callable, there must be a selected currentCaptureName_. Change
+   * the displayed HTML to show no captures if none exist now, or to show
+   * none selected (since we just deleted the selected one).
+   * @private
+   */
+  this.doDelete_ = function() {
+    this.cyclerData_.deleteCapture(this.cyclerUI_.currentCaptureName);
+    this.cyclerUI_.currentCaptureName = null;
+    this.updatePlaybackChoices_();
+    if (this.cyclerData_.getCaptures().length == 0) {
+      this.enableShowNoCaptures_();
+    } else {
+      this.enableChoosePlayback_();
+    }
+  }
+
+  // Set up listeners on buttons.
+  this.doPlaybackButton_.addEventListener('click', this.doPlayback_.bind(this));
+  this.doDeleteButton_.addEventListener('click', this.doDelete_.bind(this));
+
+  // Set up initial selection list for existing captures, and selection
+  // event listener.
+  this.updatePlaybackChoices_();
+  this.playbackName_.addEventListener('change',
+      this.selectPlaybackName.bind(this));
+};
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/HelloLicenseServlet.java b/chrome/common/extensions/docs/examples/apps/hello-java/HelloLicenseServlet.java
new file mode 100644
index 0000000..45b7790
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/HelloLicenseServlet.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2012 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.
+ *
+ * The "Hello world!" of the Chrome Web Store Licensing API, in Java. This
+ * program logs the user in with OpenID, fetches their license state with OAuth,
+ * and prints one of these greetings as appropriate:
+ *
+ *   1. Hello *no* license!
+ *   2. Hello *free trial* license!
+ *   3. Hello *full* license!
+ *
+ * Brian Kennish <bkennish@chromium.org>
+ */
+package com.example;
+
+import java.io.*;
+import java.net.*;
+import java.util.HashSet;
+import javax.servlet.http.*;
+import com.google.appengine.api.users.*;
+import com.google.appengine.repackaged.org.json.JSONObject;
+import oauth.signpost.OAuthConsumer;
+import oauth.signpost.basic.DefaultOAuthConsumer;
+
+/* A Google App Engine servlet. */
+@SuppressWarnings("serial")
+public class HelloLicenseServlet extends HttpServlet {
+  /* TODO: The app ID from the Chrome Developer Dashboard. */
+  public static final String APP_ID = "[INSERT APP ID HERE]";
+
+  /* TODO: The token from the Chrome Developer Dashboard. */
+  private static final String TOKEN = "[INSERT TOKEN HERE]";
+
+  /* TODO: The token secret from the Chrome Developer Dashboard. */
+  private static final String TOKEN_SECRET = "[INSERT TOKEN SECRET HERE]";
+
+  /*
+   * The license server URL, where %s are placeholders for app and
+   * user IDs
+   */
+  public static final String SERVER_URL =
+      "https://www.googleapis.com/chromewebstore/v1/licenses/%s/%s";
+
+  /* The consumer key. */
+  public static final String CONSUMER_KEY = "anonymous";
+
+  /* The consumer secret. */
+  public static final String CONSUMER_SECRET = CONSUMER_KEY;
+
+  /* Handles "GET" requests. */
+  public void doGet(HttpServletRequest request, HttpServletResponse response)
+      throws IOException {
+    response.setContentType("text/html; charset=UTF-8");
+    UserService userService = UserServiceFactory.getUserService();
+    PrintWriter output = response.getWriter();
+    String url = request.getRequestURI();
+
+    if (userService.isUserLoggedIn()) {
+      // Provide a logout path.
+      User user = userService.getCurrentUser();
+      output.printf(
+        "<strong>%s</strong> | <a href=\"%s\">Sign out</a><br><br>",
+        user.getEmail(),
+        userService.createLogoutURL(url)
+      );
+
+      try {
+        // Send a signed request for the user's license state.
+        OAuthConsumer oauth =
+            new DefaultOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
+        oauth.setTokenWithSecret(TOKEN, TOKEN_SECRET);
+        URLConnection http =
+            new URL(
+              String.format(
+                SERVER_URL,
+                APP_ID,
+                URLEncoder.encode(user.getFederatedIdentity(), "UTF-8")
+              )
+            ).openConnection();
+        oauth.sign(http);
+        http.connect();
+
+        // Convert the response from the license server to a string.
+        BufferedReader input =
+            new BufferedReader(new InputStreamReader(http.getInputStream()));
+        String file = "";
+        for (String line; (line = input.readLine()) != null; file += line);
+        input.close();
+
+        // Parse the string as JSON and display the license state.
+        JSONObject json = new JSONObject(file);
+        output.printf(
+          "Hello <strong>%s</strong> license!",
+          "YES".equals(json.get("result")) ?
+              "FULL".equals(json.get("accessLevel")) ? "full" : "free trial" :
+              "no"
+        );
+      } catch (Exception exception) {
+        // Dump any error.
+        output.printf("Oops! <strong>%s</strong>", exception.getMessage());
+      }
+    } else { // The user isn't logged in.
+      // Prompt for login.
+      output.printf(
+        "<a href=\"%s\">Sign in</a>",
+        userService.createLoginURL(
+          url,
+          null,
+          "https://www.google.com/accounts/o8/id",
+          new HashSet<String>()
+        )
+      );
+    }
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/README b/chrome/common/extensions/docs/examples/apps/hello-java/README
new file mode 100644
index 0000000..11ef20c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/README
@@ -0,0 +1,10 @@
+See the documentation at
+    http://code.google.com/chrome/webstore/docs/get_started.html
+for instructions on how to use these files.
+
+"HelloLicenseServlet.java" contains all the code you need to talk to the Chrome
+Web Store Licensing API.
+
+"workspace" contains a complete Eclipse project, "HelloLicense", which you can
+import and deploy to Google App Engine after updating
+"HelloLicenseServlet.java".
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/.classpath b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/.classpath
new file mode 100644
index 0000000..6e68fb3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/.classpath
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="com.google.appengine.eclipse.core.GAE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="lib" path="war/WEB-INF/lib/commons-codec-1.4.jar"/>
+	<classpathentry kind="lib" path="war/WEB-INF/lib/signpost-core-1.2.1.1.jar"/>
+	<classpathentry kind="output" path="war/WEB-INF/classes"/>
+</classpath>
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/.project b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/.project
new file mode 100644
index 0000000..9e3bf60
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/.project
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>HelloLicense</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.google.gdt.eclipse.core.webAppProjectValidator</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.google.appengine.eclipse.core.enhancerbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.google.appengine.eclipse.core.projectValidator</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>com.google.appengine.eclipse.core.gaeNature</nature>
+	</natures>
+</projectDescription>
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/.settings/com.google.appengine.eclipse.core.prefs b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/.settings/com.google.appengine.eclipse.core.prefs
new file mode 100644
index 0000000..a5e4c39
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/.settings/com.google.appengine.eclipse.core.prefs
@@ -0,0 +1,3 @@
+#Sun Aug 15 09:29:11 PDT 2010
+eclipse.preferences.version=1
+filesCopiedToWebInfLib=appengine-api-1.0-sdk-1.3.5.jar|appengine-api-labs-1.3.5.jar|appengine-jsr107cache-1.3.5.jar|jsr107cache-1.1.jar|datanucleus-appengine-1.0.7.final.jar|datanucleus-core-1.1.5.jar|datanucleus-jpa-1.1.5.jar|geronimo-jpa_3.0_spec-1.1.1.jar|geronimo-jta_1.1_spec-1.1.1.jar|jdo2-api-2.3-eb.jar
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/.settings/com.google.gdt.eclipse.core.prefs b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/.settings/com.google.gdt.eclipse.core.prefs
new file mode 100644
index 0000000..23a0f0a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/.settings/com.google.gdt.eclipse.core.prefs
@@ -0,0 +1,5 @@
+#Tue Aug 17 23:40:51 PDT 2010
+eclipse.preferences.version=1
+jarsExcludedFromWebInfLib=
+warSrcDir=war
+warSrcDirIsOutput=true
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/.settings/com.google.gwt.eclipse.core.prefs b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/.settings/com.google.gwt.eclipse.core.prefs
new file mode 100644
index 0000000..7c8408d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/.settings/com.google.gwt.eclipse.core.prefs
@@ -0,0 +1,3 @@
+#Tue Aug 17 23:40:55 PDT 2010
+eclipse.preferences.version=1
+filesCopiedToWebInfLib=
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/src/META-INF/jdoconfig.xml b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/src/META-INF/jdoconfig.xml
new file mode 100644
index 0000000..5f56aa1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/src/META-INF/jdoconfig.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">
+
+   <persistence-manager-factory name="transactions-optional">
+       <property name="javax.jdo.PersistenceManagerFactoryClass"
+           value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
+       <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
+       <property name="javax.jdo.option.NontransactionalRead" value="true"/>
+       <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
+       <property name="javax.jdo.option.RetainValues" value="true"/>
+       <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
+   </persistence-manager-factory>
+</jdoconfig>
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/src/com/example/HelloLicenseServlet.java b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/src/com/example/HelloLicenseServlet.java
new file mode 100644
index 0000000..45b7790
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/src/com/example/HelloLicenseServlet.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2012 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.
+ *
+ * The "Hello world!" of the Chrome Web Store Licensing API, in Java. This
+ * program logs the user in with OpenID, fetches their license state with OAuth,
+ * and prints one of these greetings as appropriate:
+ *
+ *   1. Hello *no* license!
+ *   2. Hello *free trial* license!
+ *   3. Hello *full* license!
+ *
+ * Brian Kennish <bkennish@chromium.org>
+ */
+package com.example;
+
+import java.io.*;
+import java.net.*;
+import java.util.HashSet;
+import javax.servlet.http.*;
+import com.google.appengine.api.users.*;
+import com.google.appengine.repackaged.org.json.JSONObject;
+import oauth.signpost.OAuthConsumer;
+import oauth.signpost.basic.DefaultOAuthConsumer;
+
+/* A Google App Engine servlet. */
+@SuppressWarnings("serial")
+public class HelloLicenseServlet extends HttpServlet {
+  /* TODO: The app ID from the Chrome Developer Dashboard. */
+  public static final String APP_ID = "[INSERT APP ID HERE]";
+
+  /* TODO: The token from the Chrome Developer Dashboard. */
+  private static final String TOKEN = "[INSERT TOKEN HERE]";
+
+  /* TODO: The token secret from the Chrome Developer Dashboard. */
+  private static final String TOKEN_SECRET = "[INSERT TOKEN SECRET HERE]";
+
+  /*
+   * The license server URL, where %s are placeholders for app and
+   * user IDs
+   */
+  public static final String SERVER_URL =
+      "https://www.googleapis.com/chromewebstore/v1/licenses/%s/%s";
+
+  /* The consumer key. */
+  public static final String CONSUMER_KEY = "anonymous";
+
+  /* The consumer secret. */
+  public static final String CONSUMER_SECRET = CONSUMER_KEY;
+
+  /* Handles "GET" requests. */
+  public void doGet(HttpServletRequest request, HttpServletResponse response)
+      throws IOException {
+    response.setContentType("text/html; charset=UTF-8");
+    UserService userService = UserServiceFactory.getUserService();
+    PrintWriter output = response.getWriter();
+    String url = request.getRequestURI();
+
+    if (userService.isUserLoggedIn()) {
+      // Provide a logout path.
+      User user = userService.getCurrentUser();
+      output.printf(
+        "<strong>%s</strong> | <a href=\"%s\">Sign out</a><br><br>",
+        user.getEmail(),
+        userService.createLogoutURL(url)
+      );
+
+      try {
+        // Send a signed request for the user's license state.
+        OAuthConsumer oauth =
+            new DefaultOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
+        oauth.setTokenWithSecret(TOKEN, TOKEN_SECRET);
+        URLConnection http =
+            new URL(
+              String.format(
+                SERVER_URL,
+                APP_ID,
+                URLEncoder.encode(user.getFederatedIdentity(), "UTF-8")
+              )
+            ).openConnection();
+        oauth.sign(http);
+        http.connect();
+
+        // Convert the response from the license server to a string.
+        BufferedReader input =
+            new BufferedReader(new InputStreamReader(http.getInputStream()));
+        String file = "";
+        for (String line; (line = input.readLine()) != null; file += line);
+        input.close();
+
+        // Parse the string as JSON and display the license state.
+        JSONObject json = new JSONObject(file);
+        output.printf(
+          "Hello <strong>%s</strong> license!",
+          "YES".equals(json.get("result")) ?
+              "FULL".equals(json.get("accessLevel")) ? "full" : "free trial" :
+              "no"
+        );
+      } catch (Exception exception) {
+        // Dump any error.
+        output.printf("Oops! <strong>%s</strong>", exception.getMessage());
+      }
+    } else { // The user isn't logged in.
+      // Prompt for login.
+      output.printf(
+        "<a href=\"%s\">Sign in</a>",
+        userService.createLoginURL(
+          url,
+          null,
+          "https://www.google.com/accounts/o8/id",
+          new HashSet<String>()
+        )
+      );
+    }
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/src/log4j.properties b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/src/log4j.properties
new file mode 100644
index 0000000..d9c3edc
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/src/log4j.properties
@@ -0,0 +1,24 @@
+# A default log4j configuration for log4j users.
+#
+# To use this configuration, deploy it into your application's WEB-INF/classes
+# directory.  You are also encouraged to edit it as you like.
+
+# Configure the console as our one appender
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n
+
+# tighten logging on the DataNucleus Categories
+log4j.category.DataNucleus.JDO=WARN, A1
+log4j.category.DataNucleus.Persistence=WARN, A1
+log4j.category.DataNucleus.Cache=WARN, A1
+log4j.category.DataNucleus.MetaData=WARN, A1
+log4j.category.DataNucleus.General=WARN, A1
+log4j.category.DataNucleus.Utility=WARN, A1
+log4j.category.DataNucleus.Transaction=WARN, A1
+log4j.category.DataNucleus.Datastore=WARN, A1
+log4j.category.DataNucleus.ClassLoading=WARN, A1
+log4j.category.DataNucleus.Plugin=WARN, A1
+log4j.category.DataNucleus.ValueGeneration=WARN, A1
+log4j.category.DataNucleus.Enhancer=WARN, A1
+log4j.category.DataNucleus.SchemaTool=WARN, A1
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/WEB-INF/appengine-web.xml b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/WEB-INF/appengine-web.xml
new file mode 100644
index 0000000..99223a5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/WEB-INF/appengine-web.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
+	<application></application>
+	<version>1</version>
+	
+	<!-- Configure java.util.logging -->
+	<system-properties>
+		<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
+	</system-properties>
+	
+</appengine-web-app>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/WEB-INF/classes/META-INF/jdoconfig.xml b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/WEB-INF/classes/META-INF/jdoconfig.xml
new file mode 100644
index 0000000..5f56aa1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/WEB-INF/classes/META-INF/jdoconfig.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">
+
+   <persistence-manager-factory name="transactions-optional">
+       <property name="javax.jdo.PersistenceManagerFactoryClass"
+           value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
+       <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
+       <property name="javax.jdo.option.NontransactionalRead" value="true"/>
+       <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
+       <property name="javax.jdo.option.RetainValues" value="true"/>
+       <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
+   </persistence-manager-factory>
+</jdoconfig>
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/WEB-INF/classes/log4j.properties b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/WEB-INF/classes/log4j.properties
new file mode 100644
index 0000000..d9c3edc
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/WEB-INF/classes/log4j.properties
@@ -0,0 +1,24 @@
+# A default log4j configuration for log4j users.
+#
+# To use this configuration, deploy it into your application's WEB-INF/classes
+# directory.  You are also encouraged to edit it as you like.
+
+# Configure the console as our one appender
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n
+
+# tighten logging on the DataNucleus Categories
+log4j.category.DataNucleus.JDO=WARN, A1
+log4j.category.DataNucleus.Persistence=WARN, A1
+log4j.category.DataNucleus.Cache=WARN, A1
+log4j.category.DataNucleus.MetaData=WARN, A1
+log4j.category.DataNucleus.General=WARN, A1
+log4j.category.DataNucleus.Utility=WARN, A1
+log4j.category.DataNucleus.Transaction=WARN, A1
+log4j.category.DataNucleus.Datastore=WARN, A1
+log4j.category.DataNucleus.ClassLoading=WARN, A1
+log4j.category.DataNucleus.Plugin=WARN, A1
+log4j.category.DataNucleus.ValueGeneration=WARN, A1
+log4j.category.DataNucleus.Enhancer=WARN, A1
+log4j.category.DataNucleus.SchemaTool=WARN, A1
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/WEB-INF/logging.properties b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/WEB-INF/logging.properties
new file mode 100644
index 0000000..a172066
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/WEB-INF/logging.properties
@@ -0,0 +1,13 @@
+# A default java.util.logging configuration.
+# (All App Engine logging is through java.util.logging by default).
+#
+# To use this configuration, copy it into your application's WEB-INF
+# folder and add the following to your appengine-web.xml:
+# 
+# <system-properties>
+#   <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
+# </system-properties>
+#
+
+# Set the default logging level for all loggers to WARNING
+.level = WARNING
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/WEB-INF/web.xml b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/WEB-INF/web.xml
new file mode 100644
index 0000000..7226eb0
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/WEB-INF/web.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+xmlns="http://java.sun.com/xml/ns/javaee"
+xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
+http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
+	<servlet>
+		<servlet-name>HelloLicense</servlet-name>
+		<servlet-class>com.example.HelloLicenseServlet</servlet-class>
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>HelloLicense</servlet-name>
+		<url-pattern>/hellolicense</url-pattern>
+	</servlet-mapping>
+	<welcome-file-list>
+		<welcome-file>index.html</welcome-file>
+	</welcome-file-list>
+</web-app>
diff --git a/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/index.html b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/index.html
new file mode 100644
index 0000000..173070d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-java/workspace/HelloLicense/war/index.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!-- The HTML 4.01 Transitional DOCTYPE declaration-->
+<!-- above set at the top of the file will set     -->
+<!-- the browser's rendering engine into           -->
+<!-- "Quirks Mode". Replacing this declaration     -->
+<!-- with a "Standards Mode" doctype is supported, -->
+<!-- but may lead to some differences in layout.   -->
+
+<html>
+  <head>
+    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+    <title>Hello App Engine</title>
+  </head>
+
+  <body>
+    <h1>Hello App Engine!</h1>
+    <table>
+      <tr>
+        <td colspan="2" style="font-weight:bold;">Available Servlets:</td>
+      </tr>
+      <tr>
+        <td><a href="hellolicense">HelloLicense</a></td>
+      </tr>
+    </table>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/NOTICE b/chrome/common/extensions/docs/examples/apps/hello-php/NOTICE
new file mode 100644
index 0000000..261a409
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-php/NOTICE
@@ -0,0 +1,97 @@
+======================================================================
+./popuplib.js (PopupManager):
+======================================================================
+
+//  Copyright 2009 Google Inc.
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+
+======================================================================
+./index.php JS templating engine:
+======================================================================
+
+The MIT License
+
+Copyright (c) 2010 John Resig
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+======================================================================
+./lib/oauth/ is licensed as follows
+  (Cf. ../lib/oauth/LICENSE.txt):
+======================================================================
+
+The MIT License
+
+Copyright (c) 2007 Andy Smith
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+======================================================================
+./lib/lightopenid/ is licensed as follows
+  (Cf. ../lib/lightopenid/openid.php):
+======================================================================
+
+The MIT License
+
+Copyright (c) 2010, Mewp
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/README b/chrome/common/extensions/docs/examples/apps/hello-php/README
new file mode 100644
index 0000000..c6566e9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-php/README
@@ -0,0 +1,9 @@
+See the documentation at
+    http://code.google.com/chrome/webstore/docs/get_started.html
+for instructions on how to use these files.
+
+"lib" contains the necessary OAuth and OpenID libraries to talk to the Chrome
+Web Store Licensing API and Google's OpenID endpoint.
+
+To "index.php", replace APP_ID, TOKEN, and TOKEN_SECRET with your app's id, your
+developer OAuth access token, and its token secret, respectively.
diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/index.php b/chrome/common/extensions/docs/examples/apps/hello-php/index.php
new file mode 100644
index 0000000..8b0d3cf
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-php/index.php
@@ -0,0 +1,288 @@
+<?php
+/**
+ * Copyright (c) 2012 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.
+ *
+ * A "Hello world!" for the Chrome Web Store Licensing API, in PHP. This
+ * program logs the user in with Google's Federated Login API (OpenID), fetches
+ * their license state with OAuth, and prints one of these greetings as
+ * appropriate:
+ *
+ *   1. This user has FREE_TRIAL access to this application ( appId: 1 )
+ *   2. This user has FULL access to this application ( appId: 1 )
+ *   3. This user has NO access to this application ( appId: 1 )
+ *
+ * This code makes use of a popup ui extension to the OpenID protocol. Instead
+ * of the user being redirected to the Google login page, a popup window opens
+ * to the login page, keeping the user on the main application page. See
+ * popuplib.js
+ *
+ * Eric Bidelman <ericbidelman@chromium.org>
+ */
+
+session_start();
+
+require_once 'lib/oauth/OAuth.php';
+require_once 'lib/lightopenid/openid.php';
+
+// Full URL of the current application is running under.
+$scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on') ? 'http' :
+                                                                     'https';
+$selfUrl = "$scheme://{$_SERVER['HTTP_HOST']}{$_SERVER['PHP_SELF']}";
+
+
+/**
+ * Wrapper class to make calls to the Chrome Web Store License Server.
+ */
+class LicenseServerClient {
+
+  const LICENSE_SERVER_HOST = 'https://www.googleapis.com';
+  const CONSUMER_KEY = 'anonymous';
+  const CONSUMER_SECRET = 'anonymous';
+  const APP_ID = '1';  // Change to the correct id of your application.
+  const TOKEN = '[REPLACE THIS WITH YOUR OAUTH TOKEN]';
+  const TOKEN_SECRET = '[REPLACE THIS WITH YOUR OAUTH TOKEN SECRET]';
+  public $consumer;
+  public $token;
+  public $signatureMethod;
+
+  public function __construct() {
+    $this->consumer = new OAuthConsumer(
+        self::CONSUMER_KEY, self::CONSUMER_SECRET, NULL);
+    $this->token = new OAuthToken(self::TOKEN, self::TOKEN_SECRET);
+    $this->signatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
+  }
+
+  /**
+   * Makes an HTTP GET request to the specified URL.
+   *
+   * @param string $url Full URL of the resource to access
+   * @param string $request OAuthRequest containing the signed request to make.
+   * @param array $extraHeaders (optional) Array of headers.
+   * @param bool $returnResponseHeaders True if resp headers should be returned.
+   * @return string Response body from the server.
+   */
+  protected function send_signed_get($request, $extraHeaders=NULL,
+                                     $returnRequestHeaders=false,
+                                     $returnResponseHeaders=false) {
+    $url = explode('?', $request->to_url());
+    $curl = curl_init($url[0]);
+    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+    curl_setopt($curl, CURLOPT_FAILONERROR, false);
+    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+
+    // Return request headers in the response.
+    curl_setopt($curl, CURLINFO_HEADER_OUT, $returnRequestHeaders);
+
+    // Return response headers in the response?
+    if ($returnResponseHeaders) {
+      curl_setopt($curl, CURLOPT_HEADER, true);
+    }
+
+    $headers = array($request->to_header());
+    if (is_array($extraHeaders)) {
+      $headers = array_merge($headers, $extraHeaders);
+    }
+    curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
+
+    // Execute the request.  If an error occurs fill the response body with it.
+    $response = curl_exec($curl);
+    if (!$response) {
+      $response = curl_error($curl);
+    }
+
+    // Add server's response headers to our response body
+    $response = curl_getinfo($curl, CURLINFO_HEADER_OUT) . $response;
+
+    curl_close($curl);
+
+    return $response;
+  }
+
+  public function checkLicense($userId) {
+    $url = self::LICENSE_SERVER_HOST . '/chromewebstore/v1/licenses/' .
+           self::APP_ID . '/' . urlencode($userId);
+
+    $request = OAuthRequest::from_consumer_and_token(
+        $this->consumer, $this->token, 'GET', $url, array());
+
+    $request->sign_request($this->signatureMethod, $this->consumer,
+                           $this->token);
+
+    return $this->send_signed_get($request);
+  }
+}
+
+try {
+  $openid = new LightOpenID();
+  $userId = $openid->identity;
+  if (!isset($_GET['openid_mode'])) {
+    // This section performs the OpenID dance with the normal redirect. Use it
+    // if you want an alternative to the popup UI.
+    if (isset($_GET['login'])) {
+      $openid->identity = 'https://www.google.com/accounts/o8/id';
+      $openid->required = array('namePerson/first', 'namePerson/last',
+                                'contact/email');
+      header('Location: ' . $openid->authUrl());
+    }
+  } else if ($_GET['openid_mode'] == 'cancel') {
+    echo 'User has canceled authentication!';
+  } else {
+    $userId = $openid->validate() ? $openid->identity : '';
+    $_SESSION['userId'] = $userId;
+    $attributes = $openid->getAttributes();
+    $_SESSION['attributes'] = $attributes;
+  }
+} catch(ErrorException $e) {
+  echo $e->getMessage();
+  exit;
+}
+
+if (isset($_REQUEST['popup']) && !isset($_SESSION['redirect_to'])) {
+  $_SESSION['redirect_to'] = $selfUrl;
+  echo '<script type = "text/javascript">window.close();</script>';
+  exit;
+} else if (isset($_SESSION['redirect_to'])) {
+  $redirect = $_SESSION['redirect_to'];
+  unset($_SESSION['redirect_to']);
+  header('Location: ' . $redirect);
+} else if (isset($_REQUEST['queryLicenseServer'])) {
+  $ls = new LicenseServerClient();
+  echo $ls->checkLicense($_REQUEST['user_id']);
+  exit;
+} else if (isset($_GET['logout'])) {
+  unset($_SESSION['attributes']);
+  unset($_SESSION['userId']);
+  header('Location: ' . $selfUrl);
+}
+?>
+
+<!DOCTYPE html>
+<html>
+  <head>
+  <meta charset="utf-8" />
+  <link href="main.css" type="text/css" rel="stylesheet" />
+  <script type="text/javascript" src="popuplib.js"></script>
+  <script type="text/html" id="ls_tmpl">
+    <div id="access-level">
+      <% if (result.toLowerCase() == 'yes') { %>
+        This user has <span class="<%= accessLevel.toLowerCase() %>"><%= accessLevel %></span> access to this application ( appId: <%= appId %> )
+      <% } else { %>
+        This user has <span class="<%= result.toLowerCase() %>"><%= result %></span> access to this application ( appId: <%= appId %> )
+      <% } %>
+    </div>
+  </script>
+  </head>
+  <body>
+    <nav>
+      <?php if (!isset($_SESSION['userId'])): ?>
+        <a href="javascript:" onclick="openPopup(450, 500, this);">Sign in</a>
+      <?php else: ?>
+        <span>Welcome <?php echo @$_SESSION['attributes']['namePerson/first'] ?> <?php echo @$_SESSION['attributes']['namePerson/last'] ?> ( <?php echo $_SESSION['attributes']['contact/email'] ?> )</span>
+        <a href="?logout">Sign out</a>
+      <?php endif; ?>
+    </nav>
+    <?php if (isset($_SESSION['attributes'])): ?>
+      <div id="container">
+        <form action="<?php echo "$selfUrl?queryLicenseServer" ?>" onsubmit="return queryLicenseServer(this);">
+          <input type="hidden" id="user_id" name="user_id" value="<?php echo $_SESSION['userId'] ?>" />
+          <input type="submit" value="Check user's access" />
+        </form>
+        <div id="license-server-response"></div>
+      </div>
+    <?php endif; ?>
+    <script>
+      // Simple JavaScript Templating
+      // John Resig - http://ejohn.org/ - MIT Licensed
+      (function(){
+        var cache = {};
+
+        this.tmpl = function tmpl(str, data){
+          // Figure out if we're getting a template, or if we need to
+          // load the template - and be sure to cache the result.
+          var fn = !/\W/.test(str) ?
+            cache[str] = cache[str] ||
+              tmpl(document.getElementById(str).innerHTML) :
+
+            // Generate a reusable function that will serve as a template
+            // generator (and which will be cached).
+            new Function("obj",
+              "var p=[],print=function(){p.push.apply(p,arguments);};" +
+
+              // Introduce the data as local variables using with(){}
+              "with(obj){p.push('" +
+
+              // Convert the template into pure JavaScript
+              str
+                .replace(/[\r\t\n]/g, " ")
+                .split("<%").join("\t")
+                .replace(/((^|%>)[^\t]*)'/g, "$1\r")
+                .replace(/\t=(.*?)%>/g, "',$1,'")
+                .split("\t").join("');")
+                .split("%>").join("p.push('")
+                .split("\r").join("\\'")
+            + "');}return p.join('');");
+
+          // Provide some basic currying to the user
+          return data ? fn( data ) : fn;
+        };
+      })();
+
+      function queryLicenseServer(form) {
+        var userId = form.user_id.value;
+
+        if (!userId) {
+          alert('No OpenID specified!');
+          return false;
+        }
+
+        var req = new XMLHttpRequest();
+        req.onreadystatechange = function(e) {
+          if (this.readyState == 4) {
+            var resp = JSON.parse(this.responseText);
+            var el = document.getElementById('license-server-response');
+            if (resp.error) {
+              el.innerHTML = ['<div class="error">Error ', resp.error.code,
+                              ': ', resp.error.message, '</div>'].join('');
+            } else {
+              el.innerHTML = tmpl('ls_tmpl', resp);
+            }
+          }
+        };
+        var url =
+            [form.action, '&user_id=', encodeURIComponent(userId)].join('');
+        req.open('GET', url, true);
+        req.send(null);
+
+        return false;
+      }
+
+      function openPopup(w, h, link) {
+        var extensions = {
+          'openid.ns.ext1': 'http://openid.net/srv/ax/1.0',
+          'openid.ext1.mode': 'fetch_request',
+          'openid.ext1.type.email': 'http://axschema.org/contact/email',
+          'openid.ext1.type.first': 'http://axschema.org/namePerson/first',
+          'openid.ext1.type.last': 'http://axschema.org/namePerson/last',
+          'openid.ext1.required': 'email,first,last',
+          'openid.ui.icon': 'true'
+        };
+
+        var googleOpener = popupManager.createPopupOpener({
+          opEndpoint: 'https://www.google.com/accounts/o8/ud',
+          returnToUrl: '<?php echo "$selfUrl?popup=true" ?>',
+          onCloseHandler: function() {
+            window.location = '<?php echo $selfUrl ?>';
+          },
+          shouldEncodeUrls: false,
+          extensions: extensions
+        });
+        link.parentNode.appendChild(
+            document.createTextNode('Authenticating...'));
+        link.parentNode.removeChild(link);
+        googleOpener.popup(w, h);
+      }
+    </script>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/lib/lightopenid/openid.php b/chrome/common/extensions/docs/examples/apps/hello-php/lib/lightopenid/openid.php
new file mode 100644
index 0000000..2f5b0ff
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-php/lib/lightopenid/openid.php
@@ -0,0 +1,563 @@
+<?php
+/**
+ * This class provides a simple interface for OpenID (1.1 and 2.0) authentication.
+ * Supports Yadis discovery.
+ * The authentication process is stateless/dumb.
+ *
+ * Usage:
+ * Sign-on with OpenID is a two step process:
+ * Step one is authentication with the provider:
+ * <code>
+ * $openid = new LightOpenID;
+ * $openid->identity = 'ID supplied by user';
+ * header('Location: ' . $openid->authUrl());
+ * </code>
+ * The provider then sends various parameters via GET, one of them is openid_mode.
+ * Step two is verification:
+ * <code>
+ * if ($this->data['openid_mode']) {
+ *     $openid = new LightOpenID;
+ *     echo $openid->validate() ? 'Logged in.' : 'Failed';
+ * }
+ * </code>
+ *
+ * Optionally, you can set $returnUrl and $realm (or $trustRoot, which is an alias).
+ * The default values for those are:
+ * $openid->realm     = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'];
+ * $openid->returnUrl = $openid->realm . $_SERVER['REQUEST_URI'];
+ * If you don't know their meaning, refer to any openid tutorial, or specification. Or just guess.
+ *
+ * AX and SREG extensions are supported.
+ * To use them, specify $openid->required and/or $openid->optional.
+ * These are arrays, with values being AX schema paths (the 'path' part of the URL).
+ * For example:
+ *   $openid->required = array('namePerson/friendly', 'contact/email');
+ *   $openid->optional = array('namePerson/first');
+ * If the server supports only SREG or OpenID 1.1, these are automaticaly
+ * mapped to SREG names, so that user doesn't have to know anything about the server.
+ *
+ * To get the values, use $openid->getAttributes().
+ *
+ *
+ * The library depends on curl, and requires PHP 5.
+ * @author Mewp
+ * @copyright Copyright (c) 2010, Mewp
+ * @license http://www.opensource.org/licenses/mit-license.php MIT
+ */
+class LightOpenID
+{
+    public $returnUrl
+         , $required = array()
+         , $optional = array();
+    private $identity, $claimed_id;
+    protected $server, $version, $trustRoot, $aliases, $identifier_select = false
+            , $ax = false, $sreg = false, $data;
+    static protected $ax_to_sreg = array(
+        'namePerson/friendly'     => 'nickname',
+        'contact/email'           => 'email',
+        'namePerson'              => 'fullname',
+        'birthDate'               => 'dob',
+        'person/gender'           => 'gender',
+        'contact/postalCode/home' => 'postcode',
+        'contact/country/home'    => 'country',
+        'pref/language'           => 'language',
+        'pref/timezone'           => 'timezone',
+        );
+
+    function __construct()
+    {
+        $this->trustRoot = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'];
+        $this->returnUrl = $this->trustRoot . $_SERVER['REQUEST_URI'];
+
+        if (!function_exists('curl_exec')) {
+            throw new ErrorException('Curl extension is required.');
+        }
+
+        $this->data = $_POST + $_GET; # OPs may send data as POST or GET.
+    }
+
+    function __set($name, $value)
+    {
+        switch ($name) {
+        case 'identity':
+            if (strlen($value = trim($value))) {
+                if (preg_match('#^xri:/*#i', $value, $m)) {
+                    $value = substr($value, strlen($m[0]));
+                } elseif (!preg_match('/^(?:[=@+\$!\(]|https?:)/i', $value)) {
+                    $value = "http://$value";
+                }
+                if (preg_match('#^https?://[^/]+$#i', $value, $m)) {
+                    $value .= '/';
+                }
+            }
+            $this->$name = $this->claimed_id = $value;
+            break;
+        case 'trustRoot':
+        case 'realm':
+            $this->trustRoot = trim($value);
+        }
+    }
+
+    function __get($name)
+    {
+        switch ($name) {
+        case 'identity':
+            # We return claimed_id instead of identity,
+            # because the developer should see the claimed identifier,
+            # i.e. what he set as identity, not the op-local identifier (which is what we verify)
+            return $this->claimed_id;
+        case 'trustRoot':
+        case 'realm':
+            return $this->trustRoot;
+        }
+    }
+
+    protected function request($url, $method='GET', $params=array())
+    {
+        $params = http_build_query($params, '', '&');
+        $curl = curl_init($url . ($method == 'GET' && $params ? '?' . $params : ''));
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($curl, CURLOPT_HEADER, false);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+        if ($method == 'POST') {
+            curl_setopt($curl, CURLOPT_POST, true);
+            curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
+        } elseif ($method == 'HEAD') {
+            curl_setopt($curl, CURLOPT_HEADER, true);
+            curl_setopt($curl, CURLOPT_NOBODY, true);
+        } else {
+            curl_setopt($curl, CURLOPT_HTTPGET, true);
+        }
+        $response = curl_exec($curl);
+
+        if (curl_errno($curl)) {
+            throw new ErrorException(curl_error($curl), curl_errno($curl));
+        }
+
+        return $response;
+    }
+
+    protected function build_url($url, $parts)
+    {
+        if (isset($url['query'], $parts['query'])) {
+            $parts['query'] = $url['query'] . '&' . $parts['query'];
+        }
+
+        $url = $parts + $url;
+        $url = $url['scheme'] . '://'
+             . (empty($url['username'])?''
+                 :(empty($url['password'])? "{$url['username']}@"
+                 :"{$url['username']}:{$url['password']}@"))
+             . $url['host']
+             . (empty($url['port'])?'':":{$url['port']}")
+             . (empty($url['path'])?'':$url['path'])
+             . (empty($url['query'])?'':"?{$url['query']}")
+             . (empty($url['fragment'])?'':":{$url['fragment']}");
+        return $url;
+    }
+
+    /**
+     * Helper function used to scan for <meta>/<link> tags and extract information
+     * from them
+     */
+    protected function htmlTag($content, $tag, $attrName, $attrValue, $valueName)
+    {
+        preg_match_all("#<{$tag}[^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*$valueName=['\"](.+?)['\"][^>]*/?>#i", $content, $matches1);
+        preg_match_all("#<{$tag}[^>]*$valueName=['\"](.+?)['\"][^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*/?>#i", $content, $matches2);
+
+        $result = array_merge($matches1[1], $matches2[1]);
+        return empty($result)?false:$result[0];
+    }
+
+    /**
+     * Performs Yadis and HTML discovery. Normally not used.
+     * @param $url Identity URL.
+     * @return String OP Endpoint (i.e. OpenID provider address).
+     * @throws ErrorException
+     */
+    function discover($url)
+    {
+        if (!$url) throw new ErrorException('No identity supplied.');
+        # Use xri.net proxy to resolve i-name identities
+        if (!preg_match('#^https?:#', $url)) {
+            $url = "https://xri.net/$url";
+        }
+
+        # We save the original url in case of Yadis discovery failure.
+        # It can happen when we'll be lead to an XRDS document
+        # which does not have any OpenID2 services.
+        $originalUrl = $url;
+
+        # A flag to disable yadis discovery in case of failure in headers.
+        $yadis = true;
+
+        # We'll jump a maximum of 5 times, to avoid endless redirections.
+        for ($i = 0; $i < 5; $i ++) {
+            if ($yadis) {
+                $headers = explode("\n",$this->request($url, 'HEAD'));
+
+                $next = false;
+                foreach ($headers as $header) {
+                    if (preg_match('#X-XRDS-Location\s*:\s*(.*)#', $header, $m)) {
+                        $url = $this->build_url(parse_url($url), parse_url(trim($m[1])));
+                        $next = true;
+                    }
+
+                    if (preg_match('#Content-Type\s*:\s*application/xrds\+xml#i', $header)) {
+                        # Found an XRDS document, now let's find the server, and optionally delegate.
+                        $content = $this->request($url, 'GET');
+
+                        # OpenID 2
+                        # We ignore it for MyOpenID, as it breaks sreg if using OpenID 2.0
+                        $ns = preg_quote('http://specs.openid.net/auth/2.0/');
+                        if (preg_match('#<Service.*?>(.*)<Type>\s*'.$ns.'(.*?)\s*</Type>(.*)</Service>#s', $content, $m)) {
+                            $content = ' ' . $m[1] . $m[3]; # The space is added, so that strpos doesn't return 0.
+                            if ($m[2] == 'server') $this->identifier_select = true;
+
+                            preg_match('#<URI.*?>(.*)</URI>#', $content, $server);
+                            preg_match('#<(Local|Canonical)ID>(.*)</\1ID>#', $content, $delegate);
+                            if (empty($server)) {
+                                return false;
+                            }
+                            # Does the server advertise support for either AX or SREG?
+                            $this->ax   = (bool) strpos($content, '<Type>http://openid.net/srv/ax/1.0</Type>');
+                            $this->sreg = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>')
+                                       || strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>');
+
+                            $server = $server[1];
+                            if (isset($delegate[2])) $this->identity = trim($delegate[2]);
+                            $this->version = 2;
+
+                            $this->server = $server;
+                            return $server;
+                        }
+
+                        # OpenID 1.1
+                        $ns = preg_quote('http://openid.net/signon/1.1');
+                        if (preg_match('#<Service.*?>(.*)<Type>\s*'.$ns.'\s*</Type>(.*)</Service>#s', $content, $m)) {
+                            $content = ' ' . $m[1] . $m[2];
+
+                            preg_match('#<URI.*?>(.*)</URI>#', $content, $server);
+                            preg_match('#<.*?Delegate>(.*)</.*?Delegate>#', $content, $delegate);
+                            if (empty($server)) {
+                                return false;
+                            }
+                            # AX can be used only with OpenID 2.0, so checking only SREG
+                            $this->sreg = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>')
+                                       || strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>');
+
+                            $server = $server[1];
+                            if (isset($delegate[1])) $this->identity = $delegate[1];
+                            $this->version = 1;
+
+                            $this->server = $server;
+                            return $server;
+                        }
+
+                        $next = true;
+                        $yadis = false;
+                        $url = $originalUrl;
+                        $content = null;
+                        break;
+                    }
+                }
+                if ($next) continue;
+
+                # There are no relevant information in headers, so we search the body.
+                $content = $this->request($url, 'GET');
+                if ($location = $this->htmlTag($content, 'meta', 'http-equiv', 'X-XRDS-Location', 'value')) {
+                    $url = $this->build_url(parse_url($url), parse_url($location));
+                    continue;
+                }
+            }
+
+            if (!$content) $content = $this->request($url, 'GET');
+
+            # At this point, the YADIS Discovery has failed, so we'll switch
+            # to openid2 HTML discovery, then fallback to openid 1.1 discovery.
+            $server   = $this->htmlTag($content, 'link', 'rel', 'openid2.provider', 'href');
+            $delegate = $this->htmlTag($content, 'link', 'rel', 'openid2.local_id', 'href');
+            $this->version = 2;
+
+            if (!$server) {
+                # The same with openid 1.1
+                $server   = $this->htmlTag($content, 'link', 'rel', 'openid.server', 'href');
+                $delegate = $this->htmlTag($content, 'link', 'rel', 'openid.delegate', 'href');
+                $this->version = 1;
+            }
+
+            if ($server) {
+                # We found an OpenID2 OP Endpoint
+                if ($delegate) {
+                    # We have also found an OP-Local ID.
+                    $this->identity = $delegate;
+                }
+                $this->server = $server;
+                return $server;
+            }
+
+            throw new ErrorException('No servers found!');
+        }
+        throw new ErrorException('Endless redirection!');
+    }
+
+    protected function sregParams()
+    {
+        $params = array();
+        # We always use SREG 1.1, even if the server is advertising only support for 1.0.
+        # That's because it's fully backwards compatibile with 1.0, and some providers
+        # advertise 1.0 even if they accept only 1.1. One such provider is myopenid.com
+        $params['openid.ns.sreg'] = 'http://openid.net/extensions/sreg/1.1';
+        if ($this->required) {
+            $params['openid.sreg.required'] = array();
+            foreach ($this->required as $required) {
+                if (!isset(self::$ax_to_sreg[$required])) continue;
+                $params['openid.sreg.required'][] = self::$ax_to_sreg[$required];
+            }
+            $params['openid.sreg.required'] = implode(',', $params['openid.sreg.required']);
+        }
+
+        if ($this->optional) {
+            $params['openid.sreg.optional'] = array();
+            foreach ($this->optional as $optional) {
+                if (!isset(self::$ax_to_sreg[$optional])) continue;
+                $params['openid.sreg.optional'][] = self::$ax_to_sreg[$optional];
+            }
+            $params['openid.sreg.optional'] = implode(',', $params['openid.sreg.optional']);
+        }
+        return $params;
+    }
+    protected function axParams()
+    {
+        $params = array();
+        if ($this->required || $this->optional) {
+            $params['openid.ns.ax'] = 'http://openid.net/srv/ax/1.0';
+            $params['openid.ax.mode'] = 'fetch_request';
+            $this->aliases  = array();
+            $counts   = array();
+            $required = array();
+            $optional = array();
+            foreach (array('required','optional') as $type) {
+                foreach ($this->$type as $alias => $field) {
+                    if (is_int($alias)) $alias = strtr($field, '/', '_');
+                    $this->aliases[$alias] = 'http://axschema.org/' . $field;
+                    if (empty($counts[$alias])) $counts[$alias] = 0;
+                    $counts[$alias] += 1;
+                    ${$type}[] = $alias;
+                }
+            }
+            foreach ($this->aliases as $alias => $ns) {
+                $params['openid.ax.type.' . $alias] = $ns;
+            }
+            foreach ($counts as $alias => $count) {
+                if ($count == 1) continue;
+                $params['openid.ax.count.' . $alias] = $count;
+            }
+
+            # Don't send empty ax.requied and ax.if_available.
+            # Google and possibly other providers refuse to support ax when one of these is empty.
+            if($required) {
+                $params['openid.ax.required'] = implode(',', $required);
+            }
+            if($optional) {
+                $params['openid.ax.if_available'] = implode(',', $optional);
+            }
+        }
+        return $params;
+    }
+
+    protected function authUrl_v1()
+    {
+	$returnUrl = $this->returnUrl;
+        # If we have an openid.delegate that is different from our claimed id,
+        # we need to somehow preserve the claimed id between requests.
+        # The simplest way is to just send it along with the return_to url.
+        if($this->identity != $this->claimed_id) {
+            $returnUrl .= (strpos($returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $this->claimed_id;
+        }
+
+        $params = array(
+            'openid.return_to'  => $returnUrl,
+            'openid.mode'       => 'checkid_setup',
+            'openid.identity'   => $this->identity,
+            'openid.trust_root' => $this->trustRoot,
+            ) + $this->sregParams();
+
+        return $this->build_url(parse_url($this->server)
+                               , array('query' => http_build_query($params, '', '&')));
+    }
+
+    protected function authUrl_v2($identifier_select)
+    {
+        $params = array(
+            'openid.ns'          => 'http://specs.openid.net/auth/2.0',
+            'openid.mode'        => 'checkid_setup',
+            'openid.return_to'   => $this->returnUrl,
+            'openid.realm'       => $this->trustRoot,
+        );
+        if ($this->ax) {
+            $params += $this->axParams();
+        }
+        if ($this->sreg) {
+            $params += $this->sregParams();
+        }
+        if (!$this->ax && !$this->sreg) {
+            # If OP doesn't advertise either SREG, nor AX, let's send them both
+            # in worst case we don't get anything in return.
+            $params += $this->axParams() + $this->sregParams();
+        }
+
+        if ($identifier_select) {
+            $params['openid.identity'] = $params['openid.claimed_id']
+                 = 'http://specs.openid.net/auth/2.0/identifier_select';
+        } else {
+            $params['openid.identity'] = $this->identity;
+            $params['openid.claimed_id'] = $this->claimed_id;
+        }
+
+        return $this->build_url(parse_url($this->server)
+                               , array('query' => http_build_query($params, '', '&')));
+    }
+
+    /**
+     * Returns authentication url. Usually, you want to redirect your user to it.
+     * @return String The authentication url.
+     * @param String $select_identifier Whether to request OP to select identity for an user in OpenID 2. Does not affect OpenID 1.
+     * @throws ErrorException
+     */
+    function authUrl($identifier_select = null)
+    {
+        if (!$this->server) $this->discover($this->identity);
+
+        if ($this->version == 2) {
+            if ($identifier_select === null) {
+                return $this->authUrl_v2($this->identifier_select);
+            }
+            return $this->authUrl_v2($identifier_select);
+        }
+        return $this->authUrl_v1();
+    }
+
+    /**
+     * Performs OpenID verification with the OP.
+     * @return Bool Whether the verification was successful.
+     * @throws ErrorException
+     */
+    function validate()
+    {
+        $this->claimed_id = isset($this->data['openid_claimed_id'])?$this->data['openid_claimed_id']:$this->data['openid_identity'];
+        $params = array(
+            'openid.assoc_handle' => $this->data['openid_assoc_handle'],
+            'openid.signed'       => $this->data['openid_signed'],
+            'openid.sig'          => $this->data['openid_sig'],
+            );
+
+        if (isset($this->data['openid_op_endpoint'])) {
+            # We're dealing with an OpenID 2.0 server, so let's set an ns
+            # Even though we should know location of the endpoint,
+            # we still need to verify it by discovery, so $server is not set here
+            $params['openid.ns'] = 'http://specs.openid.net/auth/2.0';
+        }
+        $server = $this->discover($this->data['openid_identity']);
+
+        foreach (explode(',', $this->data['openid_signed']) as $item) {
+            # Checking whether magic_quotes_gpc is turned on, because
+            # the function may fail if it is. For example, when fetching
+            # AX namePerson, it might containg an apostrophe, which will be escaped.
+            # In such case, validation would fail, since we'd send different data than OP
+            # wants to verify. stripslashes() should solve that problem, but we can't
+            # use it when magic_quotes is off.
+            $value = $this->data['openid_' . str_replace('.','_',$item)];
+            $params['openid.' . $item] = get_magic_quotes_gpc() ? stripslashes($value) : $value; 
+        }
+
+        $params['openid.mode'] = 'check_authentication';
+
+        $response = $this->request($server, 'POST', $params);
+
+        return preg_match('/is_valid\s*:\s*true/i', $response);
+    }
+    protected function getAxAttributes()
+    {
+        $alias = null;
+        if (isset($this->data['openid_ns_ax'])
+            && $this->data['openid_ns_ax'] != 'http://openid.net/srv/ax/1.0'
+        ) { # It's the most likely case, so we'll check it before
+            $alias = 'ax';
+        } else {
+            # 'ax' prefix is either undefined, or points to another extension,
+            # so we search for another prefix
+            foreach ($this->data as $key => $val) {
+                if (substr($key, 0, strlen('openid_ns_')) == 'openid_ns_'
+                    && $val == 'http://openid.net/srv/ax/1.0'
+                ) {
+                    $alias = substr($key, strlen('openid_ns_'));
+                    break;
+                }
+            }
+        }
+        if (!$alias) {
+            # An alias for AX schema has not been found,
+            # so there is no AX data in the OP's response
+            return array();
+        }
+
+        foreach ($this->data as $key => $value) {
+            $keyMatch = 'openid_' . $alias . '_value_';
+            if (substr($key, 0, strlen($keyMatch)) != $keyMatch) {
+                continue;
+            }
+            $key = substr($key, strlen($keyMatch));
+            if (!isset($this->data['openid_' . $alias . '_type_' . $key])) {
+                # OP is breaking the spec by returning a field without
+                # associated ns. This shouldn't happen, but it's better
+                # to check, than cause an E_NOTICE.
+                continue;
+            }
+            $key = substr($this->data['openid_' . $alias . '_type_' . $key],
+                          strlen('http://axschema.org/'));
+            $attributes[$key] = $value;
+        }
+        # Found the AX attributes, so no need to scan for SREG.
+        return $attributes;
+    }
+    protected function getSregAttributes()
+    {
+        $attributes = array();
+        $sreg_to_ax = array_flip(self::$ax_to_sreg);
+        foreach ($this->data as $key => $value) {
+            $keyMatch = 'openid_sreg_';
+            if (substr($key, 0, strlen($keyMatch)) != $keyMatch) {
+                continue;
+            }
+            $key = substr($key, strlen($keyMatch));
+            if (!isset($sreg_to_ax[$key])) {
+                # The field name isn't part of the SREG spec, so we ignore it.
+                continue;
+            }
+            $attributes[$sreg_to_ax[$key]] = $value;
+        }
+        return $attributes;
+    }
+    /**
+     * Gets AX/SREG attributes provided by OP. should be used only after successful validaton.
+     * Note that it does not guarantee that any of the required/optional parameters will be present,
+     * or that there will be no other attributes besides those specified.
+     * In other words. OP may provide whatever information it wants to.
+     *     * SREG names will be mapped to AX names.
+     *     * @return Array Array of attributes with keys being the AX schema names, e.g. 'contact/email'
+     * @see http://www.axschema.org/types/
+     */
+    function getAttributes()
+    {
+        $attributes;
+        if (isset($this->data['openid_ns'])
+            && $this->data['openid_ns'] == 'http://specs.openid.net/auth/2.0'
+        ) { # OpenID 2.0
+            # We search for both AX and SREG attributes, with AX taking precedence.
+            return $this->getAxAttributes() + $this->getSregAttributes();
+        }
+        return $this->getSregAttributes();
+    }
+}
diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/CHANGELOG.txt b/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/CHANGELOG.txt
new file mode 100644
index 0000000..b12ff1c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/CHANGELOG.txt
@@ -0,0 +1,15 @@
+== 2008.08.04 ==
+* Added LICENSE.txt file with MIT license, copyright owner is perhaps
+	dubious however.
+== 2008.07.22 ==
+* Change to encoding to fix last change to encoding of spaces
+== 2008.07.15 ==
+* Another change to encoding per 
+	http://groups.google.com/group/oauth/browse_thread/thread/d39931d39b4af4bd
+* A change to port handling to better deal with https and the like per
+  http://groups.google.com/group/oauth/browse_thread/thread/1b203a51d9590226
+* Fixed a small bug per
+	http://code.google.com/p/oauth/issues/detail?id=26
+* Added missing base_string debug info when using RSA-SHA1
+* Increased size of example endpoint input field and added note about
+  query strings
diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/LICENSE.txt b/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/LICENSE.txt
new file mode 100644
index 0000000..89f0591
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/LICENSE.txt
@@ -0,0 +1,22 @@
+The MIT License
+
+Copyright (c) 2007 Andy Smith
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/OAuth.php b/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/OAuth.php
new file mode 100644
index 0000000..e9c4bdf
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-php/lib/oauth/OAuth.php
@@ -0,0 +1,879 @@
+<?php
+// vim: foldmethod=marker
+
+/* Generic exception class
+ */
+class OAuthException extends Exception {
+  // pass
+}
+
+class OAuthConsumer {
+  public $key;
+  public $secret;
+
+  function __construct($key, $secret, $callback_url=NULL) {
+    $this->key = $key;
+    $this->secret = $secret;
+    $this->callback_url = $callback_url;
+  }
+
+  function __toString() {
+    return "OAuthConsumer[key=$this->key,secret=$this->secret]";
+  }
+}
+
+class OAuthToken {
+  // access tokens and request tokens
+  public $key;
+  public $secret;
+
+  /**
+   * key = the token
+   * secret = the token secret
+   */
+  function __construct($key, $secret) {
+    $this->key = $key;
+    $this->secret = $secret;
+  }
+
+  /**
+   * generates the basic string serialization of a token that a server
+   * would respond to request_token and access_token calls with
+   */
+  function to_string() {
+    return "oauth_token=" .
+           OAuthUtil::urlencode_rfc3986($this->key) .
+           "&oauth_token_secret=" .
+           OAuthUtil::urlencode_rfc3986($this->secret);
+  }
+
+  function __toString() {
+    return $this->to_string();
+  }
+}
+
+/**
+ * A class for implementing a Signature Method
+ * See section 9 ("Signing Requests") in the spec
+ */
+abstract class OAuthSignatureMethod {
+  /**
+   * Needs to return the name of the Signature Method (ie HMAC-SHA1)
+   * @return string
+   */
+  abstract public function get_name();
+
+  /**
+   * Build up the signature
+   * NOTE: The output of this function MUST NOT be urlencoded.
+   * the encoding is handled in OAuthRequest when the final
+   * request is serialized
+   * @param OAuthRequest $request
+   * @param OAuthConsumer $consumer
+   * @param OAuthToken $token
+   * @return string
+   */
+  abstract public function build_signature($request, $consumer, $token);
+
+  /**
+   * Verifies that a given signature is correct
+   * @param OAuthRequest $request
+   * @param OAuthConsumer $consumer
+   * @param OAuthToken $token
+   * @param string $signature
+   * @return bool
+   */
+  public function check_signature($request, $consumer, $token, $signature) {
+    $built = $this->build_signature($request, $consumer, $token);
+    return $built == $signature;
+  }
+}
+
+/**
+ * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] 
+ * where the Signature Base String is the text and the key is the concatenated values (each first 
+ * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&' 
+ * character (ASCII code 38) even if empty.
+ *   - Chapter 9.2 ("HMAC-SHA1")
+ */
+class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
+  function get_name() {
+    return "HMAC-SHA1";
+  }
+
+  public function build_signature($request, $consumer, $token) {
+    $base_string = $request->get_signature_base_string();
+    $request->base_string = $base_string;
+
+    $key_parts = array(
+      $consumer->secret,
+      ($token) ? $token->secret : ""
+    );
+
+    $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
+    $key = implode('&', $key_parts);
+
+    return base64_encode(hash_hmac('sha1', $base_string, $key, true));
+  }
+}
+
+/**
+ * The PLAINTEXT method does not provide any security protection and SHOULD only be used 
+ * over a secure channel such as HTTPS. It does not use the Signature Base String.
+ *   - Chapter 9.4 ("PLAINTEXT")
+ */
+class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
+  public function get_name() {
+    return "PLAINTEXT";
+  }
+
+  /**
+   * oauth_signature is set to the concatenated encoded values of the Consumer Secret and 
+   * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is 
+   * empty. The result MUST be encoded again.
+   *   - Chapter 9.4.1 ("Generating Signatures")
+   *
+   * Please note that the second encoding MUST NOT happen in the SignatureMethod, as
+   * OAuthRequest handles this!
+   */
+  public function build_signature($request, $consumer, $token) {
+    $key_parts = array(
+      $consumer->secret,
+      ($token) ? $token->secret : ""
+    );
+
+    $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
+    $key = implode('&', $key_parts);
+    $request->base_string = $key;
+
+    return $key;
+  }
+}
+
+/**
+ * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in 
+ * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for 
+ * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a 
+ * verified way to the Service Provider, in a manner which is beyond the scope of this 
+ * specification.
+ *   - Chapter 9.3 ("RSA-SHA1")
+ */
+abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
+  public function get_name() {
+    return "RSA-SHA1";
+  }
+
+  // Up to the SP to implement this lookup of keys. Possible ideas are:
+  // (1) do a lookup in a table of trusted certs keyed off of consumer
+  // (2) fetch via http using a url provided by the requester
+  // (3) some sort of specific discovery code based on request
+  //
+  // Either way should return a string representation of the certificate
+  protected abstract function fetch_public_cert(&$request);
+
+  // Up to the SP to implement this lookup of keys. Possible ideas are:
+  // (1) do a lookup in a table of trusted certs keyed off of consumer
+  //
+  // Either way should return a string representation of the certificate
+  protected abstract function fetch_private_cert(&$request);
+
+  public function build_signature($request, $consumer, $token) {
+    $base_string = $request->get_signature_base_string();
+    $request->base_string = $base_string;
+
+    // Fetch the private key cert based on the request
+    $cert = $this->fetch_private_cert($request);
+
+    // Pull the private key ID from the certificate
+    $privatekeyid = openssl_get_privatekey($cert);
+
+    // Sign using the key
+    $ok = openssl_sign($base_string, $signature, $privatekeyid);
+
+    // Release the key resource
+    openssl_free_key($privatekeyid);
+
+    return base64_encode($signature);
+  }
+
+  public function check_signature($request, $consumer, $token, $signature) {
+    $decoded_sig = base64_decode($signature);
+
+    $base_string = $request->get_signature_base_string();
+
+    // Fetch the public key cert based on the request
+    $cert = $this->fetch_public_cert($request);
+
+    // Pull the public key ID from the certificate
+    $publickeyid = openssl_get_publickey($cert);
+
+    // Check the computed signature against the one passed in the query
+    $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
+
+    // Release the key resource
+    openssl_free_key($publickeyid);
+
+    return $ok == 1;
+  }
+}
+
+class OAuthRequest {
+  protected $parameters;
+  protected $http_method;
+  protected $http_url;
+  // for debug purposes
+  public $base_string;
+  public static $version = '1.0';
+  public static $POST_INPUT = 'php://input';
+
+  function __construct($http_method, $http_url, $parameters=NULL) {
+    $parameters = ($parameters) ? $parameters : array();
+    $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
+    $this->parameters = $parameters;
+    $this->http_method = $http_method;
+    $this->http_url = $http_url;
+  }
+
+
+  /**
+   * attempt to build up a request from what was passed to the server
+   */
+  public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {
+    $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
+              ? 'http'
+              : 'https';
+    $http_url = ($http_url) ? $http_url : $scheme .
+                              '://' . $_SERVER['HTTP_HOST'] .
+                              ':' .
+                              $_SERVER['SERVER_PORT'] .
+                              $_SERVER['REQUEST_URI'];
+    $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD'];
+
+    // We weren't handed any parameters, so let's find the ones relevant to
+    // this request.
+    // If you run XML-RPC or similar you should use this to provide your own
+    // parsed parameter-list
+    if (!$parameters) {
+      // Find request headers
+      $request_headers = OAuthUtil::get_headers();
+
+      // Parse the query-string to find GET parameters
+      $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
+
+      // It's a POST request of the proper content-type, so parse POST
+      // parameters and add those overriding any duplicates from GET
+      if ($http_method == "POST"
+          &&  isset($request_headers['Content-Type'])
+          && strstr($request_headers['Content-Type'],
+                     'application/x-www-form-urlencoded')
+          ) {
+        $post_data = OAuthUtil::parse_parameters(
+          file_get_contents(self::$POST_INPUT)
+        );
+        $parameters = array_merge($parameters, $post_data);
+      }
+
+      // We have a Authorization-header with OAuth data. Parse the header
+      // and add those overriding any duplicates from GET or POST
+      if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') {
+        $header_parameters = OAuthUtil::split_header(
+          $request_headers['Authorization']
+        );
+        $parameters = array_merge($parameters, $header_parameters);
+      }
+
+    }
+
+    return new OAuthRequest($http_method, $http_url, $parameters);
+  }
+
+  /**
+   * pretty much a helper function to set up the request
+   */
+  public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {
+    $parameters = ($parameters) ?  $parameters : array();
+    $defaults = array("oauth_version" => OAuthRequest::$version,
+                      "oauth_nonce" => OAuthRequest::generate_nonce(),
+                      "oauth_timestamp" => OAuthRequest::generate_timestamp(),
+                      "oauth_consumer_key" => $consumer->key);
+    if ($token)
+      $defaults['oauth_token'] = $token->key;
+
+    $parameters = array_merge($defaults, $parameters);
+
+    return new OAuthRequest($http_method, $http_url, $parameters);
+  }
+
+  public function set_parameter($name, $value, $allow_duplicates = true) {
+    if ($allow_duplicates && isset($this->parameters[$name])) {
+      // We have already added parameter(s) with this name, so add to the list
+      if (is_scalar($this->parameters[$name])) {
+        // This is the first duplicate, so transform scalar (string)
+        // into an array so we can add the duplicates
+        $this->parameters[$name] = array($this->parameters[$name]);
+      }
+
+      $this->parameters[$name][] = $value;
+    } else {
+      $this->parameters[$name] = $value;
+    }
+  }
+
+  public function get_parameter($name) {
+    return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
+  }
+
+  public function get_parameters() {
+    return $this->parameters;
+  }
+
+  public function unset_parameter($name) {
+    unset($this->parameters[$name]);
+  }
+
+  /**
+   * The request parameters, sorted and concatenated into a normalized string.
+   * @return string
+   */
+  public function get_signable_parameters() {
+    // Grab all parameters
+    $params = $this->parameters;
+
+    // Remove oauth_signature if present
+    // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
+    if (isset($params['oauth_signature'])) {
+      unset($params['oauth_signature']);
+    }
+
+    return OAuthUtil::build_http_query($params);
+  }
+
+  /**
+   * Returns the base string of this request
+   *
+   * The base string defined as the method, the url
+   * and the parameters (normalized), each urlencoded
+   * and the concated with &.
+   */
+  public function get_signature_base_string() {
+    $parts = array(
+      $this->get_normalized_http_method(),
+      $this->get_normalized_http_url(),
+      $this->get_signable_parameters()
+    );
+
+    $parts = OAuthUtil::urlencode_rfc3986($parts);
+
+    return implode('&', $parts);
+  }
+
+  /**
+   * just uppercases the http method
+   */
+  public function get_normalized_http_method() {
+    return strtoupper($this->http_method);
+  }
+
+  /**
+   * parses the url and rebuilds it to be
+   * scheme://host/path
+   */
+  public function get_normalized_http_url() {
+    $parts = parse_url($this->http_url);
+
+    $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
+    $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80');
+    $host = (isset($parts['host'])) ? $parts['host'] : '';
+    $path = (isset($parts['path'])) ? $parts['path'] : '';
+
+    if (($scheme == 'https' && $port != '443')
+        || ($scheme == 'http' && $port != '80')) {
+      $host = "$host:$port";
+    }
+    return "$scheme://$host$path";
+  }
+
+  /**
+   * builds a url usable for a GET request
+   */
+  public function to_url() {
+    $post_data = $this->to_postdata();
+    $out = $this->get_normalized_http_url();
+    if ($post_data) {
+      $out .= '?'.$post_data;
+    }
+    return $out;
+  }
+
+  /**
+   * builds the data one would send in a POST request
+   */
+  public function to_postdata() {
+    return OAuthUtil::build_http_query($this->parameters);
+  }
+
+  /**
+   * builds the Authorization: header
+   */
+  public function to_header($realm=null) {
+    $first = true;
+	if($realm) {
+      $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"';
+      $first = false;
+    } else
+      $out = 'Authorization: OAuth';
+
+    $total = array();
+    foreach ($this->parameters as $k => $v) {
+      if (substr($k, 0, 5) != "oauth") continue;
+      if (is_array($v)) {
+        throw new OAuthException('Arrays not supported in headers');
+      }
+      $out .= ($first) ? ' ' : ',';
+      $out .= OAuthUtil::urlencode_rfc3986($k) .
+              '="' .
+              OAuthUtil::urlencode_rfc3986($v) .
+              '"';
+      $first = false;
+    }
+    return $out;
+  }
+
+  public function __toString() {
+    return $this->to_url();
+  }
+
+
+  public function sign_request($signature_method, $consumer, $token) {
+    $this->set_parameter(
+      "oauth_signature_method",
+      $signature_method->get_name(),
+      false
+    );
+    $signature = $this->build_signature($signature_method, $consumer, $token);
+    $this->set_parameter("oauth_signature", $signature, false);
+  }
+
+  public function build_signature($signature_method, $consumer, $token) {
+    $signature = $signature_method->build_signature($this, $consumer, $token);
+    return $signature;
+  }
+
+  /**
+   * util function: current timestamp
+   */
+  private static function generate_timestamp() {
+    return time();
+  }
+
+  /**
+   * util function: current nonce
+   */
+  private static function generate_nonce() {
+    $mt = microtime();
+    $rand = mt_rand();
+
+    return md5($mt . $rand); // md5s look nicer than numbers
+  }
+}
+
+class OAuthServer {
+  protected $timestamp_threshold = 300; // in seconds, five minutes
+  protected $version = '1.0';             // hi blaine
+  protected $signature_methods = array();
+
+  protected $data_store;
+
+  function __construct($data_store) {
+    $this->data_store = $data_store;
+  }
+
+  public function add_signature_method($signature_method) {
+    $this->signature_methods[$signature_method->get_name()] =
+      $signature_method;
+  }
+
+  // high level functions
+
+  /**
+   * process a request_token request
+   * returns the request token on success
+   */
+  public function fetch_request_token(&$request) {
+    $this->get_version($request);
+
+    $consumer = $this->get_consumer($request);
+
+    // no token required for the initial token request
+    $token = NULL;
+
+    $this->check_signature($request, $consumer, $token);
+
+    // Rev A change
+    $callback = $request->get_parameter('oauth_callback');
+    $new_token = $this->data_store->new_request_token($consumer, $callback);
+
+    return $new_token;
+  }
+
+  /**
+   * process an access_token request
+   * returns the access token on success
+   */
+  public function fetch_access_token(&$request) {
+    $this->get_version($request);
+
+    $consumer = $this->get_consumer($request);
+
+    // requires authorized request token
+    $token = $this->get_token($request, $consumer, "request");
+
+    $this->check_signature($request, $consumer, $token);
+
+    // Rev A change
+    $verifier = $request->get_parameter('oauth_verifier');
+    $new_token = $this->data_store->new_access_token($token, $consumer, $verifier);
+
+    return $new_token;
+  }
+
+  /**
+   * verify an api call, checks all the parameters
+   */
+  public function verify_request(&$request) {
+    $this->get_version($request);
+    $consumer = $this->get_consumer($request);
+    $token = $this->get_token($request, $consumer, "access");
+    $this->check_signature($request, $consumer, $token);
+    return array($consumer, $token);
+  }
+
+  // Internals from here
+  /**
+   * version 1
+   */
+  private function get_version(&$request) {
+    $version = $request->get_parameter("oauth_version");
+    if (!$version) {
+      // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. 
+      // Chapter 7.0 ("Accessing Protected Ressources")
+      $version = '1.0';
+    }
+    if ($version !== $this->version) {
+      throw new OAuthException("OAuth version '$version' not supported");
+    }
+    return $version;
+  }
+
+  /**
+   * figure out the signature with some defaults
+   */
+  private function get_signature_method($request) {
+    $signature_method = $request instanceof OAuthRequest 
+        ? $request->get_parameter("oauth_signature_method")
+        : NULL;
+
+    if (!$signature_method) {
+      // According to chapter 7 ("Accessing Protected Ressources") the signature-method
+      // parameter is required, and we can't just fallback to PLAINTEXT
+      throw new OAuthException('No signature method parameter. This parameter is required');
+    }
+
+    if (!in_array($signature_method,
+                  array_keys($this->signature_methods))) {
+      throw new OAuthException(
+        "Signature method '$signature_method' not supported " .
+        "try one of the following: " .
+        implode(", ", array_keys($this->signature_methods))
+      );
+    }
+    return $this->signature_methods[$signature_method];
+  }
+
+  /**
+   * try to find the consumer for the provided request's consumer key
+   */
+  private function get_consumer($request) {
+    $consumer_key = $request instanceof OAuthRequest 
+        ? $request->get_parameter("oauth_consumer_key")
+        : NULL;
+
+    if (!$consumer_key) {
+      throw new OAuthException("Invalid consumer key");
+    }
+
+    $consumer = $this->data_store->lookup_consumer($consumer_key);
+    if (!$consumer) {
+      throw new OAuthException("Invalid consumer");
+    }
+
+    return $consumer;
+  }
+
+  /**
+   * try to find the token for the provided request's token key
+   */
+  private function get_token($request, $consumer, $token_type="access") {
+    $token_field = $request instanceof OAuthRequest
+         ? $request->get_parameter('oauth_token')
+         : NULL;
+
+    $token = $this->data_store->lookup_token(
+      $consumer, $token_type, $token_field
+    );
+    if (!$token) {
+      throw new OAuthException("Invalid $token_type token: $token_field");
+    }
+    return $token;
+  }
+
+  /**
+   * all-in-one function to check the signature on a request
+   * should guess the signature method appropriately
+   */
+  private function check_signature($request, $consumer, $token) {
+    // this should probably be in a different method
+    $timestamp = $request instanceof OAuthRequest
+        ? $request->get_parameter('oauth_timestamp')
+        : NULL;
+    $nonce = $request instanceof OAuthRequest
+        ? $request->get_parameter('oauth_nonce')
+        : NULL;
+
+    $this->check_timestamp($timestamp);
+    $this->check_nonce($consumer, $token, $nonce, $timestamp);
+
+    $signature_method = $this->get_signature_method($request);
+
+    $signature = $request->get_parameter('oauth_signature');
+    $valid_sig = $signature_method->check_signature(
+      $request,
+      $consumer,
+      $token,
+      $signature
+    );
+
+    if (!$valid_sig) {
+      throw new OAuthException("Invalid signature");
+    }
+  }
+
+  /**
+   * check that the timestamp is new enough
+   */
+  private function check_timestamp($timestamp) {
+    if( ! $timestamp )
+      throw new OAuthException(
+        'Missing timestamp parameter. The parameter is required'
+      );
+    
+    // verify that timestamp is recentish
+    $now = time();
+    if (abs($now - $timestamp) > $this->timestamp_threshold) {
+      throw new OAuthException(
+        "Expired timestamp, yours $timestamp, ours $now"
+      );
+    }
+  }
+
+  /**
+   * check that the nonce is not repeated
+   */
+  private function check_nonce($consumer, $token, $nonce, $timestamp) {
+    if( ! $nonce )
+      throw new OAuthException(
+        'Missing nonce parameter. The parameter is required'
+      );
+
+    // verify that the nonce is uniqueish
+    $found = $this->data_store->lookup_nonce(
+      $consumer,
+      $token,
+      $nonce,
+      $timestamp
+    );
+    if ($found) {
+      throw new OAuthException("Nonce already used: $nonce");
+    }
+  }
+
+}
+
+class OAuthDataStore {
+  function lookup_consumer($consumer_key) {
+    // implement me
+  }
+
+  function lookup_token($consumer, $token_type, $token) {
+    // implement me
+  }
+
+  function lookup_nonce($consumer, $token, $nonce, $timestamp) {
+    // implement me
+  }
+
+  function new_request_token($consumer, $callback = null) {
+    // return a new token attached to this consumer
+  }
+
+  function new_access_token($token, $consumer, $verifier = null) {
+    // return a new access token attached to this consumer
+    // for the user associated with this token if the request token
+    // is authorized
+    // should also invalidate the request token
+  }
+
+}
+
+class OAuthUtil {
+  public static function urlencode_rfc3986($input) {
+  if (is_array($input)) {
+    return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input);
+  } else if (is_scalar($input)) {
+    return str_replace(
+      '+',
+      ' ',
+      str_replace('%7E', '~', rawurlencode($input))
+    );
+  } else {
+    return '';
+  }
+}
+
+
+  // This decode function isn't taking into consideration the above
+  // modifications to the encoding process. However, this method doesn't
+  // seem to be used anywhere so leaving it as is.
+  public static function urldecode_rfc3986($string) {
+    return urldecode($string);
+  }
+
+  // Utility function for turning the Authorization: header into
+  // parameters, has to do some unescaping
+  // Can filter out any non-oauth parameters if needed (default behaviour)
+  // May 28th, 2010 - method updated to tjerk.meesters for a speed improvement.
+  //                  see http://code.google.com/p/oauth/issues/detail?id=163
+  public static function split_header($header, $only_allow_oauth_parameters = true) {
+    $params = array();
+    if (preg_match_all('/('.($only_allow_oauth_parameters ? 'oauth_' : '').'[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) {
+      foreach ($matches[1] as $i => $h) {
+        $params[$h] = OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]);
+      }
+      if (isset($params['realm'])) {
+        unset($params['realm']);
+      }
+    }
+    return $params;
+  }
+
+  // helper to try to sort out headers for people who aren't running apache
+  public static function get_headers() {
+    if (function_exists('apache_request_headers')) {
+      // we need this to get the actual Authorization: header
+      // because apache tends to tell us it doesn't exist
+      $headers = apache_request_headers();
+
+      // sanitize the output of apache_request_headers because
+      // we always want the keys to be Cased-Like-This and arh()
+      // returns the headers in the same case as they are in the
+      // request
+      $out = array();
+      foreach ($headers AS $key => $value) {
+        $key = str_replace(
+            " ",
+            "-",
+            ucwords(strtolower(str_replace("-", " ", $key)))
+          );
+        $out[$key] = $value;
+      }
+    } else {
+      // otherwise we don't have apache and are just going to have to hope
+      // that $_SERVER actually contains what we need
+      $out = array();
+      if( isset($_SERVER['CONTENT_TYPE']) )
+        $out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
+      if( isset($_ENV['CONTENT_TYPE']) )
+        $out['Content-Type'] = $_ENV['CONTENT_TYPE'];
+
+      foreach ($_SERVER as $key => $value) {
+        if (substr($key, 0, 5) == "HTTP_") {
+          // this is chaos, basically it is just there to capitalize the first
+          // letter of every word that is not an initial HTTP and strip HTTP
+          // code from przemek
+          $key = str_replace(
+            " ",
+            "-",
+            ucwords(strtolower(str_replace("_", " ", substr($key, 5))))
+          );
+          $out[$key] = $value;
+        }
+      }
+    }
+    return $out;
+  }
+
+  // This function takes a input like a=b&a=c&d=e and returns the parsed
+  // parameters like this
+  // array('a' => array('b','c'), 'd' => 'e')
+  public static function parse_parameters( $input ) {
+    if (!isset($input) || !$input) return array();
+
+    $pairs = explode('&', $input);
+
+    $parsed_parameters = array();
+    foreach ($pairs as $pair) {
+      $split = explode('=', $pair, 2);
+      $parameter = OAuthUtil::urldecode_rfc3986($split[0]);
+      $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : '';
+
+      if (isset($parsed_parameters[$parameter])) {
+        // We have already recieved parameter(s) with this name, so add to the list
+        // of parameters with this name
+
+        if (is_scalar($parsed_parameters[$parameter])) {
+          // This is the first duplicate, so transform scalar (string) into an array
+          // so we can add the duplicates
+          $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);
+        }
+
+        $parsed_parameters[$parameter][] = $value;
+      } else {
+        $parsed_parameters[$parameter] = $value;
+      }
+    }
+    return $parsed_parameters;
+  }
+
+  public static function build_http_query($params) {
+    if (!$params) return '';
+
+    // Urlencode both keys and values
+    $keys = OAuthUtil::urlencode_rfc3986(array_keys($params));
+    $values = OAuthUtil::urlencode_rfc3986(array_values($params));
+    $params = array_combine($keys, $values);
+
+    // Parameters are sorted by name, using lexicographical byte value ordering.
+    // Ref: Spec: 9.1.1 (1)
+    uksort($params, 'strcmp');
+
+    $pairs = array();
+    foreach ($params as $parameter => $value) {
+      if (is_array($value)) {
+        // If two or more parameters share the same name, they are sorted by their value
+        // Ref: Spec: 9.1.1 (1)
+        // June 12th, 2010 - changed to sort because of issue 164 by hidetaka
+        sort($value, SORT_STRING);
+        foreach ($value as $duplicate_value) {
+          $pairs[] = $parameter . '=' . $duplicate_value;
+        }
+      } else {
+        $pairs[] = $parameter . '=' . $value;
+      }
+    }
+    // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
+    // Each name-value pair is separated by an '&' character (ASCII code 38)
+    return implode('&', $pairs);
+  }
+}
+
+?>
diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/main.css b/chrome/common/extensions/docs/examples/apps/hello-php/main.css
new file mode 100644
index 0000000..109547d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-php/main.css
@@ -0,0 +1,89 @@
+body {
+  font-family: Arial, Verdana, san-serif;
+  font-size: 11pt;
+  color: #666;
+  margin: 0;
+}
+
+nav {
+  display: block;  /* for older browsers */
+  text-align: right;
+  margin-bottom: 10px;
+  padding: 10px;
+  border-bottom: 1px solid #C6D2EB;
+  background: -moz-linear-gradient(#fff, #EEF1F9 60%, #EEF1F9);
+  background: -webkit-linear-gradient(#fff, #EEF1F9 60%, #EEF1F9);
+  text-shadow: 1px 1px 1px #fff;
+}
+
+nav span {
+  float: left;
+  font-weight: bold;
+}
+
+a {
+  color: #2F58A4;
+  text-decoration: none;
+}
+
+a:hover {
+  text-decoration: underline;
+}
+
+form {
+  margin: 0;
+}
+
+input[type='text'] {
+  padding: 3px;
+}
+
+li {
+  list-style: none;
+}
+
+.error {
+  margin: 10px 0 10px;
+  padding: 10px;
+  text-align: center;
+  border: 1px solid red;
+  color: red;
+  font-weight: bold;
+  background-color: #ffffcc;
+  -moz-border-radius: 5px;
+  border-radius: 5px;
+  text-shadow: 2px 2px 2px #ccc;
+}
+
+#container {
+  margin-top: 10px;
+  padding: 10px;
+}
+
+#access-level {
+  text-align: center;
+  padding: 15px;
+  border: 1px dotted #C6D2EB;
+  font-size: 12pt;
+  -moz-border-radius: 15px;
+  border-radius: 15px;
+  width: 550px;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+#license-server-response span {
+  font-weight: bold;
+}
+
+#license-server-response span.full {
+  color: green;
+}
+
+#license-server-response span.free_trial {
+  color: orange;
+}
+
+#license-server-response span.no {
+  color: red;
+}
diff --git a/chrome/common/extensions/docs/examples/apps/hello-php/popuplib.js b/chrome/common/extensions/docs/examples/apps/hello-php/popuplib.js
new file mode 100644
index 0000000..4b0fd40
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-php/popuplib.js
@@ -0,0 +1,279 @@
+//  Copyright 2009 Google Inc.
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+
+
+//  PopupManager is a library to facilitate integration with OpenID
+//  identity providers (OP)s that support a pop-up authentication interface.
+//  To create a popup window, you first construct a popupOpener customized
+//  for your site and a particular identity provider, E.g.:
+//
+//  var googleOpener = popupManager.createOpener(openidParams);
+//
+//  where 'openidParams' are customized for Google in this instance.
+//  (typically you just change the openidpoint, the version number
+//  (the openid.ns parameter) and the extensions based on what
+//  the OP supports.
+//  OpenID libraries can often discover these properties
+//  automatically from the location of an XRD document.
+//
+//  Then, you can either directly call
+//  googleOpener.popup(width, height), where 'width' and 'height' are your choices
+//  for popup size, or you can display a button 'Sign in with Google' and set the
+//..'onclick' handler of the button to googleOpener.popup()
+
+var popupManager = {};
+
+// Library constants
+
+popupManager.constants = {
+  'darkCover' : 'popupManager_darkCover_div',
+  'darkCoverStyle' : ['position:absolute;',
+                      'top:0px;',
+                      'left:0px;',
+                      'padding-right:0px;',
+                      'padding-bottom:0px;',
+                      'background-color:#000000;',
+                      'opacity:0.5;', //standard-compliant browsers
+                      '-moz-opacity:0.5;',           // old Mozilla
+                      'filter:alpha(opacity=0.5);',  // IE
+                      'z-index:10000;',
+                      'width:100%;',
+                      'height:100%;'
+                      ].join(''),
+  'openidSpec' : {
+     'identifier_select' : 'http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select',
+     'namespace2' : 'http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0'
+  } };
+
+// Computes the size of the window contents. Returns a pair of
+// coordinates [width, height] which can be [0, 0] if it was not possible
+// to compute the values.
+popupManager.getWindowInnerSize = function() {
+  var width = 0;
+  var height = 0;
+  var elem = null;
+  if ('innerWidth' in window) {
+    // For non-IE
+    width = window.innerWidth;
+    height = window.innerHeight;
+  } else {
+    // For IE,
+    if (('BackCompat' === window.document.compatMode)
+        && ('body' in window.document)) {
+        elem = window.document.body;
+    } else if ('documentElement' in window.document) {
+      elem = window.document.documentElement;
+    }
+    if (elem !== null) {
+      width = elem.offsetWidth;
+      height = elem.offsetHeight;
+    }
+  }
+  return [width, height];
+};
+
+// Computes the coordinates of the parent window.
+// Gets the coordinates of the parent frame
+popupManager.getParentCoords = function() {
+  var width = 0;
+  var height = 0;
+  if ('screenLeft' in window) {
+    // IE-compatible variants
+    width = window.screenLeft;
+    height = window.screenTop;
+  } else if ('screenX' in window) {
+    // Firefox-compatible
+    width = window.screenX;
+    height = window.screenY;
+  }
+  return [width, height];
+};
+
+// Computes the coordinates of the new window, so as to center it
+// over the parent frame
+popupManager.getCenteredCoords = function(width, height) {
+   var parentSize = this.getWindowInnerSize();
+   var parentPos = this.getParentCoords();
+   var xPos = parentPos[0] +
+       Math.max(0, Math.floor((parentSize[0] - width) / 2));
+   var yPos = parentPos[1] +
+       Math.max(0, Math.floor((parentSize[1] - height) / 2));
+   return [xPos, yPos];
+};
+
+//  A utility class, implements an onOpenHandler that darkens the screen
+//  by overlaying it with a semi-transparent black layer. To use, ensure that
+//  no screen element has a z-index at or above 10000.
+//  This layer will be suppressed automatically after the screen closes.
+//
+//  Note: If you want to perform other operations before opening the popup, but
+//  also would like the screen to darken, you can define a custom handler
+//  as such:
+//  var myOnOpenHandler = function(inputs) {
+//    .. do something
+//    popupManager.darkenScreen();
+//    .. something else
+//  };
+//  Then you pass myOnOpenHandler as input to the opener, as in:
+//  var openidParams = {};
+//  openidParams.onOpenHandler = myOnOpenHandler;
+//  ... other customizations
+//  var myOpener = popupManager.createOpener(openidParams);
+popupManager.darkenScreen = function() {
+  var darkCover = window.document.getElementById(window.popupManager.constants['darkCover']);
+  if (!darkCover) {
+    darkCover = window.document.createElement('div');
+    darkCover['id'] = window.popupManager.constants['darkCover'];
+    darkCover.setAttribute('style', window.popupManager.constants['darkCoverStyle']);
+    window.document.body.appendChild(darkCover);
+  }
+  darkCover.style.visibility = 'visible';
+};
+
+//  Returns a an object that can open a popup window customized for an OP & RP.
+//  to use you call var opener = popupManager.cretePopupOpener(openidParams);
+//  and then you can assign the 'onclick' handler of a button to
+//  opener.popup(width, height), where width and height are the values of the popup size;
+//
+//  To use it, you would typically have code such as:
+//  var myLoginCheckFunction = ...  some AJAXy call or page refresh operation
+//  that will cause the user to see the logged-in experience in the current page.
+//  var openidParams = { realm : 'openid.realm', returnToUrl : 'openid.return_to',
+//  opEndpoint : 'openid.op_endpoint', onCloseHandler : myLoginCheckFunction,
+//  shouldEncodeUrls : 'true' (default) or 'false', extensions : myOpenIDExtensions };
+//
+//  Here extensions include any OpenID extensions that you support. For instance,
+//  if you support Attribute Exchange v.1.0, you can say:
+//  (Example for attribute exchange request for email and name,
+//  assuming that shouldEncodeUrls = 'true':)
+//  var myOpenIDExtensions = {
+//      'openid.ax.ns' : 'http://openid.net/srv/ax/1.0',
+//      'openid.ax.type.email' : 'http://axschema.org/contact/email',
+//      'openid.ax.type.name1' : 'http://axschema.org/namePerson/first',
+//      'openid.ax.type.name2' : 'http://axschema.org/namePerson/last',
+//      'openid.ax.required' : 'email,name1,name2' };
+//  Note that the 'ui' namespace is reserved by this library for the OpenID
+//  UI extension, and that the mode 'popup' is automatically applied.
+//  If you wish to make use of the 'language' feature of the OpenID UI extension
+//  simply add the following entry (example assumes the language requested
+//  is Swiss French:
+//  var my OpenIDExtensions = {
+//    ... // other extension parameters
+//    'openid.ui.language' : 'fr_CH',
+//    ... };
+popupManager.createPopupOpener = (function(openidParams) {
+  var interval_ = null;
+  var popupWindow_ = null;
+  var that = this;
+  var shouldEscape_ = ('shouldEncodeUrls' in openidParams) ? openidParams.shouldEncodeUrls : true;
+  var encodeIfRequested_ = function(url) {
+    return (shouldEscape_ ? encodeURIComponent(url) : url);
+  };
+  var identifier_ = ('identifier' in openidParams) ? encodeIfRequested_(openidParams.identifier) :
+      this.constants.openidSpec.identifier_select;
+  var identity_ = ('identity' in openidParams) ? encodeIfRequested_(openidParams.identity) :
+      this.constants.openidSpec.identifier_select;
+  var openidNs_ = ('namespace' in openidParams) ? encodeIfRequested_(openidParams.namespace) :
+      this.constants.openidSpec.namespace2;
+  var onOpenHandler_ = (('onOpenHandler' in openidParams) &&
+      ('function' === typeof(openidParams.onOpenHandler))) ?
+          openidParams.onOpenHandler : this.darkenScreen;
+  var onCloseHandler_ = (('onCloseHandler' in openidParams) &&
+      ('function' === typeof(openidParams.onCloseHandler))) ?
+          openidParams.onCloseHandler : null;
+  var returnToUrl_ = ('returnToUrl' in openidParams) ? openidParams.returnToUrl : null;
+  var realm_ = ('realm' in openidParams) ? openidParams.realm : null;
+  var endpoint_ = ('opEndpoint' in openidParams) ? openidParams.opEndpoint : null;
+  var extensions_ = ('extensions' in openidParams) ? openidParams.extensions : null;
+
+  // processes key value pairs, escaping any input;
+  var keyValueConcat_ = function(keyValuePairs) {
+    var result = "";
+    for (key in keyValuePairs) {
+      result += ['&', key, '=', encodeIfRequested_(keyValuePairs[key])].join('');
+    }
+    return result;
+  };
+
+  //Assembles the OpenID request from customizable parameters
+  var buildUrlToOpen_ = function() {
+    var connector = '&';
+    var encodedUrl = null;
+    var urlToOpen = null;
+    if ((null === endpoint_) || (null === returnToUrl_)) {
+      return;
+    }
+    if (endpoint_.indexOf('?') === -1) {
+      connector = '?';
+    }
+    encodedUrl = encodeIfRequested_(returnToUrl_);
+    urlToOpen = [ endpoint_, connector,
+        'openid.ns=', openidNs_,
+        '&openid.mode=checkid_setup',
+        '&openid.claimed_id=', identifier_,
+        '&openid.identity=', identity_,
+        '&openid.return_to=', encodedUrl ].join('');
+    if (realm_ !== null) {
+      urlToOpen += "&openid.realm=" + encodeIfRequested_(realm_);
+    }
+    if (extensions_ !== null) {
+      urlToOpen += keyValueConcat_(extensions_);
+    }
+    urlToOpen += '&openid.ns.ui=' + encodeURIComponent(
+        'http://specs.openid.net/extensions/ui/1.0');
+    urlToOpen += '&openid.ui.mode=popup';
+    return urlToOpen;
+  };
+
+  // Tests that the popup window has closed
+  var isPopupClosed_ = function() {
+    return (!popupWindow_ || popupWindow_.closed);
+  };
+
+  // Check to perform at each execution of the timed loop. It also triggers
+  // the action that follows the closing of the popup
+  var waitForPopupClose_ = function() {
+    if (isPopupClosed_()) {
+      popupWindow_ = null;
+      var darkCover = window.document.getElementById(window.popupManager.constants['darkCover']);
+      if (darkCover) {
+        darkCover.style.visibility = 'hidden';
+      }
+      if (onCloseHandler_ !== null) {
+        onCloseHandler_();
+      }
+      if ((null !== interval_)) {
+        window.clearInterval(interval_);
+        interval_ = null;
+      }
+    }
+  };
+
+  return {
+    // Function that opens the window.
+    popup: function(width, height) {
+      var urlToOpen = buildUrlToOpen_();
+      if (onOpenHandler_ !== null) {
+        onOpenHandler_();
+      }
+      var coordinates = that.getCenteredCoords(width, height);
+      popupWindow_ = window.open(urlToOpen, "",
+          "width=" + width + ",height=" + height +
+          ",status=1,location=1,resizable=yes" +
+          ",left=" + coordinates[0] +",top=" + coordinates[1]);
+      interval_ = window.setInterval(waitForPopupClose_, 80);
+      return true;
+    }
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/apps/hello-python/NOTICE b/chrome/common/extensions/docs/examples/apps/hello-python/NOTICE
new file mode 100644
index 0000000..2e0d217
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-python/NOTICE
@@ -0,0 +1,53 @@
+This sample application includes two Python libraries:
+
+
+Name: python-oauth2
+URL:  http://github.com/simplegeo/python-oauth2/
+================================================================================
+The MIT License
+
+Copyright (c) 2007 Leah Culver
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+
+Name: httplib2
+URL:  http://code.google.com/p/httplib2/
+================================================================================
+The MIT License
+
+Copyright (c) <year> <copyright holders>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/chrome/common/extensions/docs/examples/apps/hello-python/README b/chrome/common/extensions/docs/examples/apps/hello-python/README
new file mode 100644
index 0000000..53ff640
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-python/README
@@ -0,0 +1,30 @@
+Hello License Python
+====================
+
+Overview
+--------
+This application implements a sample client for the Chrome Web Store Licensing
+API on Google's Python App Engine service.
+
+For information about this API, please see:
+http://code.google.com/chrome/webstore/
+
+Usage
+-----
+First, register an App Engine app at www.appspot.com.  Make sure you select
+Federated Login as the login provider for the app.
+
+You'll need to configure this sample before it will be functional.  Edit
+app.yaml and replace the text INSERT APPLICATION NAME HERE with the application
+identifier you registered.
+
+Second, obtain a token for the Chrome Web Store license server.  Check the
+license server documentation for instructions on how to do this.  Edit main.py
+and replace the following three configuration lines with your own information:
+
+  'oauth_token': 'INSERT OAUTH TOKEN HERE',
+  'oauth_token_secret': 'INSERT OAUTH TOKEN SECRET HERE',
+  'app_id': 'INSERT APPLICATION ID HERE',
+
+Then deploy your application to App Engine, and you will be able to log in
+with OpenID and check the license status of your account.
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/apps/hello-python/app.yaml b/chrome/common/extensions/docs/examples/apps/hello-python/app.yaml
new file mode 100644
index 0000000..14eca12
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-python/app.yaml
@@ -0,0 +1,16 @@
+# Copyright (c) 2012 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.
+
+application: INSERT APPLICATION NAME HERE
+version: 1
+runtime: python
+api_version: 1
+
+handlers:
+- url: /favicon.ico
+  static_files: favicon.ico
+  upload: favicon.ico
+
+- url: .*
+  script: main.py
diff --git a/chrome/common/extensions/docs/examples/apps/hello-python/favicon.ico b/chrome/common/extensions/docs/examples/apps/hello-python/favicon.ico
new file mode 100644
index 0000000..8e5f1b5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-python/favicon.ico
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/apps/hello-python/httplib2/__init__.py b/chrome/common/extensions/docs/examples/apps/hello-python/httplib2/__init__.py
new file mode 100644
index 0000000..3cebcb3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-python/httplib2/__init__.py
@@ -0,0 +1,1202 @@
+from __future__ import generators
+"""
+httplib2
+
+A caching http interface that supports ETags and gzip
+to conserve bandwidth. 
+
+Requires Python 2.3 or later
+
+Changelog:
+2007-08-18, Rick: Modified so it's able to use a socks proxy if needed.
+
+"""
+
+__author__ = "Joe Gregorio (joe@bitworking.org)"
+__copyright__ = "Copyright 2006, Joe Gregorio"
+__contributors__ = ["Thomas Broyer (t.broyer@ltgt.net)",
+    "James Antill",
+    "Xavier Verges Farrero",
+    "Jonathan Feinberg",
+    "Blair Zajac",
+    "Sam Ruby",
+    "Louis Nyffenegger"]
+__license__ = "MIT"
+__version__ = "$Rev$"
+
+import re 
+import sys 
+import email
+import email.Utils
+import email.Message
+import email.FeedParser
+import StringIO
+import gzip
+import zlib
+import httplib
+import urlparse
+import base64
+import os
+import copy
+import calendar
+import time
+import random
+# remove depracated warning in python2.6
+try:
+    from hashlib import sha1 as _sha, md5 as _md5
+except ImportError:
+    import sha
+    import md5
+    _sha = sha.new
+    _md5 = md5.new
+import hmac
+from gettext import gettext as _
+import socket
+
+try:
+    import socks
+except ImportError:
+    socks = None
+
+# Build the appropriate socket wrapper for ssl
+try:
+    import ssl # python 2.6
+    _ssl_wrap_socket = ssl.wrap_socket
+except ImportError:
+    def _ssl_wrap_socket(sock, key_file, cert_file):
+        ssl_sock = socket.ssl(sock, key_file, cert_file)
+        return httplib.FakeSocket(sock, ssl_sock)
+
+
+if sys.version_info >= (2,3):
+    from iri2uri import iri2uri
+else:
+    def iri2uri(uri):
+        return uri
+
+def has_timeout(timeout): # python 2.6
+    if hasattr(socket, '_GLOBAL_DEFAULT_TIMEOUT'):
+        return (timeout is not None and timeout is not socket._GLOBAL_DEFAULT_TIMEOUT)
+    return (timeout is not None)
+
+__all__ = ['Http', 'Response', 'ProxyInfo', 'HttpLib2Error',
+  'RedirectMissingLocation', 'RedirectLimit', 'FailedToDecompressContent', 
+  'UnimplementedDigestAuthOptionError', 'UnimplementedHmacDigestAuthOptionError',
+  'debuglevel']
+
+
+# The httplib debug level, set to a non-zero value to get debug output
+debuglevel = 0
+
+
+# Python 2.3 support
+if sys.version_info < (2,4):
+    def sorted(seq):
+        seq.sort()
+        return seq
+
+# Python 2.3 support
+def HTTPResponse__getheaders(self):
+    """Return list of (header, value) tuples."""
+    if self.msg is None:
+        raise httplib.ResponseNotReady()
+    return self.msg.items()
+
+if not hasattr(httplib.HTTPResponse, 'getheaders'):
+    httplib.HTTPResponse.getheaders = HTTPResponse__getheaders
+
+# All exceptions raised here derive from HttpLib2Error
+class HttpLib2Error(Exception): pass
+
+# Some exceptions can be caught and optionally 
+# be turned back into responses. 
+class HttpLib2ErrorWithResponse(HttpLib2Error):
+    def __init__(self, desc, response, content):
+        self.response = response
+        self.content = content
+        HttpLib2Error.__init__(self, desc)
+
+class RedirectMissingLocation(HttpLib2ErrorWithResponse): pass
+class RedirectLimit(HttpLib2ErrorWithResponse): pass
+class FailedToDecompressContent(HttpLib2ErrorWithResponse): pass
+class UnimplementedDigestAuthOptionError(HttpLib2ErrorWithResponse): pass
+class UnimplementedHmacDigestAuthOptionError(HttpLib2ErrorWithResponse): pass
+
+class RelativeURIError(HttpLib2Error): pass
+class ServerNotFoundError(HttpLib2Error): pass
+
+# Open Items:
+# -----------
+# Proxy support
+
+# Are we removing the cached content too soon on PUT (only delete on 200 Maybe?)
+
+# Pluggable cache storage (supports storing the cache in
+#   flat files by default. We need a plug-in architecture
+#   that can support Berkeley DB and Squid)
+
+# == Known Issues ==
+# Does not handle a resource that uses conneg and Last-Modified but no ETag as a cache validator.
+# Does not handle Cache-Control: max-stale
+# Does not use Age: headers when calculating cache freshness.
+
+
+# The number of redirections to follow before giving up.
+# Note that only GET redirects are automatically followed.
+# Will also honor 301 requests by saving that info and never
+# requesting that URI again.
+DEFAULT_MAX_REDIRECTS = 5
+
+# Which headers are hop-by-hop headers by default
+HOP_BY_HOP = ['connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailers', 'transfer-encoding', 'upgrade']
+
+def _get_end2end_headers(response):
+    hopbyhop = list(HOP_BY_HOP)
+    hopbyhop.extend([x.strip() for x in response.get('connection', '').split(',')])
+    return [header for header in response.keys() if header not in hopbyhop]
+
+URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?")
+
+def parse_uri(uri):
+    """Parses a URI using the regex given in Appendix B of RFC 3986.
+
+        (scheme, authority, path, query, fragment) = parse_uri(uri)
+    """
+    groups = URI.match(uri).groups()
+    return (groups[1], groups[3], groups[4], groups[6], groups[8])
+
+def urlnorm(uri):
+    (scheme, authority, path, query, fragment) = parse_uri(uri)
+    if not scheme or not authority:
+        raise RelativeURIError("Only absolute URIs are allowed. uri = %s" % uri)
+    authority = authority.lower()
+    scheme = scheme.lower()
+    if not path: 
+        path = "/"
+    # Could do syntax based normalization of the URI before
+    # computing the digest. See Section 6.2.2 of Std 66.
+    request_uri = query and "?".join([path, query]) or path
+    scheme = scheme.lower()
+    defrag_uri = scheme + "://" + authority + request_uri
+    return scheme, authority, request_uri, defrag_uri
+
+
+# Cache filename construction (original borrowed from Venus http://intertwingly.net/code/venus/)
+re_url_scheme    = re.compile(r'^\w+://')
+re_slash         = re.compile(r'[?/:|]+')
+
+def safename(filename):
+    """Return a filename suitable for the cache.
+
+    Strips dangerous and common characters to create a filename we
+    can use to store the cache in.
+    """
+
+    try:
+        if re_url_scheme.match(filename):
+            if isinstance(filename,str):
+                filename = filename.decode('utf-8')
+                filename = filename.encode('idna')
+            else:
+                filename = filename.encode('idna')
+    except UnicodeError:
+        pass
+    if isinstance(filename,unicode):
+        filename=filename.encode('utf-8')
+    filemd5 = _md5(filename).hexdigest()
+    filename = re_url_scheme.sub("", filename)
+    filename = re_slash.sub(",", filename)
+
+    # limit length of filename
+    if len(filename)>200:
+        filename=filename[:200]
+    return ",".join((filename, filemd5))
+
+NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
+def _normalize_headers(headers):
+    return dict([ (key.lower(), NORMALIZE_SPACE.sub(value, ' ').strip())  for (key, value) in headers.iteritems()])
+
+def _parse_cache_control(headers):
+    retval = {}
+    if headers.has_key('cache-control'):
+        parts =  headers['cache-control'].split(',')
+        parts_with_args = [tuple([x.strip().lower() for x in part.split("=", 1)]) for part in parts if -1 != part.find("=")]
+        parts_wo_args = [(name.strip().lower(), 1) for name in parts if -1 == name.find("=")]
+        retval = dict(parts_with_args + parts_wo_args)
+    return retval 
+
+# Whether to use a strict mode to parse WWW-Authenticate headers
+# Might lead to bad results in case of ill-formed header value,
+# so disabled by default, falling back to relaxed parsing.
+# Set to true to turn on, usefull for testing servers.
+USE_WWW_AUTH_STRICT_PARSING = 0
+
+# In regex below:
+#    [^\0-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+             matches a "token" as defined by HTTP
+#    "(?:[^\0-\x08\x0A-\x1f\x7f-\xff\\\"]|\\[\0-\x7f])*?"    matches a "quoted-string" as defined by HTTP, when LWS have already been replaced by a single space
+# Actually, as an auth-param value can be either a token or a quoted-string, they are combined in a single pattern which matches both:
+#    \"?((?<=\")(?:[^\0-\x1f\x7f-\xff\\\"]|\\[\0-\x7f])*?(?=\")|(?<!\")[^\0-\x08\x0A-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+(?!\"))\"?
+WWW_AUTH_STRICT = re.compile(r"^(?:\s*(?:,\s*)?([^\0-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+)\s*=\s*\"?((?<=\")(?:[^\0-\x08\x0A-\x1f\x7f-\xff\\\"]|\\[\0-\x7f])*?(?=\")|(?<!\")[^\0-\x1f\x7f-\xff()<>@,;:\\\"/[\]?={} \t]+(?!\"))\"?)(.*)$")
+WWW_AUTH_RELAXED = re.compile(r"^(?:\s*(?:,\s*)?([^ \t\r\n=]+)\s*=\s*\"?((?<=\")(?:[^\\\"]|\\.)*?(?=\")|(?<!\")[^ \t\r\n,]+(?!\"))\"?)(.*)$")
+UNQUOTE_PAIRS = re.compile(r'\\(.)')
+def _parse_www_authenticate(headers, headername='www-authenticate'):
+    """Returns a dictionary of dictionaries, one dict
+    per auth_scheme."""
+    retval = {}
+    if headers.has_key(headername):
+        authenticate = headers[headername].strip()
+        www_auth = USE_WWW_AUTH_STRICT_PARSING and WWW_AUTH_STRICT or WWW_AUTH_RELAXED
+        while authenticate:
+            # Break off the scheme at the beginning of the line
+            if headername == 'authentication-info':
+                (auth_scheme, the_rest) = ('digest', authenticate)                
+            else:
+                (auth_scheme, the_rest) = authenticate.split(" ", 1)
+            # Now loop over all the key value pairs that come after the scheme, 
+            # being careful not to roll into the next scheme
+            match = www_auth.search(the_rest)
+            auth_params = {}
+            while match:
+                if match and len(match.groups()) == 3:
+                    (key, value, the_rest) = match.groups()
+                    auth_params[key.lower()] = UNQUOTE_PAIRS.sub(r'\1', value) # '\\'.join([x.replace('\\', '') for x in value.split('\\\\')])
+                match = www_auth.search(the_rest)
+            retval[auth_scheme.lower()] = auth_params
+            authenticate = the_rest.strip()
+    return retval
+
+
+def _entry_disposition(response_headers, request_headers):
+    """Determine freshness from the Date, Expires and Cache-Control headers.
+
+    We don't handle the following:
+
+    1. Cache-Control: max-stale
+    2. Age: headers are not used in the calculations.
+
+    Not that this algorithm is simpler than you might think 
+    because we are operating as a private (non-shared) cache.
+    This lets us ignore 's-maxage'. We can also ignore
+    'proxy-invalidate' since we aren't a proxy.
+    We will never return a stale document as 
+    fresh as a design decision, and thus the non-implementation 
+    of 'max-stale'. This also lets us safely ignore 'must-revalidate' 
+    since we operate as if every server has sent 'must-revalidate'.
+    Since we are private we get to ignore both 'public' and
+    'private' parameters. We also ignore 'no-transform' since
+    we don't do any transformations.    
+    The 'no-store' parameter is handled at a higher level.
+    So the only Cache-Control parameters we look at are:
+
+    no-cache
+    only-if-cached
+    max-age
+    min-fresh
+    """
+    
+    retval = "STALE"
+    cc = _parse_cache_control(request_headers)
+    cc_response = _parse_cache_control(response_headers)
+
+    if request_headers.has_key('pragma') and request_headers['pragma'].lower().find('no-cache') != -1:
+        retval = "TRANSPARENT"
+        if 'cache-control' not in request_headers:
+            request_headers['cache-control'] = 'no-cache'
+    elif cc.has_key('no-cache'):
+        retval = "TRANSPARENT"
+    elif cc_response.has_key('no-cache'):
+        retval = "STALE"
+    elif cc.has_key('only-if-cached'):
+        retval = "FRESH"
+    elif response_headers.has_key('date'):
+        date = calendar.timegm(email.Utils.parsedate_tz(response_headers['date']))
+        now = time.time()
+        current_age = max(0, now - date)
+        if cc_response.has_key('max-age'):
+            try:
+                freshness_lifetime = int(cc_response['max-age'])
+            except ValueError:
+                freshness_lifetime = 0
+        elif response_headers.has_key('expires'):
+            expires = email.Utils.parsedate_tz(response_headers['expires'])
+            if None == expires:
+                freshness_lifetime = 0
+            else:
+                freshness_lifetime = max(0, calendar.timegm(expires) - date)
+        else:
+            freshness_lifetime = 0
+        if cc.has_key('max-age'):
+            try:
+                freshness_lifetime = int(cc['max-age'])
+            except ValueError:
+                freshness_lifetime = 0
+        if cc.has_key('min-fresh'):
+            try:
+                min_fresh = int(cc['min-fresh'])
+            except ValueError:
+                min_fresh = 0
+            current_age += min_fresh 
+        if freshness_lifetime > current_age:
+            retval = "FRESH"
+    return retval 
+
+def _decompressContent(response, new_content):
+    content = new_content
+    try:
+        encoding = response.get('content-encoding', None)
+        if encoding in ['gzip', 'deflate']:
+            if encoding == 'gzip':
+                content = gzip.GzipFile(fileobj=StringIO.StringIO(new_content)).read()
+            if encoding == 'deflate':
+                content = zlib.decompress(content)
+            response['content-length'] = str(len(content))
+            # Record the historical presence of the encoding in a way the won't interfere.
+            response['-content-encoding'] = response['content-encoding']
+            del response['content-encoding']
+    except IOError:
+        content = ""
+        raise FailedToDecompressContent(_("Content purported to be compressed with %s but failed to decompress.") % response.get('content-encoding'), response, content)
+    return content
+
+def _updateCache(request_headers, response_headers, content, cache, cachekey):
+    if cachekey:
+        cc = _parse_cache_control(request_headers)
+        cc_response = _parse_cache_control(response_headers)
+        if cc.has_key('no-store') or cc_response.has_key('no-store'):
+            cache.delete(cachekey)
+        else:
+            info = email.Message.Message()
+            for key, value in response_headers.iteritems():
+                if key not in ['status','content-encoding','transfer-encoding']:
+                    info[key] = value
+
+            # Add annotations to the cache to indicate what headers
+            # are variant for this request.
+            vary = response_headers.get('vary', None)
+            if vary:
+                vary_headers = vary.lower().replace(' ', '').split(',')
+                for header in vary_headers:
+                    key = '-varied-%s' % header
+                    try:
+                        info[key] = request_headers[header]
+                    except KeyError:
+                        pass
+
+            status = response_headers.status
+            if status == 304:
+                status = 200
+
+            status_header = 'status: %d\r\n' % response_headers.status
+
+            header_str = info.as_string()
+
+            header_str = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", header_str)
+            text = "".join([status_header, header_str, content])
+
+            cache.set(cachekey, text)
+
+def _cnonce():
+    dig = _md5("%s:%s" % (time.ctime(), ["0123456789"[random.randrange(0, 9)] for i in range(20)])).hexdigest()
+    return dig[:16]
+
+def _wsse_username_token(cnonce, iso_now, password):
+    return base64.b64encode(_sha("%s%s%s" % (cnonce, iso_now, password)).digest()).strip()
+
+
+# For credentials we need two things, first 
+# a pool of credential to try (not necesarily tied to BAsic, Digest, etc.)
+# Then we also need a list of URIs that have already demanded authentication
+# That list is tricky since sub-URIs can take the same auth, or the 
+# auth scheme may change as you descend the tree.
+# So we also need each Auth instance to be able to tell us
+# how close to the 'top' it is.
+
+class Authentication(object):
+    def __init__(self, credentials, host, request_uri, headers, response, content, http):
+        (scheme, authority, path, query, fragment) = parse_uri(request_uri)
+        self.path = path
+        self.host = host
+        self.credentials = credentials
+        self.http = http
+
+    def depth(self, request_uri):
+        (scheme, authority, path, query, fragment) = parse_uri(request_uri)
+        return request_uri[len(self.path):].count("/")
+
+    def inscope(self, host, request_uri):
+        # XXX Should we normalize the request_uri?
+        (scheme, authority, path, query, fragment) = parse_uri(request_uri)
+        return (host == self.host) and path.startswith(self.path)
+
+    def request(self, method, request_uri, headers, content):
+        """Modify the request headers to add the appropriate
+        Authorization header. Over-rise this in sub-classes."""
+        pass
+
+    def response(self, response, content):
+        """Gives us a chance to update with new nonces
+        or such returned from the last authorized response.
+        Over-rise this in sub-classes if necessary.
+
+        Return TRUE is the request is to be retried, for 
+        example Digest may return stale=true.
+        """
+        return False
+
+
+
+class BasicAuthentication(Authentication):
+    def __init__(self, credentials, host, request_uri, headers, response, content, http):
+        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
+
+    def request(self, method, request_uri, headers, content):
+        """Modify the request headers to add the appropriate
+        Authorization header."""
+        headers['authorization'] = 'Basic ' + base64.b64encode("%s:%s" % self.credentials).strip()
+
+
+class DigestAuthentication(Authentication):
+    """Only do qop='auth' and MD5, since that 
+    is all Apache currently implements"""
+    def __init__(self, credentials, host, request_uri, headers, response, content, http):
+        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
+        challenge = _parse_www_authenticate(response, 'www-authenticate')
+        self.challenge = challenge['digest']
+        qop = self.challenge.get('qop', 'auth')
+        self.challenge['qop'] = ('auth' in [x.strip() for x in qop.split()]) and 'auth' or None
+        if self.challenge['qop'] is None:
+            raise UnimplementedDigestAuthOptionError( _("Unsupported value for qop: %s." % qop))
+        self.challenge['algorithm'] = self.challenge.get('algorithm', 'MD5').upper()
+        if self.challenge['algorithm'] != 'MD5':
+            raise UnimplementedDigestAuthOptionError( _("Unsupported value for algorithm: %s." % self.challenge['algorithm']))
+        self.A1 = "".join([self.credentials[0], ":", self.challenge['realm'], ":", self.credentials[1]])   
+        self.challenge['nc'] = 1
+
+    def request(self, method, request_uri, headers, content, cnonce = None):
+        """Modify the request headers"""
+        H = lambda x: _md5(x).hexdigest()
+        KD = lambda s, d: H("%s:%s" % (s, d))
+        A2 = "".join([method, ":", request_uri])
+        self.challenge['cnonce'] = cnonce or _cnonce() 
+        request_digest  = '"%s"' % KD(H(self.A1), "%s:%s:%s:%s:%s" % (self.challenge['nonce'], 
+                    '%08x' % self.challenge['nc'], 
+                    self.challenge['cnonce'], 
+                    self.challenge['qop'], H(A2)
+                    )) 
+        headers['Authorization'] = 'Digest username="%s", realm="%s", nonce="%s", uri="%s", algorithm=%s, response=%s, qop=%s, nc=%08x, cnonce="%s"' % (
+                self.credentials[0], 
+                self.challenge['realm'],
+                self.challenge['nonce'],
+                request_uri, 
+                self.challenge['algorithm'],
+                request_digest,
+                self.challenge['qop'],
+                self.challenge['nc'],
+                self.challenge['cnonce'],
+                )
+        self.challenge['nc'] += 1
+
+    def response(self, response, content):
+        if not response.has_key('authentication-info'):
+            challenge = _parse_www_authenticate(response, 'www-authenticate').get('digest', {})
+            if 'true' == challenge.get('stale'):
+                self.challenge['nonce'] = challenge['nonce']
+                self.challenge['nc'] = 1 
+                return True
+        else:
+            updated_challenge = _parse_www_authenticate(response, 'authentication-info').get('digest', {})
+
+            if updated_challenge.has_key('nextnonce'):
+                self.challenge['nonce'] = updated_challenge['nextnonce']
+                self.challenge['nc'] = 1 
+        return False
+
+
+class HmacDigestAuthentication(Authentication):
+    """Adapted from Robert Sayre's code and DigestAuthentication above."""
+    __author__ = "Thomas Broyer (t.broyer@ltgt.net)"
+
+    def __init__(self, credentials, host, request_uri, headers, response, content, http):
+        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
+        challenge = _parse_www_authenticate(response, 'www-authenticate')
+        self.challenge = challenge['hmacdigest']
+        # TODO: self.challenge['domain']
+        self.challenge['reason'] = self.challenge.get('reason', 'unauthorized')
+        if self.challenge['reason'] not in ['unauthorized', 'integrity']:
+            self.challenge['reason'] = 'unauthorized'
+        self.challenge['salt'] = self.challenge.get('salt', '')
+        if not self.challenge.get('snonce'):
+            raise UnimplementedHmacDigestAuthOptionError( _("The challenge doesn't contain a server nonce, or this one is empty."))
+        self.challenge['algorithm'] = self.challenge.get('algorithm', 'HMAC-SHA-1')
+        if self.challenge['algorithm'] not in ['HMAC-SHA-1', 'HMAC-MD5']:
+            raise UnimplementedHmacDigestAuthOptionError( _("Unsupported value for algorithm: %s." % self.challenge['algorithm']))
+        self.challenge['pw-algorithm'] = self.challenge.get('pw-algorithm', 'SHA-1')
+        if self.challenge['pw-algorithm'] not in ['SHA-1', 'MD5']:
+            raise UnimplementedHmacDigestAuthOptionError( _("Unsupported value for pw-algorithm: %s." % self.challenge['pw-algorithm']))
+        if self.challenge['algorithm'] == 'HMAC-MD5':
+            self.hashmod = _md5
+        else:
+            self.hashmod = _sha
+        if self.challenge['pw-algorithm'] == 'MD5':
+            self.pwhashmod = _md5
+        else:
+            self.pwhashmod = _sha
+        self.key = "".join([self.credentials[0], ":",
+                    self.pwhashmod.new("".join([self.credentials[1], self.challenge['salt']])).hexdigest().lower(),
+                    ":", self.challenge['realm']
+                    ])
+        self.key = self.pwhashmod.new(self.key).hexdigest().lower()
+
+    def request(self, method, request_uri, headers, content):
+        """Modify the request headers"""
+        keys = _get_end2end_headers(headers)
+        keylist = "".join(["%s " % k for k in keys])
+        headers_val = "".join([headers[k] for k in keys])
+        created = time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime())
+        cnonce = _cnonce()
+        request_digest = "%s:%s:%s:%s:%s" % (method, request_uri, cnonce, self.challenge['snonce'], headers_val)
+        request_digest  = hmac.new(self.key, request_digest, self.hashmod).hexdigest().lower()
+        headers['Authorization'] = 'HMACDigest username="%s", realm="%s", snonce="%s", cnonce="%s", uri="%s", created="%s", response="%s", headers="%s"' % (
+                self.credentials[0], 
+                self.challenge['realm'],
+                self.challenge['snonce'],
+                cnonce,
+                request_uri, 
+                created,
+                request_digest,
+                keylist,
+                )
+
+    def response(self, response, content):
+        challenge = _parse_www_authenticate(response, 'www-authenticate').get('hmacdigest', {})
+        if challenge.get('reason') in ['integrity', 'stale']:
+            return True
+        return False
+
+
+class WsseAuthentication(Authentication):
+    """This is thinly tested and should not be relied upon.
+    At this time there isn't any third party server to test against.
+    Blogger and TypePad implemented this algorithm at one point
+    but Blogger has since switched to Basic over HTTPS and 
+    TypePad has implemented it wrong, by never issuing a 401
+    challenge but instead requiring your client to telepathically know that
+    their endpoint is expecting WSSE profile="UsernameToken"."""
+    def __init__(self, credentials, host, request_uri, headers, response, content, http):
+        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
+
+    def request(self, method, request_uri, headers, content):
+        """Modify the request headers to add the appropriate
+        Authorization header."""
+        headers['Authorization'] = 'WSSE profile="UsernameToken"'
+        iso_now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
+        cnonce = _cnonce()
+        password_digest = _wsse_username_token(cnonce, iso_now, self.credentials[1])
+        headers['X-WSSE'] = 'UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"' % (
+                self.credentials[0],
+                password_digest,
+                cnonce,
+                iso_now)
+
+class GoogleLoginAuthentication(Authentication):
+    def __init__(self, credentials, host, request_uri, headers, response, content, http):
+        from urllib import urlencode
+        Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http)
+        challenge = _parse_www_authenticate(response, 'www-authenticate')
+        service = challenge['googlelogin'].get('service', 'xapi')
+        # Bloggger actually returns the service in the challenge
+        # For the rest we guess based on the URI
+        if service == 'xapi' and  request_uri.find("calendar") > 0:
+            service = "cl"
+        # No point in guessing Base or Spreadsheet
+        #elif request_uri.find("spreadsheets") > 0:
+        #    service = "wise"
+
+        auth = dict(Email=credentials[0], Passwd=credentials[1], service=service, source=headers['user-agent'])
+        resp, content = self.http.request("https://www.google.com/accounts/ClientLogin", method="POST", body=urlencode(auth), headers={'Content-Type': 'application/x-www-form-urlencoded'})
+        lines = content.split('\n')
+        d = dict([tuple(line.split("=", 1)) for line in lines if line])
+        if resp.status == 403:
+            self.Auth = ""
+        else:
+            self.Auth = d['Auth']
+
+    def request(self, method, request_uri, headers, content):
+        """Modify the request headers to add the appropriate
+        Authorization header."""
+        headers['authorization'] = 'GoogleLogin Auth=' + self.Auth 
+
+
+AUTH_SCHEME_CLASSES = {
+    "basic": BasicAuthentication,
+    "wsse": WsseAuthentication,
+    "digest": DigestAuthentication,
+    "hmacdigest": HmacDigestAuthentication,
+    "googlelogin": GoogleLoginAuthentication
+}
+
+AUTH_SCHEME_ORDER = ["hmacdigest", "googlelogin", "digest", "wsse", "basic"]
+
+class FileCache(object):
+    """Uses a local directory as a store for cached files.
+    Not really safe to use if multiple threads or processes are going to 
+    be running on the same cache.
+    """
+    def __init__(self, cache, safe=safename): # use safe=lambda x: md5.new(x).hexdigest() for the old behavior
+        self.cache = cache
+        self.safe = safe
+        if not os.path.exists(cache): 
+            os.makedirs(self.cache)
+
+    def get(self, key):
+        retval = None
+        cacheFullPath = os.path.join(self.cache, self.safe(key))
+        try:
+            f = file(cacheFullPath, "rb")
+            retval = f.read()
+            f.close()
+        except IOError:
+            pass
+        return retval
+
+    def set(self, key, value):
+        cacheFullPath = os.path.join(self.cache, self.safe(key))
+        f = file(cacheFullPath, "wb")
+        f.write(value)
+        f.close()
+
+    def delete(self, key):
+        cacheFullPath = os.path.join(self.cache, self.safe(key))
+        if os.path.exists(cacheFullPath):
+            os.remove(cacheFullPath)
+
+class Credentials(object):
+    def __init__(self):
+        self.credentials = []
+
+    def add(self, name, password, domain=""):
+        self.credentials.append((domain.lower(), name, password))
+
+    def clear(self):
+        self.credentials = []
+
+    def iter(self, domain):
+        for (cdomain, name, password) in self.credentials:
+            if cdomain == "" or domain == cdomain:
+                yield (name, password) 
+
+class KeyCerts(Credentials):
+    """Identical to Credentials except that
+    name/password are mapped to key/cert."""
+    pass
+
+
+class ProxyInfo(object):
+  """Collect information required to use a proxy."""
+  def __init__(self, proxy_type, proxy_host, proxy_port, proxy_rdns=None, proxy_user=None, proxy_pass=None):
+      """The parameter proxy_type must be set to one of socks.PROXY_TYPE_XXX
+      constants. For example:
+
+p = ProxyInfo(proxy_type=socks.PROXY_TYPE_HTTP, proxy_host='localhost', proxy_port=8000)
+      """
+      self.proxy_type, self.proxy_host, self.proxy_port, self.proxy_rdns, self.proxy_user, self.proxy_pass = proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass
+
+  def astuple(self):
+    return (self.proxy_type, self.proxy_host, self.proxy_port, self.proxy_rdns,
+        self.proxy_user, self.proxy_pass)
+
+  def isgood(self):
+    return socks and (self.proxy_host != None) and (self.proxy_port != None)
+
+
+class HTTPConnectionWithTimeout(httplib.HTTPConnection):
+    """HTTPConnection subclass that supports timeouts"""
+
+    def __init__(self, host, port=None, strict=None, timeout=None, proxy_info=None):
+        httplib.HTTPConnection.__init__(self, host, port, strict)
+        self.timeout = timeout
+        self.proxy_info = proxy_info
+
+    def connect(self):
+        """Connect to the host and port specified in __init__."""
+        # Mostly verbatim from httplib.py.
+        msg = "getaddrinfo returns an empty list"
+        for res in socket.getaddrinfo(self.host, self.port, 0,
+                socket.SOCK_STREAM):
+            af, socktype, proto, canonname, sa = res
+            try:
+                if self.proxy_info and self.proxy_info.isgood():
+                    self.sock = socks.socksocket(af, socktype, proto)
+                    self.sock.setproxy(*self.proxy_info.astuple())
+                else:
+                    self.sock = socket.socket(af, socktype, proto)
+                # Different from httplib: support timeouts.
+                if has_timeout(self.timeout):
+                    self.sock.settimeout(self.timeout)
+                    # End of difference from httplib.
+                if self.debuglevel > 0:
+                    print "connect: (%s, %s)" % (self.host, self.port)
+
+                self.sock.connect(sa)
+            except socket.error, msg:
+                if self.debuglevel > 0:
+                    print 'connect fail:', (self.host, self.port)
+                if self.sock:
+                    self.sock.close()
+                self.sock = None
+                continue
+            break
+        if not self.sock:
+            raise socket.error, msg
+
+class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
+    "This class allows communication via SSL."
+
+    def __init__(self, host, port=None, key_file=None, cert_file=None,
+                 strict=None, timeout=None, proxy_info=None):
+        httplib.HTTPSConnection.__init__(self, host, port=port, key_file=key_file,
+                cert_file=cert_file, strict=strict)
+        self.timeout = timeout
+        self.proxy_info = proxy_info
+
+    def connect(self):
+        "Connect to a host on a given (SSL) port."
+
+        if self.proxy_info and self.proxy_info.isgood():
+            sock = socks.socksocket(socket.AF_INET, socket.SOCK_STREAM)
+            sock.setproxy(*self.proxy_info.astuple())
+        else:
+            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        
+        if has_timeout(self.timeout):
+            sock.settimeout(self.timeout)
+        sock.connect((self.host, self.port))
+        self.sock =_ssl_wrap_socket(sock, self.key_file, self.cert_file)
+
+
+
+class Http(object):
+    """An HTTP client that handles:
+- all methods
+- caching
+- ETags
+- compression,
+- HTTPS
+- Basic
+- Digest
+- WSSE
+
+and more.
+    """
+    def __init__(self, cache=None, timeout=None, proxy_info=None):
+        """The value of proxy_info is a ProxyInfo instance.
+
+If 'cache' is a string then it is used as a directory name
+for a disk cache. Otherwise it must be an object that supports
+the same interface as FileCache."""
+        self.proxy_info = proxy_info
+        # Map domain name to an httplib connection
+        self.connections = {}
+        # The location of the cache, for now a directory
+        # where cached responses are held.
+        if cache and isinstance(cache, str):
+            self.cache = FileCache(cache)
+        else:
+            self.cache = cache
+
+        # Name/password
+        self.credentials = Credentials()
+
+        # Key/cert
+        self.certificates = KeyCerts()
+
+        # authorization objects
+        self.authorizations = []
+
+        # If set to False then no redirects are followed, even safe ones.
+        self.follow_redirects = True
+        
+        # Which HTTP methods do we apply optimistic concurrency to, i.e.
+        # which methods get an "if-match:" etag header added to them.
+        self.optimistic_concurrency_methods = ["PUT"]
+
+        # If 'follow_redirects' is True, and this is set to True then
+        # all redirecs are followed, including unsafe ones.
+        self.follow_all_redirects = False
+
+        self.ignore_etag = False
+
+        self.force_exception_to_status_code = False 
+
+        self.timeout = timeout
+
+    def _auth_from_challenge(self, host, request_uri, headers, response, content):
+        """A generator that creates Authorization objects
+           that can be applied to requests.
+        """
+        challenges = _parse_www_authenticate(response, 'www-authenticate')
+        for cred in self.credentials.iter(host):
+            for scheme in AUTH_SCHEME_ORDER:
+                if challenges.has_key(scheme):
+                    yield AUTH_SCHEME_CLASSES[scheme](cred, host, request_uri, headers, response, content, self)
+
+    def add_credentials(self, name, password, domain=""):
+        """Add a name and password that will be used
+        any time a request requires authentication."""
+        self.credentials.add(name, password, domain)
+
+    def add_certificate(self, key, cert, domain):
+        """Add a key and cert that will be used
+        any time a request requires authentication."""
+        self.certificates.add(key, cert, domain)
+
+    def clear_credentials(self):
+        """Remove all the names and passwords
+        that are used for authentication"""
+        self.credentials.clear()
+        self.authorizations = []
+
+    def _conn_request(self, conn, request_uri, method, body, headers):
+        for i in range(2):
+            try:
+                conn.request(method, request_uri, body, headers)
+            except socket.gaierror:
+                conn.close()
+                raise ServerNotFoundError("Unable to find the server at %s" % conn.host)
+            except (socket.error, httplib.HTTPException):
+                # Just because the server closed the connection doesn't apparently mean
+                # that the server didn't send a response.
+                pass
+            try:
+                response = conn.getresponse()
+            except (socket.error, httplib.HTTPException):
+                if i == 0:
+                    conn.close()
+                    conn.connect()
+                    continue
+                else:
+                    raise
+            else:
+                content = ""
+                if method == "HEAD":
+                    response.close()
+                else:
+                    content = response.read()
+                response = Response(response)
+                if method != "HEAD":
+                    content = _decompressContent(response, content)
+            break
+        return (response, content)
+
+
+    def _request(self, conn, host, absolute_uri, request_uri, method, body, headers, redirections, cachekey):
+        """Do the actual request using the connection object
+        and also follow one level of redirects if necessary"""
+
+        auths = [(auth.depth(request_uri), auth) for auth in self.authorizations if auth.inscope(host, request_uri)]
+        auth = auths and sorted(auths)[0][1] or None
+        if auth: 
+            auth.request(method, request_uri, headers, body)
+
+        (response, content) = self._conn_request(conn, request_uri, method, body, headers)
+
+        if auth: 
+            if auth.response(response, body):
+                auth.request(method, request_uri, headers, body)
+                (response, content) = self._conn_request(conn, request_uri, method, body, headers )
+                response._stale_digest = 1
+
+        if response.status == 401:
+            for authorization in self._auth_from_challenge(host, request_uri, headers, response, content):
+                authorization.request(method, request_uri, headers, body) 
+                (response, content) = self._conn_request(conn, request_uri, method, body, headers, )
+                if response.status != 401:
+                    self.authorizations.append(authorization)
+                    authorization.response(response, body)
+                    break
+
+        if (self.follow_all_redirects or (method in ["GET", "HEAD"]) or response.status == 303):
+            if self.follow_redirects and response.status in [300, 301, 302, 303, 307]:
+                # Pick out the location header and basically start from the beginning
+                # remembering first to strip the ETag header and decrement our 'depth'
+                if redirections:
+                    if not response.has_key('location') and response.status != 300:
+                        raise RedirectMissingLocation( _("Redirected but the response is missing a Location: header."), response, content)
+                    # Fix-up relative redirects (which violate an RFC 2616 MUST)
+                    if response.has_key('location'):
+                        location = response['location']
+                        (scheme, authority, path, query, fragment) = parse_uri(location)
+                        if authority == None:
+                            response['location'] = urlparse.urljoin(absolute_uri, location)
+                    if response.status == 301 and method in ["GET", "HEAD"]:
+                        response['-x-permanent-redirect-url'] = response['location']
+                        if not response.has_key('content-location'):
+                            response['content-location'] = absolute_uri 
+                        _updateCache(headers, response, content, self.cache, cachekey)
+                    if headers.has_key('if-none-match'):
+                        del headers['if-none-match']
+                    if headers.has_key('if-modified-since'):
+                        del headers['if-modified-since']
+                    if response.has_key('location'):
+                        location = response['location']
+                        old_response = copy.deepcopy(response)
+                        if not old_response.has_key('content-location'):
+                            old_response['content-location'] = absolute_uri 
+                        redirect_method = ((response.status == 303) and (method not in ["GET", "HEAD"])) and "GET" or method
+                        (response, content) = self.request(location, redirect_method, body=body, headers = headers, redirections = redirections - 1)
+                        response.previous = old_response
+                else:
+                    raise RedirectLimit( _("Redirected more times than rediection_limit allows."), response, content)
+            elif response.status in [200, 203] and method == "GET":
+                # Don't cache 206's since we aren't going to handle byte range requests
+                if not response.has_key('content-location'):
+                    response['content-location'] = absolute_uri 
+                _updateCache(headers, response, content, self.cache, cachekey)
+
+        return (response, content)
+
+    def _normalize_headers(self, headers):
+        return _normalize_headers(headers)
+
+# Need to catch and rebrand some exceptions
+# Then need to optionally turn all exceptions into status codes
+# including all socket.* and httplib.* exceptions.
+
+
+    def request(self, uri, method="GET", body=None, headers=None, redirections=DEFAULT_MAX_REDIRECTS, connection_type=None):
+        """ Performs a single HTTP request.
+The 'uri' is the URI of the HTTP resource and can begin 
+with either 'http' or 'https'. The value of 'uri' must be an absolute URI.
+
+The 'method' is the HTTP method to perform, such as GET, POST, DELETE, etc. 
+There is no restriction on the methods allowed.
+
+The 'body' is the entity body to be sent with the request. It is a string
+object.
+
+Any extra headers that are to be sent with the request should be provided in the
+'headers' dictionary.
+
+The maximum number of redirect to follow before raising an 
+exception is 'redirections. The default is 5.
+
+The return value is a tuple of (response, content), the first 
+being and instance of the 'Response' class, the second being 
+a string that contains the response entity body.
+        """
+        try:
+            if headers is None:
+                headers = {}
+            else:
+                headers = self._normalize_headers(headers)
+
+            if not headers.has_key('user-agent'):
+                headers['user-agent'] = "Python-httplib2/%s" % __version__
+
+            uri = iri2uri(uri)
+
+            (scheme, authority, request_uri, defrag_uri) = urlnorm(uri)
+            domain_port = authority.split(":")[0:2]
+            if len(domain_port) == 2 and domain_port[1] == '443' and scheme == 'http':
+                scheme = 'https'
+                authority = domain_port[0]
+
+            conn_key = scheme+":"+authority
+            if conn_key in self.connections:
+                conn = self.connections[conn_key]
+            else:
+                if not connection_type:
+                    connection_type = (scheme == 'https') and HTTPSConnectionWithTimeout or HTTPConnectionWithTimeout
+                certs = list(self.certificates.iter(authority))
+                if scheme == 'https' and certs:
+                    conn = self.connections[conn_key] = connection_type(authority, key_file=certs[0][0],
+                        cert_file=certs[0][1], timeout=self.timeout, proxy_info=self.proxy_info)
+                else:
+                    conn = self.connections[conn_key] = connection_type(authority, timeout=self.timeout, proxy_info=self.proxy_info)
+                conn.set_debuglevel(debuglevel)
+
+            if method in ["GET", "HEAD"] and 'range' not in headers and 'accept-encoding' not in headers:
+                headers['accept-encoding'] = 'gzip, deflate'
+
+            info = email.Message.Message()
+            cached_value = None
+            if self.cache:
+                cachekey = defrag_uri
+                cached_value = self.cache.get(cachekey)
+                if cached_value:
+                    # info = email.message_from_string(cached_value)
+                    #
+                    # Need to replace the line above with the kludge below
+                    # to fix the non-existent bug not fixed in this
+                    # bug report: http://mail.python.org/pipermail/python-bugs-list/2005-September/030289.html
+                    try:
+                        info, content = cached_value.split('\r\n\r\n', 1)
+                        feedparser = email.FeedParser.FeedParser()
+                        feedparser.feed(info)
+                        info = feedparser.close()
+                        feedparser._parse = None
+                    except IndexError:
+                        self.cache.delete(cachekey)
+                        cachekey = None
+                        cached_value = None
+            else:
+                cachekey = None
+
+            if method in self.optimistic_concurrency_methods and self.cache and info.has_key('etag') and not self.ignore_etag and 'if-match' not in headers:
+                # http://www.w3.org/1999/04/Editing/
+                headers['if-match'] = info['etag']
+
+            if method not in ["GET", "HEAD"] and self.cache and cachekey:
+                # RFC 2616 Section 13.10
+                self.cache.delete(cachekey)
+
+            # Check the vary header in the cache to see if this request
+            # matches what varies in the cache.
+            if method in ['GET', 'HEAD'] and 'vary' in info:
+                vary = info['vary']
+                vary_headers = vary.lower().replace(' ', '').split(',')
+                for header in vary_headers:
+                    key = '-varied-%s' % header
+                    value = info[key]
+                    if headers.get(header, '') != value:
+                            cached_value = None
+                            break
+
+            if cached_value and method in ["GET", "HEAD"] and self.cache and 'range' not in headers:
+                if info.has_key('-x-permanent-redirect-url'):
+                    # Should cached permanent redirects be counted in our redirection count? For now, yes.
+                    (response, new_content) = self.request(info['-x-permanent-redirect-url'], "GET", headers = headers, redirections = redirections - 1)
+                    response.previous = Response(info)
+                    response.previous.fromcache = True
+                else:
+                    # Determine our course of action:
+                    #   Is the cached entry fresh or stale?
+                    #   Has the client requested a non-cached response?
+                    #   
+                    # There seems to be three possible answers: 
+                    # 1. [FRESH] Return the cache entry w/o doing a GET
+                    # 2. [STALE] Do the GET (but add in cache validators if available)
+                    # 3. [TRANSPARENT] Do a GET w/o any cache validators (Cache-Control: no-cache) on the request
+                    entry_disposition = _entry_disposition(info, headers) 
+                    
+                    if entry_disposition == "FRESH":
+                        if not cached_value:
+                            info['status'] = '504'
+                            content = ""
+                        response = Response(info)
+                        if cached_value:
+                            response.fromcache = True
+                        return (response, content)
+
+                    if entry_disposition == "STALE":
+                        if info.has_key('etag') and not self.ignore_etag and not 'if-none-match' in headers:
+                            headers['if-none-match'] = info['etag']
+                        if info.has_key('last-modified') and not 'last-modified' in headers:
+                            headers['if-modified-since'] = info['last-modified']
+                    elif entry_disposition == "TRANSPARENT":
+                        pass
+
+                    (response, new_content) = self._request(conn, authority, uri, request_uri, method, body, headers, redirections, cachekey)
+
+                if response.status == 304 and method == "GET":
+                    # Rewrite the cache entry with the new end-to-end headers
+                    # Take all headers that are in response 
+                    # and overwrite their values in info.
+                    # unless they are hop-by-hop, or are listed in the connection header.
+
+                    for key in _get_end2end_headers(response):
+                        info[key] = response[key]
+                    merged_response = Response(info)
+                    if hasattr(response, "_stale_digest"):
+                        merged_response._stale_digest = response._stale_digest
+                    _updateCache(headers, merged_response, content, self.cache, cachekey)
+                    response = merged_response
+                    response.status = 200
+                    response.fromcache = True 
+
+                elif response.status == 200:
+                    content = new_content
+                else:
+                    self.cache.delete(cachekey)
+                    content = new_content 
+            else: 
+                cc = _parse_cache_control(headers)
+                if cc.has_key('only-if-cached'):
+                    info['status'] = '504'
+                    response = Response(info)
+                    content = ""
+                else:
+                    (response, content) = self._request(conn, authority, uri, request_uri, method, body, headers, redirections, cachekey)
+        except Exception, e:
+            if self.force_exception_to_status_code:
+                if isinstance(e, HttpLib2ErrorWithResponse):
+                    response = e.response
+                    content = e.content
+                    response.status = 500
+                    response.reason = str(e) 
+                elif isinstance(e, socket.timeout):
+                    content = "Request Timeout"
+                    response = Response( {
+                            "content-type": "text/plain",
+                            "status": "408",
+                            "content-length": len(content)
+                            })
+                    response.reason = "Request Timeout"
+                else:
+                    content = str(e) 
+                    response = Response( {
+                            "content-type": "text/plain",
+                            "status": "400",
+                            "content-length": len(content)
+                            })
+                    response.reason = "Bad Request" 
+            else:
+                raise
+
+ 
+        return (response, content)
+
+ 
+
+class Response(dict):
+    """An object more like email.Message than httplib.HTTPResponse."""
+   
+    """Is this response from our local cache"""
+    fromcache = False
+
+    """HTTP protocol version used by server. 10 for HTTP/1.0, 11 for HTTP/1.1. """
+    version = 11
+
+    "Status code returned by server. "
+    status = 200
+
+    """Reason phrase returned by server."""
+    reason = "Ok"
+
+    previous = None
+
+    def __init__(self, info):
+        # info is either an email.Message or 
+        # an httplib.HTTPResponse object.
+        if isinstance(info, httplib.HTTPResponse):
+            for key, value in info.getheaders(): 
+                self[key.lower()] = value 
+            self.status = info.status
+            self['status'] = str(self.status)
+            self.reason = info.reason
+            self.version = info.version
+        elif isinstance(info, email.Message.Message):
+            for key, value in info.items(): 
+                self[key] = value 
+            self.status = int(self['status'])
+        else:
+            for key, value in info.iteritems(): 
+                self[key] = value 
+            self.status = int(self.get('status', self.status))
+
+
+    def __getattr__(self, name):
+        if name == 'dict':
+            return self 
+        else:  
+            raise AttributeError, name 
diff --git a/chrome/common/extensions/docs/examples/apps/hello-python/httplib2/iri2uri.py b/chrome/common/extensions/docs/examples/apps/hello-python/httplib2/iri2uri.py
new file mode 100644
index 0000000..70667ed
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-python/httplib2/iri2uri.py
@@ -0,0 +1,110 @@
+"""
+iri2uri
+
+Converts an IRI to a URI.
+
+"""
+__author__ = "Joe Gregorio (joe@bitworking.org)"
+__copyright__ = "Copyright 2006, Joe Gregorio"
+__contributors__ = []
+__version__ = "1.0.0"
+__license__ = "MIT"
+__history__ = """
+"""
+
+import urlparse
+
+
+# Convert an IRI to a URI following the rules in RFC 3987
+# 
+# The characters we need to enocde and escape are defined in the spec:
+#
+# iprivate =  %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD
+# ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF
+#         / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD
+#         / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD
+#         / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD
+#         / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD
+#         / %xD0000-DFFFD / %xE1000-EFFFD
+
+escape_range = [
+   (0xA0, 0xD7FF ),
+   (0xE000, 0xF8FF ),
+   (0xF900, 0xFDCF ),
+   (0xFDF0, 0xFFEF),
+   (0x10000, 0x1FFFD ),
+   (0x20000, 0x2FFFD ),
+   (0x30000, 0x3FFFD),
+   (0x40000, 0x4FFFD ),
+   (0x50000, 0x5FFFD ),
+   (0x60000, 0x6FFFD),
+   (0x70000, 0x7FFFD ),
+   (0x80000, 0x8FFFD ),
+   (0x90000, 0x9FFFD),
+   (0xA0000, 0xAFFFD ),
+   (0xB0000, 0xBFFFD ),
+   (0xC0000, 0xCFFFD),
+   (0xD0000, 0xDFFFD ),
+   (0xE1000, 0xEFFFD),
+   (0xF0000, 0xFFFFD ),
+   (0x100000, 0x10FFFD)
+]
+ 
+def encode(c):
+    retval = c
+    i = ord(c)
+    for low, high in escape_range:
+        if i < low:
+            break
+        if i >= low and i <= high:
+            retval = "".join(["%%%2X" % ord(o) for o in c.encode('utf-8')])
+            break
+    return retval
+
+
+def iri2uri(uri):
+    """Convert an IRI to a URI. Note that IRIs must be 
+    passed in a unicode strings. That is, do not utf-8 encode
+    the IRI before passing it into the function.""" 
+    if isinstance(uri ,unicode):
+        (scheme, authority, path, query, fragment) = urlparse.urlsplit(uri)
+        authority = authority.encode('idna')
+        # For each character in 'ucschar' or 'iprivate'
+        #  1. encode as utf-8
+        #  2. then %-encode each octet of that utf-8 
+        uri = urlparse.urlunsplit((scheme, authority, path, query, fragment))
+        uri = "".join([encode(c) for c in uri])
+    return uri
+        
+if __name__ == "__main__":
+    import unittest
+
+    class Test(unittest.TestCase):
+
+        def test_uris(self):
+            """Test that URIs are invariant under the transformation."""
+            invariant = [ 
+                u"ftp://ftp.is.co.za/rfc/rfc1808.txt",
+                u"http://www.ietf.org/rfc/rfc2396.txt",
+                u"ldap://[2001:db8::7]/c=GB?objectClass?one",
+                u"mailto:John.Doe@example.com",
+                u"news:comp.infosystems.www.servers.unix",
+                u"tel:+1-816-555-1212",
+                u"telnet://192.0.2.16:80/",
+                u"urn:oasis:names:specification:docbook:dtd:xml:4.1.2" ]
+            for uri in invariant:
+                self.assertEqual(uri, iri2uri(uri))
+            
+        def test_iri(self):
+            """ Test that the right type of escaping is done for each part of the URI."""
+            self.assertEqual("http://xn--o3h.com/%E2%98%84", iri2uri(u"http://\N{COMET}.com/\N{COMET}"))
+            self.assertEqual("http://bitworking.org/?fred=%E2%98%84", iri2uri(u"http://bitworking.org/?fred=\N{COMET}"))
+            self.assertEqual("http://bitworking.org/#%E2%98%84", iri2uri(u"http://bitworking.org/#\N{COMET}"))
+            self.assertEqual("#%E2%98%84", iri2uri(u"#\N{COMET}"))
+            self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"))
+            self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")))
+            self.assertNotEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode('utf-8')))
+
+    unittest.main()
+
+    
diff --git a/chrome/common/extensions/docs/examples/apps/hello-python/index.yaml b/chrome/common/extensions/docs/examples/apps/hello-python/index.yaml
new file mode 100644
index 0000000..da8f7f3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-python/index.yaml
@@ -0,0 +1,15 @@
+# Copyright (c) 2012 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.
+
+indexes:
+
+# AUTOGENERATED
+
+# This index.yaml is automatically updated whenever the dev_appserver
+# detects that a new type of query is run.  If you want to manage the
+# index.yaml file manually, remove the above marker line (the line
+# saying "# AUTOGENERATED").  If you want to manage some indexes
+# manually, move them above the marker line.  The index.yaml file is
+# automatically uploaded to the admin console when you next deploy
+# your application using appcfg.py.
diff --git a/chrome/common/extensions/docs/examples/apps/hello-python/main.py b/chrome/common/extensions/docs/examples/apps/hello-python/main.py
new file mode 100755
index 0000000..3e3738b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-python/main.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+from google.appengine.ext import webapp
+from google.appengine.ext.webapp import util
+from google.appengine.api import users
+from google.appengine.api import urlfetch
+from google.appengine.ext.webapp import template
+from google.appengine.api.urlfetch import DownloadError
+import oauth2
+import urllib
+import logging
+import os
+import time
+from django.utils import simplejson
+
+# Configuration
+
+CONFIG = {
+  'oauth_consumer_key': 'anonymous',
+  'oauth_consumer_secret': 'anonymous',
+  'license_server': 'https://www.googleapis.com',
+  'license_path': '%(server)s/chromewebstore/v1/licenses/%(appid)s/%(userid)s',
+  'oauth_token': 'INSERT OAUTH TOKEN HERE',
+  'oauth_token_secret': 'INSERT OAUTH TOKEN SECRET HERE',
+  'app_id': 'INSERT APPLICATION ID HERE',
+}
+
+# Check to see if the server has been deployed.  In the dev server, this
+# env variable will start with 'Development', in production, it will start with
+# 'Google App Engine'
+IS_PRODUCTION = os.environ['SERVER_SOFTWARE'].startswith('Google App Engine')
+
+# Valid access levels that may be returned by the license server.
+VALID_ACCESS_LEVELS = ['FREE_TRIAL', 'FULL']
+
+
+def fetch_license_data(userid):
+  """Fetches the license for a given user by making an OAuth signed request
+  to the license server.
+
+  Args:
+    userid OpenID of the user you are checking access for.
+
+  Returns:
+    The server's response as text.
+  """
+  url = CONFIG['license_path'] % {
+    'server': CONFIG['license_server'],
+    'appid': CONFIG['app_id'],
+    'userid': urllib.quote_plus(userid),
+  }
+
+  oauth_token = oauth2.Token(**{
+    'key': CONFIG['oauth_token'],
+    'secret': CONFIG['oauth_token_secret']
+  })
+
+  oauth_consumer = oauth2.Consumer(**{
+    'key': CONFIG['oauth_consumer_key'],
+    'secret': CONFIG['oauth_consumer_secret']
+  })
+
+  logging.debug('Requesting %s' % url)
+  client = oauth2.Client(oauth_consumer, oauth_token)
+  resp, content = client.request(url, 'GET')
+  logging.debug('Got response code %s, content %s' % (resp, content))
+  return content
+
+
+def parse_license_data(userid):
+  """Returns the license for a given user as a structured object.
+
+  Args:
+    userid: The OpenID of the user to check.
+
+  Returns:
+    An object with the following parameters:
+      error:  True if something went wrong, False otherwise.
+      message: A descriptive message if error is True.
+      access: One of 'NO', 'FREE_TRIAL', or 'FULL' depending on the access.
+  """
+  license = {'error': False, 'message': '', 'access': 'NO'}
+  try:
+    response_text = fetch_license_data(userid)
+    try:
+      logging.debug('Attempting to JSON parse: %s' % response_text)
+      json = simplejson.loads(response_text)
+      logging.debug('Got license server response: %s' % json)
+    except ValueError:
+      logging.exception('Could not parse response as JSON: %s' % response_text)
+      license['error'] = True
+      license['message'] = 'Could not parse the license server response'
+  except DownloadError:
+    logging.exception('Could not fetch license data')
+    license['error'] = True
+    license['message'] = 'Could not fetch license data'
+
+  if json.has_key('error'):
+    license['error'] = True
+    license['message'] = json['error']['message']
+  elif json['result'] == 'YES' and json['accessLevel'] in VALID_ACCESS_LEVELS:
+    license['access'] = json['accessLevel']
+
+  return license
+
+
+class MainHandler(webapp.RequestHandler):
+  """Request handler class."""
+  def get(self):
+    """Handler for GET requests."""
+    user = users.get_current_user()
+    if user:
+      if IS_PRODUCTION:
+        # We should use federated_identity in production, since the license
+        # server requires an OpenID
+        userid = user.federated_identity()
+      else:
+        # On the dev server, we won't have access to federated_identity, so
+        # just use a default OpenID which will never return YES.
+        # If you want to test different response values on the development
+        # server, just change this default value (e.g. append '-yes' or
+        # '-trial').
+        userid = ('https://www.google.com/accounts/o8/id?'
+                  'id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
+      license_data = parse_license_data(userid)
+      template_data = {
+        'license': license_data,
+        'user_name': user.nickname(),
+        'user_id': userid,
+        'user_logout': users.create_logout_url(self.request.uri),
+      }
+    else:
+      # Force the OpenID login endpoint to be for Google accounts only, since
+      # the license server doesn't support any other type of OpenID provider.
+      login_url = users.create_login_url(dest_url='/',
+                      federated_identity='google.com/accounts/o8/id')
+      template_data = {
+        'user_login': login_url,
+      }
+
+    # Render a simple template
+    path = os.path.join(os.path.dirname(__file__), 'templates', 'index.html')
+    self.response.out.write(template.render(path, template_data))
+
+
+if __name__ == '__main__':
+  application = webapp.WSGIApplication([
+    ('/', MainHandler),
+  ], debug=False)
+  util.run_wsgi_app(application)
diff --git a/chrome/common/extensions/docs/examples/apps/hello-python/oauth2/__init__.py b/chrome/common/extensions/docs/examples/apps/hello-python/oauth2/__init__.py
new file mode 100644
index 0000000..65c3f07
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-python/oauth2/__init__.py
@@ -0,0 +1,758 @@
+"""
+The MIT License
+
+Copyright (c) 2007-2010 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+"""
+
+import urllib
+import time
+import random
+import urlparse
+import hmac
+import binascii
+import httplib2
+
+try:
+    from urlparse import parse_qs, parse_qsl
+except ImportError:
+    from cgi import parse_qs, parse_qsl
+
+
+VERSION = '1.0'  # Hi Blaine!
+HTTP_METHOD = 'GET'
+SIGNATURE_METHOD = 'PLAINTEXT'
+
+
+class Error(RuntimeError):
+    """Generic exception class."""
+
+    def __init__(self, message='OAuth error occurred.'):
+        self._message = message
+
+    @property
+    def message(self):
+        """A hack to get around the deprecation errors in 2.6."""
+        return self._message
+
+    def __str__(self):
+        return self._message
+
+
+class MissingSignature(Error):
+    pass
+
+
+def build_authenticate_header(realm=''):
+    """Optional WWW-Authenticate header (401 error)"""
+    return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
+
+
+def build_xoauth_string(url, consumer, token=None):
+    """Build an XOAUTH string for use in SMTP/IMPA authentication."""
+    request = Request.from_consumer_and_token(consumer, token,
+        "GET", url)
+
+    signing_method = SignatureMethod_HMAC_SHA1()
+    request.sign_request(signing_method, consumer, token)
+
+    params = []
+    for k, v in sorted(request.iteritems()):
+        if v is not None:
+            params.append('%s="%s"' % (k, escape(v)))
+
+    return "%s %s %s" % ("GET", url, ','.join(params))
+
+
+def escape(s):
+    """Escape a URL including any /."""
+    return urllib.quote(s, safe='~')
+
+
+def generate_timestamp():
+    """Get seconds since epoch (UTC)."""
+    return int(time.time())
+
+
+def generate_nonce(length=8):
+    """Generate pseudorandom number."""
+    return ''.join([str(random.randint(0, 9)) for i in range(length)])
+
+
+def generate_verifier(length=8):
+    """Generate pseudorandom number."""
+    return ''.join([str(random.randint(0, 9)) for i in range(length)])
+
+
+class Consumer(object):
+    """A consumer of OAuth-protected services.
+ 
+    The OAuth consumer is a "third-party" service that wants to access
+    protected resources from an OAuth service provider on behalf of an end
+    user. It's kind of the OAuth client.
+ 
+    Usually a consumer must be registered with the service provider by the
+    developer of the consumer software. As part of that process, the service
+    provider gives the consumer a *key* and a *secret* with which the consumer
+    software can identify itself to the service. The consumer will include its
+    key in each request to identify itself, but will use its secret only when
+    signing requests, to prove that the request is from that particular
+    registered consumer.
+ 
+    Once registered, the consumer can then use its consumer credentials to ask
+    the service provider for a request token, kicking off the OAuth
+    authorization process.
+    """
+
+    key = None
+    secret = None
+
+    def __init__(self, key, secret):
+        self.key = key
+        self.secret = secret
+
+        if self.key is None or self.secret is None:
+            raise ValueError("Key and secret must be set.")
+
+    def __str__(self):
+        data = {'oauth_consumer_key': self.key,
+            'oauth_consumer_secret': self.secret}
+
+        return urllib.urlencode(data)
+
+
+class Token(object):
+    """An OAuth credential used to request authorization or a protected
+    resource.
+ 
+    Tokens in OAuth comprise a *key* and a *secret*. The key is included in
+    requests to identify the token being used, but the secret is used only in
+    the signature, to prove that the requester is who the server gave the
+    token to.
+ 
+    When first negotiating the authorization, the consumer asks for a *request
+    token* that the live user authorizes with the service provider. The
+    consumer then exchanges the request token for an *access token* that can
+    be used to access protected resources.
+    """
+
+    key = None
+    secret = None
+    callback = None
+    callback_confirmed = None
+    verifier = None
+
+    def __init__(self, key, secret):
+        self.key = key
+        self.secret = secret
+
+        if self.key is None or self.secret is None:
+            raise ValueError("Key and secret must be set.")
+
+    def set_callback(self, callback):
+        self.callback = callback
+        self.callback_confirmed = 'true'
+
+    def set_verifier(self, verifier=None):
+        if verifier is not None:
+            self.verifier = verifier
+        else:
+            self.verifier = generate_verifier()
+
+    def get_callback_url(self):
+        if self.callback and self.verifier:
+            # Append the oauth_verifier.
+            parts = urlparse.urlparse(self.callback)
+            scheme, netloc, path, params, query, fragment = parts[:6]
+            if query:
+                query = '%s&oauth_verifier=%s' % (query, self.verifier)
+            else:
+                query = 'oauth_verifier=%s' % self.verifier
+            return urlparse.urlunparse((scheme, netloc, path, params,
+                query, fragment))
+        return self.callback
+
+    def to_string(self):
+        """Returns this token as a plain string, suitable for storage.
+ 
+        The resulting string includes the token's secret, so you should never
+        send or store this string where a third party can read it.
+        """
+
+        data = {
+            'oauth_token': self.key,
+            'oauth_token_secret': self.secret,
+        }
+
+        if self.callback_confirmed is not None:
+            data['oauth_callback_confirmed'] = self.callback_confirmed
+        return urllib.urlencode(data)
+ 
+    @staticmethod
+    def from_string(s):
+        """Deserializes a token from a string like one returned by
+        `to_string()`."""
+
+        if not len(s):
+            raise ValueError("Invalid parameter string.")
+
+        params = parse_qs(s, keep_blank_values=False)
+        if not len(params):
+            raise ValueError("Invalid parameter string.")
+
+        try:
+            key = params['oauth_token'][0]
+        except Exception:
+            raise ValueError("'oauth_token' not found in OAuth request.")
+
+        try:
+            secret = params['oauth_token_secret'][0]
+        except Exception:
+            raise ValueError("'oauth_token_secret' not found in " 
+                "OAuth request.")
+
+        token = Token(key, secret)
+        try:
+            token.callback_confirmed = params['oauth_callback_confirmed'][0]
+        except KeyError:
+            pass  # 1.0, no callback confirmed.
+        return token
+
+    def __str__(self):
+        return self.to_string()
+
+
+def setter(attr):
+    name = attr.__name__
+ 
+    def getter(self):
+        try:
+            return self.__dict__[name]
+        except KeyError:
+            raise AttributeError(name)
+ 
+    def deleter(self):
+        del self.__dict__[name]
+ 
+    return property(getter, attr, deleter)
+
+
+class Request(dict):
+ 
+    """The parameters and information for an HTTP request, suitable for
+    authorizing with OAuth credentials.
+ 
+    When a consumer wants to access a service's protected resources, it does
+    so using a signed HTTP request identifying itself (the consumer) with its
+    key, and providing an access token authorized by the end user to access
+    those resources.
+ 
+    """
+ 
+    version = VERSION
+ 
+    def __init__(self, method=HTTP_METHOD, url=None, parameters=None):
+        self.method = method
+        self.url = url
+        if parameters is not None:
+            self.update(parameters)
+ 
+    @setter
+    def url(self, value):
+        self.__dict__['url'] = value
+        if value is not None:
+            scheme, netloc, path, params, query, fragment = urlparse.urlparse(value)
+
+            # Exclude default port numbers.
+            if scheme == 'http' and netloc[-3:] == ':80':
+                netloc = netloc[:-3]
+            elif scheme == 'https' and netloc[-4:] == ':443':
+                netloc = netloc[:-4]
+            if scheme not in ('http', 'https'):
+                raise ValueError("Unsupported URL %s (%s)." % (value, scheme))
+
+            # Normalized URL excludes params, query, and fragment.
+            self.normalized_url = urlparse.urlunparse((scheme, netloc, path, None, None, None))
+        else:
+            self.normalized_url = None
+            self.__dict__['url'] = None
+ 
+    @setter
+    def method(self, value):
+        self.__dict__['method'] = value.upper()
+ 
+    def _get_timestamp_nonce(self):
+        return self['oauth_timestamp'], self['oauth_nonce']
+ 
+    def get_nonoauth_parameters(self):
+        """Get any non-OAuth parameters."""
+        return dict([(k, v) for k, v in self.iteritems() 
+                    if not k.startswith('oauth_')])
+ 
+    def to_header(self, realm=''):
+        """Serialize as a header for an HTTPAuth request."""
+        oauth_params = ((k, v) for k, v in self.items() 
+                            if k.startswith('oauth_'))
+        stringy_params = ((k, escape(str(v))) for k, v in oauth_params)
+        header_params = ('%s="%s"' % (k, v) for k, v in stringy_params)
+        params_header = ', '.join(header_params)
+ 
+        auth_header = 'OAuth realm="%s"' % realm
+        if params_header:
+            auth_header = "%s, %s" % (auth_header, params_header)
+ 
+        return {'Authorization': auth_header}
+ 
+    def to_postdata(self):
+        """Serialize as post data for a POST request."""
+        # tell urlencode to deal with sequence values and map them correctly
+        # to resulting querystring. for example self["k"] = ["v1", "v2"] will
+        # result in 'k=v1&k=v2' and not k=%5B%27v1%27%2C+%27v2%27%5D
+        return urllib.urlencode(self, True).replace('+', '%20')
+ 
+    def to_url(self):
+        """Serialize as a URL for a GET request."""
+        base_url = urlparse.urlparse(self.url)
+        try:
+            query = base_url.query
+        except AttributeError:
+            # must be python <2.5
+            query = base_url[4]
+        query = parse_qs(query)
+        for k, v in self.items():
+            query.setdefault(k, []).append(v)
+        
+        try:
+            scheme = base_url.scheme
+            netloc = base_url.netloc
+            path = base_url.path
+            params = base_url.params
+            fragment = base_url.fragment
+        except AttributeError:
+            # must be python <2.5
+            scheme = base_url[0]
+            netloc = base_url[1]
+            path = base_url[2]
+            params = base_url[3]
+            fragment = base_url[5]
+        
+        url = (scheme, netloc, path, params,
+               urllib.urlencode(query, True), fragment)
+        return urlparse.urlunparse(url)
+
+    def get_parameter(self, parameter):
+        ret = self.get(parameter)
+        if ret is None:
+            raise Error('Parameter not found: %s' % parameter)
+
+        return ret
+ 
+    def get_normalized_parameters(self):
+        """Return a string that contains the parameters that must be signed."""
+        items = []
+        for key, value in self.iteritems():
+            if key == 'oauth_signature':
+                continue
+            # 1.0a/9.1.1 states that kvp must be sorted by key, then by value,
+            # so we unpack sequence values into multiple items for sorting.
+            if hasattr(value, '__iter__'):
+                items.extend((key, item) for item in value)
+            else:
+                items.append((key, value))
+
+        # Include any query string parameters from the provided URL
+        query = urlparse.urlparse(self.url)[4]
+        
+        url_items = self._split_url_string(query).items()
+        non_oauth_url_items = list([(k, v) for k, v in url_items  if not k.startswith('oauth_')])
+        items.extend(non_oauth_url_items)
+
+        encoded_str = urllib.urlencode(sorted(items))
+        # Encode signature parameters per Oauth Core 1.0 protocol
+        # spec draft 7, section 3.6
+        # (http://tools.ietf.org/html/draft-hammer-oauth-07#section-3.6)
+        # Spaces must be encoded with "%20" instead of "+"
+        return encoded_str.replace('+', '%20').replace('%7E', '~')
+ 
+    def sign_request(self, signature_method, consumer, token):
+        """Set the signature parameter to the result of sign."""
+
+        if 'oauth_consumer_key' not in self:
+            self['oauth_consumer_key'] = consumer.key
+
+        if token and 'oauth_token' not in self:
+            self['oauth_token'] = token.key
+
+        self['oauth_signature_method'] = signature_method.name
+        self['oauth_signature'] = signature_method.sign(self, consumer, token)
+ 
+    @classmethod
+    def make_timestamp(cls):
+        """Get seconds since epoch (UTC)."""
+        return str(int(time.time()))
+ 
+    @classmethod
+    def make_nonce(cls):
+        """Generate pseudorandom number."""
+        return str(random.randint(0, 100000000))
+ 
+    @classmethod
+    def from_request(cls, http_method, http_url, headers=None, parameters=None,
+            query_string=None):
+        """Combines multiple parameter sources."""
+        if parameters is None:
+            parameters = {}
+ 
+        # Headers
+        if headers and 'Authorization' in headers:
+            auth_header = headers['Authorization']
+            # Check that the authorization header is OAuth.
+            if auth_header[:6] == 'OAuth ':
+                auth_header = auth_header[6:]
+                try:
+                    # Get the parameters from the header.
+                    header_params = cls._split_header(auth_header)
+                    parameters.update(header_params)
+                except:
+                    raise Error('Unable to parse OAuth parameters from '
+                        'Authorization header.')
+ 
+        # GET or POST query string.
+        if query_string:
+            query_params = cls._split_url_string(query_string)
+            parameters.update(query_params)
+ 
+        # URL parameters.
+        param_str = urlparse.urlparse(http_url)[4] # query
+        url_params = cls._split_url_string(param_str)
+        parameters.update(url_params)
+ 
+        if parameters:
+            return cls(http_method, http_url, parameters)
+ 
+        return None
+ 
+    @classmethod
+    def from_consumer_and_token(cls, consumer, token=None,
+            http_method=HTTP_METHOD, http_url=None, parameters=None):
+        if not parameters:
+            parameters = {}
+ 
+        defaults = {
+            'oauth_consumer_key': consumer.key,
+            'oauth_timestamp': cls.make_timestamp(),
+            'oauth_nonce': cls.make_nonce(),
+            'oauth_version': cls.version,
+        }
+ 
+        defaults.update(parameters)
+        parameters = defaults
+ 
+        if token:
+            parameters['oauth_token'] = token.key
+            if token.verifier:
+                parameters['oauth_verifier'] = token.verifier
+ 
+        return Request(http_method, http_url, parameters)
+ 
+    @classmethod
+    def from_token_and_callback(cls, token, callback=None, 
+        http_method=HTTP_METHOD, http_url=None, parameters=None):
+
+        if not parameters:
+            parameters = {}
+ 
+        parameters['oauth_token'] = token.key
+ 
+        if callback:
+            parameters['oauth_callback'] = callback
+ 
+        return cls(http_method, http_url, parameters)
+ 
+    @staticmethod
+    def _split_header(header):
+        """Turn Authorization: header into parameters."""
+        params = {}
+        parts = header.split(',')
+        for param in parts:
+            # Ignore realm parameter.
+            if param.find('realm') > -1:
+                continue
+            # Remove whitespace.
+            param = param.strip()
+            # Split key-value.
+            param_parts = param.split('=', 1)
+            # Remove quotes and unescape the value.
+            params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"'))
+        return params
+ 
+    @staticmethod
+    def _split_url_string(param_str):
+        """Turn URL string into parameters."""
+        parameters = parse_qs(param_str, keep_blank_values=False)
+        for k, v in parameters.iteritems():
+            parameters[k] = urllib.unquote(v[0])
+        return parameters
+
+
+class Client(httplib2.Http):
+    """OAuthClient is a worker to attempt to execute a request."""
+
+    def __init__(self, consumer, token=None, cache=None, timeout=None,
+        proxy_info=None):
+
+        if consumer is not None and not isinstance(consumer, Consumer):
+            raise ValueError("Invalid consumer.")
+
+        if token is not None and not isinstance(token, Token):
+            raise ValueError("Invalid token.")
+
+        self.consumer = consumer
+        self.token = token
+        self.method = SignatureMethod_HMAC_SHA1()
+
+        httplib2.Http.__init__(self, cache=cache, timeout=timeout, 
+            proxy_info=proxy_info)
+
+    def set_signature_method(self, method):
+        if not isinstance(method, SignatureMethod):
+            raise ValueError("Invalid signature method.")
+
+        self.method = method
+
+    def request(self, uri, method="GET", body=None, headers=None, 
+        redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None):
+        DEFAULT_CONTENT_TYPE = 'application/x-www-form-urlencoded'
+
+        if not isinstance(headers, dict):
+            headers = {}
+
+        is_multipart = method == 'POST' and headers.get('Content-Type', 
+            DEFAULT_CONTENT_TYPE) != DEFAULT_CONTENT_TYPE
+
+        if body and method == "POST" and not is_multipart:
+            parameters = dict(parse_qsl(body))
+        else:
+            parameters = None
+
+        req = Request.from_consumer_and_token(self.consumer, 
+            token=self.token, http_method=method, http_url=uri, 
+            parameters=parameters)
+
+        req.sign_request(self.method, self.consumer, self.token)
+
+        if method == "POST":
+            headers['Content-Type'] = headers.get('Content-Type', 
+                DEFAULT_CONTENT_TYPE)
+            if is_multipart:
+                headers.update(req.to_header())
+            else:
+                body = req.to_postdata()
+        elif method == "GET":
+            uri = req.to_url()
+        else:
+            headers.update(req.to_header())
+
+        return httplib2.Http.request(self, uri, method=method, body=body, 
+            headers=headers, redirections=redirections, 
+            connection_type=connection_type)
+
+
+class Server(object):
+    """A skeletal implementation of a service provider, providing protected
+    resources to requests from authorized consumers.
+ 
+    This class implements the logic to check requests for authorization. You
+    can use it with your web server or web framework to protect certain
+    resources with OAuth.
+    """
+
+    timestamp_threshold = 300 # In seconds, five minutes.
+    version = VERSION
+    signature_methods = None
+
+    def __init__(self, signature_methods=None):
+        self.signature_methods = signature_methods or {}
+
+    def add_signature_method(self, signature_method):
+        self.signature_methods[signature_method.name] = signature_method
+        return self.signature_methods
+
+    def verify_request(self, request, consumer, token):
+        """Verifies an api call and checks all the parameters."""
+
+        version = self._get_version(request)
+        self._check_signature(request, consumer, token)
+        parameters = request.get_nonoauth_parameters()
+        return parameters
+
+    def build_authenticate_header(self, realm=''):
+        """Optional support for the authenticate header."""
+        return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
+
+    def _get_version(self, request):
+        """Verify the correct version request for this server."""
+        try:
+            version = request.get_parameter('oauth_version')
+        except:
+            version = VERSION
+
+        if version and version != self.version:
+            raise Error('OAuth version %s not supported.' % str(version))
+
+        return version
+
+    def _get_signature_method(self, request):
+        """Figure out the signature with some defaults."""
+        try:
+            signature_method = request.get_parameter('oauth_signature_method')
+        except:
+            signature_method = SIGNATURE_METHOD
+
+        try:
+            # Get the signature method object.
+            signature_method = self.signature_methods[signature_method]
+        except:
+            signature_method_names = ', '.join(self.signature_methods.keys())
+            raise Error('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names))
+
+        return signature_method
+
+    def _get_verifier(self, request):
+        return request.get_parameter('oauth_verifier')
+
+    def _check_signature(self, request, consumer, token):
+        timestamp, nonce = request._get_timestamp_nonce()
+        self._check_timestamp(timestamp)
+        signature_method = self._get_signature_method(request)
+
+        try:
+            signature = request.get_parameter('oauth_signature')
+        except:
+            raise MissingSignature('Missing oauth_signature.')
+
+        # Validate the signature.
+        valid = signature_method.check(request, consumer, token, signature)
+
+        if not valid:
+            key, base = signature_method.signing_base(request, consumer, token)
+
+            raise Error('Invalid signature. Expected signature base ' 
+                'string: %s' % base)
+
+        built = signature_method.sign(request, consumer, token)
+
+    def _check_timestamp(self, timestamp):
+        """Verify that timestamp is recentish."""
+        timestamp = int(timestamp)
+        now = int(time.time())
+        lapsed = now - timestamp
+        if lapsed > self.timestamp_threshold:
+            raise Error('Expired timestamp: given %d and now %s has a '
+                'greater difference than threshold %d' % (timestamp, now, 
+                    self.timestamp_threshold))
+
+
+class SignatureMethod(object):
+    """A way of signing requests.
+ 
+    The OAuth protocol lets consumers and service providers pick a way to sign
+    requests. This interface shows the methods expected by the other `oauth`
+    modules for signing requests. Subclass it and implement its methods to
+    provide a new way to sign requests.
+    """
+
+    def signing_base(self, request, consumer, token):
+        """Calculates the string that needs to be signed.
+
+        This method returns a 2-tuple containing the starting key for the
+        signing and the message to be signed. The latter may be used in error
+        messages to help clients debug their software.
+
+        """
+        raise NotImplementedError
+
+    def sign(self, request, consumer, token):
+        """Returns the signature for the given request, based on the consumer
+        and token also provided.
+
+        You should use your implementation of `signing_base()` to build the
+        message to sign. Otherwise it may be less useful for debugging.
+
+        """
+        raise NotImplementedError
+
+    def check(self, request, consumer, token, signature):
+        """Returns whether the given signature is the correct signature for
+        the given consumer and token signing the given request."""
+        built = self.sign(request, consumer, token)
+        return built == signature
+
+
+class SignatureMethod_HMAC_SHA1(SignatureMethod):
+    name = 'HMAC-SHA1'
+        
+    def signing_base(self, request, consumer, token):
+        if request.normalized_url is None:
+            raise ValueError("Base URL for request is not set.")
+
+        sig = (
+            escape(request.method),
+            escape(request.normalized_url),
+            escape(request.get_normalized_parameters()),
+        )
+
+        key = '%s&' % escape(consumer.secret)
+        if token:
+            key += escape(token.secret)
+        raw = '&'.join(sig)
+        return key, raw
+
+    def sign(self, request, consumer, token):
+        """Builds the base signature string."""
+        key, raw = self.signing_base(request, consumer, token)
+
+        # HMAC object.
+        try:
+            from hashlib import sha1 as sha
+        except ImportError:
+            import sha # Deprecated
+
+        hashed = hmac.new(key, raw, sha)
+
+        # Calculate the digest base 64.
+        return binascii.b2a_base64(hashed.digest())[:-1]
+
+
+class SignatureMethod_PLAINTEXT(SignatureMethod):
+
+    name = 'PLAINTEXT'
+
+    def signing_base(self, request, consumer, token):
+        """Concatenates the consumer key and secret with the token's
+        secret."""
+        sig = '%s&' % escape(consumer.secret)
+        if token:
+            sig = sig + escape(token.secret)
+        return sig, sig
+
+    def sign(self, request, consumer, token):
+        key, raw = self.signing_base(request, consumer, token)
+        return raw
diff --git a/chrome/common/extensions/docs/examples/apps/hello-python/oauth2/clients/__init__.py b/chrome/common/extensions/docs/examples/apps/hello-python/oauth2/clients/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-python/oauth2/clients/__init__.py
diff --git a/chrome/common/extensions/docs/examples/apps/hello-python/oauth2/clients/imap.py b/chrome/common/extensions/docs/examples/apps/hello-python/oauth2/clients/imap.py
new file mode 100644
index 0000000..68b7cd8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-python/oauth2/clients/imap.py
@@ -0,0 +1,40 @@
+"""
+The MIT License
+
+Copyright (c) 2007-2010 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+"""
+
+import oauth2
+import imaplib
+
+
+class IMAP4_SSL(imaplib.IMAP4_SSL):
+    """IMAP wrapper for imaplib.IMAP4_SSL that implements XOAUTH."""
+
+    def authenticate(self, url, consumer, token):
+        if consumer is not None and not isinstance(consumer, oauth2.Consumer):
+            raise ValueError("Invalid consumer.")
+
+        if token is not None and not isinstance(token, oauth2.Token):
+            raise ValueError("Invalid token.")
+
+        imaplib.IMAP4_SSL.authenticate(self, 'XOAUTH',
+            lambda x: oauth2.build_xoauth_string(url, consumer, token))
diff --git a/chrome/common/extensions/docs/examples/apps/hello-python/oauth2/clients/smtp.py b/chrome/common/extensions/docs/examples/apps/hello-python/oauth2/clients/smtp.py
new file mode 100644
index 0000000..3e7bf0b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-python/oauth2/clients/smtp.py
@@ -0,0 +1,41 @@
+"""
+The MIT License
+
+Copyright (c) 2007-2010 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+"""
+
+import oauth2
+import smtplib
+import base64
+
+
+class SMTP(smtplib.SMTP):
+    """SMTP wrapper for smtplib.SMTP that implements XOAUTH."""
+
+    def authenticate(self, url, consumer, token):
+        if consumer is not None and not isinstance(consumer, oauth2.Consumer):
+            raise ValueError("Invalid consumer.")
+
+        if token is not None and not isinstance(token, oauth2.Token):
+            raise ValueError("Invalid token.")
+
+        self.docmd('AUTH', 'XOAUTH %s' % \
+            base64.b64encode(oauth2.build_xoauth_string(url, consumer, token)))
diff --git a/chrome/common/extensions/docs/examples/apps/hello-python/templates/index.html b/chrome/common/extensions/docs/examples/apps/hello-python/templates/index.html
new file mode 100644
index 0000000..0602513
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/hello-python/templates/index.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2009 The Chromium Authors. All rights reserved.  Use of this
+ * source code is governed by a BSD-style license that can be found in the
+ * LICENSE file.
+-->
+<html>
+  <head>
+    <style>
+      body { font: 14px Arial; }
+      .credentials { text-align: right; }
+      p { padding: 100px 0; text-align: center; font-size: 24px; }
+      p strong {
+        display: inline-block; padding: 4px; border: 2px dotted #999;
+        border-radius: 10px; margin: 0 5px;
+      }
+      .noaccess strong { color: #900; border-color: #900; }
+      .fullaccess strong { color: #090; border-color: #090; }
+      .trialaccess strong { color: #009; border-color: #009; }
+    </style>
+  </head>
+  <body>
+    <div class="credentials">
+      {% if user_login %}
+        <a href="{{user_login}}">Sign in</a>
+      {% else %}
+        <strong>{{user_id}}</strong> | <a href="{{user_logout}}">Sign out</a>
+      {% endif %}
+    </div>
+    {% if not license %}
+      <p class="notsignedin">
+        You are <strong>not signed in</strong> to this app.
+      </p>
+    {% else %}
+      {% if license.error %}
+        <p><strong>There was an error loading the response:</strong>{{license.message}}</p>
+      {% else %}
+        {% ifequal license.access "NO" %}
+          <p class="noaccess">
+            You have <strong>no access</strong> to extra features in this app.
+          </p>
+        {% endifequal %}
+        {% ifequal license.access "FREE_TRIAL" %}
+          <p class="trialaccess">
+            You are currently enrolled in a <strong>free trial</strong> of this app.
+          </p>
+        {% endifequal %}
+        {% ifequal license.access "FULL" %}
+          <p class="fullaccess">
+            You have <strong>full access</strong> to the features of this app.
+          </p>
+        {% endifequal %}
+      {% endif %}
+    {% endif %}
+
+  </body>
+</html>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/app_launcher/browser_action_icon.png b/chrome/common/extensions/docs/examples/extensions/app_launcher/browser_action_icon.png
new file mode 100644
index 0000000..fb40a52
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/app_launcher/browser_action_icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/app_launcher/icon.png b/chrome/common/extensions/docs/examples/extensions/app_launcher/icon.png
new file mode 100644
index 0000000..8e64d3d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/app_launcher/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/app_launcher/manifest.json b/chrome/common/extensions/docs/examples/extensions/app_launcher/manifest.json
new file mode 100644
index 0000000..d6f92f9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/app_launcher/manifest.json
@@ -0,0 +1,14 @@
+{
+  "name": "App Launcher",
+  "version": "0.7.3",
+  "permissions": ["management"],
+  "browser_action": {
+    "default_icon": "browser_action_icon.png",
+    "default_title": "App Launcher",
+    "default_popup": "popup.html"
+  },
+  "icons": {
+    "48": "icon.png"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/app_launcher/popup.css b/chrome/common/extensions/docs/examples/extensions/app_launcher/popup.css
new file mode 100644
index 0000000..e5fd468
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/app_launcher/popup.css
@@ -0,0 +1,53 @@
+// Copyright (c) 2010 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.
+
+html {
+  overflow-x: hidden;
+}
+
+body {
+  font-family: Helvetica, Arial, sans-serif;
+}
+
+#search_container {
+  text-align: center;
+}
+
+#search {
+  width: 85%;
+}
+
+.app {
+  width: 100%;
+  margin-top: 7px;
+  margin-bottom: 7px;
+  margin-left: 2px;
+  margin-right: 20px;
+  white-space: nowrap;
+}
+
+.app img {
+  vertical-align: middle;
+  height: 38px;
+  width: 38px;
+}
+
+.app_title {
+  vertical-align: middle;
+  margin-left: 5px;
+}
+
+.app:hover {
+  background-color: rgb(250,250,250);
+  cursor:pointer;
+  outline: 1px dotted rgb(100,100,200);
+}
+
+.app_selected,.app_selected:hover {
+  background-color: rgb(230,230,230);
+}
+
+#appstore_link {
+  display: none;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/app_launcher/popup.html b/chrome/common/extensions/docs/examples/extensions/app_launcher/popup.html
new file mode 100644
index 0000000..77a9fb6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/app_launcher/popup.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<!--
+ * Copyright (c) 2010 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.
+-->
+<html>
+<head>
+<link rel="stylesheet" type="text/css" href="popup.css">
+<script src="popup.js"></script>
+</head>
+<body>
+
+<div id="spacer_dummy"></div>
+<div id="outer">
+
+<div id="appstore_link">
+  <p>No apps installed.</p><p>
+  <button>Go get some</button></p>
+</div>
+
+<div id="search_container">
+  <input id="search" type="text" placeholder="type to search" spellcheck="false">
+</div>
+
+<div id="apps"></div>
+
+</div>
+
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/app_launcher/popup.js b/chrome/common/extensions/docs/examples/extensions/app_launcher/popup.js
new file mode 100644
index 0000000..788228c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/app_launcher/popup.js
@@ -0,0 +1,213 @@
+// 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.
+
+function $(id) {
+  return document.getElementById(id);
+}
+
+// Returns the largest size icon, or a default icon, for the given |app|.
+function getIconURL(app) {
+  if (!app.icons || app.icons.length == 0) {
+    return chrome.extension.getURL('icon.png');
+  }
+  var largest = {size:0};
+  for (var i = 0; i < app.icons.length; i++) {
+    var icon = app.icons[i];
+    if (icon.size > largest.size) {
+      largest = icon;
+    }
+  }
+  return largest.url;
+}
+
+function launchApp(id) {
+  chrome.management.launchApp(id);
+  window.close(); // Only needed on OSX because of crbug.com/63594
+}
+
+// Adds DOM nodes for |app| into |appsDiv|.
+function addApp(appsDiv, app, selected) {
+  var div = document.createElement('div');
+  div.className = 'app' + (selected ? ' app_selected' : '');
+
+  div.onclick = function() {
+    launchApp(app.id);
+  };
+
+  var img = document.createElement('img');
+  img.src = getIconURL(app);
+  div.appendChild(img);
+
+  var title = document.createElement('span');
+  title.className = 'app_title';
+  title.innerText = app.name;
+  div.appendChild(title);
+
+  appsDiv.appendChild(div);
+}
+
+// The list of all apps & extensions.
+var completeList = [];
+
+// A filtered list of apps we actually want to show.
+var appList = [];
+
+// The index of an app in |appList| that should be highlighted.
+var selectedIndex = 0;
+
+function reloadAppDisplay() {
+  var appsDiv = $('apps');
+
+  // Empty the current content.
+  appsDiv.innerHTML = '';
+
+  for (var i = 0; i < appList.length; i++) {
+    var item = appList[i];
+    addApp(appsDiv, item, i == selectedIndex);
+  }
+}
+
+// Puts only enabled apps from completeList into appList.
+function rebuildAppList(filter) {
+  selectedIndex = 0;
+  appList = [];
+  for (var i = 0; i < completeList.length; i++){
+    var item = completeList[i];
+    // Skip extensions and disabled apps.
+    if (!item.isApp || !item.enabled) {
+      continue;
+    }
+    if (filter && item.name.toLowerCase().search(filter) < 0) {
+      continue;
+    }
+    appList.push(item);
+  }
+}
+
+// In order to keep the popup bubble from shrinking as your search narrows the
+// list of apps shown, we set an explicit width on the outermost div.
+var didSetExplicitWidth = false;
+
+function adjustWidthIfNeeded(filter) {
+  if (filter.length > 0 && !didSetExplicitWidth) {
+    // Set an explicit width, correcting for any scroll bar present.
+    var outer = $('outer');
+    var correction = window.innerWidth - document.documentElement.clientWidth;
+    var width = outer.offsetWidth;
+    $('spacer_dummy').style.width = width + correction + 'px';
+    didSetExplicitWidth = true;
+  }
+}
+
+// Shows the list of apps based on the search box contents.
+function onSearchInput() {
+  var filter = $('search').value;
+  adjustWidthIfNeeded(filter);
+  rebuildAppList(filter);
+  reloadAppDisplay();
+}
+
+function compare(a, b) {
+  return (a > b) ? 1 : (a == b ? 0 : -1);
+}
+
+function compareByName(app1, app2) {
+  return compare(app1.name.toLowerCase(), app2.name.toLowerCase());
+}
+
+// Changes the selected app in the list.
+function changeSelection(newIndex) {
+  if (newIndex >= 0 && newIndex <= appList.length - 1) {
+    selectedIndex = newIndex;
+    reloadAppDisplay();
+
+    var selected = document.getElementsByClassName('app_selected')[0];
+    var rect = selected.getBoundingClientRect();
+    if (newIndex == 0) {
+      window.scrollTo(0, 0);
+    } else if (newIndex == appList.length - 1) {
+      window.scrollTo(0, document.height);
+    }  else if (rect.top < 0) {
+      window.scrollBy(0, rect.top);
+    } else if (rect.bottom > innerHeight) {
+      window.scrollBy(0, rect.bottom - innerHeight);
+    }
+  }
+}
+
+var keys = {
+  ENTER : 13,
+  ESCAPE : 27,
+  END : 35,
+  HOME : 36,
+  LEFT : 37,
+  UP : 38,
+  RIGHT : 39,
+  DOWN : 40
+};
+
+// Set up a key event handler that handles moving the selected app up/down,
+// hitting enter to launch the selected app, as well as auto-focusing the
+// search box as soon as you start typing.
+window.onkeydown = function(event) {
+  switch (event.keyCode) {
+    case keys.DOWN:
+      changeSelection(selectedIndex + 1);
+      break;
+    case keys.UP:
+      changeSelection(selectedIndex - 1);
+      break;
+    case keys.HOME:
+      changeSelection(0);
+      break;
+    case keys.END:
+      changeSelection(appList.length - 1);
+      break;
+    case keys.ENTER:
+      var app = appList[selectedIndex];
+      if (app) {
+        launchApp(app.id);
+      }
+      break;
+    default:
+      // Focus the search box and return true so you can just start typing even
+      // when the search box isn't focused.
+      $('search').focus();
+      return true;
+  }
+  return false;
+};
+
+
+// Initalize the popup window.
+document.addEventListener('DOMContentLoaded', function () {
+  chrome.management.getAll(function(info) {
+    var appCount = 0;
+    for (var i = 0; i < info.length; i++) {
+      if (info[i].isApp) {
+        appCount++;
+      }
+    }
+    if (appCount == 0) {
+      $('search').style.display = 'none';
+      $('appstore_link').style.display = '';
+      return;
+    }
+    completeList = info.sort(compareByName);
+    onSearchInput();
+  });
+
+  $('search').addEventListener('input', onSearchInput);
+
+  // Opens the webstore in a new tab.
+  document.querySelector('#appstore_link button').addEventListener('click',
+      function () {
+        chrome.tabs.create({
+          'url':'https://chrome.google.com/webstore',
+          'selected':true
+        });
+        window.close();
+      });
+});
+
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/README.txt b/chrome/common/extensions/docs/examples/extensions/benchmark/README.txt
new file mode 100644
index 0000000..2267580
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/README.txt
@@ -0,0 +1,27 @@
+Benchmark Extension
+-------------------
+This extension provides basic page-level benchmarking into the browser.
+
+With the extension installed you can test web pages and then compare
+results in a subwindow.
+
+Between each page load you can optionally clear idle http connections and
+clear the cache so that page loads are more like the user experience
+when first connecting to a site.
+
+To use this benchmark, you'll need to run chrome with the the
+"--enable-benchmarking" flag.  This flag enables a v8-extension so that
+the benchmark can clear idle connections and the cache.
+
+The code found in the jst/ subdirectory is JSTemplate code from
+http://code.google.com/p/google-jstemplate/.
+
+In jquery/, jquery-1.4.2.min.js is from http://jquery.com/. jquery.flot.min.js
+is a plotting library and from http://code.google.com/p/flot/.
+jquery.flot.dashes.js is an enhancement of Flot for dashed lines and from 
+http://code.google.com/p/flot/issues/detail?id=61.
+
+In util/, sortable.js serves for sorting table content and is from
+http://www.kryogenix.org/code/browser/sorttable/. table2CSV.js is for exporting
+table data to .csv and from http://www.kunalbabre.com/projects/table2CSV.php.
+
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/background.html b/chrome/common/extensions/docs/examples/extensions/benchmark/background.html
new file mode 100644
index 0000000..2eed9f9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/background.html
@@ -0,0 +1,15 @@
+<style>
+#options {
+  position: absolute;
+  background-color: #FFFFCC;
+  display: none;
+  font-family: "Courier New";
+  font-size: 9pt;
+  padding: 5px;
+  border: 1px solid #CCCC88;
+  z-index: 3;
+}
+</style>
+
+<script src="background.js">
+</script>
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/background.js b/chrome/common/extensions/docs/examples/extensions/benchmark/background.js
new file mode 100644
index 0000000..1d11a94
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/background.js
@@ -0,0 +1,493 @@
+// Copyright (c) 2012 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.
+
+// Round a number to the 1's place.
+function formatNumber(str) {
+  str += '';
+  if (str == '0') {
+    return 'N/A ';
+  }
+  var x = str.split('.');
+  var x1 = x[0];
+  var x2 = x.length > 1 ? '.' + x[1] : '';
+  var regex = /(\d+)(\d{3})/;
+  while (regex.test(x1)) {
+    x1 = x1.replace(regex, '$1' + ',' + '$2');
+  }
+  return x1;
+}
+
+// Configuration and results are stored globally.
+window.iterations = 10;
+window.interval = 200;
+window.clearConnections = true;
+window.clearCache = true;
+window.enableSpdy = false;
+window.results = {};
+window.results.data = new Array();
+window.testUrl = "http://www.google.com/";
+window.windowId = 0;
+
+// Constant StatCounter Names
+var kTCPReadBytes = "tcp.read_bytes";
+var kTCPWriteBytes = "tcp.write_bytes";
+var kRequestCount = "HttpNetworkTransaction.Count";
+var kConnectCount = "tcp.connect";
+var kSpdySessionCount = "spdy.sessions";
+
+// The list of currently running benchmarks
+var benchmarks = new Array();
+var benchmarkIndex = 0;
+var benchmarkWindow = 0;
+
+function addBenchmark(benchmark) {
+  benchmarks.push(benchmark);
+  benchmarkIndex = 0;  // Reset the counter when adding benchmarks.
+}
+
+// Array Remove - By John Resig (MIT Licensed)
+Array.prototype.remove = function(from, to) {
+  var rest = this.slice((to || from) + 1 || this.length);
+  this.length = from < 0 ? this.length + from : from;
+  return this.push.apply(this, rest);
+};
+
+function removeBenchmark(benchmark) {
+  var index;
+  for (var index = 0; index < benchmarks.length; ++index) {
+    if (benchmarks[index] == benchmark) {
+      break;
+    }
+  }
+  benchmarks.remove(index);
+
+  // Preserve index ordering when removing from the list.
+  if (index <= benchmarkIndex) {
+    benchmarkIndex--;  // Note:  it is okay to drop to -1 here.
+  }
+}
+
+function benchmarkStillRunning() {
+  for (var index = 0; index < benchmarks.length; ++index) {
+    if (benchmarks[index].isRunning()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+function findBenchmark(url) {
+  for (var index = 0; index < benchmarks.length; ++index) {
+    // One common redirection case: if the url ends without a slash and refers
+    // to a directory, it actually would be redirected to the correct one
+    // (with a slash). In this case, the url returned by the JS and the one
+    // stored locally do not match.
+    if ((benchmarks[index].url() == url) ||
+        (benchmarks[index].url() + '/' == url)) {
+      return benchmarks[index];
+    }
+  }
+  return undefined;
+}
+
+function nextBenchmark() {
+  benchmarkIndex = (benchmarkIndex + 1) % benchmarks.length;
+  return benchmarks[benchmarkIndex];
+}
+
+function show_options() {
+  chrome.tabs.getSelected(null, function(tab) {
+    if (window.testUrl == "") {
+      window.testUrl = tab.url;
+    }
+    var tabs = chrome.extension.getViews({"type": "tab"});
+    if (tabs && tabs.length) {
+      // To avoid "Uncaught TypeError: Object Window has no method
+      // 'setUrl' ". Sometimes tabs are not the desired extension tabs.
+      if (tabs[0].$suburl != undefined) {
+        tabs[0].setUrl(testUrl);
+      }
+      var optionsUrl = chrome.extension.getURL("options.html");
+      chrome.tabs.getAllInWindow(null, function(all) {
+        for (var i = 0; i < all.length; i++) {
+          if (all[i].url == optionsUrl) {
+            chrome.tabs.update(all[i].id, {selected: true});
+            return;
+          }
+        }
+      });
+    } else {
+      chrome.tabs.create({"url":"options.html"});
+    }
+  });
+}
+
+chrome.browserAction.onClicked.addListener(show_options);
+
+function Benchmark() {
+  var runCount_ = 0;
+  var count_;
+  var totalTime_;
+  var me_ = this;
+  var current_;
+  var initialRequestCount_;
+  var initialReadBytes_;
+  var initialWriteBytes_;
+
+  // Start a test run
+  this.start = function(url) {
+    // Check if a run is already in progress.
+    if (me_.isRunning()) {
+      return;
+    }
+
+    console.log("Benchmark testing url: " + url);
+
+    // Add this benchmark to the list of benchmarks running.
+    addBenchmark(this);
+
+    runCount_ = window.iterations;
+    count_ = 0;
+    totalTime_ = 0;
+
+    current_ = {};
+    current_.url = url;
+    current_.timestamp = new Date();
+    current_.viaSpdy = false;
+    current_.startLoadResults = new Array();  // times to start
+    current_.commitLoadResults = new Array();  // times to commit
+    current_.docLoadResults = new Array();  // times to docload
+    current_.paintResults = new Array();    // times to paint
+    current_.totalResults = new Array();    // times to complete load
+    current_.KbytesRead = new Array();
+    current_.KbytesWritten = new Array();
+    current_.readbpsResults = new Array();
+    current_.writebpsResults = new Array();
+    current_.totalTime = 0;
+    current_.iterations = 0;
+    current_.requests = new Array();
+    current_.connects = new Array();
+    current_.spdySessions = new Array();
+    current_.domNum = 0;
+    current_.maxDepth = 0;
+    current_.minDepth = 0;
+    current_.avgDepth = 0;
+  };
+
+  // Is the benchmark currently in progress.
+  this.isRunning = function() {
+    return runCount_ > 0;
+  };
+
+  // The url which this benchmark is running.
+  this.url = function() { return current_.url; }
+
+  // Called when the test run completes.
+  this.finish = function() {
+    removeBenchmark(this);
+
+    // If we're the last benchmark, close the window.
+    if (benchmarks.length == 0) {
+      chrome.tabs.remove(benchmarkWindow.id);
+      benchmarkWindow = 0;
+      show_options();
+    }
+  };
+
+  // Update the UI after a test run.
+  this.displayResults = function() {
+    var score = 0;
+    if (count_ > 0) {
+      score = totalTime_ / count_;
+      var text = score.toFixed(1) + "ms avg";
+      chrome.browserAction.setTitle({"title": text});
+    }
+    if (runCount_) {
+      chrome.browserAction.setBadgeText({"text": "" + runCount_});
+      chrome.browserAction.setBadgeBackgroundColor({"color": [255, 0, 0, 255]});
+    } else {
+      chrome.browserAction.setBadgeText({"text": "" + score.toFixed()});
+      chrome.browserAction.setBadgeBackgroundColor({"color": [0, 255, 0, 255]});
+    }
+
+    // Reload the page after each run to show immediate results.
+    var tabs = chrome.extension.getViews({"type": "tab"});
+    if (tabs && tabs.length) {
+      tabs[0].location.reload(true);
+    }
+  };
+
+  // Called before starting a page load.
+  this.pageStart = function() {
+    initialReadBytes_ = chrome.benchmarking.counter(kTCPReadBytes);
+    initialWriteBytes_ = chrome.benchmarking.counter(kTCPWriteBytes);
+    initialRequestCount_ = chrome.benchmarking.counter(kRequestCount);
+    initialConnectCount_ = chrome.benchmarking.counter(kConnectCount);
+    initialSpdySessionCount_ = chrome.benchmarking.counter(kSpdySessionCount);
+  };
+
+  this.openNextPage = function() {
+    var benchmark = nextBenchmark();
+    benchmark.pageStart();
+    chrome.tabs.create({"url": benchmark.url(),"selected": true},
+                       function(tab) {
+                         benchmarkWindow = tab;
+                         // script.js only executes on tested pages
+                         // not the ones opened by the user.
+                         chrome.tabs.executeScript(tab.id, {file: "script.js"});
+                        });
+  };
+
+  this.prepareToOpenPage = function() {
+    // After the previous page is closed, this function will apply
+    // any settings needed to prepare for opening a new page.
+    // Note: the previous page must be closed, otherwie, the cache
+    // clearing and connection clearing may not be thorough.
+
+    if (window.clearCache) {
+      chrome.benchmarking.clearCache();
+    }
+
+    if (window.clearConnections) {
+      chrome.benchmarking.closeConnections();
+    }
+
+    if (window.enableSpdy) {
+      chrome.benchmarking.enableSpdy(true);
+    } else {
+      chrome.benchmarking.enableSpdy(false);
+    }
+
+    // Go back to the browser so that tasks can run.
+    setTimeout(me_.openNextPage, window.interval);
+  };
+
+  this.closePage = function() {
+    chrome.tabs.remove(benchmarkWindow.id, function() {
+      me_.prepareToOpenPage();
+    });
+  };
+
+  // Run a single page in the benchmark
+  this.runPage = function() {
+    if (benchmarkWindow) {
+      // To avoid the error "Error during tabs.remove: No tab with id xx"
+      // while debugging, due to user manually closing the benchmark tab.
+      chrome.tabs.getAllInWindow(null, function(all) {
+        for (var i = 0; i < all.length; i++) {
+          if (all[i].id == benchmarkWindow.id) {
+            me_.closePage();
+             return;
+          };
+        };
+        me_.prepareToOpenPage();
+      });
+    } else {
+      me_.prepareToOpenPage();
+    }
+  };
+
+  // Called when a page finishes loading.
+  this.pageFinished = function(load_times, domNum, depths) {
+
+     // Make sure the content can be fetched via spdy if it is enabled.
+    if (window.enableSpdy && !load_times.wasFetchedViaSpdy) {
+      alert("Can not fetch current url via spdy.\n" +
+            "Ending current test.");
+      me_.finish();
+      // Move on to next benchmarked pages.
+      if (benchmarks.length > 0) {
+        if (window.clearConnections) {
+          chrome.benchmarking.closeConnections();
+        }
+        setTimeout(me_.runPage, 100);
+      }
+      return;
+    }
+
+    // If last fetch was via spdy, current fetch should use spdy too. Same
+    // for vise versa.
+    if (current_.iterations > 0 &&
+        current_.viaSpdy != load_times.wasFetchedViaSpdy) {
+      alert("Error: viaSpdy for current fetch is different from last fetch!\n" +
+            "Ending current test.");
+      // Current data set is invalid: remove from the result array.
+      var currIndex;
+      currIndex = window.results.data.indexOf(current_, 0);
+      window.results.data.splice(currIndex, 1);
+      me_.displayResults();
+      me_.finish();
+      if (benchmarks.length > 0) {
+        if (window.clearConnections) {
+          chrome.benchmarking.closeConnections();
+        }
+        setTimeout(me_.runPage, 100);
+      }
+      return;
+    }
+
+    var requested = load_times.requestTime;
+    var started = load_times.startLoadTime;
+    var startLoadTime =
+        Math.round((load_times.startLoadTime - requested) * 1000.0);
+    var commitLoadTime =
+        Math.round((load_times.commitLoadTime - started) * 1000.0);
+    var docLoadTime =
+        Math.round((load_times.finishDocumentLoadTime - started) * 1000.0);
+    var paintTime =
+        Math.round((load_times.firstPaintTime - started) * 1000.0);
+    var totalTime =
+        Math.round((load_times.finishLoadTime - started) * 1000.0);
+    var firstPaintAfterLoadTime =
+        Math.round((load_times.firstPaintAfterLoadTime - started) * 1000.0);
+
+    if (paintTime < 0) {
+      // If the user navigates away from the test while it is running,
+      // paint may not occur.  Also, some lightweight pages, such as the
+      // google home page, never trigger a paint measurement via the chrome
+      // page load timer.
+      // In this case, the time-to-first paint is effectively the same as the
+      // time to onLoad().
+      paintTime = totalTime;
+    }
+
+    // For our toolbar counters
+    totalTime_ += totalTime;
+    count_++;
+
+    // Get the index of current benchmarked page in the result array.
+    var currIndex;
+    currIndex = window.results.data.indexOf(current_, 0);
+
+    // Record the result
+    current_.viaSpdy = load_times.wasFetchedViaSpdy;
+    current_.iterations++;
+    current_.startLoadResults.push(startLoadTime);
+    current_.commitLoadResults.push(commitLoadTime);
+    current_.docLoadResults.push(docLoadTime);
+    current_.paintResults.push(paintTime);
+    current_.totalResults.push(totalTime);
+    var bytesRead = chrome.benchmarking.counter(kTCPReadBytes) -
+                                              initialReadBytes_;
+    var bytesWrite = chrome.benchmarking.counter(kTCPWriteBytes) -
+                                               initialWriteBytes_;
+    current_.KbytesRead.push(bytesRead / 1024);
+    current_.KbytesWritten.push(bytesWrite / 1024);
+    current_.readbpsResults.push(bytesRead * 8 / totalTime);
+    current_.writebpsResults.push(bytesWrite * 8 / totalTime);
+    current_.requests.push(chrome.benchmarking.counter(kRequestCount) -
+                         initialRequestCount_);
+    current_.connects.push(chrome.benchmarking.counter(kConnectCount) -
+                         initialConnectCount_);
+    current_.spdySessions.push(chrome.benchmarking.counter(kSpdySessionCount) -
+                         initialSpdySessionCount_);
+    current_.totalTime += totalTime;
+    current_.domNum = domNum;
+    current_.maxDepth = depths[0];
+    current_.minDepth = depths[1];
+    current_.avgDepth = depths[2];
+
+    // Insert or update the result data after each run.
+    if (currIndex == -1) {
+      window.results.data.push(current_);
+    } else {
+      window.results.data[currIndex] = current_;
+    }
+
+    if (--runCount_ == 0) {
+      me_.finish();
+    }
+
+    // If there are more tests, schedule them
+    if (runCount_ > 0 || benchmarks.length > 0) {
+      if (window.clearConnections) {
+        chrome.benchmarking.closeConnections();
+      }
+      setTimeout(me_.runPage, 100);
+    }
+
+    // Update the UI
+    me_.displayResults();
+  };
+}
+
+chrome.extension.onConnect.addListener(function(port) {
+  port.onMessage.addListener(function(data) {
+    if (data.message == "load") {
+      var benchmark = findBenchmark(data.url);
+      if (benchmark == undefined && benchmarkStillRunning()) {
+        alert("Error: Loaded url(" + data.url + ") is not the same as what " +
+              "you set in url box. This could happen if the request is " +
+              "redirected. Please use the redirected url for testing.");
+        // Stop the test here.
+        benchmarks = [];
+      }
+      if (benchmark != undefined && benchmark.isRunning()) {
+        benchmark.pageFinished(data.values, data.domNum, data.domDepths);
+      }
+    }
+  });
+});
+
+function run() {
+  if (window.clearCache) {
+    // Show a warning if we will try to clear the cache between runs
+    // but will also be reusing the same WebKit instance (i.e. Chrome
+    // is in single-process mode) because the WebKit cache might not get
+    // completely cleared between runs.
+    if (chrome.benchmarking.isSingleProcess()) {
+      alert("Warning: the WebKit cache may not be cleared correctly " +
+            "between runs because Chrome is running in single-process mode.");
+    }
+  }
+  benchmarks = [];
+  var urls = testUrl.split(",");
+  for (var i = 0; i < urls.length; i++) {
+
+    // Remove extra space at the beginning or end of a url.
+    urls[i] = removeSpace(urls[i]);
+
+    // Alert about and ignore blank page which does not get loaded.
+    if (urls[i] == "about:blank") {
+      alert("blank page loaded!");
+    } else if (!checkScheme(urls[i])) {
+      // Alert about url that is not in scheme http:// or https://.
+      alert(urls[i] + " does not start with http:// or https://.");
+    } else {
+      var benchmark = new Benchmark();
+      benchmark.start(urls[i]);  // XXXMB - move to constructor
+    }
+  }
+  benchmarks[0].runPage();
+}
+
+// Remove extra whitespace in the beginning or end of a url string.
+function removeSpace(url) {
+  var tempUrl = url;
+  while (tempUrl.charAt(tempUrl.length-1) == " ") {
+    tempUrl = tempUrl.substring(0, tempUrl.length-1);
+  };
+  while (tempUrl.charAt(0) == " ") {
+    tempUrl = tempUrl.substring(1, tempUrl.length);
+  };
+  return tempUrl;
+}
+
+// Check whether a Url starts with http:// or https://.
+function checkScheme(url) {
+  var httpStr = "http://";
+  var httpsStr = "https://";
+  var urlSubStr1 = url.substring(0, httpStr.length);
+  var urlSubStr2 = url.substring(0, httpsStr.length);
+
+  if ( (urlSubStr1 == httpStr) || (urlSubStr2 == httpsStr) ) {
+    return true;
+  }
+  return false;
+}
+
+// Run at startup
+chrome.windows.getCurrent(function(currentWindow) {
+  window.windowId = currentWindow.id;
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery-1.8.2.min.js b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery-1.8.2.min.js
new file mode 100644
index 0000000..f65cf1d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery-1.8.2.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v1.8.2 jquery.com | jquery.org/license */

+(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d<e;d++)p.event.add(b,c,h[c][d])}g.data&&(g.data=p.extend({},g.data))}function bE(a,b){var c;if(b.nodeType!==1)return;b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?(b.parentNode&&(b.outerHTML=a.outerHTML),p.support.html5Clone&&a.innerHTML&&!p.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):c==="input"&&bv.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text),b.removeAttribute(p.expando)}function bF(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bG(a){bv.test(a.type)&&(a.defaultChecked=a.checked)}function bY(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=bW.length;while(e--){b=bW[e]+c;if(b in a)return b}return d}function bZ(a,b){return a=b||a,p.css(a,"display")==="none"||!p.contains(a.ownerDocument,a)}function b$(a,b){var c,d,e=[],f=0,g=a.length;for(;f<g;f++){c=a[f];if(!c.style)continue;e[f]=p._data(c,"olddisplay"),b?(!e[f]&&c.style.display==="none"&&(c.style.display=""),c.style.display===""&&bZ(c)&&(e[f]=p._data(c,"olddisplay",cc(c.nodeName)))):(d=bH(c,"display"),!e[f]&&d!=="none"&&p._data(c,"olddisplay",d))}for(f=0;f<g;f++){c=a[f];if(!c.style)continue;if(!b||c.style.display==="none"||c.style.display==="")c.style.display=b?e[f]||"":"none"}return a}function b_(a,b,c){var d=bP.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function ca(a,b,c,d){var e=c===(d?"border":"content")?4:b==="width"?1:0,f=0;for(;e<4;e+=2)c==="margin"&&(f+=p.css(a,c+bV[e],!0)),d?(c==="content"&&(f-=parseFloat(bH(a,"padding"+bV[e]))||0),c!=="margin"&&(f-=parseFloat(bH(a,"border"+bV[e]+"Width"))||0)):(f+=parseFloat(bH(a,"padding"+bV[e]))||0,c!=="padding"&&(f+=parseFloat(bH(a,"border"+bV[e]+"Width"))||0));return f}function cb(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=!0,f=p.support.boxSizing&&p.css(a,"boxSizing")==="border-box";if(d<=0||d==null){d=bH(a,b);if(d<0||d==null)d=a.style[b];if(bQ.test(d))return d;e=f&&(p.support.boxSizingReliable||d===a.style[b]),d=parseFloat(d)||0}return d+ca(a,b,c||(f?"border":"content"),e)+"px"}function cc(a){if(bS[a])return bS[a];var b=p("<"+a+">").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write("<!doctype html><html><body>"),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h<i;h++)d=g[h],f=/^\+/.test(d),f&&(d=d.substr(1)||"*"),e=a[d]=a[d]||[],e[f?"unshift":"push"](c)}}function cA(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h,i=a[f],j=0,k=i?i.length:0,l=a===cv;for(;j<k&&(l||!h);j++)h=i[j](c,d,e),typeof h=="string"&&(!l||g[h]?h=b:(c.dataTypes.unshift(h),h=cA(a,c,d,e,h,g)));return(l||!h)&&!g["*"]&&(h=cA(a,c,d,e,"*",g)),h}function cB(a,c){var d,e,f=p.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((f[d]?a:e||(e={}))[d]=c[d]);e&&p.extend(!0,a,e)}function cC(a,c,d){var e,f,g,h,i=a.contents,j=a.dataTypes,k=a.responseFields;for(f in k)f in d&&(c[k[f]]=d[f]);while(j[0]==="*")j.shift(),e===b&&(e=a.mimeType||c.getResponseHeader("content-type"));if(e)for(f in i)if(i[f]&&i[f].test(e)){j.unshift(f);break}if(j[0]in d)g=j[0];else{for(f in d){if(!j[0]||a.converters[f+" "+j[0]]){g=f;break}h||(h=f)}g=g||h}if(g)return g!==j[0]&&j.unshift(g),d[g]}function cD(a,b){var c,d,e,f,g=a.dataTypes.slice(),h=g[0],i={},j=0;a.dataFilter&&(b=a.dataFilter(b,a.dataType));if(g[1])for(c in a.converters)i[c.toLowerCase()]=a.converters[c];for(;e=g[++j];)if(e!=="*"){if(h!=="*"&&h!==e){c=i[h+" "+e]||i["* "+e];if(!c)for(d in i){f=d.split(" ");if(f[1]===e){c=i[h+" "+f[0]]||i["* "+f[0]];if(c){c===!0?c=i[d]:i[d]!==!0&&(e=f[0],g.splice(j--,0,e));break}}}if(c!==!0)if(c&&a["throws"])b=c(b);else try{b=c(b)}catch(k){return{state:"parsererror",error:c?k:"No conversion from "+h+" to "+e}}}h=e}return{state:"success",data:b}}function cL(){try{return new a.XMLHttpRequest}catch(b){}}function cM(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function cU(){return setTimeout(function(){cN=b},0),cN=p.now()}function cV(a,b){p.each(b,function(b,c){var d=(cT[b]||[]).concat(cT["*"]),e=0,f=d.length;for(;e<f;e++)if(d[e].call(a,b,c))return})}function cW(a,b,c){var d,e=0,f=0,g=cS.length,h=p.Deferred().always(function(){delete i.elem}),i=function(){var b=cN||cU(),c=Math.max(0,j.startTime+j.duration-b),d=1-(c/j.duration||0),e=0,f=j.tweens.length;for(;e<f;e++)j.tweens[e].run(d);return h.notifyWith(a,[j,d,c]),d<1&&f?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:p.extend({},b),opts:p.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:cN||cU(),duration:c.duration,tweens:[],createTween:function(b,c,d){var e=p.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(e),e},stop:function(b){var c=0,d=b?j.tweens.length:0;for(;c<d;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;cX(k,j.opts.specialEasing);for(;e<g;e++){d=cS[e].call(j,a,k,j.opts);if(d)return d}return cV(j,k),p.isFunction(j.opts.start)&&j.opts.start.call(a,j),p.fx.timer(p.extend(i,{anim:j,queue:j.opts.queue,elem:a})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}function cX(a,b){var c,d,e,f,g;for(c in a){d=p.camelCase(c),e=b[d],f=a[c],p.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=p.cssHooks[d];if(g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}}function cY(a,b,c){var d,e,f,g,h,i,j,k,l=this,m=a.style,n={},o=[],q=a.nodeType&&bZ(a);c.queue||(j=p._queueHooks(a,"fx"),j.unqueued==null&&(j.unqueued=0,k=j.empty.fire,j.empty.fire=function(){j.unqueued||k()}),j.unqueued++,l.always(function(){l.always(function(){j.unqueued--,p.queue(a,"fx").length||j.empty.fire()})})),a.nodeType===1&&("height"in b||"width"in b)&&(c.overflow=[m.overflow,m.overflowX,m.overflowY],p.css(a,"display")==="inline"&&p.css(a,"float")==="none"&&(!p.support.inlineBlockNeedsLayout||cc(a.nodeName)==="inline"?m.display="inline-block":m.zoom=1)),c.overflow&&(m.overflow="hidden",p.support.shrinkWrapBlocks||l.done(function(){m.overflow=c.overflow[0],m.overflowX=c.overflow[1],m.overflowY=c.overflow[2]}));for(d in b){f=b[d];if(cP.exec(f)){delete b[d];if(f===(q?"hide":"show"))continue;o.push(d)}}g=o.length;if(g){h=p._data(a,"fxshow")||p._data(a,"fxshow",{}),q?p(a).show():l.done(function(){p(a).hide()}),l.done(function(){var b;p.removeData(a,"fxshow",!0);for(b in n)p.style(a,b,n[b])});for(d=0;d<g;d++)e=o[d],i=l.createTween(e,q?h[e]:0),n[e]=h[e]||p.style(a,e),e in h||(h[e]=i.start,q&&(i.end=i.start,i.start=e==="width"||e==="height"?1:0))}}function cZ(a,b,c,d,e){return new cZ.prototype.init(a,b,c,d,e)}function c$(a,b){var c,d={height:a},e=0;b=b?1:0;for(;e<4;e+=2-b)c=bV[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function da(a){return p.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}var c,d,e=a.document,f=a.location,g=a.navigator,h=a.jQuery,i=a.$,j=Array.prototype.push,k=Array.prototype.slice,l=Array.prototype.indexOf,m=Object.prototype.toString,n=Object.prototype.hasOwnProperty,o=String.prototype.trim,p=function(a,b){return new p.fn.init(a,b,c)},q=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,r=/\S/,s=/\s+/,t=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,u=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.2",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i<j;i++)if((a=arguments[i])!=null)for(c in a){d=h[c],e=a[c];if(h===e)continue;k&&e&&(p.isPlainObject(e)||(f=p.isArray(e)))?(f?(f=!1,g=d&&p.isArray(d)?d:[]):g=d&&p.isPlainObject(d)?d:{},h[c]=p.extend(k,g,e)):e!==b&&(h[c]=e)}return h},p.extend({noConflict:function(b){return a.$===p&&(a.$=i),b&&a.jQuery===p&&(a.jQuery=h),p},isReady:!1,readyWait:1,holdReady:function(a){a?p.readyWait++:p.ready(!0)},ready:function(a){if(a===!0?--p.readyWait:p.isReady)return;if(!e.body)return setTimeout(p.ready,1);p.isReady=!0;if(a!==!0&&--p.readyWait>0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f<g;)if(c.apply(a[f++],d)===!1)break}else if(h){for(e in a)if(c.call(a[e],e,a[e])===!1)break}else for(;f<g;)if(c.call(a[f],f,a[f++])===!1)break;return a},trim:o&&!o.call(" ")?function(a){return a==null?"":o.call(a)}:function(a){return a==null?"":(a+"").replace(t,"")},makeArray:function(a,b){var c,d=b||[];return a!=null&&(c=p.type(a),a.length==null||c==="string"||c==="function"||c==="regexp"||p.isWindow(a)?j.call(d,a):p.merge(d,a)),d},inArray:function(a,b,c){var d;if(b){if(l)return l.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=c.length,e=a.length,f=0;if(typeof d=="number")for(;f<d;f++)a[e++]=c[f];else while(c[f]!==b)a[e++]=c[f++];return a.length=e,a},grep:function(a,b,c){var d,e=[],f=0,g=a.length;c=!!c;for(;f<g;f++)d=!!b(a[f],f),c!==d&&e.push(a[f]);return e},map:function(a,c,d){var e,f,g=[],h=0,i=a.length,j=a instanceof p||i!==b&&typeof i=="number"&&(i>0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h<i;h++)e=c(a[h],h,d),e!=null&&(g[g.length]=e);else for(f in a)e=c(a[f],f,d),e!=null&&(g[g.length]=e);return g.concat.apply([],g)},guid:1,proxy:function(a,c){var d,e,f;return typeof c=="string"&&(d=a[c],c=a,a=d),p.isFunction(a)?(e=k.call(arguments,2),f=function(){return a.apply(c,e.concat(k.call(arguments)))},f.guid=a.guid=a.guid||p.guid++,f):b},access:function(a,c,d,e,f,g,h){var i,j=d==null,k=0,l=a.length;if(d&&typeof d=="object"){for(k in d)p.access(a,c,k,d[k],1,g,e);f=1}else if(e!==b){i=h===b&&p.isFunction(e),j&&(i?(i=c,c=function(a,b,c){return i.call(p(a),c)}):(c.call(a,e),c=null));if(c)for(;k<l;k++)c(a[k],d,i?e.call(a[k],k,c(a[k],d)):e,h);f=1}return f?a:j?c.call(a):l?c(a[0],d):g},now:function(){return(new Date).getTime()}}),p.ready.promise=function(b){if(!d){d=p.Deferred();if(e.readyState==="complete")setTimeout(p.ready,1);else if(e.addEventListener)e.addEventListener("DOMContentLoaded",D,!1),a.addEventListener("load",p.ready,!1);else{e.attachEvent("onreadystatechange",D),a.attachEvent("onload",p.ready);var c=!1;try{c=a.frameElement==null&&e.documentElement}catch(f){}c&&c.doScroll&&function g(){if(!p.isReady){try{c.doScroll("left")}catch(a){return setTimeout(g,50)}p.ready()}}()}}return d.promise(b)},p.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){E["[object "+b+"]"]=b.toLowerCase()}),c=p(e);var F={};p.Callbacks=function(a){a=typeof a=="string"?F[a]||G(a):p.extend({},a);var c,d,e,f,g,h,i=[],j=!a.once&&[],k=function(b){c=a.memory&&b,d=!0,h=f||0,f=0,g=i.length,e=!0;for(;i&&h<g;h++)if(i[h].apply(b[0],b[1])===!1&&a.stopOnFalse){c=!1;break}e=!1,i&&(j?j.length&&k(j.shift()):c?i=[]:l.disable())},l={add:function(){if(i){var b=i.length;(function d(b){p.each(b,function(b,c){var e=p.type(c);e==="function"&&(!a.unique||!l.has(c))?i.push(c):c&&c.length&&e!=="string"&&d(c)})})(arguments),e?g=i.length:c&&(f=b,k(c))}return this},remove:function(){return i&&p.each(arguments,function(a,b){var c;while((c=p.inArray(b,i,c))>-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return a!=null?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b<d;b++)c[b]&&p.isFunction(c[b].promise)?c[b].promise().done(g(b,j,c)).fail(f.reject).progress(g(b,i,h)):--e}return e||f.resolveWith(j,c),f.promise()}}),p.support=function(){var b,c,d,f,g,h,i,j,k,l,m,n=e.createElement("div");n.setAttribute("className","t"),n.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="<div></div>",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||p.guid++:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e<f;e++)delete d[b[e]];if(!(c?K:p.isEmptyObject)(d))return}}if(!c){delete h[i].data;if(!K(h[i]))return}g?p.cleanData([a],!0):p.support.deleteExpando||h!=h.window?delete h[i]:h[i]=null},_data:function(a,b,c){return p.data(a,b,c,!0)},acceptData:function(a){var b=a.nodeName&&p.noData[a.nodeName.toLowerCase()];return!b||b!==!0&&a.getAttribute("classid")===b}}),p.fn.extend({data:function(a,c){var d,e,f,g,h,i=this[0],j=0,k=null;if(a===b){if(this.length){k=p.data(i);if(i.nodeType===1&&!p._data(i,"parsedAttrs")){f=i.attributes;for(h=f.length;j<h;j++)g=f[j].name,g.indexOf("data-")||(g=p.camelCase(g.substring(5)),J(i,g,k[g]));p._data(i,"parsedAttrs",!0)}}return k}return typeof a=="object"?this.each(function(){p.data(this,a)}):(d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!",p.access(this,function(c){if(c===b)return k=this.triggerHandler("getData"+e,[d[0]]),k===b&&i&&(k=p.data(i,a),k=J(i,a,k)),k===b&&d[1]?this.data(d[0]):k;d[1]=c,this.each(function(){var b=p(this);b.triggerHandler("setData"+e,d),p.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length<d?p.queue(this[0],a):c===b?this:this.each(function(){var b=p.queue(this,a,c);p._queueHooks(this,a),a==="fx"&&b[0]!=="inprogress"&&p.dequeue(this,a)})},dequeue:function(a){return this.each(function(){p.dequeue(this,a)})},delay:function(a,b){return a=p.fx?p.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){var d,e=1,f=p.Deferred(),g=this,h=this.length,i=function(){--e||f.resolveWith(g,[g])};typeof a!="string"&&(c=a,a=b),a=a||"fx";while(h--)d=p._data(g[h],a+"queueHooks"),d&&d.empty&&(e++,d.empty.add(i));return i(),f.promise(c)}});var L,M,N,O=/[\t\r\n]/g,P=/\r/g,Q=/^(?:button|input)$/i,R=/^(?:button|input|object|select|textarea)$/i,S=/^a(?:rea|)$/i,T=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,U=p.support.getSetAttribute;p.fn.extend({attr:function(a,b){return p.access(this,p.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{f=" "+e.className+" ";for(g=0,h=b.length;g<h;g++)f.indexOf(" "+b[g]+" ")<0&&(f+=b[g]+" ");e.className=p.trim(f)}}}return this},removeClass:function(a){var c,d,e,f,g,h,i;if(p.isFunction(a))return this.each(function(b){p(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(s);for(h=0,i=this.length;h<i;h++){e=this[h];if(e.nodeType===1&&e.className){d=(" "+e.className+" ").replace(O," ");for(f=0,g=c.length;f<g;f++)while(d.indexOf(" "+c[f]+" ")>=0)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(O," ").indexOf(b)>=0)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c<d;c++){e=h[c];if(e.selected&&(p.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!p.nodeName(e.parentNode,"optgroup"))){b=p(e).val();if(i)return b;g.push(b)}}return i&&!g.length&&h.length?p(h[f]).val():g},set:function(a,b){var c=p.makeArray(b);return p(a).find("option").each(function(){this.selected=p.inArray(p(this).val(),c)>=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,d+""),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g<d.length;g++)e=d[g],e&&(c=p.propFix[e]||e,f=T.test(e),f||p.attr(a,e,""),a.removeAttribute(U?e:c),f&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(Q.test(a.nodeName)&&a.parentNode)p.error("type property can't be changed");else if(!p.support.radioValue&&b==="radio"&&p.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}},value:{get:function(a,b){return L&&p.nodeName(a,"button")?L.get(a,b):b in a?a.value:null},set:function(a,b,c){if(L&&p.nodeName(a,"button"))return L.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,f,g,h=a.nodeType;if(!a||h===3||h===8||h===2)return;return g=h!==1||!p.isXMLDoc(a),g&&(c=p.propFix[c]||c,f=p.propHooks[c]),d!==b?f&&"set"in f&&(e=f.set(a,d,c))!==b?e:a[c]=d:f&&"get"in f&&(e=f.get(a,c))!==null?e:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):R.test(a.nodeName)||S.test(a.nodeName)&&a.href?0:b}}}}),M={get:function(a,c){var d,e=p.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;return b===!1?p.removeAttr(a,c):(d=p.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase())),c}},U||(N={name:!0,id:!0,coords:!0},L=p.valHooks.button={get:function(a,c){var d;return d=a.getAttributeNode(c),d&&(N[c]?d.value!=="":d.specified)?d.value:b},set:function(a,b,c){var d=a.getAttributeNode(c);return d||(d=e.createAttribute(c),a.setAttributeNode(d)),d.value=b+""}},p.each(["width","height"],function(a,b){p.attrHooks[b]=p.extend(p.attrHooks[b],{set:function(a,c){if(c==="")return a.setAttribute(b,"auto"),c}})}),p.attrHooks.contenteditable={get:L.get,set:function(a,b,c){b===""&&(b="false"),L.set(a,b,c)}}),p.support.hrefNormalized||p.each(["href","src","width","height"],function(a,c){p.attrHooks[c]=p.extend(p.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),p.support.style||(p.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=b+""}}),p.support.optSelected||(p.propHooks.selected=p.extend(p.propHooks.selected,{get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}})),p.support.enctype||(p.propFix.enctype="encoding"),p.support.checkOn||p.each(["radio","checkbox"],function(){p.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),p.each(["radio","checkbox"],function(){p.valHooks[this]=p.extend(p.valHooks[this],{set:function(a,b){if(p.isArray(b))return a.checked=p.inArray(p(a).val(),b)>=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j<c.length;j++){k=W.exec(c[j])||[],l=k[1],m=(k[2]||"").split(".").sort(),r=p.event.special[l]||{},l=(f?r.delegateType:r.bindType)||l,r=p.event.special[l]||{},n=p.extend({type:l,origType:k[1],data:e,handler:d,guid:d.guid,selector:f,needsContext:f&&p.expr.match.needsContext.test(f),namespace:m.join(".")},o),q=i[l];if(!q){q=i[l]=[],q.delegateCount=0;if(!r.setup||r.setup.call(a,e,m,h)===!1)a.addEventListener?a.addEventListener(l,h,!1):a.attachEvent&&a.attachEvent("on"+l,h)}r.add&&(r.add.call(a,n),n.handler.guid||(n.handler.guid=d.guid)),f?q.splice(q.delegateCount++,0,n):q.push(n),p.event.global[l]=!0}a=null},global:{},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,q,r=p.hasData(a)&&p._data(a);if(!r||!(m=r.events))return;b=p.trim(_(b||"")).split(" ");for(f=0;f<b.length;f++){g=W.exec(b[f])||[],h=i=g[1],j=g[2];if(!h){for(h in m)p.event.remove(a,h+b[f],c,d,!0);continue}n=p.event.special[h]||{},h=(d?n.delegateType:n.bindType)||h,o=m[h]||[],k=o.length,j=j?new RegExp("(^|\\.)"+j.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(l=0;l<o.length;l++)q=o[l],(e||i===q.origType)&&(!c||c.guid===q.guid)&&(!j||j.test(q.namespace))&&(!d||d===q.selector||d==="**"&&q.selector)&&(o.splice(l--,1),q.selector&&o.delegateCount--,n.remove&&n.remove.call(a,q));o.length===0&&k!==o.length&&((!n.teardown||n.teardown.call(a,j,r.handle)===!1)&&p.removeEvent(a,h,r.handle),delete m[h])}p.isEmptyObject(m)&&(delete r.handle,p.removeData(a,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,f,g){if(!f||f.nodeType!==3&&f.nodeType!==8){var h,i,j,k,l,m,n,o,q,r,s=c.type||c,t=[];if($.test(s+p.event.triggered))return;s.indexOf("!")>=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j<q.length&&!c.isPropagationStopped();j++)k=q[j][0],c.type=q[j][1],o=(p._data(k,"events")||{})[c.type]&&p._data(k,"handle"),o&&o.apply(k,d),o=m&&k[m],o&&p.acceptData(k)&&o.apply&&o.apply(k,d)===!1&&c.preventDefault();return c.type=s,!g&&!c.isDefaultPrevented()&&(!n._default||n._default.apply(f.ownerDocument,d)===!1)&&(s!=="click"||!p.nodeName(f,"a"))&&p.acceptData(f)&&m&&f[s]&&(s!=="focus"&&s!=="blur"||c.target.offsetWidth!==0)&&!p.isWindow(f)&&(l=f[m],l&&(f[m]=null),p.event.triggered=s,f[s](),p.event.triggered=b,l&&(f[m]=l)),c.result}return},dispatch:function(c){c=p.event.fix(c||a.event);var d,e,f,g,h,i,j,l,m,n,o=(p._data(this,"events")||{})[c.type]||[],q=o.delegateCount,r=k.call(arguments),s=!c.exclusive&&!c.namespace,t=p.event.special[c.type]||{},u=[];r[0]=c,c.delegateTarget=this;if(t.preDispatch&&t.preDispatch.call(this,c)===!1)return;if(q&&(!c.button||c.type!=="click"))for(f=c.target;f!=this;f=f.parentNode||this)if(f.disabled!==!0||c.type!=="click"){h={},j=[];for(d=0;d<q;d++)l=o[d],m=l.selector,h[m]===b&&(h[m]=l.needsContext?p(m,this).index(f)>=0:p.find(m,this,null,[f]).length),h[m]&&j.push(l);j.length&&u.push({elem:f,matches:j})}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d<u.length&&!c.isPropagationStopped();d++){i=u[d],c.currentTarget=i.elem;for(e=0;e<i.matches.length&&!c.isImmediatePropagationStopped();e++){l=i.matches[e];if(s||!c.namespace&&!l.namespace||c.namespace_re&&c.namespace_re.test(l.namespace))c.data=l.data,c.handleObj=l,g=((p.event.special[l.origType]||{}).handle||l.handler).apply(i.elem,r),g!==b&&(c.result=g,g===!1&&(c.preventDefault(),c.stopPropagation()))}}return t.postDispatch&&t.postDispatch.call(this,c),c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,c){var d,f,g,h=c.button,i=c.fromElement;return a.pageX==null&&c.clientX!=null&&(d=a.target.ownerDocument||e,f=d.documentElement,g=d.body,a.pageX=c.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=c.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?c.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0),a}},fix:function(a){if(a[p.expando])return a;var b,c,d=a,f=p.event.fixHooks[a.type]||{},g=f.props?this.props.concat(f.props):this.props;a=p.Event(d);for(b=g.length;b;)c=g[--b],a[c]=d[c];return a.target||(a.target=d.srcElement||e),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,f.filter?f.filter(a,d):a},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){p.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=p.extend(new p.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?p.event.trigger(e,null,b):p.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},p.event.handle=p.event.dispatch,p.removeEvent=e.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]=="undefined"&&(a[d]=null),a.detachEvent(d,c))},p.Event=function(a,b){if(this instanceof p.Event)a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?bb:ba):this.type=a,b&&p.extend(this,b),this.timeStamp=a&&a.timeStamp||p.now(),this[p.expando]=!0;else return new p.Event(a,b)},p.Event.prototype={preventDefault:function(){this.isDefaultPrevented=bb;var a=this.originalEvent;if(!a)return;a.preventDefault?a.preventDefault():a.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=bb;var a=this.originalEvent;if(!a)return;a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=bb,this.stopPropagation()},isDefaultPrevented:ba,isPropagationStopped:ba,isImmediatePropagationStopped:ba},p.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){p.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj,g=f.selector;if(!e||e!==d&&!p.contains(d,e))a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b;return c}}}),p.support.submitBubbles||(p.event.special.submit={setup:function(){if(p.nodeName(this,"form"))return!1;p.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=p.nodeName(c,"input")||p.nodeName(c,"button")?c.form:b;d&&!p._data(d,"_submit_attached")&&(p.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),p._data(d,"_submit_attached",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&p.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(p.nodeName(this,"form"))return!1;p.event.remove(this,"._submit")}}),p.support.changeBubbles||(p.event.special.change={setup:function(){if(V.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")p.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),p.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),p.event.simulate("change",this,a,!0)});return!1}p.event.add(this,"beforeactivate._change",function(a){var b=a.target;V.test(b.nodeName)&&!p._data(b,"_change_attached")&&(p.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&p.event.simulate("change",this.parentNode,a,!0)}),p._data(b,"_change_attached",!0))})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){return p.event.remove(this,"._change"),!V.test(this.nodeName)}}),p.support.focusinBubbles||p.each({focus:"focusin",blur:"focusout"},function(a,b){var c=0,d=function(a){p.event.simulate(b,a.target,p.event.fix(a),!0)};p.event.special[b]={setup:function(){c++===0&&e.addEventListener(a,d,!0)},teardown:function(){--c===0&&e.removeEventListener(a,d,!0)}}}),p.fn.extend({on:function(a,c,d,e,f){var g,h;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(h in a)this.on(h,c,d,a[h],f);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=ba;else if(!e)return this;return f===1&&(g=e,e=function(a){return p().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=p.guid++)),this.each(function(){p.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){var e,f;if(a&&a.preventDefault&&a.handleObj)return e=a.handleObj,p(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler),this;if(typeof a=="object"){for(f in a)this.off(f,c,a[f]);return this}if(c===!1||typeof c=="function")d=c,c=b;return d===!1&&(d=ba),this.each(function(){p.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){return p(this.context).on(a,this.selector,b,c),this},die:function(a,b){return p(this.context).off(a,this.selector||"**",b),this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length===1?this.off(a,"**"):this.off(b,a||"**",c)},trigger:function(a,b){return this.each(function(){p.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return p.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||p.guid++,d=0,e=function(c){var e=(p._data(this,"lastToggle"+a.guid)||0)%d;return p._data(this,"lastToggle"+a.guid,e+1),c.preventDefault(),b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),p.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){p.fn[b]=function(a,c){return c==null&&(c=a,a=null),arguments.length>0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bc(a,b,c,d){c=c||[],b=b||r;var e,f,i,j,k=b.nodeType;if(!a||typeof a!="string")return c;if(k!==1&&k!==9)return[];i=g(b);if(!i&&!d)if(e=P.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&h(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return w.apply(c,x.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&_&&b.getElementsByClassName)return w.apply(c,x.call(b.getElementsByClassName(j),0)),c}return bp(a.replace(L,"$1"),b,c,d,i)}function bd(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function be(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bf(a){return z(function(b){return b=+b,z(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function bg(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bh(a,b){var c,d,f,g,h,i,j,k=C[o][a];if(k)return b?0:k.slice(0);h=a,i=[],j=e.preFilter;while(h){if(!c||(d=M.exec(h)))d&&(h=h.slice(d[0].length)),i.push(f=[]);c=!1;if(d=N.exec(h))f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=d[0].replace(L," ");for(g in e.filter)(d=W[g].exec(h))&&(!j[g]||(d=j[g](d,r,!0)))&&(f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=g,c.matches=d);if(!c)break}return b?h.length:h?bc.error(a):C(a,i).slice(0)}function bi(a,b,d){var e=b.dir,f=d&&b.dir==="parentNode",g=u++;return b.first?function(b,c,d){while(b=b[e])if(f||b.nodeType===1)return a(b,c,d)}:function(b,d,h){if(!h){var i,j=t+" "+g+" ",k=j+c;while(b=b[e])if(f||b.nodeType===1){if((i=b[o])===k)return b.sizset;if(typeof i=="string"&&i.indexOf(j)===0){if(b.sizset)return b}else{b[o]=k;if(a(b,d,h))return b.sizset=!0,b;b.sizset=!1}}}else while(b=b[e])if(f||b.nodeType===1)if(a(b,d,h))return b}}function bj(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function bk(a,b,c,d,e){var f,g=[],h=0,i=a.length,j=b!=null;for(;h<i;h++)if(f=a[h])if(!c||c(f,d,e))g.push(f),j&&b.push(h);return g}function bl(a,b,c,d,e,f){return d&&!d[o]&&(d=bl(d)),e&&!e[o]&&(e=bl(e,f)),z(function(f,g,h,i){if(f&&e)return;var j,k,l,m=[],n=[],o=g.length,p=f||bo(b||"*",h.nodeType?[h]:h,[],f),q=a&&(f||!b)?bk(p,m,a,h,i):p,r=c?e||(f?a:o||d)?[]:g:q;c&&c(q,r,h,i);if(d){l=bk(r,n),d(l,[],h,i),j=l.length;while(j--)if(k=l[j])r[n[j]]=!(q[n[j]]=k)}if(f){j=a&&r.length;while(j--)if(k=r[j])f[m[j]]=!(g[m[j]]=k)}else r=bk(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):w.apply(g,r)})}function bm(a){var b,c,d,f=a.length,g=e.relative[a[0].type],h=g||e.relative[" "],i=g?1:0,j=bi(function(a){return a===b},h,!0),k=bi(function(a){return y.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==l)||((b=c).nodeType?j(a,c,d):k(a,c,d))}];for(;i<f;i++)if(c=e.relative[a[i].type])m=[bi(bj(m),c)];else{c=e.filter[a[i].type].apply(null,a[i].matches);if(c[o]){d=++i;for(;d<f;d++)if(e.relative[a[d].type])break;return bl(i>1&&bj(m),i>1&&a.slice(0,i-1).join("").replace(L,"$1"),c,i<d&&bm(a.slice(i,d)),d<f&&bm(a=a.slice(d)),d<f&&a.join(""))}m.push(c)}return bj(m)}function bn(a,b){var d=b.length>0,f=a.length>0,g=function(h,i,j,k,m){var n,o,p,q=[],s=0,u="0",x=h&&[],y=m!=null,z=l,A=h||f&&e.find.TAG("*",m&&i.parentNode||i),B=t+=z==null?1:Math.E;y&&(l=i!==r&&i,c=g.el);for(;(n=A[u])!=null;u++){if(f&&n){for(o=0;p=a[o];o++)if(p(n,i,j)){k.push(n);break}y&&(t=B,c=++g.el)}d&&((n=!p&&n)&&s--,h&&x.push(n))}s+=u;if(d&&u!==s){for(o=0;p=b[o];o++)p(x,q,i,j);if(h){if(s>0)while(u--)!x[u]&&!q[u]&&(q[u]=v.call(k));q=bk(q)}w.apply(k,q),y&&!h&&q.length>0&&s+b.length>1&&bc.uniqueSort(k)}return y&&(t=B,l=z),x};return g.el=0,d?z(g):g}function bo(a,b,c,d){var e=0,f=b.length;for(;e<f;e++)bc(a,b[e],c,d);return c}function bp(a,b,c,d,f){var g,h,j,k,l,m=bh(a),n=m.length;if(!d&&m.length===1){h=m[0]=m[0].slice(0);if(h.length>2&&(j=h[0]).type==="ID"&&b.nodeType===9&&!f&&e.relative[h[1].type]){b=e.find.ID(j.matches[0].replace(V,""),b,f)[0];if(!b)return c;a=a.slice(h.shift().length)}for(g=W.POS.test(a)?-1:h.length-1;g>=0;g--){j=h[g];if(e.relative[k=j.type])break;if(l=e.find[k])if(d=l(j.matches[0].replace(V,""),R.test(h[0].type)&&b.parentNode||b,f)){h.splice(g,1),a=d.length&&h.join("");if(!a)return w.apply(c,x.call(d,0)),c;break}}}return i(a,m)(d,b,f,c,R.test(a)),c}function bq(){}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=String,r=a.document,s=r.documentElement,t=0,u=0,v=[].pop,w=[].push,x=[].slice,y=[].indexOf||function(a){var b=0,c=this.length;for(;b<c;b++)if(this[b]===a)return b;return-1},z=function(a,b){return a[o]=b==null||b,a},A=function(){var a={},b=[];return z(function(c,d){return b.push(c)>e.cacheLength&&delete a[b.shift()],a[c]=d},a)},B=A(),C=A(),D=A(),E="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",G=F.replace("w","w#"),H="([*^$|!~]?=)",I="\\["+E+"*("+F+")"+E+"*(?:"+H+E+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+G+")|)|)"+E+"*\\]",J=":("+F+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+I+")|[^:]|\\\\.)*|.*))\\)|)",K=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)",L=new RegExp("^"+E+"+|((?:^|[^\\\\])(?:\\\\.)*)"+E+"+$","g"),M=new RegExp("^"+E+"*,"+E+"*"),N=new RegExp("^"+E+"*([\\x20\\t\\r\\n\\f>+~])"+E+"*"),O=new RegExp(J),P=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,Q=/^:not/,R=/[\x20\t\r\n\f]*[+~]/,S=/:not\($/,T=/h\d/i,U=/input|select|textarea|button/i,V=/\\(?!\\)/g,W={ID:new RegExp("^#("+F+")"),CLASS:new RegExp("^\\.("+F+")"),NAME:new RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:new RegExp("^("+F.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+J),POS:new RegExp(K,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i"),needsContext:new RegExp("^"+E+"*[>+~]|"+K,"i")},X=function(a){var b=r.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},Y=X(function(a){return a.appendChild(r.createComment("")),!a.getElementsByTagName("*").length}),Z=X(function(a){return a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),$=X(function(a){a.innerHTML="<select></select>";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),_=X(function(a){return a.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),ba=X(function(a){a.id=o+0,a.innerHTML="<a name='"+o+"'></a><div name='"+o+"'></div>",s.insertBefore(a,s.firstChild);var b=r.getElementsByName&&r.getElementsByName(o).length===2+r.getElementsByName(o+0).length;return d=!r.getElementById(o),s.removeChild(a),b});try{x.call(s.childNodes,0)[0].nodeType}catch(bb){x=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}bc.matches=function(a,b){return bc(a,null,null,b)},bc.matchesSelector=function(a,b){return bc(b,null,null,[a]).length>0},f=bc.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=f(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=f(b);return c},g=bc.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},h=bc.contains=s.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:s.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc.attr=function(a,b){var c,d=g(a);return d||(b=b.toLowerCase()),(c=e.attrHandle[b])?c(a):d||$?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},e=bc.selectors={cacheLength:50,createPseudo:z,match:W,attrHandle:Z?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:d?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:Y?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:ba&&function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:_&&function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(V,""),a[3]=(a[4]||a[5]||"").replace(V,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||bc.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&bc.error(a[0]),a},PSEUDO:function(a){var b,c;if(W.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(b=a[4])O.test(b)&&(c=bh(b,!0))&&(c=b.indexOf(")",b.length-c)-b.length)&&(b=b.slice(0,c),a[0]=a[0].slice(0,c)),a[2]=b;return a.slice(0,3)}},filter:{ID:d?function(a){return a=a.replace(V,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(V,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(V,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=B[o][a];return b||(b=B(a,new RegExp("(^|"+E+")"+a+"("+E+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return function(d,e){var f=bc.attr(d,a);return f==null?b==="!=":b?(f+="",b==="="?f===c:b==="!="?f!==c:b==="^="?c&&f.indexOf(c)===0:b==="*="?c&&f.indexOf(c)>-1:b==="$="?c&&f.substr(f.length-c.length)===c:b==="~="?(" "+f+" ").indexOf(c)>-1:b==="|="?f===c||f.substr(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d){return a==="nth"?function(a){var b,e,f=a.parentNode;if(c===1&&d===0)return!0;if(f){e=0;for(b=f.firstChild;b;b=b.nextSibling)if(b.nodeType===1){e++;if(a===b)break}}return e-=d,e===c||e%c===0&&e/c>=0}:function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b){var c,d=e.pseudos[a]||e.setFilters[a.toLowerCase()]||bc.error("unsupported pseudo: "+a);return d[o]?d(b):d.length>1?(c=[a,a,"",b],e.setFilters.hasOwnProperty(a.toLowerCase())?z(function(a,c){var e,f=d(a,b),g=f.length;while(g--)e=y.call(a,f[g]),a[e]=!(c[e]=f[g])}):function(a){return d(a,0,c)}):d}},pseudos:{not:z(function(a){var b=[],c=[],d=i(a.replace(L,"$1"));return d[o]?z(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)if(f=g[h])a[h]=!(b[h]=f)}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:z(function(a){return function(b){return bc(a,b).length>0}}),contains:z(function(a){return function(b){return(b.textContent||b.innerText||f(b)).indexOf(a)>-1}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!e.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},header:function(a){return T.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:bd("radio"),checkbox:bd("checkbox"),file:bd("file"),password:bd("password"),image:bd("image"),submit:be("submit"),reset:be("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return U.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement},first:bf(function(a,b,c){return[0]}),last:bf(function(a,b,c){return[b-1]}),eq:bf(function(a,b,c){return[c<0?c+b:c]}),even:bf(function(a,b,c){for(var d=0;d<b;d+=2)a.push(d);return a}),odd:bf(function(a,b,c){for(var d=1;d<b;d+=2)a.push(d);return a}),lt:bf(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:bf(function(a,b,c){for(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},j=s.compareDocumentPosition?function(a,b){return a===b?(k=!0,0):(!a.compareDocumentPosition||!b.compareDocumentPosition?a.compareDocumentPosition:a.compareDocumentPosition(b)&4)?-1:1}:function(a,b){if(a===b)return k=!0,0;if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,h=b.parentNode,i=g;if(g===h)return bg(a,b);if(!g)return-1;if(!h)return 1;while(i)e.unshift(i),i=i.parentNode;i=h;while(i)f.unshift(i),i=i.parentNode;c=e.length,d=f.length;for(var j=0;j<c&&j<d;j++)if(e[j]!==f[j])return bg(e[j],f[j]);return j===c?bg(a,f[j],-1):bg(e[j],b,1)},[0,0].sort(j),m=!k,bc.uniqueSort=function(a){var b,c=1;k=m,a.sort(j);if(k)for(;b=a[c];c++)b===a[c-1]&&a.splice(c--,1);return a},bc.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},i=bc.compile=function(a,b){var c,d=[],e=[],f=D[o][a];if(!f){b||(b=bh(a)),c=b.length;while(c--)f=bm(b[c]),f[o]?d.push(f):e.push(f);f=D(a,bn(e,d))}return f},r.querySelectorAll&&function(){var a,b=bp,c=/'|\\/g,d=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,e=[":focus"],f=[":active",":focus"],h=s.matchesSelector||s.mozMatchesSelector||s.webkitMatchesSelector||s.oMatchesSelector||s.msMatchesSelector;X(function(a){a.innerHTML="<select><option selected=''></option></select>",a.querySelectorAll("[selected]").length||e.push("\\["+E+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),X(function(a){a.innerHTML="<p test=''></p>",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+E+"*(?:\"\"|'')"),a.innerHTML="<input type='hidden'/>",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=new RegExp(e.join("|")),bp=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a))){var i,j,k=!0,l=o,m=d,n=d.nodeType===9&&a;if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){i=bh(a),(k=d.getAttribute("id"))?l=k.replace(c,"\\$&"):d.setAttribute("id",l),l="[id='"+l+"'] ",j=i.length;while(j--)i[j]=l+i[j].join("");m=R.test(a)&&d.parentNode||d,n=i.join(",")}if(n)try{return w.apply(f,x.call(m.querySelectorAll(n),0)),f}catch(p){}finally{k||d.removeAttribute("id")}}return b(a,d,f,g,h)},h&&(X(function(b){a=h.call(b,"div");try{h.call(b,"[test!='']:sizzle"),f.push("!=",J)}catch(c){}}),f=new RegExp(f.join("|")),bc.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!g(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=h.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return bc(c,null,null,[b]).length>0})}(),e.pseudos.nth=e.pseudos.eq,e.filters=bq.prototype=e.pseudos,e.setFilters=new bq,bc.attr=p.attr,p.find=bc,p.expr=bc.selectors,p.expr[":"]=p.expr.pseudos,p.unique=bc.uniqueSort,p.text=bc.getText,p.isXMLDoc=bc.isXML,p.contains=bc.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b<c;b++)if(p.contains(h[b],this))return!0});g=this.pushStack("","find",a);for(b=0,c=this.length;b<c;b++){d=g.length,p.find(a,this[b],g);if(b>0)for(e=d;e<g.length;e++)for(f=0;f<d;f++)if(g[f]===g[e]){g.splice(e--,1);break}}return g},has:function(a){var b,c=p(a,this),d=c.length;return this.filter(function(){for(b=0;b<d;b++)if(p.contains(this,c[b]))return!0})},not:function(a){return this.pushStack(bj(this,a,!1),"not",a)},filter:function(a){return this.pushStack(bj(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?bf.test(a)?p(a,this.context).index(this[0])>=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d<e;d++){c=this[d];while(c&&c.ownerDocument&&c!==b&&c.nodeType!==11){if(g?g.index(c)>-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/<tbody/i,br=/<|&#?\w+;/,bs=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,bu=new RegExp("<(?:"+bl+")[\\s/>]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,bz={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X<div>","</div>"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(f){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){return bh(this[0])?this.length?this.pushStack(p(p.isFunction(a)?a():a),"replaceWith",a):this:p.isFunction(a)?this.each(function(b){var c=p(this),d=c.html();c.replaceWith(a.call(this,b,d))}):(typeof a!="string"&&(a=p(a).detach()),this.each(function(){var b=this.nextSibling,c=this.parentNode;p(this).remove(),b?p(b).before(a):p(c).append(a)}))},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){a=[].concat.apply([],a);var e,f,g,h,i=0,j=a[0],k=[],l=this.length;if(!p.support.checkClone&&l>1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i<l;i++)d.call(c&&p.nodeName(this[i],"table")?bC(this[i],"tbody"):this[i],i===h?g:p.clone(g,!0,!0))}g=f=null,k.length&&p.each(k,function(a,b){b.src?p.ajax?p.ajax({url:b.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):p.error("no ajax"):p.globalEval((b.text||b.textContent||b.innerHTML||"").replace(by,"")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),p.buildFragment=function(a,c,d){var f,g,h,i=a[0];return c=c||e,c=!c.nodeType&&c[0]||c,c=c.ownerDocument||c,a.length===1&&typeof i=="string"&&i.length<512&&c===e&&i.charAt(0)==="<"&&!bt.test(i)&&(p.support.checkClone||!bw.test(i))&&(p.support.html5Clone||!bu.test(i))&&(g=!0,f=p.fragments[i],h=f!==b),f||(f=c.createDocumentFragment(),p.clean(a,c,f,d),g&&(p.fragments[i]=h&&f)),{fragment:f,cacheable:g}},p.fragments={},p.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){p.fn[a]=function(c){var d,e=0,f=[],g=p(c),h=g.length,i=this.length===1&&this[0].parentNode;if((i==null||i&&i.nodeType===11&&i.childNodes.length===1)&&h===1)return g[b](this[0]),this;for(;e<h;e++)d=(e>0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1></$2>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]==="<table>"&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{ck=f.href}catch(cy){ck=e.createElement("a"),ck.href="",ck=ck.href}cj=ct.exec(ck.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("<div>").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:ck,isLocal:cn.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=(c||y)+"",k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase())||!1,l.crossDomain=i&&i.join(":")+(i[3]?"":i[1]==="http:"?80:443)!==cj.join(":")+(cj[3]?"":cj[1]==="http:"?80:443)),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e=this.createTween(a,b),f=cQ.exec(b),g=e.cur(),h=+g||0,i=1,j=20;if(f){c=+f[2],d=f[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&h){h=p.css(e.elem,a,!0)||c||1;do i=i||".5",h=h/i,p.style(e.elem,a,h+d);while(i!==(i=e.cur()/g)&&i!==1&&--j)}e.unit=d,e.start=h,e.end=f[1]?h+(f[1]+1)*c:c}return e}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d<e;d++)c=a[d],cT[c]=cT[c]||[],cT[c].unshift(b)},prefilter:function(a,b){b?cS.unshift(a):cS.push(a)}}),p.Tween=cZ,cZ.prototype={constructor:cZ,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(p.cssNumber[c]?"":"px")},cur:function(){var a=cZ.propHooks[this.prop];return a&&a.get?a.get(this):cZ.propHooks._default.get(this)},run:function(a){var b,c=cZ.propHooks[this.prop];return this.options.duration?this.pos=b=p.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):cZ.propHooks._default.set(this),this}},cZ.prototype.init.prototype=cZ.prototype,cZ.propHooks={_default:{get:function(a){var b;return a.elem[a.prop]==null||!!a.elem.style&&a.elem.style[a.prop]!=null?(b=p.css(a.elem,a.prop,!1,""),!b||b==="auto"?0:b):a.elem[a.prop]},set:function(a){p.fx.step[a.prop]?p.fx.step[a.prop](a):a.elem.style&&(a.elem.style[p.cssProps[a.prop]]!=null||p.cssHooks[a.prop])?p.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},cZ.propHooks.scrollTop=cZ.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},p.each(["toggle","show","hide"],function(a,b){var c=p.fn[b];p.fn[b]=function(d,e,f){return d==null||typeof d=="boolean"||!a&&p.isFunction(d)&&p.isFunction(e)?c.apply(this,arguments):this.animate(c$(b,!0),d,e,f)}}),p.fn.extend({fadeTo:function(a,b,c,d){return this.filter(bZ).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=p.isEmptyObject(a),f=p.speed(b,c,d),g=function(){var b=cW(this,p.extend({},a),f);e&&b.stop(!0)};return e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,c,d){var e=function(a){var b=a.stop;delete a.stop,b(d)};return typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,c=a!=null&&a+"queueHooks",f=p.timers,g=p._data(this);if(c)g[c]&&g[c].stop&&e(g[c]);else for(c in g)g[c]&&g[c].stop&&cR.test(c)&&e(g[c]);for(c=f.length;c--;)f[c].elem===this&&(a==null||f[c].queue===a)&&(f[c].anim.stop(d),b=!1,f.splice(c,1));(b||!d)&&p.dequeue(this,a)})}}),p.each({slideDown:c$("show"),slideUp:c$("hide"),slideToggle:c$("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){p.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),p.speed=function(a,b,c){var d=a&&typeof a=="object"?p.extend({},a):{complete:c||!c&&b||p.isFunction(a)&&a,duration:a,easing:c&&b||b&&!p.isFunction(b)&&b};d.duration=p.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in p.fx.speeds?p.fx.speeds[d.duration]:p.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";return d.old=d.complete,d.complete=function(){p.isFunction(d.old)&&d.old.call(this),d.queue&&p.dequeue(this,d.queue)},d},p.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},p.timers=[],p.fx=cZ.prototype.init,p.fx.tick=function(){var a,b=p.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||p.fx.stop()},p.fx.timer=function(a){a()&&p.timers.push(a)&&!cO&&(cO=setInterval(p.fx.tick,p.fx.interval))},p.fx.interval=13,p.fx.stop=function(){clearInterval(cO),cO=null},p.fx.speeds={slow:600,fast:200,_default:400},p.fx.step={},p.expr&&p.expr.filters&&(p.expr.filters.animated=function(a){return p.grep(p.timers,function(b){return a===b.elem}).length});var c_=/^(?:body|html)$/i;p.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){p.offset.setOffset(this,a,b)});var c,d,e,f,g,h,i,j={top:0,left:0},k=this[0],l=k&&k.ownerDocument;if(!l)return;return(d=l.body)===k?p.offset.bodyOffset(k):(c=l.documentElement,p.contains(c,k)?(typeof k.getBoundingClientRect!="undefined"&&(j=k.getBoundingClientRect()),e=da(l),f=c.clientTop||d.clientTop||0,g=c.clientLeft||d.clientLeft||0,h=e.pageYOffset||c.scrollTop,i=e.pageXOffset||c.scrollLeft,{top:j.top+h-f,left:j.left+i-g}):j)},p.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;return p.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(p.css(a,"marginTop"))||0,c+=parseFloat(p.css(a,"marginLeft"))||0),{top:b,left:c}},setOffset:function(a,b,c){var d=p.css(a,"position");d==="static"&&(a.style.position="relative");var e=p(a),f=e.offset(),g=p.css(a,"top"),h=p.css(a,"left"),i=(d==="absolute"||d==="fixed")&&p.inArray("auto",[g,h])>-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window);
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery-ui-1.8.4.custom.min.js b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery-ui-1.8.4.custom.min.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery-ui-1.8.4.custom.min.js
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.client.js b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.client.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.client.js
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.flot.dashes.js b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.flot.dashes.js
new file mode 100644
index 0000000..7cebc92
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.flot.dashes.js
@@ -0,0 +1,237 @@
+/*
+ * jQuery.flot.dashes
+ * 
+ * options = {
+ *   series: {
+ *     dashes: {
+ *       
+ *       // show
+ *       // default: false
+ *       // Whether to show dashes for the series.
+ *       show: <boolean>,
+ *       
+ *       // lineWidth
+ *       // default: 2
+ *       // The width of the dashed line in pixels.
+ *       lineWidth: <number>,
+ *       
+ *       // dashLength
+ *       // default: 10
+ *       // Controls the length of the individual dashes and the amount of 
+ *       // space between them.
+ *       // If this is a number, the dashes and spaces will have that length.
+ *       // If this is an array, it is read as [ dashLength, spaceLength ]
+ *       dashLength: <number> or <array[2]>
+ *     }
+ *   }
+ * }
+ */
+(function($){
+  
+  function init(plot) {
+    
+    plot.hooks.processDatapoints.push(function(plot, series, datapoints) {
+      
+      if (!series.dashes.show) return;
+      
+      plot.hooks.draw.push(function(plot, ctx) {
+        
+        var plotOffset = plot.getPlotOffset(), 
+            axisx = series.xaxis,
+            axisy = series.yaxis;
+        
+        function plotDashes(xoffset, yoffset) {
+          
+          var points = datapoints.points,
+              ps = datapoints.pointsize,
+              prevx = null, 
+              prevy = null,
+              dashRemainder = 0, 
+              dashOn = true,
+              dashOnLength,
+              dashOffLength;
+          
+          if (series.dashes.dashLength[0]) {
+            dashOnLength = series.dashes.dashLength[0];
+            if (series.dashes.dashLength[1]) {
+              dashOffLength = series.dashes.dashLength[1];
+            } else {
+              dashOffLength = dashOnLength;
+            }
+          } else {
+            dashOffLength = dashOnLength = series.dashes.dashLength;
+          }
+          
+          ctx.beginPath();
+          
+          for (var i = ps; i < points.length; i += ps) {
+            
+            var x1 = points[i - ps], 
+                y1 = points[i - ps + 1],
+                x2 = points[i], 
+                y2 = points[i + 1];
+            
+            if (x1 == null || x2 == null) continue;
+
+            // clip with ymin
+            if (y1 <= y2 && y1 < axisy.min) {
+              if (y2 < axisy.min) continue;   // line segment is outside
+              // compute new intersection point
+              x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+              y1 = axisy.min;
+            } else if (y2 <= y1 && y2 < axisy.min) {
+              if (y1 < axisy.min) continue;
+              x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+              y2 = axisy.min;
+            }
+
+            // clip with ymax
+            if (y1 >= y2 && y1 > axisy.max) {
+              if (y2 > axisy.max) continue;
+              x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+              y1 = axisy.max;
+            } else if (y2 >= y1 && y2 > axisy.max) {
+              if (y1 > axisy.max) continue;
+              x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+              y2 = axisy.max;
+            }
+
+            // clip with xmin
+            if (x1 <= x2 && x1 < axisx.min) {
+              if (x2 < axisx.min) continue;
+              y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+              x1 = axisx.min;
+            } else if (x2 <= x1 && x2 < axisx.min) {
+              if (x1 < axisx.min) continue;
+              y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+              x2 = axisx.min;
+            }
+
+            // clip with xmax
+            if (x1 >= x2 && x1 > axisx.max) {
+              if (x2 > axisx.max) continue;
+              y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+              x1 = axisx.max;
+            } else if (x2 >= x1 && x2 > axisx.max) {
+              if (x1 > axisx.max) continue;
+              y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+              x2 = axisx.max;
+            }
+
+            if (x1 != prevx || y1 != prevy) {
+              ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
+            }
+            
+            var ax1 = axisx.p2c(x1) + xoffset,
+                ay1 = axisy.p2c(y1) + yoffset,
+                ax2 = axisx.p2c(x2) + xoffset,
+                ay2 = axisy.p2c(y2) + yoffset,
+                dashOffset;
+            
+            function lineSegmentOffset(segmentLength) {
+              
+              var c = Math.sqrt(Math.pow(ax2 - ax1, 2) + Math.pow(ay2 - ay1, 2));
+              
+              if (c <= segmentLength) {
+                return {
+                  deltaX: ax2 - ax1,
+                  deltaY: ay2 - ay1,
+                  distance: c,
+                  remainder: segmentLength - c
+                }
+              } else {
+                var xsign = ax2 > ax1 ? 1 : -1,
+                    ysign = ay2 > ay1 ? 1 : -1;
+                return {
+                  deltaX: xsign * Math.sqrt(Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),
+                  deltaY: ysign * Math.sqrt(Math.pow(segmentLength, 2) - Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),
+                  distance: segmentLength,
+                  remainder: 0
+                };
+              }
+            }
+            //-end lineSegmentOffset
+            
+            do {
+              
+              dashOffset = lineSegmentOffset(
+                  dashRemainder > 0 ? dashRemainder : 
+                    dashOn ? dashOnLength : dashOffLength);
+              
+              if (dashOffset.deltaX != 0 || dashOffset.deltaY != 0) {
+                if (dashOn) {
+                  ctx.lineTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);
+                } else {
+                  ctx.moveTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);
+                }
+              }
+              
+              dashOn = !dashOn;
+              dashRemainder = dashOffset.remainder;
+              ax1 += dashOffset.deltaX;
+              ay1 += dashOffset.deltaY;
+              
+            } while (dashOffset.distance > 0);
+            
+            prevx = x2;
+            prevy = y2;
+          }
+          
+          ctx.stroke();
+        }
+        //-end plotDashes
+        
+        ctx.save();
+        ctx.translate(plotOffset.left, plotOffset.top);
+        ctx.lineJoin = 'round';
+        
+        var lw = series.dashes.lineWidth,
+            sw = series.shadowSize;
+        
+        // FIXME: consider another form of shadow when filling is turned on
+        if (lw > 0 && sw > 0) {
+          // draw shadow as a thick and thin line with transparency
+          ctx.lineWidth = sw;
+          ctx.strokeStyle = "rgba(0,0,0,0.1)";
+          // position shadow at angle from the mid of line
+          var angle = Math.PI/18;
+          plotDashes(Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2));
+          ctx.lineWidth = sw/2;
+          plotDashes(Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4));
+        }
+
+        ctx.lineWidth = lw;
+        ctx.strokeStyle = series.color;
+
+        if (lw > 0) {
+          plotDashes(0, 0);
+        }
+        
+        ctx.restore();
+        
+      });
+      //-end draw hook
+      
+    });
+    //-end processDatapoints hook
+    
+  }
+  //-end init
+
+  $.plot.plugins.push({
+    init: init,
+    options: {
+      series: {
+        dashes: {
+          show: false,
+          lineWidth: 2,
+          dashLength: 10
+        }
+      }
+    },
+    name: 'dashes',
+    version: '0.1'
+  });
+
+})(jQuery)
+
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.flot.js b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.flot.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.flot.js
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.flot.min.js b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.flot.min.js
new file mode 100644
index 0000000..31f465b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.flot.min.js
@@ -0,0 +1 @@
+(function(){jQuery.color={};jQuery.color.make=function(G,H,J,I){var A={};A.r=G||0;A.g=H||0;A.b=J||0;A.a=I!=null?I:1;A.add=function(C,D){for(var E=0;E<C.length;++E){A[C.charAt(E)]+=D}return A.normalize()};A.scale=function(C,D){for(var E=0;E<C.length;++E){A[C.charAt(E)]*=D}return A.normalize()};A.toString=function(){if(A.a>=1){return"rgb("+[A.r,A.g,A.b].join(",")+")"}else{return"rgba("+[A.r,A.g,A.b,A.a].join(",")+")"}};A.normalize=function(){function C(E,D,F){return D<E?E:(D>F?F:D)}A.r=C(0,parseInt(A.r),255);A.g=C(0,parseInt(A.g),255);A.b=C(0,parseInt(A.b),255);A.a=C(0,A.a,1);return A};A.clone=function(){return jQuery.color.make(A.r,A.b,A.g,A.a)};return A.normalize()};jQuery.color.extract=function(E,F){var A;do{A=E.css(F).toLowerCase();if(A!=""&&A!="transparent"){break}E=E.parent()}while(!jQuery.nodeName(E.get(0),"body"));if(A=="rgba(0, 0, 0, 0)"){A="transparent"}return jQuery.color.parse(A)};jQuery.color.parse=function(A){var F,H=jQuery.color.make;if(F=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(A)){return H(parseInt(F[1],10),parseInt(F[2],10),parseInt(F[3],10))}if(F=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(A)){return H(parseInt(F[1],10),parseInt(F[2],10),parseInt(F[3],10),parseFloat(F[4]))}if(F=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(A)){return H(parseFloat(F[1])*2.55,parseFloat(F[2])*2.55,parseFloat(F[3])*2.55)}if(F=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(A)){return H(parseFloat(F[1])*2.55,parseFloat(F[2])*2.55,parseFloat(F[3])*2.55,parseFloat(F[4]))}if(F=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(A)){return H(parseInt(F[1],16),parseInt(F[2],16),parseInt(F[3],16))}if(F=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(A)){return H(parseInt(F[1]+F[1],16),parseInt(F[2]+F[2],16),parseInt(F[3]+F[3],16))}var G=jQuery.trim(A).toLowerCase();if(G=="transparent"){return H(255,255,255,0)}else{F=B[G];return H(F[0],F[1],F[2])}};var B={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})();(function(C){function B(l,W,X,E){var O=[],g={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:true,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:0.85},xaxis:{mode:null,transform:null,inverseTransform:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,tickDecimals:null,tickSize:null,minTickSize:null,monthNames:null,timeformat:null,twelveHourClock:false},yaxis:{autoscaleMargin:0.02},x2axis:{autoscaleMargin:null},y2axis:{autoscaleMargin:0.02},series:{points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#ffffff"},lines:{lineWidth:2,fill:false,fillColor:null,steps:false},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,align:"left",horizontal:false},shadowSize:3},grid:{show:true,aboveData:false,color:"#545454",backgroundColor:null,tickColor:"rgba(0,0,0,0.15)",labelMargin:5,borderWidth:2,borderColor:null,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:false,hoverable:false,autoHighlight:true,mouseActiveRadius:10},hooks:{}},P=null,AC=null,AD=null,Y=null,AJ=null,s={xaxis:{},yaxis:{},x2axis:{},y2axis:{}},e={left:0,right:0,top:0,bottom:0},y=0,Q=0,I=0,t=0,L={processOptions:[],processRawData:[],processDatapoints:[],draw:[],bindEvents:[],drawOverlay:[]},G=this;G.setData=f;G.setupGrid=k;G.draw=AH;G.getPlaceholder=function(){return l};G.getCanvas=function(){return P};G.getPlotOffset=function(){return e};G.width=function(){return I};G.height=function(){return t};G.offset=function(){var AK=AD.offset();AK.left+=e.left;AK.top+=e.top;return AK};G.getData=function(){return O};G.getAxes=function(){return s};G.getOptions=function(){return g};G.highlight=AE;G.unhighlight=x;G.triggerRedrawOverlay=q;G.pointOffset=function(AK){return{left:parseInt(T(AK,"xaxis").p2c(+AK.x)+e.left),top:parseInt(T(AK,"yaxis").p2c(+AK.y)+e.top)}};G.hooks=L;b(G);r(X);c();f(W);k();AH();AG();function Z(AM,AK){AK=[G].concat(AK);for(var AL=0;AL<AM.length;++AL){AM[AL].apply(this,AK)}}function b(){for(var AK=0;AK<E.length;++AK){var AL=E[AK];AL.init(G);if(AL.options){C.extend(true,g,AL.options)}}}function r(AK){C.extend(true,g,AK);if(g.grid.borderColor==null){g.grid.borderColor=g.grid.color}if(g.xaxis.noTicks&&g.xaxis.ticks==null){g.xaxis.ticks=g.xaxis.noTicks}if(g.yaxis.noTicks&&g.yaxis.ticks==null){g.yaxis.ticks=g.yaxis.noTicks}if(g.grid.coloredAreas){g.grid.markings=g.grid.coloredAreas}if(g.grid.coloredAreasColor){g.grid.markingsColor=g.grid.coloredAreasColor}if(g.lines){C.extend(true,g.series.lines,g.lines)}if(g.points){C.extend(true,g.series.points,g.points)}if(g.bars){C.extend(true,g.series.bars,g.bars)}if(g.shadowSize){g.series.shadowSize=g.shadowSize}for(var AL in L){if(g.hooks[AL]&&g.hooks[AL].length){L[AL]=L[AL].concat(g.hooks[AL])}}Z(L.processOptions,[g])}function f(AK){O=M(AK);U();m()}function M(AN){var AL=[];for(var AK=0;AK<AN.length;++AK){var AM=C.extend(true,{},g.series);if(AN[AK].data){AM.data=AN[AK].data;delete AN[AK].data;C.extend(true,AM,AN[AK]);AN[AK].data=AM.data}else{AM.data=AN[AK]}AL.push(AM)}return AL}function T(AM,AK){var AL=AM[AK];if(!AL||AL==1){return s[AK]}if(typeof AL=="number"){return s[AK.charAt(0)+AL+AK.slice(1)]}return AL}function U(){var AP;var AV=O.length,AK=[],AN=[];for(AP=0;AP<O.length;++AP){var AS=O[AP].color;if(AS!=null){--AV;if(typeof AS=="number"){AN.push(AS)}else{AK.push(C.color.parse(O[AP].color))}}}for(AP=0;AP<AN.length;++AP){AV=Math.max(AV,AN[AP]+1)}var AL=[],AO=0;AP=0;while(AL.length<AV){var AR;if(g.colors.length==AP){AR=C.color.make(100,100,100)}else{AR=C.color.parse(g.colors[AP])}var AM=AO%2==1?-1:1;AR.scale("rgb",1+AM*Math.ceil(AO/2)*0.2);AL.push(AR);++AP;if(AP>=g.colors.length){AP=0;++AO}}var AQ=0,AW;for(AP=0;AP<O.length;++AP){AW=O[AP];if(AW.color==null){AW.color=AL[AQ].toString();++AQ}else{if(typeof AW.color=="number"){AW.color=AL[AW.color].toString()}}if(AW.lines.show==null){var AU,AT=true;for(AU in AW){if(AW[AU].show){AT=false;break}}if(AT){AW.lines.show=true}}AW.xaxis=T(AW,"xaxis");AW.yaxis=T(AW,"yaxis")}}function m(){var AW=Number.POSITIVE_INFINITY,AQ=Number.NEGATIVE_INFINITY,Ac,Aa,AZ,AV,AL,AR,Ab,AX,AP,AO,AK,Ai,Af,AT;for(AK in s){s[AK].datamin=AW;s[AK].datamax=AQ;s[AK].used=false}function AN(Al,Ak,Aj){if(Ak<Al.datamin){Al.datamin=Ak}if(Aj>Al.datamax){Al.datamax=Aj}}for(Ac=0;Ac<O.length;++Ac){AR=O[Ac];AR.datapoints={points:[]};Z(L.processRawData,[AR,AR.data,AR.datapoints])}for(Ac=0;Ac<O.length;++Ac){AR=O[Ac];var Ah=AR.data,Ae=AR.datapoints.format;if(!Ae){Ae=[];Ae.push({x:true,number:true,required:true});Ae.push({y:true,number:true,required:true});if(AR.bars.show){Ae.push({y:true,number:true,required:false,defaultValue:0})}AR.datapoints.format=Ae}if(AR.datapoints.pointsize!=null){continue}if(AR.datapoints.pointsize==null){AR.datapoints.pointsize=Ae.length}AX=AR.datapoints.pointsize;Ab=AR.datapoints.points;insertSteps=AR.lines.show&&AR.lines.steps;AR.xaxis.used=AR.yaxis.used=true;for(Aa=AZ=0;Aa<Ah.length;++Aa,AZ+=AX){AT=Ah[Aa];var AM=AT==null;if(!AM){for(AV=0;AV<AX;++AV){Ai=AT[AV];Af=Ae[AV];if(Af){if(Af.number&&Ai!=null){Ai=+Ai;if(isNaN(Ai)){Ai=null}}if(Ai==null){if(Af.required){AM=true}if(Af.defaultValue!=null){Ai=Af.defaultValue}}}Ab[AZ+AV]=Ai}}if(AM){for(AV=0;AV<AX;++AV){Ai=Ab[AZ+AV];if(Ai!=null){Af=Ae[AV];if(Af.x){AN(AR.xaxis,Ai,Ai)}if(Af.y){AN(AR.yaxis,Ai,Ai)}}Ab[AZ+AV]=null}}else{if(insertSteps&&AZ>0&&Ab[AZ-AX]!=null&&Ab[AZ-AX]!=Ab[AZ]&&Ab[AZ-AX+1]!=Ab[AZ+1]){for(AV=0;AV<AX;++AV){Ab[AZ+AX+AV]=Ab[AZ+AV]}Ab[AZ+1]=Ab[AZ-AX+1];AZ+=AX}}}}for(Ac=0;Ac<O.length;++Ac){AR=O[Ac];Z(L.processDatapoints,[AR,AR.datapoints])}for(Ac=0;Ac<O.length;++Ac){AR=O[Ac];Ab=AR.datapoints.points,AX=AR.datapoints.pointsize;var AS=AW,AY=AW,AU=AQ,Ad=AQ;for(Aa=0;Aa<Ab.length;Aa+=AX){if(Ab[Aa]==null){continue}for(AV=0;AV<AX;++AV){Ai=Ab[Aa+AV];Af=Ae[AV];if(!Af){continue}if(Af.x){if(Ai<AS){AS=Ai}if(Ai>AU){AU=Ai}}if(Af.y){if(Ai<AY){AY=Ai}if(Ai>Ad){Ad=Ai}}}}if(AR.bars.show){var Ag=AR.bars.align=="left"?0:-AR.bars.barWidth/2;if(AR.bars.horizontal){AY+=Ag;Ad+=Ag+AR.bars.barWidth}else{AS+=Ag;AU+=Ag+AR.bars.barWidth}}AN(AR.xaxis,AS,AU);AN(AR.yaxis,AY,Ad)}for(AK in s){if(s[AK].datamin==AW){s[AK].datamin=null}if(s[AK].datamax==AQ){s[AK].datamax=null}}}function c(){function AK(AM,AL){var AN=document.createElement("canvas");AN.width=AM;AN.height=AL;if(C.browser.msie){AN=window.G_vmlCanvasManager.initElement(AN)}return AN}y=l.width();Q=l.height();l.html("");if(l.css("position")=="static"){l.css("position","relative")}if(y<=0||Q<=0){throw"Invalid dimensions for plot, width = "+y+", height = "+Q}if(C.browser.msie){window.G_vmlCanvasManager.init_(document)}P=C(AK(y,Q)).appendTo(l).get(0);Y=P.getContext("2d");AC=C(AK(y,Q)).css({position:"absolute",left:0,top:0}).appendTo(l).get(0);AJ=AC.getContext("2d");AJ.stroke()}function AG(){AD=C([AC,P]);if(g.grid.hoverable){AD.mousemove(D)}if(g.grid.clickable){AD.click(d)}Z(L.bindEvents,[AD])}function k(){function AL(AT,AU){function AP(AV){return AV}var AS,AO,AQ=AU.transform||AP,AR=AU.inverseTransform;if(AT==s.xaxis||AT==s.x2axis){AS=AT.scale=I/(AQ(AT.max)-AQ(AT.min));AO=AQ(AT.min);if(AQ==AP){AT.p2c=function(AV){return(AV-AO)*AS}}else{AT.p2c=function(AV){return(AQ(AV)-AO)*AS}}if(!AR){AT.c2p=function(AV){return AO+AV/AS}}else{AT.c2p=function(AV){return AR(AO+AV/AS)}}}else{AS=AT.scale=t/(AQ(AT.max)-AQ(AT.min));AO=AQ(AT.max);if(AQ==AP){AT.p2c=function(AV){return(AO-AV)*AS}}else{AT.p2c=function(AV){return(AO-AQ(AV))*AS}}if(!AR){AT.c2p=function(AV){return AO-AV/AS}}else{AT.c2p=function(AV){return AR(AO-AV/AS)}}}}function AN(AR,AT){var AQ,AS=[],AP;AR.labelWidth=AT.labelWidth;AR.labelHeight=AT.labelHeight;if(AR==s.xaxis||AR==s.x2axis){if(AR.labelWidth==null){AR.labelWidth=y/(AR.ticks.length>0?AR.ticks.length:1)}if(AR.labelHeight==null){AS=[];for(AQ=0;AQ<AR.ticks.length;++AQ){AP=AR.ticks[AQ].label;if(AP){AS.push('<div class="tickLabel" style="float:left;width:'+AR.labelWidth+'px">'+AP+"</div>")}}if(AS.length>0){var AO=C('<div style="position:absolute;top:-10000px;width:10000px;font-size:smaller">'+AS.join("")+'<div style="clear:left"></div></div>').appendTo(l);AR.labelHeight=AO.height();AO.remove()}}}else{if(AR.labelWidth==null||AR.labelHeight==null){for(AQ=0;AQ<AR.ticks.length;++AQ){AP=AR.ticks[AQ].label;if(AP){AS.push('<div class="tickLabel">'+AP+"</div>")}}if(AS.length>0){var AO=C('<div style="position:absolute;top:-10000px;font-size:smaller">'+AS.join("")+"</div>").appendTo(l);if(AR.labelWidth==null){AR.labelWidth=AO.width()}if(AR.labelHeight==null){AR.labelHeight=AO.find("div").height()}AO.remove()}}}if(AR.labelWidth==null){AR.labelWidth=0}if(AR.labelHeight==null){AR.labelHeight=0}}function AM(){var AP=g.grid.borderWidth;for(i=0;i<O.length;++i){AP=Math.max(AP,2*(O[i].points.radius+O[i].points.lineWidth/2))}e.left=e.right=e.top=e.bottom=AP;var AO=g.grid.labelMargin+g.grid.borderWidth;if(s.xaxis.labelHeight>0){e.bottom=Math.max(AP,s.xaxis.labelHeight+AO)}if(s.yaxis.labelWidth>0){e.left=Math.max(AP,s.yaxis.labelWidth+AO)}if(s.x2axis.labelHeight>0){e.top=Math.max(AP,s.x2axis.labelHeight+AO)}if(s.y2axis.labelWidth>0){e.right=Math.max(AP,s.y2axis.labelWidth+AO)}I=y-e.left-e.right;t=Q-e.bottom-e.top}var AK;for(AK in s){K(s[AK],g[AK])}if(g.grid.show){for(AK in s){F(s[AK],g[AK]);p(s[AK],g[AK]);AN(s[AK],g[AK])}AM()}else{e.left=e.right=e.top=e.bottom=0;I=y;t=Q}for(AK in s){AL(s[AK],g[AK])}if(g.grid.show){h()}AI()}function K(AN,AQ){var AM=+(AQ.min!=null?AQ.min:AN.datamin),AK=+(AQ.max!=null?AQ.max:AN.datamax),AP=AK-AM;if(AP==0){var AL=AK==0?1:0.01;if(AQ.min==null){AM-=AL}if(AQ.max==null||AQ.min!=null){AK+=AL}}else{var AO=AQ.autoscaleMargin;if(AO!=null){if(AQ.min==null){AM-=AP*AO;if(AM<0&&AN.datamin!=null&&AN.datamin>=0){AM=0}}if(AQ.max==null){AK+=AP*AO;if(AK>0&&AN.datamax!=null&&AN.datamax<=0){AK=0}}}}AN.min=AM;AN.max=AK}function F(AP,AS){var AO;if(typeof AS.ticks=="number"&&AS.ticks>0){AO=AS.ticks}else{if(AP==s.xaxis||AP==s.x2axis){AO=0.3*Math.sqrt(y)}else{AO=0.3*Math.sqrt(Q)}}var AX=(AP.max-AP.min)/AO,AZ,AT,AV,AW,AR,AM,AL;if(AS.mode=="time"){var AU={second:1000,minute:60*1000,hour:60*60*1000,day:24*60*60*1000,month:30*24*60*60*1000,year:365.2425*24*60*60*1000};var AY=[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[0.25,"month"],[0.5,"month"],[1,"month"],[2,"month"],[3,"month"],[6,"month"],[1,"year"]];var AN=0;if(AS.minTickSize!=null){if(typeof AS.tickSize=="number"){AN=AS.tickSize}else{AN=AS.minTickSize[0]*AU[AS.minTickSize[1]]}}for(AR=0;AR<AY.length-1;++AR){if(AX<(AY[AR][0]*AU[AY[AR][1]]+AY[AR+1][0]*AU[AY[AR+1][1]])/2&&AY[AR][0]*AU[AY[AR][1]]>=AN){break}}AZ=AY[AR][0];AV=AY[AR][1];if(AV=="year"){AM=Math.pow(10,Math.floor(Math.log(AX/AU.year)/Math.LN10));AL=(AX/AU.year)/AM;if(AL<1.5){AZ=1}else{if(AL<3){AZ=2}else{if(AL<7.5){AZ=5}else{AZ=10}}}AZ*=AM}if(AS.tickSize){AZ=AS.tickSize[0];AV=AS.tickSize[1]}AT=function(Ac){var Ah=[],Af=Ac.tickSize[0],Ai=Ac.tickSize[1],Ag=new Date(Ac.min);var Ab=Af*AU[Ai];if(Ai=="second"){Ag.setUTCSeconds(A(Ag.getUTCSeconds(),Af))}if(Ai=="minute"){Ag.setUTCMinutes(A(Ag.getUTCMinutes(),Af))}if(Ai=="hour"){Ag.setUTCHours(A(Ag.getUTCHours(),Af))}if(Ai=="month"){Ag.setUTCMonth(A(Ag.getUTCMonth(),Af))}if(Ai=="year"){Ag.setUTCFullYear(A(Ag.getUTCFullYear(),Af))}Ag.setUTCMilliseconds(0);if(Ab>=AU.minute){Ag.setUTCSeconds(0)}if(Ab>=AU.hour){Ag.setUTCMinutes(0)}if(Ab>=AU.day){Ag.setUTCHours(0)}if(Ab>=AU.day*4){Ag.setUTCDate(1)}if(Ab>=AU.year){Ag.setUTCMonth(0)}var Ak=0,Aj=Number.NaN,Ad;do{Ad=Aj;Aj=Ag.getTime();Ah.push({v:Aj,label:Ac.tickFormatter(Aj,Ac)});if(Ai=="month"){if(Af<1){Ag.setUTCDate(1);var Aa=Ag.getTime();Ag.setUTCMonth(Ag.getUTCMonth()+1);var Ae=Ag.getTime();Ag.setTime(Aj+Ak*AU.hour+(Ae-Aa)*Af);Ak=Ag.getUTCHours();Ag.setUTCHours(0)}else{Ag.setUTCMonth(Ag.getUTCMonth()+Af)}}else{if(Ai=="year"){Ag.setUTCFullYear(Ag.getUTCFullYear()+Af)}else{Ag.setTime(Aj+Ab)}}}while(Aj<Ac.max&&Aj!=Ad);return Ah};AW=function(Aa,Ad){var Af=new Date(Aa);if(AS.timeformat!=null){return C.plot.formatDate(Af,AS.timeformat,AS.monthNames)}var Ab=Ad.tickSize[0]*AU[Ad.tickSize[1]];var Ac=Ad.max-Ad.min;var Ae=(AS.twelveHourClock)?" %p":"";if(Ab<AU.minute){fmt="%h:%M:%S"+Ae}else{if(Ab<AU.day){if(Ac<2*AU.day){fmt="%h:%M"+Ae}else{fmt="%b %d %h:%M"+Ae}}else{if(Ab<AU.month){fmt="%b %d"}else{if(Ab<AU.year){if(Ac<AU.year){fmt="%b"}else{fmt="%b %y"}}else{fmt="%y"}}}}return C.plot.formatDate(Af,fmt,AS.monthNames)}}else{var AK=AS.tickDecimals;var AQ=-Math.floor(Math.log(AX)/Math.LN10);if(AK!=null&&AQ>AK){AQ=AK}AM=Math.pow(10,-AQ);AL=AX/AM;if(AL<1.5){AZ=1}else{if(AL<3){AZ=2;if(AL>2.25&&(AK==null||AQ+1<=AK)){AZ=2.5;++AQ}}else{if(AL<7.5){AZ=5}else{AZ=10}}}AZ*=AM;if(AS.minTickSize!=null&&AZ<AS.minTickSize){AZ=AS.minTickSize}if(AS.tickSize!=null){AZ=AS.tickSize}AP.tickDecimals=Math.max(0,(AK!=null)?AK:AQ);AT=function(Ac){var Ae=[];var Af=A(Ac.min,Ac.tickSize),Ab=0,Aa=Number.NaN,Ad;do{Ad=Aa;Aa=Af+Ab*Ac.tickSize;Ae.push({v:Aa,label:Ac.tickFormatter(Aa,Ac)});++Ab}while(Aa<Ac.max&&Aa!=Ad);return Ae};AW=function(Aa,Ab){return Aa.toFixed(Ab.tickDecimals)}}AP.tickSize=AV?[AZ,AV]:AZ;AP.tickGenerator=AT;if(C.isFunction(AS.tickFormatter)){AP.tickFormatter=function(Aa,Ab){return""+AS.tickFormatter(Aa,Ab)}}else{AP.tickFormatter=AW}}function p(AO,AQ){AO.ticks=[];if(!AO.used){return }if(AQ.ticks==null){AO.ticks=AO.tickGenerator(AO)}else{if(typeof AQ.ticks=="number"){if(AQ.ticks>0){AO.ticks=AO.tickGenerator(AO)}}else{if(AQ.ticks){var AP=AQ.ticks;if(C.isFunction(AP)){AP=AP({min:AO.min,max:AO.max})}var AN,AK;for(AN=0;AN<AP.length;++AN){var AL=null;var AM=AP[AN];if(typeof AM=="object"){AK=AM[0];if(AM.length>1){AL=AM[1]}}else{AK=AM}if(AL==null){AL=AO.tickFormatter(AK,AO)}AO.ticks[AN]={v:AK,label:AL}}}}}if(AQ.autoscaleMargin!=null&&AO.ticks.length>0){if(AQ.min==null){AO.min=Math.min(AO.min,AO.ticks[0].v)}if(AQ.max==null&&AO.ticks.length>1){AO.max=Math.max(AO.max,AO.ticks[AO.ticks.length-1].v)}}}function AH(){Y.clearRect(0,0,y,Q);var AL=g.grid;if(AL.show&&!AL.aboveData){S()}for(var AK=0;AK<O.length;++AK){AA(O[AK])}Z(L.draw,[Y]);if(AL.show&&AL.aboveData){S()}}function N(AL,AR){var AO=AR+"axis",AK=AR+"2axis",AN,AQ,AP,AM;if(AL[AO]){AN=s[AO];AQ=AL[AO].from;AP=AL[AO].to}else{if(AL[AK]){AN=s[AK];AQ=AL[AK].from;AP=AL[AK].to}else{AN=s[AO];AQ=AL[AR+"1"];AP=AL[AR+"2"]}}if(AQ!=null&&AP!=null&&AQ>AP){return{from:AP,to:AQ,axis:AN}}return{from:AQ,to:AP,axis:AN}}function S(){var AO;Y.save();Y.translate(e.left,e.top);if(g.grid.backgroundColor){Y.fillStyle=R(g.grid.backgroundColor,t,0,"rgba(255, 255, 255, 0)");Y.fillRect(0,0,I,t)}var AL=g.grid.markings;if(AL){if(C.isFunction(AL)){AL=AL({xmin:s.xaxis.min,xmax:s.xaxis.max,ymin:s.yaxis.min,ymax:s.yaxis.max,xaxis:s.xaxis,yaxis:s.yaxis,x2axis:s.x2axis,y2axis:s.y2axis})}for(AO=0;AO<AL.length;++AO){var AK=AL[AO],AQ=N(AK,"x"),AN=N(AK,"y");if(AQ.from==null){AQ.from=AQ.axis.min}if(AQ.to==null){AQ.to=AQ.axis.max}if(AN.from==null){AN.from=AN.axis.min}if(AN.to==null){AN.to=AN.axis.max}if(AQ.to<AQ.axis.min||AQ.from>AQ.axis.max||AN.to<AN.axis.min||AN.from>AN.axis.max){continue}AQ.from=Math.max(AQ.from,AQ.axis.min);AQ.to=Math.min(AQ.to,AQ.axis.max);AN.from=Math.max(AN.from,AN.axis.min);AN.to=Math.min(AN.to,AN.axis.max);if(AQ.from==AQ.to&&AN.from==AN.to){continue}AQ.from=AQ.axis.p2c(AQ.from);AQ.to=AQ.axis.p2c(AQ.to);AN.from=AN.axis.p2c(AN.from);AN.to=AN.axis.p2c(AN.to);if(AQ.from==AQ.to||AN.from==AN.to){Y.beginPath();Y.strokeStyle=AK.color||g.grid.markingsColor;Y.lineWidth=AK.lineWidth||g.grid.markingsLineWidth;Y.moveTo(AQ.from,AN.from);Y.lineTo(AQ.to,AN.to);Y.stroke()}else{Y.fillStyle=AK.color||g.grid.markingsColor;Y.fillRect(AQ.from,AN.to,AQ.to-AQ.from,AN.from-AN.to)}}}Y.lineWidth=1;Y.strokeStyle=g.grid.tickColor;Y.beginPath();var AM,AP=s.xaxis;for(AO=0;AO<AP.ticks.length;++AO){AM=AP.ticks[AO].v;if(AM<=AP.min||AM>=s.xaxis.max){continue}Y.moveTo(Math.floor(AP.p2c(AM))+Y.lineWidth/2,0);Y.lineTo(Math.floor(AP.p2c(AM))+Y.lineWidth/2,t)}AP=s.yaxis;for(AO=0;AO<AP.ticks.length;++AO){AM=AP.ticks[AO].v;if(AM<=AP.min||AM>=AP.max){continue}Y.moveTo(0,Math.floor(AP.p2c(AM))+Y.lineWidth/2);Y.lineTo(I,Math.floor(AP.p2c(AM))+Y.lineWidth/2)}AP=s.x2axis;for(AO=0;AO<AP.ticks.length;++AO){AM=AP.ticks[AO].v;if(AM<=AP.min||AM>=AP.max){continue}Y.moveTo(Math.floor(AP.p2c(AM))+Y.lineWidth/2,-5);Y.lineTo(Math.floor(AP.p2c(AM))+Y.lineWidth/2,5)}AP=s.y2axis;for(AO=0;AO<AP.ticks.length;++AO){AM=AP.ticks[AO].v;if(AM<=AP.min||AM>=AP.max){continue}Y.moveTo(I-5,Math.floor(AP.p2c(AM))+Y.lineWidth/2);Y.lineTo(I+5,Math.floor(AP.p2c(AM))+Y.lineWidth/2)}Y.stroke();if(g.grid.borderWidth){var AR=g.grid.borderWidth;Y.lineWidth=AR;Y.strokeStyle=g.grid.borderColor;Y.strokeRect(-AR/2,-AR/2,I+AR,t+AR)}Y.restore()}function h(){l.find(".tickLabels").remove();var AK=['<div class="tickLabels" style="font-size:smaller;color:'+g.grid.color+'">'];function AM(AP,AQ){for(var AO=0;AO<AP.ticks.length;++AO){var AN=AP.ticks[AO];if(!AN.label||AN.v<AP.min||AN.v>AP.max){continue}AK.push(AQ(AN,AP))}}var AL=g.grid.labelMargin+g.grid.borderWidth;AM(s.xaxis,function(AN,AO){return'<div style="position:absolute;top:'+(e.top+t+AL)+"px;left:"+Math.round(e.left+AO.p2c(AN.v)-AO.labelWidth/2)+"px;width:"+AO.labelWidth+'px;text-align:center" class="tickLabel">'+AN.label+"</div>"});AM(s.yaxis,function(AN,AO){return'<div style="position:absolute;top:'+Math.round(e.top+AO.p2c(AN.v)-AO.labelHeight/2)+"px;right:"+(e.right+I+AL)+"px;width:"+AO.labelWidth+'px;text-align:right" class="tickLabel">'+AN.label+"</div>"});AM(s.x2axis,function(AN,AO){return'<div style="position:absolute;bottom:'+(e.bottom+t+AL)+"px;left:"+Math.round(e.left+AO.p2c(AN.v)-AO.labelWidth/2)+"px;width:"+AO.labelWidth+'px;text-align:center" class="tickLabel">'+AN.label+"</div>"});AM(s.y2axis,function(AN,AO){return'<div style="position:absolute;top:'+Math.round(e.top+AO.p2c(AN.v)-AO.labelHeight/2)+"px;left:"+(e.left+I+AL)+"px;width:"+AO.labelWidth+'px;text-align:left" class="tickLabel">'+AN.label+"</div>"});AK.push("</div>");l.append(AK.join(""))}function AA(AK){if(AK.lines.show){a(AK)}if(AK.bars.show){n(AK)}if(AK.points.show){o(AK)}}function a(AN){function AM(AY,AZ,AR,Ad,Ac){var Ae=AY.points,AS=AY.pointsize,AW=null,AV=null;Y.beginPath();for(var AX=AS;AX<Ae.length;AX+=AS){var AU=Ae[AX-AS],Ab=Ae[AX-AS+1],AT=Ae[AX],Aa=Ae[AX+1];if(AU==null||AT==null){continue}if(Ab<=Aa&&Ab<Ac.min){if(Aa<Ac.min){continue}AU=(Ac.min-Ab)/(Aa-Ab)*(AT-AU)+AU;Ab=Ac.min}else{if(Aa<=Ab&&Aa<Ac.min){if(Ab<Ac.min){continue}AT=(Ac.min-Ab)/(Aa-Ab)*(AT-AU)+AU;Aa=Ac.min}}if(Ab>=Aa&&Ab>Ac.max){if(Aa>Ac.max){continue}AU=(Ac.max-Ab)/(Aa-Ab)*(AT-AU)+AU;Ab=Ac.max}else{if(Aa>=Ab&&Aa>Ac.max){if(Ab>Ac.max){continue}AT=(Ac.max-Ab)/(Aa-Ab)*(AT-AU)+AU;Aa=Ac.max}}if(AU<=AT&&AU<Ad.min){if(AT<Ad.min){continue}Ab=(Ad.min-AU)/(AT-AU)*(Aa-Ab)+Ab;AU=Ad.min}else{if(AT<=AU&&AT<Ad.min){if(AU<Ad.min){continue}Aa=(Ad.min-AU)/(AT-AU)*(Aa-Ab)+Ab;AT=Ad.min}}if(AU>=AT&&AU>Ad.max){if(AT>Ad.max){continue}Ab=(Ad.max-AU)/(AT-AU)*(Aa-Ab)+Ab;AU=Ad.max}else{if(AT>=AU&&AT>Ad.max){if(AU>Ad.max){continue}Aa=(Ad.max-AU)/(AT-AU)*(Aa-Ab)+Ab;AT=Ad.max}}if(AU!=AW||Ab!=AV){Y.moveTo(Ad.p2c(AU)+AZ,Ac.p2c(Ab)+AR)}AW=AT;AV=Aa;Y.lineTo(Ad.p2c(AT)+AZ,Ac.p2c(Aa)+AR)}Y.stroke()}function AO(AX,Ae,Ac){var Af=AX.points,AR=AX.pointsize,AS=Math.min(Math.max(0,Ac.min),Ac.max),Aa,AV=0,Ad=false;for(var AW=AR;AW<Af.length;AW+=AR){var AU=Af[AW-AR],Ab=Af[AW-AR+1],AT=Af[AW],AZ=Af[AW+1];if(Ad&&AU!=null&&AT==null){Y.lineTo(Ae.p2c(AV),Ac.p2c(AS));Y.fill();Ad=false;continue}if(AU==null||AT==null){continue}if(AU<=AT&&AU<Ae.min){if(AT<Ae.min){continue}Ab=(Ae.min-AU)/(AT-AU)*(AZ-Ab)+Ab;AU=Ae.min}else{if(AT<=AU&&AT<Ae.min){if(AU<Ae.min){continue}AZ=(Ae.min-AU)/(AT-AU)*(AZ-Ab)+Ab;AT=Ae.min}}if(AU>=AT&&AU>Ae.max){if(AT>Ae.max){continue}Ab=(Ae.max-AU)/(AT-AU)*(AZ-Ab)+Ab;AU=Ae.max}else{if(AT>=AU&&AT>Ae.max){if(AU>Ae.max){continue}AZ=(Ae.max-AU)/(AT-AU)*(AZ-Ab)+Ab;AT=Ae.max}}if(!Ad){Y.beginPath();Y.moveTo(Ae.p2c(AU),Ac.p2c(AS));Ad=true}if(Ab>=Ac.max&&AZ>=Ac.max){Y.lineTo(Ae.p2c(AU),Ac.p2c(Ac.max));Y.lineTo(Ae.p2c(AT),Ac.p2c(Ac.max));AV=AT;continue}else{if(Ab<=Ac.min&&AZ<=Ac.min){Y.lineTo(Ae.p2c(AU),Ac.p2c(Ac.min));Y.lineTo(Ae.p2c(AT),Ac.p2c(Ac.min));AV=AT;continue}}var Ag=AU,AY=AT;if(Ab<=AZ&&Ab<Ac.min&&AZ>=Ac.min){AU=(Ac.min-Ab)/(AZ-Ab)*(AT-AU)+AU;Ab=Ac.min}else{if(AZ<=Ab&&AZ<Ac.min&&Ab>=Ac.min){AT=(Ac.min-Ab)/(AZ-Ab)*(AT-AU)+AU;AZ=Ac.min}}if(Ab>=AZ&&Ab>Ac.max&&AZ<=Ac.max){AU=(Ac.max-Ab)/(AZ-Ab)*(AT-AU)+AU;Ab=Ac.max}else{if(AZ>=Ab&&AZ>Ac.max&&Ab<=Ac.max){AT=(Ac.max-Ab)/(AZ-Ab)*(AT-AU)+AU;AZ=Ac.max}}if(AU!=Ag){if(Ab<=Ac.min){Aa=Ac.min}else{Aa=Ac.max}Y.lineTo(Ae.p2c(Ag),Ac.p2c(Aa));Y.lineTo(Ae.p2c(AU),Ac.p2c(Aa))}Y.lineTo(Ae.p2c(AU),Ac.p2c(Ab));Y.lineTo(Ae.p2c(AT),Ac.p2c(AZ));if(AT!=AY){if(AZ<=Ac.min){Aa=Ac.min}else{Aa=Ac.max}Y.lineTo(Ae.p2c(AT),Ac.p2c(Aa));Y.lineTo(Ae.p2c(AY),Ac.p2c(Aa))}AV=Math.max(AT,AY)}if(Ad){Y.lineTo(Ae.p2c(AV),Ac.p2c(AS));Y.fill()}}Y.save();Y.translate(e.left,e.top);Y.lineJoin="round";var AP=AN.lines.lineWidth,AK=AN.shadowSize;if(AP>0&&AK>0){Y.lineWidth=AK;Y.strokeStyle="rgba(0,0,0,0.1)";var AQ=Math.PI/18;AM(AN.datapoints,Math.sin(AQ)*(AP/2+AK/2),Math.cos(AQ)*(AP/2+AK/2),AN.xaxis,AN.yaxis);Y.lineWidth=AK/2;AM(AN.datapoints,Math.sin(AQ)*(AP/2+AK/4),Math.cos(AQ)*(AP/2+AK/4),AN.xaxis,AN.yaxis)}Y.lineWidth=AP;Y.strokeStyle=AN.color;var AL=V(AN.lines,AN.color,0,t);if(AL){Y.fillStyle=AL;AO(AN.datapoints,AN.xaxis,AN.yaxis)}if(AP>0){AM(AN.datapoints,0,0,AN.xaxis,AN.yaxis)}Y.restore()}function o(AN){function AP(AU,AT,Ab,AR,AV,AZ,AY){var Aa=AU.points,AQ=AU.pointsize;for(var AS=0;AS<Aa.length;AS+=AQ){var AX=Aa[AS],AW=Aa[AS+1];if(AX==null||AX<AZ.min||AX>AZ.max||AW<AY.min||AW>AY.max){continue}Y.beginPath();Y.arc(AZ.p2c(AX),AY.p2c(AW)+AR,AT,0,AV,false);if(Ab){Y.fillStyle=Ab;Y.fill()}Y.stroke()}}Y.save();Y.translate(e.left,e.top);var AO=AN.lines.lineWidth,AL=AN.shadowSize,AK=AN.points.radius;if(AO>0&&AL>0){var AM=AL/2;Y.lineWidth=AM;Y.strokeStyle="rgba(0,0,0,0.1)";AP(AN.datapoints,AK,null,AM+AM/2,Math.PI,AN.xaxis,AN.yaxis);Y.strokeStyle="rgba(0,0,0,0.2)";AP(AN.datapoints,AK,null,AM/2,Math.PI,AN.xaxis,AN.yaxis)}Y.lineWidth=AO;Y.strokeStyle=AN.color;AP(AN.datapoints,AK,V(AN.points,AN.color),0,2*Math.PI,AN.xaxis,AN.yaxis);Y.restore()}function AB(AV,AU,Ad,AQ,AY,AN,AL,AT,AS,Ac,AZ){var AM,Ab,AR,AX,AO,AK,AW,AP,Aa;if(AZ){AP=AK=AW=true;AO=false;AM=Ad;Ab=AV;AX=AU+AQ;AR=AU+AY;if(Ab<AM){Aa=Ab;Ab=AM;AM=Aa;AO=true;AK=false}}else{AO=AK=AW=true;AP=false;AM=AV+AQ;Ab=AV+AY;AR=Ad;AX=AU;if(AX<AR){Aa=AX;AX=AR;AR=Aa;AP=true;AW=false}}if(Ab<AT.min||AM>AT.max||AX<AS.min||AR>AS.max){return }if(AM<AT.min){AM=AT.min;AO=false}if(Ab>AT.max){Ab=AT.max;AK=false}if(AR<AS.min){AR=AS.min;AP=false}if(AX>AS.max){AX=AS.max;AW=false}AM=AT.p2c(AM);AR=AS.p2c(AR);Ab=AT.p2c(Ab);AX=AS.p2c(AX);if(AL){Ac.beginPath();Ac.moveTo(AM,AR);Ac.lineTo(AM,AX);Ac.lineTo(Ab,AX);Ac.lineTo(Ab,AR);Ac.fillStyle=AL(AR,AX);Ac.fill()}if(AO||AK||AW||AP){Ac.beginPath();Ac.moveTo(AM,AR+AN);if(AO){Ac.lineTo(AM,AX+AN)}else{Ac.moveTo(AM,AX+AN)}if(AW){Ac.lineTo(Ab,AX+AN)}else{Ac.moveTo(Ab,AX+AN)}if(AK){Ac.lineTo(Ab,AR+AN)}else{Ac.moveTo(Ab,AR+AN)}if(AP){Ac.lineTo(AM,AR+AN)}else{Ac.moveTo(AM,AR+AN)}Ac.stroke()}}function n(AM){function AL(AS,AR,AU,AP,AT,AW,AV){var AX=AS.points,AO=AS.pointsize;for(var AQ=0;AQ<AX.length;AQ+=AO){if(AX[AQ]==null){continue}AB(AX[AQ],AX[AQ+1],AX[AQ+2],AR,AU,AP,AT,AW,AV,Y,AM.bars.horizontal)}}Y.save();Y.translate(e.left,e.top);Y.lineWidth=AM.bars.lineWidth;Y.strokeStyle=AM.color;var AK=AM.bars.align=="left"?0:-AM.bars.barWidth/2;var AN=AM.bars.fill?function(AO,AP){return V(AM.bars,AM.color,AO,AP)}:null;AL(AM.datapoints,AK,AK+AM.bars.barWidth,0,AN,AM.xaxis,AM.yaxis);Y.restore()}function V(AM,AK,AL,AO){var AN=AM.fill;if(!AN){return null}if(AM.fillColor){return R(AM.fillColor,AL,AO,AK)}var AP=C.color.parse(AK);AP.a=typeof AN=="number"?AN:0.4;AP.normalize();return AP.toString()}function AI(){l.find(".legend").remove();if(!g.legend.show){return }var AP=[],AN=false,AV=g.legend.labelFormatter,AU,AR;for(i=0;i<O.length;++i){AU=O[i];AR=AU.label;if(!AR){continue}if(i%g.legend.noColumns==0){if(AN){AP.push("</tr>")}AP.push("<tr>");AN=true}if(AV){AR=AV(AR,AU)}AP.push('<td class="legendColorBox"><div style="border:1px solid '+g.legend.labelBoxBorderColor+';padding:1px"><div style="width:4px;height:0;border:5px solid '+AU.color+';overflow:hidden"></div></div></td><td class="legendLabel">'+AR+"</td>")}if(AN){AP.push("</tr>")}if(AP.length==0){return }var AT='<table style="font-size:smaller;color:'+g.grid.color+'">'+AP.join("")+"</table>";if(g.legend.container!=null){C(g.legend.container).html(AT)}else{var AQ="",AL=g.legend.position,AM=g.legend.margin;if(AM[0]==null){AM=[AM,AM]}if(AL.charAt(0)=="n"){AQ+="top:"+(AM[1]+e.top)+"px;"}else{if(AL.charAt(0)=="s"){AQ+="bottom:"+(AM[1]+e.bottom)+"px;"}}if(AL.charAt(1)=="e"){AQ+="right:"+(AM[0]+e.right)+"px;"}else{if(AL.charAt(1)=="w"){AQ+="left:"+(AM[0]+e.left)+"px;"}}var AS=C('<div class="legend">'+AT.replace('style="','style="position:absolute;'+AQ+";")+"</div>").appendTo(l);if(g.legend.backgroundOpacity!=0){var AO=g.legend.backgroundColor;if(AO==null){AO=g.grid.backgroundColor;if(AO&&typeof AO=="string"){AO=C.color.parse(AO)}else{AO=C.color.extract(AS,"background-color")}AO.a=1;AO=AO.toString()}var AK=AS.children();C('<div style="position:absolute;width:'+AK.width()+"px;height:"+AK.height()+"px;"+AQ+"background-color:"+AO+';"> </div>').prependTo(AS).css("opacity",g.legend.backgroundOpacity)}}}var w=[],J=null;function AF(AR,AP,AM){var AX=g.grid.mouseActiveRadius,Aj=AX*AX+1,Ah=null,Aa=false,Af,Ad;for(Af=0;Af<O.length;++Af){if(!AM(O[Af])){continue}var AY=O[Af],AQ=AY.xaxis,AO=AY.yaxis,Ae=AY.datapoints.points,Ac=AY.datapoints.pointsize,AZ=AQ.c2p(AR),AW=AO.c2p(AP),AL=AX/AQ.scale,AK=AX/AO.scale;if(AY.lines.show||AY.points.show){for(Ad=0;Ad<Ae.length;Ad+=Ac){var AT=Ae[Ad],AS=Ae[Ad+1];if(AT==null){continue}if(AT-AZ>AL||AT-AZ<-AL||AS-AW>AK||AS-AW<-AK){continue}var AV=Math.abs(AQ.p2c(AT)-AR),AU=Math.abs(AO.p2c(AS)-AP),Ab=AV*AV+AU*AU;if(Ab<=Aj){Aj=Ab;Ah=[Af,Ad/Ac]}}}if(AY.bars.show&&!Ah){var AN=AY.bars.align=="left"?0:-AY.bars.barWidth/2,Ag=AN+AY.bars.barWidth;for(Ad=0;Ad<Ae.length;Ad+=Ac){var AT=Ae[Ad],AS=Ae[Ad+1],Ai=Ae[Ad+2];if(AT==null){continue}if(O[Af].bars.horizontal?(AZ<=Math.max(Ai,AT)&&AZ>=Math.min(Ai,AT)&&AW>=AS+AN&&AW<=AS+Ag):(AZ>=AT+AN&&AZ<=AT+Ag&&AW>=Math.min(Ai,AS)&&AW<=Math.max(Ai,AS))){Ah=[Af,Ad/Ac]}}}}if(Ah){Af=Ah[0];Ad=Ah[1];Ac=O[Af].datapoints.pointsize;return{datapoint:O[Af].datapoints.points.slice(Ad*Ac,(Ad+1)*Ac),dataIndex:Ad,series:O[Af],seriesIndex:Af}}return null}function D(AK){if(g.grid.hoverable){H("plothover",AK,function(AL){return AL.hoverable!=false})}}function d(AK){H("plotclick",AK,function(AL){return AL.clickable!=false})}function H(AL,AK,AM){var AN=AD.offset(),AS={pageX:AK.pageX,pageY:AK.pageY},AQ=AK.pageX-AN.left-e.left,AO=AK.pageY-AN.top-e.top;if(s.xaxis.used){AS.x=s.xaxis.c2p(AQ)}if(s.yaxis.used){AS.y=s.yaxis.c2p(AO)}if(s.x2axis.used){AS.x2=s.x2axis.c2p(AQ)}if(s.y2axis.used){AS.y2=s.y2axis.c2p(AO)}var AT=AF(AQ,AO,AM);if(AT){AT.pageX=parseInt(AT.series.xaxis.p2c(AT.datapoint[0])+AN.left+e.left);AT.pageY=parseInt(AT.series.yaxis.p2c(AT.datapoint[1])+AN.top+e.top)}if(g.grid.autoHighlight){for(var AP=0;AP<w.length;++AP){var AR=w[AP];if(AR.auto==AL&&!(AT&&AR.series==AT.series&&AR.point==AT.datapoint)){x(AR.series,AR.point)}}if(AT){AE(AT.series,AT.datapoint,AL)}}l.trigger(AL,[AS,AT])}function q(){if(!J){J=setTimeout(v,30)}}function v(){J=null;AJ.save();AJ.clearRect(0,0,y,Q);AJ.translate(e.left,e.top);var AL,AK;for(AL=0;AL<w.length;++AL){AK=w[AL];if(AK.series.bars.show){z(AK.series,AK.point)}else{u(AK.series,AK.point)}}AJ.restore();Z(L.drawOverlay,[AJ])}function AE(AM,AK,AN){if(typeof AM=="number"){AM=O[AM]}if(typeof AK=="number"){AK=AM.data[AK]}var AL=j(AM,AK);if(AL==-1){w.push({series:AM,point:AK,auto:AN});q()}else{if(!AN){w[AL].auto=false}}}function x(AM,AK){if(AM==null&&AK==null){w=[];q()}if(typeof AM=="number"){AM=O[AM]}if(typeof AK=="number"){AK=AM.data[AK]}var AL=j(AM,AK);if(AL!=-1){w.splice(AL,1);q()}}function j(AM,AN){for(var AK=0;AK<w.length;++AK){var AL=w[AK];if(AL.series==AM&&AL.point[0]==AN[0]&&AL.point[1]==AN[1]){return AK}}return -1}function u(AN,AM){var AL=AM[0],AR=AM[1],AQ=AN.xaxis,AP=AN.yaxis;if(AL<AQ.min||AL>AQ.max||AR<AP.min||AR>AP.max){return }var AO=AN.points.radius+AN.points.lineWidth/2;AJ.lineWidth=AO;AJ.strokeStyle=C.color.parse(AN.color).scale("a",0.5).toString();var AK=1.5*AO;AJ.beginPath();AJ.arc(AQ.p2c(AL),AP.p2c(AR),AK,0,2*Math.PI,false);AJ.stroke()}function z(AN,AK){AJ.lineWidth=AN.bars.lineWidth;AJ.strokeStyle=C.color.parse(AN.color).scale("a",0.5).toString();var AM=C.color.parse(AN.color).scale("a",0.5).toString();var AL=AN.bars.align=="left"?0:-AN.bars.barWidth/2;AB(AK[0],AK[1],AK[2]||0,AL,AL+AN.bars.barWidth,0,function(){return AM},AN.xaxis,AN.yaxis,AJ,AN.bars.horizontal)}function R(AM,AL,AQ,AO){if(typeof AM=="string"){return AM}else{var AP=Y.createLinearGradient(0,AQ,0,AL);for(var AN=0,AK=AM.colors.length;AN<AK;++AN){var AR=AM.colors[AN];if(typeof AR!="string"){AR=C.color.parse(AO).scale("rgb",AR.brightness);AR.a*=AR.opacity;AR=AR.toString()}AP.addColorStop(AN/(AK-1),AR)}return AP}}}C.plot=function(G,E,D){var F=new B(C(G),E,D,C.plot.plugins);return F};C.plot.plugins=[];C.plot.formatDate=function(H,E,G){var L=function(N){N=""+N;return N.length==1?"0"+N:N};var D=[];var M=false;var K=H.getUTCHours();var I=K<12;if(G==null){G=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]}if(E.search(/%p|%P/)!=-1){if(K>12){K=K-12}else{if(K==0){K=12}}}for(var F=0;F<E.length;++F){var J=E.charAt(F);if(M){switch(J){case"h":J=""+K;break;case"H":J=L(K);break;case"M":J=L(H.getUTCMinutes());break;case"S":J=L(H.getUTCSeconds());break;case"d":J=""+H.getUTCDate();break;case"m":J=""+(H.getUTCMonth()+1);break;case"y":J=""+H.getUTCFullYear();break;case"b":J=""+G[H.getUTCMonth()];break;case"p":J=(I)?("am"):("pm");break;case"P":J=(I)?("AM"):("PM");break}D.push(J);M=false}else{if(J=="%"){M=true}else{D.push(J)}}}return D.join("")};function A(E,D){return D*Math.floor(E/D)}})(jQuery);
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.flot.navigate.js b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.flot.navigate.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.flot.navigate.js
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.flot.valuelabels.js b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.flot.valuelabels.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/jquery/jquery.flot.valuelabels.js
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/jst/jsevalcontext.js b/chrome/common/extensions/docs/examples/extensions/benchmark/jst/jsevalcontext.js
new file mode 100644
index 0000000..4f2a5ee
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/jst/jsevalcontext.js
@@ -0,0 +1,409 @@
+// Copyright 2006 Google Inc.

+//

+// Licensed under the Apache License, Version 2.0 (the "License");

+// you may not use this file except in compliance with the License.

+// You may obtain a copy of the License at

+//

+// http://www.apache.org/licenses/LICENSE-2.0

+//

+// Unless required by applicable law or agreed to in writing, software

+// distributed under the License is distributed on an "AS IS" BASIS,

+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or

+// implied. See the License for the specific language governing

+// permissions and limitations under the License.

+/**

+ * Author: Steffen Meschkat <mesch@google.com>

+ *

+ * @fileoverview This class is used to evaluate expressions in a local

+ * context. Used by JstProcessor.

+ */

+

+

+/**

+ * Names of special variables defined by the jstemplate evaluation

+ * context. These can be used in js expression in jstemplate

+ * attributes.

+ */

+var VAR_index = '$index';

+var VAR_count = '$count';

+var VAR_this = '$this';

+var VAR_context = '$context';

+var VAR_top = '$top';

+

+

+/**

+ * The name of the global variable which holds the value to be returned if

+ * context evaluation results in an error. 

+ * Use JsEvalContext.setGlobal(GLOB_default, value) to set this.

+ */

+var GLOB_default = '$default';

+

+

+/**

+ * Un-inlined literals, to avoid object creation in IE6. TODO(mesch):

+ * So far, these are only used here, but we could use them thoughout

+ * the code and thus move them to constants.js.

+ */

+var CHAR_colon = ':';

+var REGEXP_semicolon = /\s*;\s*/;

+

+

+/**

+ * See constructor_()

+ * @param {Object|null} opt_data

+ * @param {Object} opt_parent

+ * @constructor

+ */

+function JsEvalContext(opt_data, opt_parent) {

+  this.constructor_.apply(this, arguments);

+}

+

+/**

+ * Context for processing a jstemplate. The context contains a context

+ * object, whose properties can be referred to in jstemplate

+ * expressions, and it holds the locally defined variables.

+ *

+ * @param {Object|null} opt_data The context object. Null if no context.

+ *

+ * @param {Object} opt_parent The parent context, from which local

+ * variables are inherited. Normally the context object of the parent

+ * context is the object whose property the parent object is. Null for the

+ * context of the root object.

+ */

+JsEvalContext.prototype.constructor_ = function(opt_data, opt_parent) {

+  var me = this;

+

+  /**

+   * The context for variable definitions in which the jstemplate

+   * expressions are evaluated. Other than for the local context,

+   * which replaces the parent context, variable definitions of the

+   * parent are inherited. The special variable $this points to data_.

+   *

+   * If this instance is recycled from the cache, then the property is

+   * already initialized.

+   *

+   * @type {Object}

+   */

+  if (!me.vars_) {

+    me.vars_ = {};

+  }

+  if (opt_parent) {

+    // If there is a parent node, inherit local variables from the

+    // parent.

+    copyProperties(me.vars_, opt_parent.vars_);

+  } else {

+    // If a root node, inherit global symbols. Since every parent

+    // chain has a root with no parent, global variables will be

+    // present in the case above too. This means that globals can be

+    // overridden by locals, as it should be.

+    copyProperties(me.vars_, JsEvalContext.globals_);

+  }

+

+  /**

+   * The current context object is assigned to the special variable

+   * $this so it is possible to use it in expressions.

+   * @type Object

+   */

+  me.vars_[VAR_this] = opt_data;

+

+  /**

+   * The entire context structure is exposed as a variable so it can be

+   * passed to javascript invocations through jseval.

+   */

+  me.vars_[VAR_context] = me;

+

+  /**

+   * The local context of the input data in which the jstemplate

+   * expressions are evaluated. Notice that this is usually an Object,

+   * but it can also be a scalar value (and then still the expression

+   * $this can be used to refer to it). Notice this can even be value,

+   * undefined or null. Hence, we have to protect jsexec() from using

+   * undefined or null, yet we want $this to reflect the true value of

+   * the current context. Thus we assign the original value to $this,

+   * above, but for the expression context we replace null and

+   * undefined by the empty string.

+   *

+   * @type {Object|null}

+   */

+  me.data_ = getDefaultObject(opt_data, STRING_empty);

+

+  if (!opt_parent) {

+    // If this is a top-level context, create a variable reference to the data

+    // to allow for  accessing top-level properties of the original context

+    // data from child contexts.

+    me.vars_[VAR_top] = me.data_;

+  }

+};

+

+

+/**

+ * A map of globally defined symbols. Every instance of JsExprContext

+ * inherits them in its vars_.

+ * @type Object

+ */

+JsEvalContext.globals_ = {}

+

+

+/**

+ * Sets a global symbol. It will be available like a variable in every

+ * JsEvalContext instance. This is intended mainly to register

+ * immutable global objects, such as functions, at load time, and not

+ * to add global data at runtime. I.e. the same objections as to

+ * global variables in general apply also here. (Hence the name

+ * "global", and not "global var".)

+ * @param {string} name

+ * @param {Object|null} value

+ */

+JsEvalContext.setGlobal = function(name, value) {

+  JsEvalContext.globals_[name] = value;

+};

+

+

+/**

+ * Set the default value to be returned if context evaluation results in an 

+ * error. (This can occur if a non-existent value was requested). 

+ */

+JsEvalContext.setGlobal(GLOB_default, null);

+

+

+/**

+ * A cache to reuse JsEvalContext instances. (IE6 perf)

+ *

+ * @type Array.<JsEvalContext>

+ */

+JsEvalContext.recycledInstances_ = [];

+

+

+/**

+ * A factory to create a JsEvalContext instance, possibly reusing

+ * one from recycledInstances_. (IE6 perf)

+ *

+ * @param {Object} opt_data

+ * @param {JsEvalContext} opt_parent

+ * @return {JsEvalContext}

+ */

+JsEvalContext.create = function(opt_data, opt_parent) {

+  if (jsLength(JsEvalContext.recycledInstances_) > 0) {

+    var instance = JsEvalContext.recycledInstances_.pop();

+    JsEvalContext.call(instance, opt_data, opt_parent);

+    return instance;

+  } else {

+    return new JsEvalContext(opt_data, opt_parent);

+  }

+};

+

+

+/**

+ * Recycle a used JsEvalContext instance, so we can avoid creating one

+ * the next time we need one. (IE6 perf)

+ *

+ * @param {JsEvalContext} instance

+ */

+JsEvalContext.recycle = function(instance) {

+  for (var i in instance.vars_) {

+    // NOTE(mesch): We avoid object creation here. (IE6 perf)

+    delete instance.vars_[i];

+  }

+  instance.data_ = null;

+  JsEvalContext.recycledInstances_.push(instance);

+};

+

+

+/**

+ * Executes a function created using jsEvalToFunction() in the context

+ * of vars, data, and template.

+ *

+ * @param {Function} exprFunction A javascript function created from

+ * a jstemplate attribute value.

+ *

+ * @param {Element} template DOM node of the template.

+ *

+ * @return {Object|null} The value of the expression from which

+ * exprFunction was created in the current js expression context and

+ * the context of template.

+ */

+JsEvalContext.prototype.jsexec = function(exprFunction, template) {

+  try {

+    return exprFunction.call(template, this.vars_, this.data_);

+  } catch (e) {

+    log('jsexec EXCEPTION: ' + e + ' at ' + template +

+        ' with ' + exprFunction);

+    return JsEvalContext.globals_[GLOB_default];

+  }

+};

+

+

+/**

+ * Clones the current context for a new context object. The cloned

+ * context has the data object as its context object and the current

+ * context as its parent context. It also sets the $index variable to

+ * the given value. This value usually is the position of the data

+ * object in a list for which a template is instantiated multiply.

+ *

+ * @param {Object} data The new context object.

+ *

+ * @param {number} index Position of the new context when multiply

+ * instantiated. (See implementation of jstSelect().)

+ * 

+ * @param {number} count The total number of contexts that were multiply

+ * instantiated. (See implementation of jstSelect().)

+ *

+ * @return {JsEvalContext}

+ */

+JsEvalContext.prototype.clone = function(data, index, count) {

+  var ret = JsEvalContext.create(data, this);

+  ret.setVariable(VAR_index, index);

+  ret.setVariable(VAR_count, count);

+  return ret;

+};

+

+

+/**

+ * Binds a local variable to the given value. If set from jstemplate

+ * jsvalue expressions, variable names must start with $, but in the

+ * API they only have to be valid javascript identifier.

+ *

+ * @param {string} name

+ *

+ * @param {Object?} value

+ */

+JsEvalContext.prototype.setVariable = function(name, value) {

+  this.vars_[name] = value;

+};

+

+

+/**

+ * Returns the value bound to the local variable of the given name, or

+ * undefined if it wasn't set. There is no way to distinguish a

+ * variable that wasn't set from a variable that was set to

+ * undefined. Used mostly for testing.

+ *

+ * @param {string} name

+ *

+ * @return {Object?} value

+ */

+JsEvalContext.prototype.getVariable = function(name) {

+  return this.vars_[name];

+};

+

+

+/**

+ * Evaluates a string expression within the scope of this context

+ * and returns the result.

+ *

+ * @param {string} expr A javascript expression

+ * @param {Element} opt_template An optional node to serve as "this"

+ *

+ * @return {Object?} value

+ */

+JsEvalContext.prototype.evalExpression = function(expr, opt_template) {

+  var exprFunction = jsEvalToFunction(expr);

+  return this.jsexec(exprFunction, opt_template);

+};

+

+

+/**

+ * Uninlined string literals for jsEvalToFunction() (IE6 perf).

+ */

+var STRING_a = 'a_';

+var STRING_b = 'b_';

+var STRING_with = 'with (a_) with (b_) return ';

+

+

+/**

+ * Cache for jsEvalToFunction results.

+ * @type Object

+ */

+JsEvalContext.evalToFunctionCache_ = {};

+

+

+/**

+ * Evaluates the given expression as the body of a function that takes

+ * vars and data as arguments. Since the resulting function depends

+ * only on expr, we cache the result so we save some Function

+ * invocations, and some object creations in IE6.

+ *

+ * @param {string} expr A javascript expression.

+ *

+ * @return {Function} A function that returns the value of expr in the

+ * context of vars and data.

+ */

+function jsEvalToFunction(expr) {

+  if (!JsEvalContext.evalToFunctionCache_[expr]) {

+    try {

+      // NOTE(mesch): The Function constructor is faster than eval().

+      JsEvalContext.evalToFunctionCache_[expr] =

+        new Function(STRING_a, STRING_b, STRING_with + expr);

+    } catch (e) {

+      log('jsEvalToFunction (' + expr + ') EXCEPTION ' + e);

+    }

+  }

+  return JsEvalContext.evalToFunctionCache_[expr];

+}

+

+

+/**

+ * Evaluates the given expression to itself. This is meant to pass

+ * through string attribute values.

+ *

+ * @param {string} expr

+ *

+ * @return {string}

+ */

+function jsEvalToSelf(expr) {

+  return expr;

+}

+

+

+/**

+ * Parses the value of the jsvalues attribute in jstemplates: splits

+ * it up into a map of labels and expressions, and creates functions

+ * from the expressions that are suitable for execution by

+ * JsEvalContext.jsexec(). All that is returned as a flattened array

+ * of pairs of a String and a Function.

+ *

+ * @param {string} expr

+ *

+ * @return {Array}

+ */

+function jsEvalToValues(expr) {

+  // TODO(mesch): It is insufficient to split the values by simply

+  // finding semi-colons, as the semi-colon may be part of a string

+  // constant or escaped.

+  var ret = [];

+  var values = expr.split(REGEXP_semicolon);

+  for (var i = 0, I = jsLength(values); i < I; ++i) {

+    var colon = values[i].indexOf(CHAR_colon);

+    if (colon < 0) {

+      continue;

+    }

+    var label = stringTrim(values[i].substr(0, colon));

+    var value = jsEvalToFunction(values[i].substr(colon + 1));

+    ret.push(label, value);

+  }

+  return ret;

+}

+

+

+/**

+ * Parses the value of the jseval attribute of jstemplates: splits it

+ * up into a list of expressions, and creates functions from the

+ * expressions that are suitable for execution by

+ * JsEvalContext.jsexec(). All that is returned as an Array of

+ * Function.

+ *

+ * @param {string} expr

+ *

+ * @return {Array.<Function>}

+ */

+function jsEvalToExpressions(expr) {

+  var ret = [];

+  var values = expr.split(REGEXP_semicolon);

+  for (var i = 0, I = jsLength(values); i < I; ++i) {

+    if (values[i]) {

+      var value = jsEvalToFunction(values[i]);

+      ret.push(value);

+    }

+  }

+  return ret;

+}

diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/jst/jstemplate.js b/chrome/common/extensions/docs/examples/extensions/benchmark/jst/jstemplate.js
new file mode 100644
index 0000000..d2cc386
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/jst/jstemplate.js
@@ -0,0 +1,1018 @@
+// Copyright 2006 Google Inc.

+//

+// Licensed under the Apache License, Version 2.0 (the "License");

+// you may not use this file except in compliance with the License.

+// You may obtain a copy of the License at

+//

+// http://www.apache.org/licenses/LICENSE-2.0

+//

+// Unless required by applicable law or agreed to in writing, software

+// distributed under the License is distributed on an "AS IS" BASIS,

+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or

+// implied. See the License for the specific language governing

+// permissions and limitations under the License.

+/**

+ * Author: Steffen Meschkat <mesch@google.com>

+ *

+ * @fileoverview A simple formatter to project JavaScript data into

+ * HTML templates. The template is edited in place. I.e. in order to

+ * instantiate a template, clone it from the DOM first, and then

+ * process the cloned template. This allows for updating of templates:

+ * If the templates is processed again, changed values are merely

+ * updated.

+ *

+ * NOTE(mesch): IE DOM doesn't have importNode().

+ *

+ * NOTE(mesch): The property name "length" must not be used in input

+ * data, see comment in jstSelect_().

+ */

+

+

+/**

+ * Names of jstemplate attributes. These attributes are attached to

+ * normal HTML elements and bind expression context data to the HTML

+ * fragment that is used as template.

+ */

+var ATT_select = 'jsselect';

+var ATT_instance = 'jsinstance';

+var ATT_display = 'jsdisplay';

+var ATT_values = 'jsvalues';

+var ATT_vars = 'jsvars';

+var ATT_eval = 'jseval';

+var ATT_transclude = 'transclude';

+var ATT_content = 'jscontent';

+var ATT_skip = 'jsskip';

+

+

+/**

+ * Name of the attribute that caches a reference to the parsed

+ * template processing attribute values on a template node.

+ */

+var ATT_jstcache = 'jstcache';

+

+

+/**

+ * Name of the property that caches the parsed template processing

+ * attribute values on a template node.

+ */

+var PROP_jstcache = '__jstcache';

+

+

+/**

+ * ID of the element that contains dynamically loaded jstemplates.

+ */

+var STRING_jsts = 'jsts';

+

+

+/**

+ * Un-inlined string literals, to avoid object creation in

+ * IE6.

+ */

+var CHAR_asterisk = '*';

+var CHAR_dollar = '$';

+var CHAR_period = '.';

+var CHAR_ampersand = '&';

+var STRING_div = 'div';

+var STRING_id = 'id';

+var STRING_asteriskzero = '*0';

+var STRING_zero = '0';

+

+

+/**

+ * HTML template processor. Data values are bound to HTML templates

+ * using the attributes transclude, jsselect, jsdisplay, jscontent,

+ * jsvalues. The template is modifed in place. The values of those

+ * attributes are JavaScript expressions that are evaluated in the

+ * context of the data object fragment.

+ *

+ * @param {JsEvalContext} context Context created from the input data

+ * object.

+ *

+ * @param {Element} template DOM node of the template. This will be

+ * processed in place. After processing, it will still be a valid

+ * template that, if processed again with the same data, will remain

+ * unchanged.

+ *

+ * @param {boolean} opt_debugging Optional flag to collect debugging

+ *     information while processing the template.  Only takes effect

+ *     in MAPS_DEBUG.

+ */

+function jstProcess(context, template, opt_debugging) {

+  var processor = new JstProcessor;

+  if (MAPS_DEBUG && opt_debugging) {

+    processor.setDebugging(opt_debugging);

+  }

+  JstProcessor.prepareTemplate_(template);

+

+  /**

+   * Caches the document of the template node, so we don't have to

+   * access it through ownerDocument.

+   * @type Document

+   */

+  processor.document_ = ownerDocument(template);

+

+  processor.run_(bindFully(processor, processor.jstProcessOuter_,

+                           context, template));

+  if (MAPS_DEBUG && opt_debugging) {

+    log('jstProcess:' + '\n' + processor.getLogs().join('\n'));

+  }

+}

+

+

+/**

+ * Internal class used by jstemplates to maintain context.  This is

+ * necessary to process deep templates in Safari which has a

+ * relatively shallow maximum recursion depth of 100.

+ * @class

+ * @constructor

+ */

+function JstProcessor() {

+  if (MAPS_DEBUG) {

+    /**

+     * An array of logging messages.  These are collected during processing

+     * and dumped to the console at the end.

+     * @type Array.<string>

+     */

+    this.logs_ = [];

+  }

+}

+

+

+/**

+ * Counter to generate node ids. These ids will be stored in

+ * ATT_jstcache and be used to lookup the preprocessed js attributes

+ * from the jstcache_. The id is stored in an attribute so it

+ * suvives cloneNode() and thus cloned template nodes can share the

+ * same cache entry.

+ * @type number

+ */

+JstProcessor.jstid_ = 0;

+

+

+/**

+ * Map from jstid to processed js attributes.

+ * @type Object

+ */

+JstProcessor.jstcache_ = {};

+

+/**

+ * The neutral cache entry. Used for all nodes that don't have any

+ * jst attributes. We still set the jsid attribute on those nodes so

+ * we can avoid to look again for all the other jst attributes that

+ * aren't there. Remember: not only the processing of the js

+ * attribute values is expensive and we thus want to cache it. The

+ * access to the attributes on the Node in the first place is

+ * expensive too.

+ */

+JstProcessor.jstcache_[0] = {};

+

+

+/**

+ * Map from concatenated attribute string to jstid.

+ * The key is the concatenation of all jst atributes found on a node

+ * formatted as "name1=value1&name2=value2&...", in the order defined by

+ * JST_ATTRIBUTES. The value is the id of the jstcache_ entry that can

+ * be used for this node. This allows the reuse of cache entries in cases

+ * when a cached entry already exists for a given combination of attribute

+ * values. (For example when two different nodes in a template share the same

+ * JST attributes.)

+ * @type Object

+ */

+JstProcessor.jstcacheattributes_ = {};

+

+

+/**

+ * Map for storing temporary attribute values in prepareNode_() so they don't

+ * have to be retrieved twice. (IE6 perf)

+ * @type Object

+ */

+JstProcessor.attributeValues_ = {};

+

+

+/**

+ * A list for storing non-empty attributes found on a node in prepareNode_().

+ * The array is global since it can be reused - this way there is no need to

+ * construct a new array object for each invocation. (IE6 perf)

+ * @type Array

+ */

+JstProcessor.attributeList_ = [];

+

+

+/**

+ * Prepares the template: preprocesses all jstemplate attributes.

+ *

+ * @param {Element} template

+ */

+JstProcessor.prepareTemplate_ = function(template) {

+  if (!template[PROP_jstcache]) {

+    domTraverseElements(template, function(node) {

+      JstProcessor.prepareNode_(node);

+    });

+  }

+};

+

+

+/**

+ * A list of attributes we use to specify jst processing instructions,

+ * and the functions used to parse their values.

+ *

+ * @type Array.<Array>

+ */

+var JST_ATTRIBUTES = [

+    [ ATT_select, jsEvalToFunction ],

+    [ ATT_display, jsEvalToFunction ],

+    [ ATT_values, jsEvalToValues ],

+    [ ATT_vars, jsEvalToValues ],

+    [ ATT_eval, jsEvalToExpressions ],

+    [ ATT_transclude, jsEvalToSelf ],

+    [ ATT_content, jsEvalToFunction ],

+    [ ATT_skip, jsEvalToFunction ]

+];

+

+

+/**

+ * Prepares a single node: preprocesses all template attributes of the

+ * node, and if there are any, assigns a jsid attribute and stores the

+ * preprocessed attributes under the jsid in the jstcache.

+ *

+ * @param {Element} node

+ *

+ * @return {Object} The jstcache entry. The processed jst attributes

+ * are properties of this object. If the node has no jst attributes,

+ * returns an object with no properties (the jscache_[0] entry).

+ */

+JstProcessor.prepareNode_ = function(node) {

+  // If the node already has a cache property, return it.

+  if (node[PROP_jstcache]) {

+    return node[PROP_jstcache];

+  }

+

+  // If it is not found, we always set the PROP_jstcache property on the node.

+  // Accessing the property is faster than executing getAttribute(). If we

+  // don't find the property on a node that was cloned in jstSelect_(), we

+  // will fall back to check for the attribute and set the property

+  // from cache.

+

+  // If the node has an attribute indexing a cache object, set it as a property

+  // and return it.

+  var jstid = domGetAttribute(node, ATT_jstcache);

+  if (jstid != null) {

+    return node[PROP_jstcache] = JstProcessor.jstcache_[jstid];

+  }

+

+  var attributeValues = JstProcessor.attributeValues_;

+  var attributeList = JstProcessor.attributeList_;

+  attributeList.length = 0;

+

+  // Look for interesting attributes.

+  for (var i = 0, I = jsLength(JST_ATTRIBUTES); i < I; ++i) {

+    var name = JST_ATTRIBUTES[i][0];

+    var value = domGetAttribute(node, name);

+    attributeValues[name] = value;

+    if (value != null) {

+      attributeList.push(name + "=" + value);

+    }

+  }

+

+  // If none found, mark this node to prevent further inspection, and return

+  // an empty cache object.

+  if (attributeList.length == 0) {

+    domSetAttribute(node, ATT_jstcache, STRING_zero);

+    return node[PROP_jstcache] = JstProcessor.jstcache_[0];

+  }

+

+  // If we already have a cache object corresponding to these attributes,

+  // annotate the node with it, and return it.

+  var attstring = attributeList.join(CHAR_ampersand);

+  if (jstid = JstProcessor.jstcacheattributes_[attstring]) {

+    domSetAttribute(node, ATT_jstcache, jstid);

+    return node[PROP_jstcache] = JstProcessor.jstcache_[jstid];

+  }

+

+  // Otherwise, build a new cache object.

+  var jstcache = {};

+  for (var i = 0, I = jsLength(JST_ATTRIBUTES); i < I; ++i) {

+    var att = JST_ATTRIBUTES[i];

+    var name = att[0];

+    var parse = att[1];

+    var value = attributeValues[name];

+    if (value != null) {

+      jstcache[name] = parse(value);

+      if (MAPS_DEBUG) {

+        jstcache.jstAttributeValues = jstcache.jstAttributeValues || {};

+        jstcache.jstAttributeValues[name] = value;

+      }

+    }

+  }

+

+  jstid = STRING_empty + ++JstProcessor.jstid_;

+  domSetAttribute(node, ATT_jstcache, jstid);

+  JstProcessor.jstcache_[jstid] = jstcache;

+  JstProcessor.jstcacheattributes_[attstring] = jstid;

+

+  return node[PROP_jstcache] = jstcache;

+};

+

+

+/**

+ * Runs the given function in our state machine.

+ *

+ * It's informative to view the set of all function calls as a tree:

+ * - nodes are states

+ * - edges are state transitions, implemented as calls to the pending

+ *   functions in the stack.

+ *   - pre-order function calls are downward edges (recursion into call).

+ *   - post-order function calls are upward edges (return from call).

+ * - leaves are nodes which do not recurse.

+ * We represent the call tree as an array of array of calls, indexed as

+ * stack[depth][index].  Here [depth] indexes into the call stack, and

+ * [index] indexes into the call queue at that depth.  We require a call

+ * queue so that a node may branch to more than one child

+ * (which will be called serially), typically due to a loop structure.

+ *

+ * @param {Function} f The first function to run.

+ */

+JstProcessor.prototype.run_ = function(f) {

+  var me = this;

+

+  /**

+   * A stack of queues of pre-order calls.

+   * The inner arrays (constituent queues) are structured as

+   * [ arg2, arg1, method, arg2, arg1, method, ...]

+   * ie. a flattened array of methods with 2 arguments, in reverse order

+   * for efficient push/pop.

+   *

+   * The outer array is a stack of such queues.

+   *

+   * @type Array.<Array>

+   */

+  var calls = me.calls_ = [];

+

+  /**

+   * The index into the queue for each depth. NOTE: Alternative would

+   * be to maintain the queues in reverse order (popping off of the

+   * end) but the repeated calls to .pop() consumed 90% of this

+   * function's execution time.

+   * @type Array.<number>

+   */

+  var queueIndices = me.queueIndices_ = [];

+

+  /**

+   * A pool of empty arrays.  Minimizes object allocation for IE6's benefit.

+   * @type Array.<Array>

+   */

+  var arrayPool = me.arrayPool_ = [];

+

+  f();

+  var queue, queueIndex;

+  var method, arg1, arg2;

+  var temp;

+  while (calls.length) {

+    queue = calls[calls.length - 1];

+    queueIndex = queueIndices[queueIndices.length - 1];

+    if (queueIndex >= queue.length) {

+      me.recycleArray_(calls.pop());

+      queueIndices.pop();

+      continue;

+    }

+

+    // Run the first function in the queue.

+    method = queue[queueIndex++];

+    arg1 = queue[queueIndex++];

+    arg2 = queue[queueIndex++];

+    queueIndices[queueIndices.length - 1] = queueIndex;

+    method.call(me, arg1, arg2);

+  }

+};

+

+

+/**

+ * Pushes one or more functions onto the stack.  These will be run in sequence,

+ * interspersed with any recursive calls that they make.

+ *

+ * This method takes ownership of the given array!

+ *

+ * @param {Array} args Array of method calls structured as

+ *     [ method, arg1, arg2, method, arg1, arg2, ... ]

+ */

+JstProcessor.prototype.push_ = function(args) {

+  this.calls_.push(args);

+  this.queueIndices_.push(0);

+};

+

+

+/**

+ * Enable/disable debugging.

+ * @param {boolean} debugging New state

+ */

+JstProcessor.prototype.setDebugging = function(debugging) {

+  if (MAPS_DEBUG) {

+    this.debugging_ = debugging;

+  }

+};

+

+

+JstProcessor.prototype.createArray_ = function() {

+  if (this.arrayPool_.length) {

+    return this.arrayPool_.pop();

+  } else {

+    return [];

+  }

+};

+

+

+JstProcessor.prototype.recycleArray_ = function(array) {

+  arrayClear(array);

+  this.arrayPool_.push(array);

+};

+

+/**

+ * Implements internals of jstProcess. This processes the two

+ * attributes transclude and jsselect, which replace or multiply

+ * elements, hence the name "outer". The remainder of the attributes

+ * is processed in jstProcessInner_(), below. That function

+ * jsProcessInner_() only processes attributes that affect an existing

+ * node, but doesn't create or destroy nodes, hence the name

+ * "inner". jstProcessInner_() is called through jstSelect_() if there

+ * is a jsselect attribute (possibly for newly created clones of the

+ * current template node), or directly from here if there is none.

+ *

+ * @param {JsEvalContext} context

+ *

+ * @param {Element} template

+ */

+JstProcessor.prototype.jstProcessOuter_ = function(context, template) {

+  var me = this;

+

+  var jstAttributes = me.jstAttributes_(template);

+  if (MAPS_DEBUG && me.debugging_) {

+    me.logState_('Outer', template, jstAttributes.jstAttributeValues);

+  }

+

+  var transclude = jstAttributes[ATT_transclude];

+  if (transclude) {

+    var tr = jstGetTemplate(transclude);

+    if (tr) {

+      domReplaceChild(tr, template);

+      var call = me.createArray_();

+      call.push(me.jstProcessOuter_, context, tr);

+      me.push_(call);

+    } else {

+      domRemoveNode(template);

+    }

+    return;

+  }

+

+  var select = jstAttributes[ATT_select];

+  if (select) {

+    me.jstSelect_(context, template, select);

+  } else {

+    me.jstProcessInner_(context, template);

+  }

+};

+

+

+/**

+ * Implements internals of jstProcess. This processes all attributes

+ * except transclude and jsselect. It is called either from

+ * jstSelect_() for nodes that have a jsselect attribute so that the

+ * jsselect attribute will not be processed again, or else directly

+ * from jstProcessOuter_(). See the comment on jstProcessOuter_() for

+ * an explanation of the name.

+ *

+ * @param {JsEvalContext} context

+ *

+ * @param {Element} template

+ */

+JstProcessor.prototype.jstProcessInner_ = function(context, template) {

+  var me = this;

+

+  var jstAttributes = me.jstAttributes_(template);

+  if (MAPS_DEBUG && me.debugging_) {

+    me.logState_('Inner', template, jstAttributes.jstAttributeValues);

+  }

+

+  // NOTE(mesch): See NOTE on ATT_content why this is a separate

+  // attribute, and not a special value in ATT_values.

+  var display = jstAttributes[ATT_display];

+  if (display) {

+    var shouldDisplay = context.jsexec(display, template);

+    if (MAPS_DEBUG && me.debugging_) {

+      me.logs_.push(ATT_display + ': ' + shouldDisplay + '<br/>');

+    }

+    if (!shouldDisplay) {

+      displayNone(template);

+      return;

+    }

+    displayDefault(template);

+  }

+

+  // NOTE(mesch): jsvars is evaluated before jsvalues, because it's

+  // more useful to be able to use var values in attribute value

+  // expressions than vice versa.

+  var values = jstAttributes[ATT_vars];

+  if (values) {

+    me.jstVars_(context, template, values);

+  }

+

+  values = jstAttributes[ATT_values];

+  if (values) {

+    me.jstValues_(context, template, values);

+  }

+

+  // Evaluate expressions immediately. Useful for hooking callbacks

+  // into jstemplates.

+  //

+  // NOTE(mesch): Evaluation order is sometimes significant, e.g. when

+  // the expression evaluated in jseval relies on the values set in

+  // jsvalues, so it needs to be evaluated *after*

+  // jsvalues. TODO(mesch): This is quite arbitrary, it would be

+  // better if this would have more necessity to it.

+  var expressions = jstAttributes[ATT_eval];

+  if (expressions) {

+    for (var i = 0, I = jsLength(expressions); i < I; ++i) {

+      context.jsexec(expressions[i], template);

+    }

+  }

+

+  var skip = jstAttributes[ATT_skip];

+  if (skip) {

+    var shouldSkip = context.jsexec(skip, template);

+    if (MAPS_DEBUG && me.debugging_) {

+      me.logs_.push(ATT_skip + ': ' + shouldSkip + '<br/>');

+    }

+    if (shouldSkip) return;

+  }

+

+  // NOTE(mesch): content is a separate attribute, instead of just a

+  // special value mentioned in values, for two reasons: (1) it is

+  // fairly common to have only mapped content, and writing

+  // content="expr" is shorter than writing values="content:expr", and

+  // (2) the presence of content actually terminates traversal, and we

+  // need to check for that. Display is a separate attribute for a

+  // reason similar to the second, in that its presence *may*

+  // terminate traversal.

+  var content = jstAttributes[ATT_content];

+  if (content) {

+    me.jstContent_(context, template, content);

+

+  } else {

+    // Newly generated children should be ignored, so we explicitly

+    // store the children to be processed.

+    var queue = me.createArray_();

+    for (var c = template.firstChild; c; c = c.nextSibling) {

+      if (c.nodeType == DOM_ELEMENT_NODE) {

+        queue.push(me.jstProcessOuter_, context, c);

+      }

+    }

+    if (queue.length) me.push_(queue);

+  }

+};

+

+

+/**

+ * Implements the jsselect attribute: evalutes the value of the

+ * jsselect attribute in the current context, with the current

+ * variable bindings (see JsEvalContext.jseval()). If the value is an

+ * array, the current template node is multiplied once for every

+ * element in the array, with the array element being the context

+ * object. If the array is empty, or the value is undefined, then the

+ * current template node is dropped. If the value is not an array,

+ * then it is just made the context object.

+ *

+ * @param {JsEvalContext} context The current evaluation context.

+ *

+ * @param {Element} template The currently processed node of the template.

+ *

+ * @param {Function} select The javascript expression to evaluate.

+ *

+ * @notypecheck FIXME(hmitchell): See OCL6434950. instance and value need

+ * type checks.

+ */

+JstProcessor.prototype.jstSelect_ = function(context, template, select) {

+  var me = this;

+

+  var value = context.jsexec(select, template);

+

+  // Enable reprocessing: if this template is reprocessed, then only

+  // fill the section instance here. Otherwise do the cardinal

+  // processing of a new template.

+  var instance = domGetAttribute(template, ATT_instance);

+

+  var instanceLast = false;

+  if (instance) {

+    if (instance.charAt(0) == CHAR_asterisk) {

+      instance = parseInt10(instance.substr(1));

+      instanceLast = true;

+    } else {

+      instance = parseInt10(/** @type string */(instance));

+    }

+  }

+

+  // The expression value instanceof Array is occasionally false for

+  // arrays, seen in Firefox. Thus we recognize an array as an object

+  // which is not null that has a length property. Notice that this

+  // also matches input data with a length property, so this property

+  // name should be avoided in input data.

+  var multiple = isArray(value);

+  var count = multiple ? jsLength(value) : 1;

+  var multipleEmpty = (multiple && count == 0);

+

+  if (multiple) {

+    if (multipleEmpty) {

+      // For an empty array, keep the first template instance and mark

+      // it last. Remove all other template instances.

+      if (!instance) {

+        domSetAttribute(template, ATT_instance, STRING_asteriskzero);

+        displayNone(template);

+      } else {

+        domRemoveNode(template);

+      }

+

+    } else {

+      displayDefault(template);

+      // For a non empty array, create as many template instances as

+      // are needed. If the template is first processed, as many

+      // template instances are needed as there are values in the

+      // array. If the template is reprocessed, new template instances

+      // are only needed if there are more array values than template

+      // instances. Those additional instances are created by

+      // replicating the last template instance.

+      //

+      // When the template is first processed, there is no jsinstance

+      // attribute. This is indicated by instance === null, except in

+      // opera it is instance === "". Notice also that the === is

+      // essential, because 0 == "", presumably via type coercion to

+      // boolean.

+      if (instance === null || instance === STRING_empty ||

+          (instanceLast && instance < count - 1)) {

+        // A queue of calls to push.

+        var queue = me.createArray_();

+

+        var instancesStart = instance || 0;

+        var i, I, clone;

+        for (i = instancesStart, I = count - 1; i < I; ++i) {

+          var node = domCloneNode(template);

+          domInsertBefore(node, template);

+

+          jstSetInstance(/** @type Element */(node), value, i);

+          clone = context.clone(value[i], i, count);

+

+          queue.push(me.jstProcessInner_, clone, node,

+                     JsEvalContext.recycle, clone, null);

+                     

+        }

+        // Push the originally present template instance last to keep

+        // the order aligned with the DOM order, because the newly

+        // created template instances are inserted *before* the

+        // original instance.

+        jstSetInstance(template, value, i);

+        clone = context.clone(value[i], i, count);

+        queue.push(me.jstProcessInner_, clone, template,

+                   JsEvalContext.recycle, clone, null);

+        me.push_(queue);

+      } else if (instance < count) {

+        var v = value[instance];

+

+        jstSetInstance(template, value, instance);

+        var clone = context.clone(v, instance, count);

+        var queue = me.createArray_();

+        queue.push(me.jstProcessInner_, clone, template,

+                   JsEvalContext.recycle, clone, null);

+        me.push_(queue);

+      } else {

+        domRemoveNode(template);

+      }

+    }

+  } else {

+    if (value == null) {

+      displayNone(template);

+    } else {

+      displayDefault(template);

+      var clone = context.clone(value, 0, 1);

+      var queue = me.createArray_();

+      queue.push(me.jstProcessInner_, clone, template,

+                 JsEvalContext.recycle, clone, null);

+      me.push_(queue);

+    }

+  }

+};

+

+

+/**

+ * Implements the jsvars attribute: evaluates each of the values and

+ * assigns them to variables in the current context. Similar to

+ * jsvalues, except that all values are treated as vars, independent

+ * of their names.

+ *

+ * @param {JsEvalContext} context Current evaluation context.

+ *

+ * @param {Element} template Currently processed template node.

+ *

+ * @param {Array} values Processed value of the jsvalues attribute: a

+ * flattened array of pairs. The second element in the pair is a

+ * function that can be passed to jsexec() for evaluation in the

+ * current jscontext, and the first element is the variable name that

+ * the value returned by jsexec is assigned to.

+ */

+JstProcessor.prototype.jstVars_ = function(context, template, values) {

+  for (var i = 0, I = jsLength(values); i < I; i += 2) {

+    var label = values[i];

+    var value = context.jsexec(values[i+1], template);

+    context.setVariable(label, value);

+  }

+};

+

+

+/**

+ * Implements the jsvalues attribute: evaluates each of the values and

+ * assigns them to variables in the current context (if the name

+ * starts with '$', javascript properties of the current template node

+ * (if the name starts with '.'), or DOM attributes of the current

+ * template node (otherwise). Since DOM attribute values are always

+ * strings, the value is coerced to string in the latter case,

+ * otherwise it's the uncoerced javascript value.

+ *

+ * @param {JsEvalContext} context Current evaluation context.

+ *

+ * @param {Element} template Currently processed template node.

+ *

+ * @param {Array} values Processed value of the jsvalues attribute: a

+ * flattened array of pairs. The second element in the pair is a

+ * function that can be passed to jsexec() for evaluation in the

+ * current jscontext, and the first element is the label that

+ * determines where the value returned by jsexec is assigned to.

+ */

+JstProcessor.prototype.jstValues_ = function(context, template, values) {

+  for (var i = 0, I = jsLength(values); i < I; i += 2) {

+    var label = values[i];

+    var value = context.jsexec(values[i+1], template);

+

+    if (label.charAt(0) == CHAR_dollar) {

+      // A jsvalues entry whose name starts with $ sets a local

+      // variable.

+      context.setVariable(label, value);

+

+    } else if (label.charAt(0) == CHAR_period) {

+      // A jsvalues entry whose name starts with . sets a property of

+      // the current template node. The name may have further dot

+      // separated components, which are translated into namespace

+      // objects. This specifically allows to set properties on .style

+      // using jsvalues. NOTE(mesch): Setting the style attribute has

+      // no effect in IE and hence should not be done anyway.

+      var nameSpaceLabel = label.substr(1).split(CHAR_period);

+      var nameSpaceObject = template;

+      var nameSpaceDepth = jsLength(nameSpaceLabel);

+      for (var j = 0, J = nameSpaceDepth - 1; j < J; ++j) {

+        var jLabel = nameSpaceLabel[j];

+        if (!nameSpaceObject[jLabel]) {

+          nameSpaceObject[jLabel] = {};

+        }

+        nameSpaceObject = nameSpaceObject[jLabel];

+      }

+      nameSpaceObject[nameSpaceLabel[nameSpaceDepth - 1]] = value;

+

+    } else if (label) {

+      // Any other jsvalues entry sets an attribute of the current

+      // template node.

+      if (typeof value == TYPE_boolean) {

+        // Handle boolean values that are set as attributes specially,

+        // according to the XML/HTML convention.

+        if (value) {

+          domSetAttribute(template, label, label);

+        } else {

+          domRemoveAttribute(template, label);

+        }

+      } else {

+        domSetAttribute(template, label, STRING_empty + value);

+      }

+    }

+  }

+};

+

+

+/**

+ * Implements the jscontent attribute. Evalutes the expression in

+ * jscontent in the current context and with the current variables,

+ * and assigns its string value to the content of the current template

+ * node.

+ *

+ * @param {JsEvalContext} context Current evaluation context.

+ *

+ * @param {Element} template Currently processed template node.

+ *

+ * @param {Function} content Processed value of the jscontent

+ * attribute.

+ */

+JstProcessor.prototype.jstContent_ = function(context, template, content) {

+  // NOTE(mesch): Profiling shows that this method costs significant

+  // time. In jstemplate_perf.html, it's about 50%. I tried to replace

+  // by HTML escaping and assignment to innerHTML, but that was even

+  // slower.

+  var value = STRING_empty + context.jsexec(content, template);

+  // Prevent flicker when refreshing a template and the value doesn't

+  // change.

+  if (template.innerHTML == value) {

+    return;

+  }

+  while (template.firstChild) {

+    domRemoveNode(template.firstChild);

+  }

+  var t = domCreateTextNode(this.document_, value);

+  domAppendChild(template, t);

+};

+

+

+/**

+ * Caches access to and parsing of template processing attributes. If

+ * domGetAttribute() is called every time a template attribute value

+ * is used, it takes more than 10% of the time.

+ *

+ * @param {Element} template A DOM element node of the template.

+ *

+ * @return {Object} A javascript object that has all js template

+ * processing attribute values of the node as properties.

+ */

+JstProcessor.prototype.jstAttributes_ = function(template) {

+  if (template[PROP_jstcache]) {

+    return template[PROP_jstcache];

+  }

+

+  var jstid = domGetAttribute(template, ATT_jstcache);

+  if (jstid) {

+    return template[PROP_jstcache] = JstProcessor.jstcache_[jstid];

+  }

+

+  return JstProcessor.prepareNode_(template);

+};

+

+

+/**

+ * Helps to implement the transclude attribute, and is the initial

+ * call to get hold of a template from its ID.

+ *

+ * If the ID is not present in the DOM, and opt_loadHtmlFn is specified, this

+ * function will call that function and add the result to the DOM, before

+ * returning the template.

+ *

+ * @param {string} name The ID of the HTML element used as template.

+ * @param {Function} opt_loadHtmlFn A function which, when called, will return

+ *   HTML that contains an element whose ID is 'name'.

+ *

+ * @return {Element|null} The DOM node of the template. (Only element nodes

+ * can be found by ID, hence it's a Element.)

+ */

+function jstGetTemplate(name, opt_loadHtmlFn) {

+  var doc = document;

+  var section;

+  if (opt_loadHtmlFn) {

+    section = jstLoadTemplateIfNotPresent(doc, name, opt_loadHtmlFn);

+  } else {

+    section = domGetElementById(doc, name);

+  }

+  if (section) {

+    JstProcessor.prepareTemplate_(section);

+    var ret = domCloneElement(section);

+    domRemoveAttribute(ret, STRING_id);

+    return ret;

+  } else {

+    return null;

+  }

+}

+

+/**

+ * This function is the same as 'jstGetTemplate' but, if the template

+ * does not exist, throw an exception.

+ *

+ * @param {string} name The ID of the HTML element used as template.

+ * @param {Function} opt_loadHtmlFn A function which, when called, will return

+ *   HTML that contains an element whose ID is 'name'.

+ *

+ * @return {Element} The DOM node of the template. (Only element nodes

+ * can be found by ID, hence it's a Element.)

+ */

+function jstGetTemplateOrDie(name, opt_loadHtmlFn) {

+  var x = jstGetTemplate(name, opt_loadHtmlFn);

+  check(x !== null);

+  return /** @type Element */(x);

+}

+

+

+/**

+ * If an element with id 'name' is not present in the document, call loadHtmlFn

+ * and insert the result into the DOM.

+ *

+ * @param {Document} doc

+ * @param {string} name

+ * @param {Function} loadHtmlFn A function that returns HTML to be inserted

+ * into the DOM.

+ * @param {string} opt_target The id of a DOM object under which to attach the

+ *   HTML once it's inserted.  An object with this id is created if it does not

+ *   exist.

+ * @return {Element} The node whose id is 'name'

+ */

+function jstLoadTemplateIfNotPresent(doc, name, loadHtmlFn, opt_target) {

+  var section = domGetElementById(doc, name);

+  if (section) {

+    return section;

+  }

+  // Load any necessary HTML and try again.

+  jstLoadTemplate_(doc, loadHtmlFn(), opt_target || STRING_jsts);

+  var section = domGetElementById(doc, name);

+  if (!section) {

+    log("Error: jstGetTemplate was provided with opt_loadHtmlFn, " +

+	"but that function did not provide the id '" + name + "'.");

+  }

+  return /** @type Element */(section);

+}

+

+

+/**

+ * Loads the given HTML text into the given document, so that

+ * jstGetTemplate can find it.

+ *

+ * We append it to the element identified by targetId, which is hidden.

+ * If it doesn't exist, it is created.

+ *

+ * @param {Document} doc The document to create the template in.

+ *

+ * @param {string} html HTML text to be inserted into the document.

+ *

+ * @param {string} targetId The id of a DOM object under which to attach the

+ *   HTML once it's inserted.  An object with this id is created if it does not

+ *   exist.

+ */

+function jstLoadTemplate_(doc, html, targetId) {

+  var existing_target = domGetElementById(doc, targetId);

+  var target;

+  if (!existing_target) {

+    target = domCreateElement(doc, STRING_div);

+    target.id = targetId;

+    displayNone(target);

+    positionAbsolute(target);

+    domAppendChild(doc.body, target);

+  } else {

+    target = existing_target;

+  }

+  var div = domCreateElement(doc, STRING_div);

+  target.appendChild(div);

+  div.innerHTML = html;

+}

+

+

+/**

+ * Sets the jsinstance attribute on a node according to its context.

+ *

+ * @param {Element} template The template DOM node to set the instance

+ * attribute on.

+ *

+ * @param {Array} values The current input context, the array of

+ * values of which the template node will render one instance.

+ *

+ * @param {number} index The index of this template node in values.

+ */

+function jstSetInstance(template, values, index) {

+  if (index == jsLength(values) - 1) {

+    domSetAttribute(template, ATT_instance, CHAR_asterisk + index);

+  } else {

+    domSetAttribute(template, ATT_instance, STRING_empty + index);

+  }

+}

+

+

+/**

+ * Log the current state.

+ * @param {string} caller An identifier for the caller of .log_.

+ * @param {Element} template The template node being processed.

+ * @param {Object} jstAttributeValues The jst attributes of the template node.

+ */

+JstProcessor.prototype.logState_ = function(

+    caller, template, jstAttributeValues) {

+  if (MAPS_DEBUG) {

+    var msg = '<table>';

+    msg += '<caption>' + caller + '</caption>';

+    msg += '<tbody>';

+    if (template.id) {

+      msg += '<tr><td>' + 'id:' + '</td><td>' + template.id + '</td></tr>';

+    }

+    if (template.name) {

+      msg += '<tr><td>' + 'name:' + '</td><td>' + template.name + '</td></tr>';

+    }

+    if (jstAttributeValues) {

+      msg += '<tr><td>' + 'attr:' +

+      '</td><td>' + jsToSource(jstAttributeValues) + '</td></tr>';

+    }

+    msg += '</tbody></table><br/>';

+    this.logs_.push(msg);

+  }

+};

+

+

+/**

+ * Retrieve the processing logs.

+ * @return {Array.<string>} The processing logs.

+ */

+JstProcessor.prototype.getLogs = function() {

+  return this.logs_;

+};

+

diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/jst/jstemplate_test.js b/chrome/common/extensions/docs/examples/extensions/benchmark/jst/jstemplate_test.js
new file mode 100644
index 0000000..b9653e1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/jst/jstemplate_test.js
@@ -0,0 +1,357 @@
+// Copyright 2006 Google Inc.

+//

+// Licensed under the Apache License, Version 2.0 (the "License");

+// you may not use this file except in compliance with the License.

+// You may obtain a copy of the License at

+//

+// http://www.apache.org/licenses/LICENSE-2.0

+//

+// Unless required by applicable law or agreed to in writing, software

+// distributed under the License is distributed on an "AS IS" BASIS,

+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or

+// implied. See the License for the specific language governing

+// permissions and limitations under the License.

+/**

+ * @author Steffen Meschkat (mesch@google.com)

+ * @fileoverview Unittest and examples for jstemplates.

+ */

+

+function jstWrap(data, template) {

+  return jstProcess(new JsEvalContext(data), template);

+}

+

+function testJstSelect() {

+  // Template cardinality from jsselect.

+  var t = document.getElementById('t1');

+  var d = {

+    items: [ 'A', 'B', 'C', '' ]

+  }

+  jstWrap(d, t);

+

+  var h = t.innerHTML;

+  var clone = domCloneNode(t);

+  assertTrue(/>A<\/div>/.test(h));

+  assertTrue(/>B<\/div>/.test(h));

+  assertTrue(/>C<\/div>/.test(h));

+  assertTrue(/><\/div>/.test(h));

+

+  // Reprocessing with identical data.

+  jstWrap(d, t);

+  assertAttributesMatch(t, clone);

+

+  // Reprocessing with changed data.

+  d.items[1] = 'BB';

+  jstWrap(d, t);

+

+  h = t.innerHTML;

+  assertTrue(/>A<\/div>/.test(h));

+  assertFalse(/>B<\/div>/.test(h));

+  assertTrue(/>BB<\/div>/.test(h));

+  assertTrue(/>C<\/div>/.test(h));

+

+  // Reprocessing with dropped data.

+  d.items.pop();

+  d.items.pop();

+  jstWrap(d, t);

+  h = t.innerHTML;

+  assertTrue(/>A<\/div>/.test(h));

+  assertTrue(/>BB<\/div>/.test(h));

+  assertFalse(/>C<\/div>/.test(h));

+  assertFalse(/><\/div>/.test(h));

+

+  // Reprocessing with dropped data, once more.

+  d.items.pop();

+  jstWrap(d, t);

+  h = t.innerHTML;

+  assertTrue(/>A<\/div>/.test(h));

+  assertFalse(/>BB<\/div>/.test(h));

+  assertFalse(/>C<\/div>/.test(h));

+

+  // Reprocessing with empty data -- the last template instance is

+  // preserved, and only hidden.

+  d.items.pop();

+  jstWrap(d, t);

+

+  assertTrue(/>A<\/div>/.test(h));

+  assertFalse(/>BB<\/div>/.test(h));

+  assertFalse(/>C<\/div>/.test(h));

+

+  // Reprocessing with added data.

+  d.items.push('D');

+  jstWrap(d, t);

+  h = t.innerHTML;

+  assertFalse(/>A<\/div>/.test(h));

+  assertTrue(/>D<\/div>/.test(h));

+}

+

+function testJstDisplay() {

+  var t = document.getElementById('t2');

+  var d = {

+    display: true

+  }

+  jstWrap(d, t);

+

+  var h = t.innerHTML;

+  assertFalse(/display:\s*none/.test(h));

+

+  d.display = false;

+  jstWrap(d, t);

+

+  h = t.innerHTML;

+  assertTrue(/display:\s*none/.test(h));

+

+  // Check that 'this' within js expressions is the template node

+  t = document.getElementById('t2a');

+  d = {

+    showId: 'x'

+  };

+  jstWrap(d, t);

+

+  h = t.innerHTML;

+  assertFalse(/display:\s*none/.test(h));

+

+  d.showId = 'y';

+  jstWrap(d, t);

+

+  h = t.innerHTML;

+  assertTrue(/display:\s*none/.test(h));

+}

+

+function stringContains(str, sub) {

+  return str.indexOf(sub) != -1;

+}

+

+function testJseval() {

+  var data = {};

+

+  var counter = 0;

+  var ctx = new JsEvalContext(data);

+  ctx.setVariable("callback1", function() {

+    ++counter;

+  });

+  ctx.setVariable("callback2", function() {

+    counter *= 2;

+  });

+

+  jstProcess(ctx, document.getElementById('testJseval1'));

+  assertEquals("testJseval1", 1, counter);

+

+  jstProcess(ctx, document.getElementById('testJseval2'));

+  assertEquals("testJseval2", 4, counter);

+}

+

+function testJstValues() {

+  var t = document.getElementById('t3');

+  var d = {};

+  jstWrap(d, t);

+  var h = t.innerHTML;

+  assertTrue(stringContains(h, 'http://maps.google.com/'));

+  var t3a = document.getElementById('t3a');

+  assertEquals('http://maps.google.com/', t3a.foo.bar.baz);

+  assertEquals('http://maps.google.com/', t3a.bar);

+  assertEquals('red', t3a.style.backgroundColor);

+}

+

+function testJstTransclude() {

+  var t = document.getElementById('t4');

+  var p = document.getElementById('parent');

+  var d = {};

+  jstWrap(d, t);

+  var h = p.innerHTML;

+  assertTrue(h, stringContains(h, 'http://maps.google.com/'));

+}

+

+function assertAttributesMatch(first, second) {

+  assertEquals('assertAttributesMatch: number of child nodes',

+               jsLength(first.childNodes), jsLength(second.childNodes));

+  var b = second.firstChild;

+  for (var a = first.firstChild; a; a = a.nextSibling) {

+    var att = a.attributes;

+    if (att) {

+      assertTrue(b.attributes != null);

+      assertEquals('assertAttributesMatch: number of attribute nodes',

+                   att.length, b.attributes.length);

+      for (var i = 0; i < jsLength(att); i++) {

+        var a = att[i];

+        assertEquals('assertAttributesMatch: value of attribute ' + a.name,

+                     a.value, b.getAttribute(a.name));

+      }

+    } else {

+      assertNull(b.attributes);

+    }

+    b = b.nextSibling;

+  }

+}

+

+function testJsskip() {

+  var div = domCreateElement(document, "DIV");

+  div.innerHTML = [

+      '<div jseval="outercallback()" jsskip="1">',

+      '<div jseval="innercallback()">',

+      '</div>',

+      '</div>'

+  ].join('');

+

+  var data = {};

+  var ctx = new JsEvalContext(data);

+  var outerCalled = false;

+  ctx.setVariable("outercallback", function() {

+    outerCalled = true;

+  });

+  var innerCalled = false;

+  ctx.setVariable("innercallback", function() {

+    innerCalled = true;

+  });

+  jstProcess(ctx, div);

+

+  assertTrue(outerCalled);

+  assertFalse(innerCalled);

+}

+

+function testScalarContext() {

+  var t = document.getElementById('testScalarContext');

+

+  jstWrap(true, t);

+  assertTrue(/>true</.test(t.innerHTML));

+

+  jstWrap(false, t);

+  assertTrue(/>false</.test(t.innerHTML));

+

+  jstWrap(0, t);

+  assertTrue(/>0</.test(t.innerHTML));

+

+  jstWrap("foo", t);

+  assertTrue(/>foo</.test(t.innerHTML));

+

+  jstWrap(undefined, t);

+  assertTrue(/>undefined</.test(t.innerHTML));

+

+  jstWrap(null, t);

+  assertTrue(/>null</.test(t.innerHTML));

+}

+

+function testJstLoadTemplate() {

+  var wrapperId = 'testJstLoadTemplateWrapper';

+  var id = 'testJstLoadTemplate';

+  jstLoadTemplate_(document, '<div id="' + id + '">content</div>', wrapperId);

+  var wrapperElem = document.getElementById(wrapperId);

+  assertTrue('Expected wrapper element to be in document',

+             !!wrapperElem);

+  var newTemplate = document.getElementById(id);

+  assertTrue('Expected newly loaded template to be in document',

+             !!newTemplate);

+  assertTrue('Expected wrapper to be grandparent of template',

+             newTemplate.parentNode.parentNode == wrapperElem);

+

+  // Make sure the next template loaded with the same wrapper id re-uses the

+  // wrapper element.

+  var id2 = 'testJstLoadTemplate2';

+  jstLoadTemplate_(document, '<div id="' + id2 + '">content</div>', wrapperId);

+  var newTemplate2 = document.getElementById(id2);

+  assertTrue('Expected newly loaded template to be in document',

+             !!newTemplate2);

+  assertTrue('Expected wrapper to be grandparent of template',

+             newTemplate2.parentNode.parentNode == wrapperElem);

+}

+

+function testJstGetTemplateFromDom() {

+  var element;

+  // Get by id a template in the document

+  // Success

+  element = jstGetTemplate('t1');

+  assertTrue("Asserted jstGetTemplate('t1') to return a dom element",

+             !!element);

+  // Failure

+  element = jstGetTemplate('asdf');

+  assertFalse("Asserted jstGetTemplate('asdf') to return null",

+              !!element);

+}

+

+function testJstGetTemplateFromFunction() {

+  var element;

+  // Fetch a jstemplate by id from within a html string, passed via a function.

+  function returnHtmlWithId(id) {

+    var html =

+        '<div>' +

+        '<div id="' + id + '">Here is the template</div>' +

+        '</div>';

+    return html;

+  }

+  // Success

+  element = jstGetTemplate('template',

+                           partial(returnHtmlWithId, 'template'));

+  assertTrue("Expected jstGetTemplate('template') to return a dom element",

+             !!element);

+

+  // Failure

+  element = jstGetTemplate('asdf',

+                           partial(returnHtmlWithId, 'zxcv'));

+  assertFalse("Expected jstGetTemplate('zxcv') to return null",

+              !!element);

+}

+

+function testPrepareNode() {

+  var id, node;

+  // Reset the cache so we're testing from a known state.

+  JstProcessor.jstCache_ = {};

+  JstProcessor.jstCache_[0] = {};

+

+  // Skip pre-processed nodes.  Preprocessed nodes are those with a

+  // PROP_jstcache property.

+  var t = document.getElementById('t1');

+  var caches = [];

+  caches.push(JstProcessor.prepareNode_(t));

+  caches.push(JstProcessor.prepareNode_(t));

+  assertEquals('The same cache should be returned on each call to prepareNode',

+               caches[0], caches[1]);

+

+  // Preprocessing a node with a jst attribute should return a valid struct

+  id = 'testPrepareNodeWithAttributes';

+  jstLoadTemplate_(document, '<div id="' + id + '" jsskip="1"></div>');

+  node = document.getElementById(id);

+  var cache = JstProcessor.prepareNode_(node);

+  try {

+    var jsskip = cache['jsskip']({}, {});

+  } catch (e) {

+    fail('Exception when evaluating jsskip from cache');

+  }

+  assertEquals(1, jsskip);

+}

+

+

+function testPrepareNodeWithNoAttributes() {

+  // Preprocessing a node with no jst attributes should return null

+  var id = 'testPrepareNodeNoAttributes';

+  jstLoadTemplate_(document, '<div id="' + id + '"></div>');

+  var node = document.getElementById(id);

+  assertEquals('prepareNode with no jst attributes should return default',

+               JstProcessor.jstcache_[0], JstProcessor.prepareNode_(node));

+}

+

+

+function testJsVars() {

+  var template = document.createElement('div');

+  document.body.appendChild(template);

+  template.innerHTML = '<div jsvars="foo:\'foo\';bar:true;$baz:1"></div>';

+

+  var context = new JsEvalContext;

+  jstProcess(context, template);

+

+  assertEquals('foo', context.getVariable('foo'));

+  assertEquals(1, context.getVariable('$baz'));

+  assertTrue(context.getVariable('bar'));

+  assertUndefined(context.getVariable('foobar'));

+}

+

+

+function testCacheReuse() {

+  var template = document.createElement('div');

+  document.body.appendChild(template);

+  template.innerHTML = 

+    '<div jsvars="foo:\'foo\';bar:true;$baz:1"></div>' +

+    '<span jsvars="foo:\'foo\';bar:true;$baz:1"></span>';

+  JstProcessor.prepareTemplate_(template);

+  assertEquals(template.firstChild.getAttribute(ATT_jstcache),

+               template.lastChild.getAttribute(ATT_jstcache));

+}

+

diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/jst/util.js b/chrome/common/extensions/docs/examples/extensions/benchmark/jst/util.js
new file mode 100644
index 0000000..570c71f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/jst/util.js
@@ -0,0 +1,471 @@
+// Copyright 2006 Google Inc.

+//

+// Licensed under the Apache License, Version 2.0 (the "License");

+// you may not use this file except in compliance with the License.

+// You may obtain a copy of the License at

+//

+// http://www.apache.org/licenses/LICENSE-2.0

+//

+// Unless required by applicable law or agreed to in writing, software

+// distributed under the License is distributed on an "AS IS" BASIS,

+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or

+// implied. See the License for the specific language governing

+// permissions and limitations under the License.

+/**

+ * @fileoverview Miscellaneous constants and functions referenced in

+ * the main source files.

+ *

+ * @author Steffen Meschkat (mesch@google.com)

+ */

+

+var MAPS_DEBUG = false;

+

+function log(msg) {}

+

+// String literals defined globally and not to be inlined. (IE6 perf)

+/** @const */ var STRING_empty = '';

+

+/** @const */ var CSS_display = 'display';

+/** @const */ var CSS_position = 'position';

+

+// Constants for possible values of the typeof operator.

+var TYPE_boolean = 'boolean';

+var TYPE_number = 'number';

+var TYPE_object = 'object';

+var TYPE_string = 'string';

+var TYPE_function = 'function';

+var TYPE_undefined = 'undefined';

+

+

+/**

+ * Wrapper for the eval() builtin function to evaluate expressions and

+ * obtain their value. It wraps the expression in parentheses such

+ * that object literals are really evaluated to objects. Without the

+ * wrapping, they are evaluated as block, and create syntax

+ * errors. Also protects against other syntax errors in the eval()ed

+ * code and returns null if the eval throws an exception.

+ *

+ * @param {string} expr

+ * @return {Object|null}

+ */

+function jsEval(expr) {

+  try {

+    // NOTE(mesch): An alternative idiom would be:

+    //

+    //   eval('(' + expr + ')');

+    //

+    // Note that using the square brackets as below, "" evals to undefined.

+    // The alternative of using parentheses does not work when evaluating

+    // function literals in IE.

+    // e.g. eval("(function() {})") returns undefined, and not a function

+    // object, in IE.

+    return eval('[' + expr + '][0]');

+  } catch (e) {

+    log('EVAL FAILED ' + expr + ': ' + e);

+    return null;

+  }

+}

+

+function jsLength(obj) {

+  return obj.length;

+}

+

+function assert(obj) {}

+

+/**

+ * Copies all properties from second object to the first.  Modifies to.

+ *

+ * @param {Object} to  The target object.

+ * @param {Object} from  The source object.

+ */

+function copyProperties(to, from) {

+  for (var p in from) {

+    to[p] = from[p];

+  }

+}

+

+

+/**

+ * @param {Object|null|undefined} value The possible value to use.

+ * @param {Object} defaultValue The default if the value is not set.

+ * @return {Object} The value, if it is

+ * defined and not null; otherwise the default

+ */

+function getDefaultObject(value, defaultValue) {

+  if (typeof value != TYPE_undefined && value != null) {

+    return /** @type Object */(value);

+  } else {

+    return defaultValue;

+  }

+}

+

+/**

+ * Detect if an object looks like an Array.

+ * Note that instanceof Array is not robust; for example an Array

+ * created in another iframe fails instanceof Array.

+ * @param {Object|null} value Object to interrogate

+ * @return {boolean} Is the object an array?

+ */

+function isArray(value) {

+  return value != null &&

+      typeof value == TYPE_object &&

+      typeof value.length == TYPE_number;

+}

+

+

+/**

+ * Finds a slice of an array.

+ *

+ * @param {Array} array  Array to be sliced.

+ * @param {number} start  The start of the slice.

+ * @param {number} opt_end  The end of the slice (optional).

+ * @return {Array} array  The slice of the array from start to end.

+ */

+function arraySlice(array, start, opt_end) {

+  // Use

+  //   return Function.prototype.call.apply(Array.prototype.slice, arguments);

+  // instead of the simpler

+  //   return Array.prototype.slice.call(array, start, opt_end);

+  // here because of a bug in the FF and IE implementations of

+  // Array.prototype.slice which causes this function to return an empty list

+  // if opt_end is not provided.

+  return Function.prototype.call.apply(Array.prototype.slice, arguments);

+}

+

+

+/**

+ * Jscompiler wrapper for parseInt() with base 10.

+ *

+ * @param {string} s string repersentation of a number.

+ *

+ * @return {number} The integer contained in s, converted on base 10.

+ */

+function parseInt10(s) {

+  return parseInt(s, 10);

+}

+

+

+/**

+ * Clears the array by setting the length property to 0. This usually

+ * works, and if it should turn out not to work everywhere, here would

+ * be the place to implement the browser specific workaround.

+ *

+ * @param {Array} array  Array to be cleared.

+ */

+function arrayClear(array) {

+  array.length = 0;

+}

+

+

+/**

+ * Prebinds "this" within the given method to an object, but ignores all 

+ * arguments passed to the resulting function.

+ * I.e. var_args are all the arguments that method is invoked with when

+ * invoking the bound function.

+ *

+ * @param {Object|null} object  The object that the method call targets.

+ * @param {Function} method  The target method.

+ * @return {Function}  Method with the target object bound to it and curried by

+ *                     the provided arguments.

+ */

+function bindFully(object, method, var_args) {

+  var args = arraySlice(arguments, 2);

+  return function() {

+    return method.apply(object, args);

+  }

+}

+

+// Based on <http://www.w3.org/TR/2000/ REC-DOM-Level-2-Core-20001113/

+// core.html#ID-1950641247>.

+var DOM_ELEMENT_NODE = 1;

+var DOM_ATTRIBUTE_NODE = 2;

+var DOM_TEXT_NODE = 3;

+var DOM_CDATA_SECTION_NODE = 4;

+var DOM_ENTITY_REFERENCE_NODE = 5;

+var DOM_ENTITY_NODE = 6;

+var DOM_PROCESSING_INSTRUCTION_NODE = 7;

+var DOM_COMMENT_NODE = 8;

+var DOM_DOCUMENT_NODE = 9;

+var DOM_DOCUMENT_TYPE_NODE = 10;

+var DOM_DOCUMENT_FRAGMENT_NODE = 11;

+var DOM_NOTATION_NODE = 12;

+

+

+

+function domGetElementById(document, id) {

+  return document.getElementById(id);

+}

+

+/**

+ * Creates a new node in the given document

+ *

+ * @param {Document} doc  Target document.

+ * @param {string} name  Name of new element (i.e. the tag name)..

+ * @return {Element}  Newly constructed element.

+ */

+function domCreateElement(doc, name) {

+  return doc.createElement(name);

+}

+

+/**

+ * Traverses the element nodes in the DOM section underneath the given

+ * node and invokes the given callback as a method on every element

+ * node encountered.

+ *

+ * @param {Element} node  Parent element of the subtree to traverse.

+ * @param {Function} callback  Called on each node in the traversal.

+ */

+function domTraverseElements(node, callback) {

+  var traverser = new DomTraverser(callback);

+  traverser.run(node);

+}

+

+/**

+ * A class to hold state for a dom traversal.

+ * @param {Function} callback  Called on each node in the traversal.

+ * @constructor

+ * @class

+ */

+function DomTraverser(callback) {

+  this.callback_ = callback;

+}

+

+/**

+ * Processes the dom tree in breadth-first order.

+ * @param {Element} root  The root node of the traversal.

+ */

+DomTraverser.prototype.run = function(root) {

+  var me = this;

+  me.queue_ = [ root ];

+  while (jsLength(me.queue_)) {

+    me.process_(me.queue_.shift());

+  }

+}

+

+/**

+ * Processes a single node.

+ * @param {Element} node  The current node of the traversal.

+ */

+DomTraverser.prototype.process_ = function(node) {

+  var me = this;

+

+  me.callback_(node);

+

+  for (var c = node.firstChild; c; c = c.nextSibling) {

+    if (c.nodeType == DOM_ELEMENT_NODE) {

+      me.queue_.push(c);

+    }

+  }

+}

+

+/**

+ * Get an attribute from the DOM.  Simple redirect, exists to compress code.

+ *

+ * @param {Element} node  Element to interrogate.

+ * @param {string} name  Name of parameter to extract.

+ * @return {string|null}  Resulting attribute.

+ */

+function domGetAttribute(node, name) {

+  return node.getAttribute(name);

+  // NOTE(mesch): Neither in IE nor in Firefox, HTML DOM attributes

+  // implement namespaces. All items in the attribute collection have

+  // null localName and namespaceURI attribute values. In IE, we even

+  // encounter DIV elements that don't implement the method

+  // getAttributeNS().

+}

+

+

+/**

+ * Set an attribute in the DOM.  Simple redirect to compress code.

+ *

+ * @param {Element} node  Element to interrogate.

+ * @param {string} name  Name of parameter to set.

+ * @param {string|number} value  Set attribute to this value.

+ */

+function domSetAttribute(node, name, value) {

+  node.setAttribute(name, value);

+}

+

+/**

+ * Remove an attribute from the DOM.  Simple redirect to compress code.

+ *

+ * @param {Element} node  Element to interrogate.

+ * @param {string} name  Name of parameter to remove.

+ */

+function domRemoveAttribute(node, name) {

+  node.removeAttribute(name);

+}

+

+/**

+ * Clone a node in the DOM.

+ *

+ * @param {Node} node  Node to clone.

+ * @return {Node}  Cloned node.

+ */

+function domCloneNode(node) {

+  return node.cloneNode(true);

+  // NOTE(mesch): we never so far wanted to use cloneNode(false),

+  // hence the default.

+}

+

+/**

+ * Clone a element in the DOM.

+ *

+ * @param {Element} element  Element to clone.

+ * @return {Element}  Cloned element.

+ */

+function domCloneElement(element) {

+  return /** @type {Element} */(domCloneNode(element));

+}

+

+/**

+ * Returns the document owner of the given element. In particular,

+ * returns window.document if node is null or the browser does not

+ * support ownerDocument.  If the node is a document itself, returns

+ * itself.

+ *

+ * @param {Node|null|undefined} node  The node whose ownerDocument is required.

+ * @returns {Document}  The owner document or window.document if unsupported.

+ */

+function ownerDocument(node) {

+  if (!node) {

+    return document;

+  } else if (node.nodeType == DOM_DOCUMENT_NODE) {

+    return /** @type Document */(node);

+  } else {

+    return node.ownerDocument || document;

+  }

+}

+

+/**

+ * Creates a new text node in the given document.

+ *

+ * @param {Document} doc  Target document.

+ * @param {string} text  Text composing new text node.

+ * @return {Text}  Newly constructed text node.

+ */

+function domCreateTextNode(doc, text) {

+  return doc.createTextNode(text);

+}

+

+/**

+ * Appends a new child to the specified (parent) node.

+ *

+ * @param {Element} node  Parent element.

+ * @param {Node} child  Child node to append.

+ * @return {Node}  Newly appended node.

+ */

+function domAppendChild(node, child) {

+  return node.appendChild(child);

+}

+

+/**

+ * Sets display to default.

+ *

+ * @param {Element} node  The dom element to manipulate.

+ */

+function displayDefault(node) {

+  node.style[CSS_display] = '';

+}

+

+/**

+ * Sets display to none. Doing this as a function saves a few bytes for

+ * the 'style.display' property and the 'none' literal.

+ *

+ * @param {Element} node  The dom element to manipulate.

+ */

+function displayNone(node) {

+  node.style[CSS_display] = 'none';

+}

+

+

+/**

+ * Sets position style attribute to absolute.

+ *

+ * @param {Element} node  The dom element to manipulate.

+ */

+function positionAbsolute(node) {

+  node.style[CSS_position] = 'absolute';

+}

+

+

+/**

+ * Inserts a new child before a given sibling.

+ *

+ * @param {Node} newChild  Node to insert.

+ * @param {Node} oldChild  Sibling node.

+ * @return {Node}  Reference to new child.

+ */

+function domInsertBefore(newChild, oldChild) {

+  return oldChild.parentNode.insertBefore(newChild, oldChild);

+}

+

+/**

+ * Replaces an old child node with a new child node.

+ *

+ * @param {Node} newChild  New child to append.

+ * @param {Node} oldChild  Old child to remove.

+ * @return {Node}  Replaced node.

+ */

+function domReplaceChild(newChild, oldChild) {

+  return oldChild.parentNode.replaceChild(newChild, oldChild);

+}

+

+/**

+ * Removes a node from the DOM.

+ *

+ * @param {Node} node  The node to remove.

+ * @return {Node}  The removed node.

+ */

+function domRemoveNode(node) {

+  return domRemoveChild(node.parentNode, node);

+}

+

+/**

+ * Remove a child from the specified (parent) node.

+ *

+ * @param {Element} node  Parent element.

+ * @param {Node} child  Child node to remove.

+ * @return {Node}  Removed node.

+ */

+function domRemoveChild(node, child) {

+  return node.removeChild(child);

+}

+

+

+/**

+ * Trim whitespace from begin and end of string.

+ *

+ * @see testStringTrim();

+ *

+ * @param {string} str  Input string.

+ * @return {string}  Trimmed string.

+ */

+function stringTrim(str) {

+  return stringTrimRight(stringTrimLeft(str));

+}

+

+/**

+ * Trim whitespace from beginning of string.

+ *

+ * @see testStringTrimLeft();

+ *

+ * @param {string} str  Input string.

+ * @return {string}  Trimmed string.

+ */

+function stringTrimLeft(str) {

+  return str.replace(/^\s+/, "");

+}

+

+/**

+ * Trim whitespace from end of string.

+ *

+ * @see testStringTrimRight();

+ *

+ * @param {string} str  Input string.

+ * @return {string}  Trimmed string.

+  */

+function stringTrimRight(str) {

+  return str.replace(/\s+$/, "");

+}

+

diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/manifest.json b/chrome/common/extensions/docs/examples/extensions/benchmark/manifest.json
new file mode 100644
index 0000000..bff719b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/manifest.json
@@ -0,0 +1,19 @@
+{
+  "name": "Page Benchmarker",
+  "version": "1.3.4.0",
+  "manifest_version": 2,
+  "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
+  "description": "Chromium Page Benchmarker.",
+  "background": {
+    "page": "background.html",
+    "persistent": false
+  },
+  "browser_action": {
+    "default_title": "Benchmark page load time",
+    "default_icon": "stopwatch.jpg"
+  },
+  "options_page": "options.html",
+  "permissions": [
+    "tabs", "https://*/*", "http://*/*"
+  ]
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/options.html b/chrome/common/extensions/docs/examples/extensions/benchmark/options.html
new file mode 100644
index 0000000..ebf95b3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/options.html
@@ -0,0 +1,298 @@
+<head>
+  <title>Page Benchmark Options</title>
+
+  <script src="jst/util.js" type="text/javascript"></script>
+  <script src="jst/jsevalcontext.js" type="text/javascript"></script>
+  <script src="jst/jstemplate.js" type="text/javascript"></script>
+  <script src="jquery/jquery-1.8.2.min.js" type="text/javascript"></script>
+  <script src="jquery/jquery.flot.min.js" type="text/javascript"></script>
+  <script src="jquery/jquery.flot.dashes.js" type="text/javascript"></script>
+  <script src="util/table2CSV.js" type="text/javascript"></script>
+  <script src="util/sorttable.js" type="text/javascript"></script>
+<style>
+body {
+  font-size: 84%;
+  font-family: Helvetica, Arial, sans-serif;
+  padding: 0.75em;
+  margin: 0;
+  min-width: 45em;
+}
+
+h1 {
+  font-size: 110%;
+  font-weight: bold;
+  color: #4a8ee6;
+  letter-spacing: -1px;
+  padding: 0;
+  margin: 0;
+}
+
+div#header {
+  padding: 0.75em 1em;
+  padding-top: 0.6em;
+  padding-left: 10;
+  margin-bottom: 0.75em;
+  position: relative;
+  overflow: hidden;
+  background: #5296de;
+  background-size: 100%;
+  border: 1px solid #3a75bd;
+  border-radius: 6px;
+  color: white;
+  text-shadow: 0 0 2px black;
+}
+div#header h1 {
+  padding-left: 37px;
+  margin: 0;
+  display: inline;
+  color: white;
+}
+div#header p {
+  font-size: 84%;
+  font-style: italic;
+  padding: 0;
+  margin: 0;
+  color: white;
+  padding-left: 0.4em;
+  display: inline;
+}
+
+table.sortable {
+  font-size: 84%;
+  table-layout: fixed;
+}
+
+table.sortable:not([class*='filtered']) tr:nth-child(even) td:not([class*='filtered']) {
+  background: #eff3ff;
+}
+
+.nobg {
+  padding: 0 0.5em;
+  vertical-align: bottom;
+  font-weight: bold;
+  color: #315d94;
+  color: black;
+  text-align: center;
+}
+
+.bg{
+  padding: 0 0.5em;
+  vertical-align: bottom;
+  font-weight: bold;
+  color: #315d94;
+  color: black;
+  text-align: center;
+  cursor: pointer;
+}
+
+.bg:hover {
+  background: #eff3aa;
+}
+
+.avg {
+  font-weight: bold;
+  text-align: center;
+}
+
+.data {
+  text-align: left;
+  white-space: nowrap;
+}
+
+.bggraph {
+  white-space: nowrap;
+}
+
+.file_input
+{
+  position: absolute;
+  width: 140px;
+  height: 26px;
+  overflow: hidden;
+}
+
+.file_input_button
+{
+  width: 140px;
+  position: absolute;
+  top: 0px;
+}
+
+.file_input_hidden
+{
+  font-size: 25px;
+  position: absolute;
+  right: 0px;
+  top: 0px;
+  opacity: 0;
+}
+</style>
+
+<script src="options.js">
+</script>
+
+</head>
+
+<body>
+
+<h1><div id="header">Page Benchmark Results</div></h1>
+
+<h1>Configuration <a href="https://sites.google.com/a/chromium.org/dev/developers/design-documents/extensions/how-the-extension-system-works/chrome-benchmarking-extension" target="_blank" style="float:right"><font size="4%">Help</font></a></h1>
+
+<span>Iterations</span>
+<input id="iterations" type="text" style="text-align:right" size="4">
+Clear Connections?<input id="clearconns" type="checkbox">
+Clear Cache?<input id="clearcache" type="checkbox">
+Enable Spdy?<input id="enablespdy" type="checkbox">
+<br>
+<span>URLs to load</span> <input type="text" id="testurl" size="80">
+<span class="file_input">
+<input class="file_input_button" type="button" value="Load URLs From File" />
+<input class="file_input_hidden" type="file" id="files" name="files[]" multiple />
+</span>
+<form>
+<input type="submit" value="Run">
+</form>
+<p>
+
+<h1>Results</h1>
+
+<input id="expand" type="button" value="Show More Details">
+<input id="clearSelected" type="button" value="Clear Selected" disabled="true">
+<input id="clearAll" type="button" value="Clear All">
+<input id="exportCsv" type="button" value="Export As .csv">
+<table id="t" class="sortable" width="100%">
+  <tr>
+  <th width=35 class="nobg"></th>
+  <th width=215 class="nobg">url</th>
+  <th width=110 class="nobg" style="display:none">timestamp</th>
+  <th width=50 class="nobg">iterations</th>
+  <th width=50 class="nobg">via spdy</th>
+  <th width=50 class="bg" style="display:none">start load mean</th>
+  <th width=50 class="bg" style="display:none">commit load mean</th>
+  <th width=50 class="bg">doc load mean</th>
+  <th width=50 class="bg">paint mean</th>
+  <th width=50 class="bg">total load mean</th>
+  <th width=50 class="bg">stddev</th>
+  <th width=50 class="bg" style="display:none">stderr</th>
+  <th width=50 class="bg" style="display:none">95% CI-low</th>
+  <th width=50 class="bg" style="display:none">95% CI-high</th>
+  <th width=50 class="bg" style="display:none">min</th>
+  <th width=50 class="bg" style="display:none">max</th>
+  <th width=60 class="bg" style="display:none"># Requests</th>
+  <th width=60 class="bg" style="display:none"># Connects</th>
+  <th width=50 class="bg" style="display:none"># SPDY Sessions</th>
+  <th width=50 class="bg" style="display:none">Read KB</th>
+  <th width=50 class="bg" style="display:none">Write KB</th>
+  <th width=50 class="bg">Read KBps</th>
+  <th width=50 class="bg">Write KBps</th>
+  <th width=50 class="bg"># DOM</th>
+  <th width=70 class="bg" style="display:none">max DOM depth</th>
+  <th width=30 class="bg" style="display:none">min</th>
+  <th width=30 class="bg" style="display:none">avg</th>
+  <th samples class="nobg" style="display:none">total loan time samples</th>
+  </tr>
+
+  <tr id="t.total" jsselect="totals">
+  <td class="avg" jseval="1"></td>
+  <td class="url">TOTALS <span jscontent="url"></span></td>
+  <td class="avg" style="display:none"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" style="display:none"><span jseval="val = startLoadMean.toFixed(1)" jscontent="val"></span></td>
+  <td class="avg" style="display:none"><span jseval="val = commitLoadMean.toFixed(1)" jscontent="val"></span></td>
+  <td class="avg"><span jseval="val = docLoadMean.toFixed(1)" jscontent="val"></span></td>
+  <td class="avg"><span jseval="val = paintMean.toFixed(1)" jscontent="val"></span></td>
+  <td class="avg"><span jseval="val = mean.toFixed(1)" jscontent="val"></span></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="data"></td>
+  </tr>
+
+  <tr jsselect="data">
+  <td align=right> <input type="checkbox" name="checkboxArr"></td>
+  <td class="url" jseval="$width = getWidth($this.mean, this); url.length > 40 ? $suburl = url.substring(0,27) + '...' + url.substring(url.length-10, url.length) : $suburl=url"><div jsvalues=".style.width:$width" class="bggraph"><a jsvalues="href:$this.url" jscontent="$suburl"></a></div></td>
+  <td class="avg" style="display:none" jseval="val = displayTime" jscontent="val"></td>
+  <td class="avg" jseval="val = iterations" jscontent="val"></td>
+  <td class="avg" jseval="val = viaSpdy" jscontent="val"></td>
+  <td class="avg" style="display:none" jseval="val = startLoadMean.toFixed(1)" jscontent="val"></td>
+  <td class="avg" style="display:none" jseval="val = commitLoadMean.toFixed(1)" jscontent="val"></td>
+  <td class="avg" jseval="val = docLoadMean.toFixed(1)" jscontent="val"></td>
+  <td class="avg" jseval="val = paintMean.toFixed(1)" jscontent="val"></td>
+  <td class="avg" jseval="val = mean.toFixed(1)" jscontent="val"></td>
+  <td class="avg" jseval="val = stddev.toFixed(1)" jscontent="val"></td>
+  <td class="avg" style="display:none" jseval="val = stderr.toFixed(1)" jscontent="val"></td>
+  <td class="avg" style="display:none" jseval="val = cilow.toFixed(1)" jscontent="val"></td>
+  <td class="avg" style="display:none" jseval="val = cihigh.toFixed(1)" jscontent="val"></td>
+  <td class="avg" style="display:none" jseval="val = min.toFixed(1)" jscontent="val"></td>
+  <td class="avg" style="display:none" jseval="val = max.toFixed(1)" jscontent="val"></td>
+  <td class="avg" style="display:none" jseval="val = displayRequests.toFixed(1)" jscontent="val"></td>
+  <td class="avg" style="display:none" jseval="val = displayConnects.toFixed(1)" jscontent="val"></td>
+  <td class="avg" style="display:none" jseval="val = displaySpdySessions.toFixed(1)" jscontent="val"></td>
+  <td class="avg" style="display:none" jseval="val = readKB.toFixed(1)" jscontent="val"></td>
+  <td class="avg" style="display:none" jseval="val = writeKB.toFixed(1)" jscontent="val"></td>
+  <td class="avg" jseval="val = readbps.toFixed(1)" jscontent="val"></td>
+  <td class="avg" jseval="val = writebps.toFixed(1)" jscontent="val"></td>
+  <td class="avg" jseval="val = displayDomNum" jscontent="val"></td>
+  <td class="avg" style="display:none" jseval="val = displayMaxDepth" jscontent="val"></td>
+  <td class="avg" style="display:none" jseval="val = displayMinDepth" jscontent="val"></td>
+  <td class="avg" style="display:none" jseval="val = displayAvgDepth.toFixed(1)" jscontent="val"></td>
+  <td class="data" style="display:none"><span jsselect="totalResults"><span jscontent="$this"></span>,</span> </td>
+  </tr>
+  <tr jsdisplay="data.length == 0">
+  <td colspan=2>No tests have been run yet.</td>
+  </tr>
+  <tr jsdisplay="data.length > 1">
+  <td width=25 jseval="1"></td>
+  <td class="url" jseval="1"></td>
+  <td class="avg" style="display:none" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" style="display:none"><input name="radioArr" type="radio"> </td>
+  <td class="avg" style="display:none"><input name="radioArr" type="radio"></td>
+  <td class="avg"><input name="radioArr" type="radio"></td>
+  <td class="avg"><input name="radioArr" type="radio"></td>
+  <td class="avg"><input name="radioArr" type="radio" checked></td>
+  <td class="avg" jseval="1"></td>
+  <td class="avg" style="display:none" jseval="1"></td>
+  <td class="avg" style="display:none" jseval="1"></td>
+  <td class="avg" style="display:none" jseval="1"></td>
+  <td class="avg" style="display:none" jseval="1"></td>
+  <td class="avg" style="display:none" jseval="1"></td>
+  <td class="avg" style="display:none"><input name="radioArr" type="radio"></td>
+  <td class="avg" style="display:none"><input name="radioArr" type="radio"></td>
+  <td class="avg" style="display:none" jseval="1"></td>
+  <td class="avg" style="display:none"><input name="radioArr" type="radio"></td>
+  <td class="avg" style="display:none"><input name="radioArr" type="radio"></td>
+  <td class="avg"><input name="radioArr" type="radio"></td>
+  <td class="avg"><input name="radioArr" type="radio"></td>
+  </tr>
+  <tr jsdisplay="data.length > 1">
+  <td> <input id="compare" type="button" value="Compare" disabled="true"></td>
+  </tr>
+</table>
+<hr>
+<center>
+<div id="placeholder" style="width:430px;height:230px;display:none">graph place</div>
+</center>
+<span id="toggle-json">JSON data</span><br>
+<textarea style="display:none" type=text id=json rows=10 cols=50></textarea><p>
+
+<span id="toggle-baseline">COMPARE to</span><br>
+<textarea style="display:none" type=text id=baseline rows=10 cols=50></textarea><p>
+</body>
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/options.js b/chrome/common/extensions/docs/examples/extensions/benchmark/options.js
new file mode 100644
index 0000000..674e1d9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/options.js
@@ -0,0 +1,751 @@
+// Copyright (c) 2012 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.
+
+var max_sample = 0;
+
+Array.max = function(array) {
+  return Math.max.apply( Math, array );
+}
+
+Array.min = function(array) {
+  return Math.min.apply( Math, array );
+};
+
+// Compute the average of an array, removing the min/max.
+Array.avg = function(array) {
+  var count = array.length;
+  var sum = 0;
+  var min = array[0];
+  var max = array[0];
+  for (var i = 0; i < count; i++) {
+    sum += array[i];
+    if (array[i] < min) {
+      min = array[i];
+    }
+    if (array[i] > max) {
+      max = array[i];
+    }
+  }
+  if (count >= 3) {
+    sum = sum - min - max;
+    count -= 2;
+  }
+  return sum / count;
+}
+
+// Compute the sample standard deviation of an array
+Array.stddev = function(array) {
+  var count = array.length;
+  var mean = 0;
+  for (var i = 0; i < count; i++) {
+    mean += array[i];
+  }
+  mean /= count;
+  var variance = 0;
+  for (var i = 0; i < count; i++) {
+    var deviation = mean - array[i];
+    variance = variance + deviation * deviation;
+  }
+  variance = variance / (count - 1);
+  return Math.sqrt(variance);
+}
+
+function handleFileSelect(evt) {
+  var files = evt.target.files;
+  for (var i = 0, f; f = files[i]; i++) {
+    var reader = new FileReader();
+    reader.onload = function(evt) {
+      document.getElementById("testurl").value = evt.target.result;
+    }
+    reader.readAsText(f);
+  };
+}
+
+var THTAG = "th";
+var TDTAG = "td";
+var NONE_DISPLAY = "none";
+var CELL_DISPLAY = "table-cell";
+var BRIEF_VIEW = "Show More Details";
+var FULL_VIEW = "Hide Details";
+
+// Expand or shrink the result table.
+// Called when clicking button "Show More Details/Hide Details".
+function expand() {
+  if (document.getElementById("expand").value == BRIEF_VIEW) {
+    // From biref view to detailed view.
+    var headers = document.getElementsByTagName(THTAG);
+    var cells = document.getElementsByTagName(TDTAG);
+
+    // Display the hidden metrics (both headers and data cells).
+    for (var i = 0; i < headers.length; i++) {
+      if (headers[i].style.display == NONE_DISPLAY) {
+        headers[i].style.display = CELL_DISPLAY;
+      }
+    }
+
+    for (i = 0; i < cells.length; i++) {
+      if (cells[i].style.display == NONE_DISPLAY) {
+        cells[i].style.display = CELL_DISPLAY;
+      }
+    }
+
+    document.getElementById("expand").value = FULL_VIEW;
+  } else {
+    // From detailed view to brief view.
+    var headers = document.getElementsByTagName(THTAG);
+    var cells = document.getElementsByTagName(TDTAG);
+
+    // Hide some metrics.
+    for (var i = 0; i < headers.length; i++) {
+      if (headers[i].style.display == CELL_DISPLAY) {
+        headers[i].style.display = NONE_DISPLAY;
+      }
+    }
+
+    for (i = 0; i < cells.length; i++) {
+      if (cells[i].style.display == CELL_DISPLAY) {
+        cells[i].style.display = NONE_DISPLAY;
+      }
+    }
+
+    document.getElementById("expand").value = BRIEF_VIEW;
+  }
+
+  // Use cookie to store current expand/hide status.
+  var lastValue = document.getElementById("expand").value;
+  document.cookie = "lastValue=" + lastValue;
+}
+
+// Reloading the page causes table to shrink (default original status).
+// Cookie remembers last status of table (in terms of expanded or shrunk).
+function restoreTable() {
+  if (document.cookie == "lastValue=Hide Details") {
+    var headers = document.getElementsByTagName(THTAG);
+    var cells = document.getElementsByTagName(TDTAG);
+
+    for (var i = 0; i < headers.length; i++) {
+      if (headers[i].style.display == NONE_DISPLAY) {
+        headers[i].style.display = CELL_DISPLAY;
+      }
+    }
+    for (i = 0; i < cells.length; i++) {
+      if (cells[i].style.display == NONE_DISPLAY) {
+        cells[i].style.display = CELL_DISPLAY;
+      }
+    }
+    document.getElementById("expand").value = FULL_VIEW;
+  }
+}
+
+// A class to store the data to plot.
+function PData() {
+  this.xAxis = "Iteration(s)";
+  this.yAxis = "";
+  this.A = []; // Two data sets for comparison.
+  this.B = [];
+  this.avgA = [];  // Avg value is plotted as a line.
+  this.avgB = [];
+  this.maxA = 0;
+  this.maxB = 0;
+  this.countA = 0; // Size of the data sets.
+  this.countB = 0;
+
+  this.setYAxis = function(str) {
+    this.yAxis = str;
+  }
+
+  this.setAvg = function(arr, cha) {
+    if (cha == 'A') {
+      var avgA = Array.avg(arr);
+      for (var i = 1; i <= this.countA; i++) {
+        this.avgA.push([i, avgA]);
+      }
+    } else if (cha == 'B') {
+      var avgB = Array.avg(arr);
+      for (var i = 1; i <= this.countB; i++) {
+        this.avgB.push([i, avgB]);
+      }
+    }
+  }
+
+  this.setMax = function(arr, cha) {
+    if (cha == 'A') {
+      this.maxA = Array.max(arr);
+    } else if (cha == 'B') {
+      this.maxB = Array.max(arr);
+    }
+  }
+
+  // Add an entry to the array.
+  this.addArr = function(val, cha) {
+    if (cha == 'A') {
+      this.countA++;
+      this.A.push([this.countA, val]);
+    } else if (cha == 'B') {
+      this.countB++;
+      this.B.push([this.countB, val]);
+    }
+  }
+
+  // Plot the graph at the specified place.
+  this.plot = function(placeholder) {
+    $.plot(placeholder,
+      [// Line A
+       {
+         data: this.A,
+         label: "A's " + this.yAxis + " in " + this.countA + " " + this.xAxis,
+         points: {
+           show: true
+         },
+         lines: {
+           show: true
+         }
+       },
+
+       // Line B
+       {
+         data: this.B,
+         label: "B's " + this.yAxis + " in " + this.countB + " " + this.xAxis,
+         points: {
+           show: true
+         },
+         lines: {
+           show: true
+         }
+       },
+
+       // Line avgA
+       {
+         data: this.avgA,
+         label: "A's avg " + this.yAxis,
+         dashes: {
+           show: true
+         }
+       },
+
+       // Line avgB
+       {
+         data: this.avgB,
+         label: "B's avg " + this.yAxis,
+         dashes: {
+           show: true
+         }
+       }],
+
+       // Axis and legend setup.
+       { xaxis: {
+           max: this.countA > this.countB ? this.countA : this.countB,
+           tickSize: 1,
+           tickDecimals: 0
+         },
+         yaxis: {
+           // Leave some space for legend.
+           max: this.maxA > this.maxB ? this.maxA * 1.5 : this.maxB * 1.5
+         },
+         legend: {
+           backgroundOpacity: 0
+         }
+       });
+  }
+}
+
+// Compare the selected metric of the two selected data sets.
+function compare() {
+  var checkboxArr = document.getElementsByName("checkboxArr");
+  var radioArr = document.getElementsByName("radioArr");
+
+  if (checkAmount(checkboxArr) != 2) {
+    alert("please select two rows to compare");
+    return;
+  }
+
+  var rowIndexArr = getSelectedIndex(checkboxArr);
+  var colIndexArr = getSelectedIndex(radioArr);
+  // To this point, it is for sure that rowIndexArr has two elements
+  // while colIndexArr has one.
+  var selectedRowA = rowIndexArr[0];
+  var selectedRowB = rowIndexArr[1];
+  var selectedCol = colIndexArr[0];
+
+  var extension = chrome.extension.getBackgroundPage();
+  var data = extension.results.data;
+  var selectedA = getSelectedResults(data,selectedRowA,selectedCol);
+  var selectedB = getSelectedResults(data,selectedRowB,selectedCol);
+  var yAxis = getMetricName(selectedCol);
+
+  // Indicate A and B on selected rows.
+  checkboxArr[selectedRowA].parentElement.firstChild.data = "A";
+  checkboxArr[selectedRowB].parentElement.firstChild.data = "B";
+
+  plot(selectedA, selectedB, yAxis);
+}
+
+// Show the comparison graph.
+function plot(A, B, axis) {
+  var plotData = new PData();
+
+  plotData.setYAxis(axis);
+  for (var i = 0; i < A.length; i++) {
+    plotData.addArr(A[i],'A');
+  }
+  for (var i = 0; i < B.length; i++) {
+    plotData.addArr(B[i],'B');
+  }
+  plotData.setAvg(A,'A');
+  plotData.setAvg(B,'B');
+  plotData.setMax(A,'A');
+  plotData.setMax(B,'B');
+
+  var placeholder = document.getElementById("placeholder");
+  placeholder.style.display = "";
+  plotData.plot(placeholder);
+}
+
+var METRIC = {"STARTLOAD": 0, "COMMITLOAD": 1, "DOCLOAD": 2, "PAINT": 3,
+               "TOTAL": 4, "REQUESTS": 5, "CONNECTS": 6, "READKB": 7,
+               "WRITEKB": 8, "READKBPS": 9, "WRITEKBPS": 10};
+
+// Retrieve the metric name from index.
+function getMetricName(index) {
+  switch (index) {
+    case METRIC.STARTLOAD:
+      return "Start Load Time";
+    case METRIC.COMMITLOAD:
+      return "Commit Load Time";
+    case METRIC.DOCLOAD:
+      return "Doc Load Time";
+    case METRIC.PAINT:
+      return "Paint Time";
+    case METRIC.TOTAL:
+      return "Total Load Time";
+    case METRIC.REQUESTS:
+      return "# Requests";
+    case METRIC.CONNECTS:
+      return "# Connects";
+    case METRIC.READKB:
+      return "Read KB";
+    case METRIC.WRITEKB:
+      return "Write KB";
+    case METRIC.READKBPS:
+      return "Read KBps";
+    case METRIC.WRITEKBPS:
+      return "Write KBps";
+    default:
+      return "";
+  }
+}
+
+// Get the results with a specific row (data set) and column (metric).
+function getSelectedResults(arr, rowIndex, colIndex) {
+  switch (colIndex) {
+    case METRIC.STARTLOAD:
+      return arr[rowIndex].startLoadResults;
+    case METRIC.COMMITLOAD:
+      return arr[rowIndex].commitLoadResults;
+    case METRIC.DOCLOAD:
+      return arr[rowIndex].docLoadResults;
+    case METRIC.PAINT:
+      return arr[rowIndex].paintResults;
+    case METRIC.TOTAL:
+      return arr[rowIndex].totalResults;
+    case METRIC.REQUESTS:
+      return arr[rowIndex].requests;
+    case METRIC.CONNECTS:
+      return arr[rowIndex].connects;
+    case METRIC.READKB:
+      return arr[rowIndex].KbytesRead;
+    case METRIC.WRITEKB:
+      return arr[rowIndex].KbytesWritten;
+    case METRIC.READKBPS:
+      return arr[rowIndex].readbpsResults;
+    case METRIC.WRITEKBPS:
+      return arr[rowIndex].writebpsResults;
+    default:
+      return undefined;
+  }
+}
+
+// Ensure only two data sets (rows) are selected.
+function checkAmount(arr) {
+  var amount = 0;
+  for (var i = 0; i < arr.length; i++) {
+    if (arr[i].checked) {
+      amount++;
+    }
+  }
+  return amount;
+}
+
+// Get the index of selected row or column.
+function getSelectedIndex(arr) {
+  var selectedArr = new Array();
+  for (var i = 0; i < arr.length; i++) {
+    if(arr[i].checked) {
+      selectedArr.push(i);
+    }
+  }
+  return selectedArr;
+}
+
+// Repaint or hide the chart.
+function updateChart(caller) {
+  var placeholder = document.getElementById("placeholder");
+  if (caller.type == "radio") {
+    // Other radio button is clicked.
+    if (placeholder.style.display == "") {
+      compare();
+    }
+  } else {
+    // Other checkbox or clearing results is clicked.
+    if (placeholder.style.display == "") {
+      placeholder.style.display = "none";
+    }
+  }
+}
+
+// Clear indicators besides checkbox.
+function clearIndicator() {
+  var checkboxArr = document.getElementsByName("checkboxArr");
+  for (var i = 0; i < checkboxArr.length; i++) {
+    checkboxArr[i].parentElement.firstChild.data = "";
+  }
+}
+
+// Enable/Disable buttons according to checkbox change.
+function checkSelected() {
+  var checkboxArr = document.getElementsByName("checkboxArr");
+  if (checkAmount(checkboxArr) !=0) {
+    document.getElementById("clearSelected").disabled = false;
+    document.getElementById("compare").disabled = false;
+  } else {
+    document.getElementById("clearSelected").disabled = true;
+    document.getElementById("compare").disabled = true;
+  }
+}
+
+// Object to summarize everything
+var totals = {};
+
+// Compute the results for a data set.
+function computeDisplayResults(data) {
+  var count = data.data.length;
+  for (var i = 0; i < count; i++) {
+    var obj = data.data[i];
+    obj.displayTime = setDisplayTime(obj.timestamp);
+    var resultList = obj.totalResults;
+    obj.mean = Array.avg(resultList);
+    obj.stddev = Array.stddev(resultList);
+    obj.stderr = obj.stddev / Math.sqrt(obj.iterations);
+    var ci = 1.96 * obj.stderr;
+    obj.cihigh = obj.mean + ci;
+    obj.cilow = obj.mean - ci;
+    obj.min = Array.min(resultList);
+    obj.max = Array.max(resultList);
+    obj.readbps = Array.avg(obj.readbpsResults);
+    obj.writebps = Array.avg(obj.writebpsResults);
+    obj.readKB = Array.avg(obj.KbytesRead);
+    obj.writeKB = Array.avg(obj.KbytesWritten);
+    obj.paintMean = Array.avg(obj.paintResults);
+    obj.startLoadMean = Array.avg(obj.startLoadResults);
+    obj.commitLoadMean = Array.avg(obj.commitLoadResults);
+    obj.docLoadMean = Array.avg(obj.docLoadResults);
+
+    obj.displayRequests = Array.avg(obj.requests);
+    obj.displayConnects = Array.avg(obj.connects);
+    obj.displaySpdySessions = Array.avg(obj.spdySessions);
+
+    obj.displayDomNum = obj.domNum;
+    obj.displayMaxDepth = obj.maxDepth;
+    obj.displayMinDepth = obj.minDepth;
+    obj.displayAvgDepth = obj.avgDepth;
+    }
+  return count;
+}
+
+// Convert timestamp to readable string.
+function setDisplayTime(ts) {
+  var year = ts.getFullYear();
+  var mon = ts.getMonth()+1;
+  var date = ts.getDate();
+  var hrs = ts.getHours();
+  var mins = ts.getMinutes();
+  var secs = ts.getSeconds();
+
+  mon = ( mon < 10 ? "0" : "" ) + mon;
+  date = ( date < 10 ? "0" : "" ) + date;
+  mins = ( mins < 10 ? "0" : "" ) + mins;
+  secs = ( secs < 10 ? "0" : "" ) + secs;
+
+  return (year + "/" + mon + "/" + date + " " + hrs + ":" + mins + ":" + secs);
+}
+
+// Subtract the results from two data sets.
+// This function could be smarter about what it subtracts,
+// for now it just subtracts everything.
+// Returns true if it was able to compare the two data sets.
+function subtractData(data, baseline) {
+  var count = data.data.length;
+  if (baseline.data.length != count) {
+    return false;
+  }
+  for (var i = 0; i < count; i++) {
+    var obj = data.data[i];
+    var obj2 = baseline.data[i];
+
+    // The data sets are different.
+    if (obj.url != obj2.url ||
+        obj.iterations != obj2.iterations) {
+      return false;
+    }
+
+    obj.mean -= obj2.mean;
+    obj.stddev -= obj2.stddev;
+    obj.min -= obj2.min;
+    obj.max -= obj2.max;
+    obj.readbps -= obj2.readbps;
+    obj.writebps -= obj2.writebps;
+    obj.readKB -= obj2.readKB;
+    obj.writeKB -= obj2.writeKB;
+    obj.paintMean -= obj2.paintMean;
+    obj.startLoadMean -= obj2.startLoadMean;
+    obj.commitLoadMean -= obj2.commitLoadMean;
+    obj.docLoadMean -= obj2.docLoadMean;
+
+    obj.displayRequests -= obj2.displayRequests;
+    obj.displayConnects -= obj2.displayConnects;
+    obj.displaySpdySessions -= obj2.displaySpdySessions;
+  }
+  return true;
+}
+
+// Compute totals based on a data set.
+function computeTotals(data) {
+  var count = data.data.length;
+  for (var i = 0; i < count; i++) {
+    var obj = data.data[i];
+    totals.mean += obj.mean;
+    totals.paintMean += obj.paintMean;
+    totals.startLoadMean += obj.startLoadMean;
+    totals.commitLoadMean += obj.commitLoadMean;
+    totals.docLoadMean += obj.docLoadMean;
+  }
+}
+
+// Compute results for the data with an optional baseline.
+// If |baseline| is undefined, will compute the results of this
+// run.  Otherwise, computes the diff between this data and the baseline.
+function computeResults(data, baseline) {
+  totals = {};
+  totals.mean = 0;
+  totals.paintMean = 0;
+  totals.startLoadMean = 0;
+  totals.commitLoadMean = 0;
+  totals.docLoadMean = 0;
+
+  var count = computeDisplayResults(data);
+
+  if (baseline) {
+    computeDisplayResults(baseline);
+    if (!subtractData(data, baseline)) {
+      alert("These data sets are different");
+      document.getElementById("baseline").value = "";
+      return;
+    }
+  }
+
+  computeTotals(data);
+  totals.url = "(" + count + " urls)";
+  if (count > 0) {
+    totals.mean /= count;
+    totals.paintMean /= count;
+    totals.startLoadMean /= count;
+    totals.commitLoadMean /= count;
+    totals.docLoadMean /= count;
+  }
+
+  // Find the biggest average for our bar graph.
+  max_sample = 0;
+  for (var i = 0; i < data.data.length; i++) {
+    if (data.data[i].max > max_sample) {
+      max_sample = data.data[i].mean;
+    }
+  }
+}
+
+function jsinit() {
+  var extension = chrome.extension.getBackgroundPage();
+
+  // Run the template to show results
+  var data = extension.results;
+
+  // Get the baseline results
+  var elt = document.getElementById("baseline");
+  var baseline_json = document.getElementById("baseline").value;
+  var baseline;
+  if (baseline_json) {
+    try {
+      baseline = JSON.parse(baseline_json);
+    } catch (e) {
+      alert("JSON parse error: " + e);
+    }
+  }
+
+  // Compute
+  computeResults(data, baseline);
+
+  var context = new JsEvalContext(data);
+  context.setVariable('$width', 0);
+  context.setVariable('$samples', 0);
+  var template = document.getElementById("t");
+  jstProcess(context, template);
+
+  // Set the options
+  document.getElementById("iterations").value = extension.iterations;
+  document.getElementById("clearconns").checked = extension.clearConnections;
+  document.getElementById("clearcache").checked = extension.clearCache;
+  document.getElementById("enablespdy").checked = extension.enableSpdy;
+  setUrl(extension.testUrl);
+
+  if (!baseline) {
+    var json_data = JSON.stringify(data);
+    document.getElementById("json").value = json_data;
+  }
+
+  // Activate loading Urls from local file.
+  document.getElementById('files').addEventListener('change',
+                                                    handleFileSelect, false);
+}
+
+function getWidth(mean, obj) {
+  var kMinWidth = 200;
+  var max_width = obj.offsetWidth;
+  if (max_width < kMinWidth) {
+    max_width = kMinWidth;
+  }
+  return Math.floor(max_width * (mean / max_sample));
+}
+
+// Apply configuration back to our extension
+function config() {
+  var extension = chrome.extension.getBackgroundPage();
+  var iterations = parseInt(document.getElementById("iterations").value);
+  var clearConnections = document.getElementById("clearconns").checked;
+  var clearCache = document.getElementById("clearcache").checked;
+  var enableSpdy = document.getElementById("enablespdy").checked;
+  if (iterations > 0) {
+    extension.iterations = iterations;
+    extension.clearConnections = clearConnections;
+    extension.clearCache = clearCache;
+    extension.enableSpdy = enableSpdy;
+  }
+}
+
+// Set the url in the benchmark url box.
+function setUrl(url) {
+  document.getElementById("testurl").value = url;
+}
+
+// Start the benchmark.
+function run() {
+  if (!chrome.benchmarking) {
+    alert("Warning:  Looks like you forgot to run chrome with " +
+          " --enable-benchmarking set.");
+    return;
+  }
+  var extension = chrome.extension.getBackgroundPage();
+  var testUrl = document.getElementById("testurl").value;
+  extension.testUrl = testUrl;
+  extension.run();
+}
+
+function showConfirm() {
+  var r = confirm("Are you sure to clear results?");
+  if (r) {
+    // Find out the event source element.
+    var evtSrc = window.event.srcElement;
+    if (evtSrc.value == "Clear Selected") {
+      clearSelected();
+    } else if (evtSrc.value == "Clear All") {
+      clearResults();
+    }
+  }
+}
+
+// Clear the selected results
+function clearSelected() {
+  var extension = chrome.extension.getBackgroundPage();
+  var checkboxArr = document.getElementsByName("checkboxArr");
+  var rowIndexArr = getSelectedIndex(checkboxArr);
+  var currIndex;
+  for (var i = 0; i < rowIndexArr.length; i++) {
+    currIndex = rowIndexArr[i];
+    // Update the index of the original row in the modified array.
+    currIndex -= i;
+    extension.results.data.splice(currIndex, 1);
+    document.location.reload(true);
+    updateChart(this);
+    jsinit();
+  }
+}
+
+// Clear all the results
+function clearResults() {
+  var extension = chrome.extension.getBackgroundPage();
+  extension.results = {};
+  extension.results.data = new Array();
+  document.getElementById("json").value = "";
+  document.getElementById("baseline").value = "";
+  updateChart(this);
+  jsinit();
+}
+
+// Export html table into CSV format.
+function exportHtml() {
+  var checkboxArr = document.getElementsByName("checkboxArr");
+  var rowNum = checkboxArr.length + 1; // # of data rows plus total-stats row.
+  $('#t').table2CSV(rowNum);
+}
+
+// Toggle display of an element
+function toggle(id) {
+  var elt = document.getElementById(id);
+  if (elt.style.display == "none") {
+    elt.style.display = "block";
+  } else {
+    elt.style.display = "none";
+  }
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+  jsinit();
+  restoreTable();
+
+  document.querySelector('form').addEventListener('click', function() {
+    config();
+    run();
+  });
+  $('#expand')[0].addEventListener('click', function() { expand(); });
+  $('#clearSelected')[0].addEventListener('click',
+                                          function() { showConfirm(); });
+  $('#clearAll')[0].addEventListener('click', function() { showConfirm(); });
+  $('#exportCsv')[0].addEventListener('click', function() { exportHtml(); });
+  var checkboxArrs = document.getElementsByName('checkboxArr');
+  for (var i = 0; i < checkboxArrs.length; ++i) {
+    checkboxArrs[i].addEventListener('click', function() {
+      updateChart(this);
+      clearIndicator();
+      checkSelected();
+    });
+  }
+  var radioArrs = document.getElementsByName('radioArr');
+  for (i = 0; i < radioArrs.length; ++i) {
+    radioArrs[i].addEventListener('click', function() { updateChart(this); });
+  }
+  $('#compare')[0].addEventListener('click', function() { compare(); });
+  $('#toggle-json')[0].addEventListener('click',
+                                        function() { toggle('json'); });
+  $('#toggle-baseline')[0].addEventListener('click',
+                                            function() { toggle('baseline'); });
+  $('#baseline')[0].addEventListener('change', function() { jsinit(); });
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/script.js b/chrome/common/extensions/docs/examples/extensions/benchmark/script.js
new file mode 100644
index 0000000..b35213e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/script.js
@@ -0,0 +1,75 @@
+// The port for communicating back to the extension.
+var benchmarkExtensionPort = chrome.extension.connect();
+
+// The url is what this page is known to the benchmark as.
+// The benchmark uses this id to differentiate the benchmark's
+// results from random pages being browsed.
+
+// TODO(mbelshe): If the page redirects, the location changed and the
+// benchmark stalls.
+var benchmarkExtensionUrl = window.location.toString();
+
+// Compute max/min/avg dom tree depth.
+function computeDepth(dom) {
+  var maxDepth = 0;
+  var minDepth = 1024; // A random large number, the depth of a
+                       // DOM tree mostly is less than that.
+  var avgDepth = 0;
+  var tempNode = 0;
+  var tempDepth = 0;
+
+  for (var i = 0; i < dom.length; i++) {
+    tempNode = dom[i];
+    tempDepth = 0;
+
+    while (tempNode.parentNode) {
+      tempNode = tempNode.parentNode;
+      tempDepth++;
+    }
+
+    if (maxDepth < tempDepth) {
+      maxDepth = tempDepth;
+    } else if (minDepth > tempDepth) {
+      minDepth = tempDepth;
+    }
+
+    avgDepth += tempDepth;
+  }
+  //The avg is the depth of each node divided by the num of nodes.
+  avgDepth = avgDepth / dom.length;
+
+  depths = new Array(3);
+  depths[0] = maxDepth;
+  depths[1] = minDepth;
+  depths[2] = avgDepth;
+
+  return depths;
+}
+
+function sendTimesToExtension() {
+  if (window.parent != window) {
+    return;
+  }
+  var load_times = window.chrome.loadTimes();
+  var dom = window.document.getElementsByTagName('*');
+
+  var depths = new Array(3);
+
+  depths = computeDepth(dom);
+
+  // If the load is not finished yet, schedule a timer to check again in a
+  // little bit.
+  if (load_times.finishLoadTime != 0) {
+    benchmarkExtensionPort.postMessage({message: 'load',
+                                        url: benchmarkExtensionUrl,
+                                        values: load_times,
+                                        domNum: dom.length,
+                                        domDepths: depths });
+  } else {
+    window.setTimeout(sendTimesToExtension, 100);
+  }
+}
+
+// We can't use the onload event because this script runs at document idle,
+// which may run after the onload has completed.
+sendTimesToExtension();
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/stopwatch.jpg b/chrome/common/extensions/docs/examples/extensions/benchmark/stopwatch.jpg
new file mode 100644
index 0000000..60628b1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/stopwatch.jpg
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/util/sorttable.js b/chrome/common/extensions/docs/examples/extensions/benchmark/util/sorttable.js
new file mode 100644
index 0000000..0a2ca4f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/util/sorttable.js
@@ -0,0 +1,487 @@
+/*
+  SortTable
+  version 2
+  7th April 2007
+  Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
+
+  Instructions:
+  Download this file
+  Add <script src="sorttable.js"></script> to your HTML
+  Add class="sortable" to any table you'd like to make sortable
+  Click on the headers to sort
+
+  Thanks to many, many people for contributions and suggestions.
+  Licenced as X11: http://www.kryogenix.org/code/browser/licence.html
+  This basically means: do what you want with it.
+*/
+
+/*
+Changes by jning:
+-Specify which coloumns to sort or not by changing index in makeSortable().
+-To avoid including the non-data rows (e.g., radio buttions) into sorting,
+ add index and row handling (deletion and appending) in makeSortable() and
+ reverse().
+-Remove the init parts for other browsers.
+*/
+
+var stIsIE = /*@cc_on!@*/false;
+
+sorttable = {
+  init: function() {
+    // quit if this function has already been called
+    if (arguments.callee.done) return;
+    // flag this function so we don't do the same thing twice
+    arguments.callee.done = true;
+
+    if (!document.createElement || !document.getElementsByTagName) return;
+
+    sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/;
+
+    forEach(document.getElementsByTagName('table'), function(table) {
+      if (table.className.search(/\bsortable\b/) != -1) {
+        sorttable.makeSortable(table);
+      }
+    });
+
+  },
+
+  makeSortable: function(table) {
+    if (table.getElementsByTagName('thead').length == 0) {
+      // table doesn't have a tHead. Since it should have, create one and
+      // put the first table row in it.
+      the = document.createElement('thead');
+      the.appendChild(table.rows[0]);
+      table.insertBefore(the,table.firstChild);
+    }
+    // Safari doesn't support table.tHead, sigh
+    if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0];
+
+    if (table.tHead.rows.length != 1) return; // can't cope with two header rows
+
+    // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as
+    // "total" rows, for example). This is B&R, since what you're supposed
+    // to do is put them in a tfoot. So, if there are sortbottom rows,
+    // for backwards compatibility, move them to tfoot (creating it if needed).
+    sortbottomrows = [];
+    for (var i=0; i<table.rows.length; i++) {
+      if (table.rows[i].className.search(/\bsortbottom\b/) != -1) {
+        sortbottomrows[sortbottomrows.length] = table.rows[i];
+      }
+    }
+    if (sortbottomrows) {
+      if (table.tFoot == null) {
+        // table doesn't have a tfoot. Create one.
+        tfo = document.createElement('tfoot');
+        table.appendChild(tfo);
+      }
+      for (var i=0; i<sortbottomrows.length; i++) {
+        tfo.appendChild(sortbottomrows[i]);
+      }
+      delete sortbottomrows;
+    }
+
+    // work through each column and calculate its type
+    headrow = table.tHead.rows[0].cells;
+    for (var i=5; i<headrow.length-1; i++) {
+      // manually override the type with a sorttable_type attribute
+      if (!headrow[i].className.match(/\bsorttable_nosort\b/)) { // skip this col
+        mtch = headrow[i].className.match(/\bsorttable_([a-z0-9]+)\b/);
+        if (mtch) { override = mtch[1]; }
+	      if (mtch && typeof sorttable["sort_"+override] == 'function') {
+	        headrow[i].sorttable_sortfunction = sorttable["sort_"+override];
+	      } else {
+	        headrow[i].sorttable_sortfunction = sorttable.guessType(table,i);
+	      }
+	      // make it clickable to sort
+	      headrow[i].sorttable_columnindex = i;
+	      headrow[i].sorttable_tbody = table.tBodies[0];
+	      dean_addEvent(headrow[i],"click", function(e) {
+
+          if (this.className.search(/\bsorttable_sorted\b/) != -1) {
+            // if we're already sorted by this column, just
+            // reverse the table, which is quicker
+            sorttable.reverse(this.sorttable_tbody);
+            this.className = this.className.replace('sorttable_sorted',
+                                                    'sorttable_sorted_reverse');
+            this.removeChild(document.getElementById('sorttable_sortfwdind'));
+            sortrevind = document.createElement('span');
+            sortrevind.id = "sorttable_sortrevind";
+            sortrevind.innerHTML = stIsIE ? '&nbsp<font face="webdings">5</font>' : '&nbsp;&#x25B4;';
+            this.appendChild(sortrevind);
+            return;
+          }
+          if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) {
+            // if we're already sorted by this column in reverse, just
+            // re-reverse the table, which is quicker
+            sorttable.reverse(this.sorttable_tbody);
+            this.className = this.className.replace('sorttable_sorted_reverse',
+                                                    'sorttable_sorted');
+            this.removeChild(document.getElementById('sorttable_sortrevind'));
+            sortfwdind = document.createElement('span');
+            sortfwdind.id = "sorttable_sortfwdind";
+            sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
+            this.appendChild(sortfwdind);
+            return;
+          }
+
+          // remove sorttable_sorted classes
+          theadrow = this.parentNode;
+          forEach(theadrow.childNodes, function(cell) {
+            if (cell.nodeType == 1) { // an element
+              cell.className = cell.className.replace('sorttable_sorted_reverse','');
+              cell.className = cell.className.replace('sorttable_sorted','');
+            }
+          });
+          sortfwdind = document.getElementById('sorttable_sortfwdind');
+          if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); }
+          sortrevind = document.getElementById('sorttable_sortrevind');
+          if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); }
+
+          this.className += ' sorttable_sorted';
+          sortfwdind = document.createElement('span');
+          sortfwdind.id = "sorttable_sortfwdind";
+          sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
+          this.appendChild(sortfwdind);
+
+	        // build an array to sort. This is a Schwartzian transform thing,
+	        // i.e., we "decorate" each row with the actual sort key,
+	        // sort based on the sort keys, and then put the rows back in order
+	        // which is a lot faster because you only do getInnerText once per row
+	        row_array = [];
+	        col = this.sorttable_columnindex;
+	        rows = this.sorttable_tbody.rows;
+	        for (var j=1; j<rows.length-3; j++) {
+	          row_array[row_array.length] = [sorttable.getInnerText(rows[j].cells[col]), rows[j]];
+	        }
+	        /* If you want a stable sort, uncomment the following line */
+	        sorttable.shaker_sort(row_array, this.sorttable_sortfunction);
+	        /* and comment out this one */
+	        //row_array.sort(this.sorttable_sortfunction);
+
+	        tb = this.sorttable_tbody;
+                var noTestRow = tb.rows[tb.rows.length-3];
+                var radioRow = tb.rows[tb.rows.length-2];
+                var compareRow = tb.rows[tb.rows.length-1];
+                tb.deleteRow(tb.rows.length-1);
+                tb.deleteRow(tb.rows.length-1);
+                tb.deleteRow(tb.rows.length-1);
+                for (var j=0; j<row_array.length; j++) {
+                   tb.appendChild(row_array[j][1]);
+	        }
+                tb.appendChild(noTestRow);
+                tb.appendChild(radioRow);
+                tb.appendChild(compareRow);
+	        delete row_array;
+                delete noTestRow;
+                delete radioRow;
+                delete compareRow;
+	      });
+	    }
+    }
+  },
+
+  guessType: function(table, column) {
+    // guess the type of a column based on its first non-blank row
+    sortfn = sorttable.sort_numeric;
+    for (var i=0; i<table.tBodies[0].rows.length-3; i++) {
+      text = sorttable.getInnerText(table.tBodies[0].rows[i].cells[column]);
+      if (text != '') {
+        if (text.match(/^-?[£$¤]?[\d,.]+%?$/)) {
+          return sorttable.sort_numeric;
+        }
+        // check for a date: dd/mm/yyyy or dd/mm/yy
+        // can have / or . or - as separator
+        // can be mm/dd as well
+        possdate = text.match(sorttable.DATE_RE)
+        if (possdate) {
+          // looks like a date
+          first = parseInt(possdate[1]);
+          second = parseInt(possdate[2]);
+          if (first > 12) {
+            // definitely dd/mm
+            return sorttable.sort_ddmm;
+          } else if (second > 12) {
+            return sorttable.sort_mmdd;
+          } else {
+            // looks like a date, but we can't tell which, so assume
+            // that it's dd/mm (English imperialism!) and keep looking
+            sortfn = sorttable.sort_ddmm;
+          }
+        }
+      }
+    }
+    return sortfn;
+  },
+
+  getInnerText: function(node) {
+    // gets the text we want to use for sorting for a cell.
+    // strips leading and trailing whitespace.
+    // this is *not* a generic getInnerText function; it's special to sorttable.
+    // for example, you can override the cell text with a customkey attribute.
+    // it also gets .value for <input> fields.
+
+    hasInputs = (typeof node.getElementsByTagName == 'function') &&
+                 node.getElementsByTagName('input').length;
+
+    if (node.getAttribute("sorttable_customkey") != null) {
+      return node.getAttribute("sorttable_customkey");
+    }
+    else if (typeof node.textContent != 'undefined' && !hasInputs) {
+      return node.textContent.replace(/^\s+|\s+$/g, '');
+    }
+    else if (typeof node.innerText != 'undefined' && !hasInputs) {
+      return node.innerText.replace(/^\s+|\s+$/g, '');
+    }
+    else if (typeof node.text != 'undefined' && !hasInputs) {
+      return node.text.replace(/^\s+|\s+$/g, '');
+    }
+    else {
+      switch (node.nodeType) {
+        case 3:
+          if (node.nodeName.toLowerCase() == 'input') {
+            return node.value.replace(/^\s+|\s+$/g, '');
+          }
+        case 4:
+          return node.nodeValue.replace(/^\s+|\s+$/g, '');
+          break;
+        case 1:
+        case 11:
+          var innerText = '';
+          for (var i = 0; i < node.childNodes.length; i++) {
+            innerText += sorttable.getInnerText(node.childNodes[i]);
+          }
+          return innerText.replace(/^\s+|\s+$/g, '');
+          break;
+        default:
+          return '';
+      }
+    }
+  },
+
+  reverse: function(tbody) {
+    // reverse the rows in a tbody
+    newrows = [];
+    for (var i=0; i<tbody.rows.length; i++) {
+      newrows[newrows.length] = tbody.rows[i];
+    }
+    tbody.appendChild(newrows[0]);
+    for (var i=newrows.length-4; i>0; i--) {
+       tbody.appendChild(newrows[i]);
+    }
+    tbody.appendChild(newrows[newrows.length-3]);
+    tbody.appendChild(newrows[newrows.length-2]);
+    tbody.appendChild(newrows[newrows.length-1]);
+    delete newrows;
+  },
+
+  /* sort functions
+     each sort function takes two parameters, a and b
+     you are comparing a[0] and b[0] */
+  sort_numeric: function(a,b) {
+    aa = parseFloat(a[0].replace(/[^0-9.-]/g,''));
+    if (isNaN(aa)) aa = 0;
+    bb = parseFloat(b[0].replace(/[^0-9.-]/g,''));
+    if (isNaN(bb)) bb = 0;
+    return aa-bb;
+  },
+  sort_alpha: function(a,b) {
+    if (a[0]==b[0]) return 0;
+    if (a[0]<b[0]) return -1;
+    return 1;
+  },
+  sort_ddmm: function(a,b) {
+    mtch = a[0].match(sorttable.DATE_RE);
+    y = mtch[3]; m = mtch[2]; d = mtch[1];
+    if (m.length == 1) m = '0'+m;
+    if (d.length == 1) d = '0'+d;
+    dt1 = y+m+d;
+    mtch = b[0].match(sorttable.DATE_RE);
+    y = mtch[3]; m = mtch[2]; d = mtch[1];
+    if (m.length == 1) m = '0'+m;
+    if (d.length == 1) d = '0'+d;
+    dt2 = y+m+d;
+    if (dt1==dt2) return 0;
+    if (dt1<dt2) return -1;
+    return 1;
+  },
+  sort_mmdd: function(a,b) {
+    mtch = a[0].match(sorttable.DATE_RE);
+    y = mtch[3]; d = mtch[2]; m = mtch[1];
+    if (m.length == 1) m = '0'+m;
+    if (d.length == 1) d = '0'+d;
+    dt1 = y+m+d;
+    mtch = b[0].match(sorttable.DATE_RE);
+    y = mtch[3]; d = mtch[2]; m = mtch[1];
+    if (m.length == 1) m = '0'+m;
+    if (d.length == 1) d = '0'+d;
+    dt2 = y+m+d;
+    if (dt1==dt2) return 0;
+    if (dt1<dt2) return -1;
+    return 1;
+  },
+
+  shaker_sort: function(list, comp_func) {
+    // A stable sort function to allow multi-level sorting of data
+    // see: http://en.wikipedia.org/wiki/Cocktail_sort
+    // thanks to Joseph Nahmias
+    var b = 0;
+    var t = list.length - 1;
+    var swap = true;
+
+    while(swap) {
+        swap = false;
+        for(var i = b; i < t; ++i) {
+            if ( comp_func(list[i], list[i+1]) > 0 ) {
+                var q = list[i]; list[i] = list[i+1]; list[i+1] = q;
+                swap = true;
+            }
+        } // for
+        t--;
+
+        if (!swap) break;
+
+        for(var i = t; i > b; --i) {
+            if ( comp_func(list[i], list[i-1]) < 0 ) {
+                var q = list[i]; list[i] = list[i-1]; list[i-1] = q;
+                swap = true;
+            }
+        } // for
+        b++;
+
+    } // while(swap)
+  }
+}
+
+/* ******************************************************************
+   Supporting functions: bundled here to avoid depending on a library
+   ****************************************************************** */
+
+// Dean Edwards/Matthias Miller/John Resig
+
+/* for other browsers */
+window.onload = sorttable.init;
+
+// written by Dean Edwards, 2005
+// with input from Tino Zijdel, Matthias Miller, Diego Perini
+
+// http://dean.edwards.name/weblog/2005/10/add-event/
+
+function dean_addEvent(element, type, handler) {
+	if (element.addEventListener) {
+		element.addEventListener(type, handler, false);
+	} else {
+		// assign each event handler a unique ID
+		if (!handler.$$guid) handler.$$guid = dean_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
+dean_addEvent.guid = 1;
+
+function removeEvent(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];
+		}
+	}
+};
+
+function handleEvent(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;
+};
+
+function fixEvent(event) {
+	// add W3C standard event methods
+	event.preventDefault = fixEvent.preventDefault;
+	event.stopPropagation = fixEvent.stopPropagation;
+	return event;
+};
+fixEvent.preventDefault = function() {
+	this.returnValue = false;
+};
+fixEvent.stopPropagation = function() {
+  this.cancelBubble = true;
+}
+
+// Dean's forEach: http://dean.edwards.name/base/forEach.js
+/*
+	forEach, version 1.0
+	Copyright 2006, Dean Edwards
+	License: http://www.opensource.org/licenses/mit-license.php
+*/
+
+// array-like enumeration
+if (!Array.forEach) { // mozilla already supports this
+	Array.forEach = function(array, block, context) {
+		for (var i = 0; i < array.length; i++) {
+			block.call(context, array[i], i, array);
+		}
+	};
+}
+
+// generic enumeration
+Function.prototype.forEach = function(object, block, context) {
+	for (var key in object) {
+		if (typeof this.prototype[key] == "undefined") {
+			block.call(context, object[key], key, object);
+		}
+	}
+};
+
+// character enumeration
+String.forEach = function(string, block, context) {
+	Array.forEach(string.split(""), function(chr, index) {
+		block.call(context, chr, index, string);
+	});
+};
+
+// globally resolve forEach enumeration
+var forEach = function(object, block, context) {
+	if (object) {
+		var resolve = Object; // default
+		if (object instanceof Function) {
+			// functions have a "length" property
+			resolve = Function;
+		} else if (object.forEach instanceof Function) {
+			// the object implements a custom forEach method so use that
+			object.forEach(block, context);
+			return;
+		} else if (typeof object == "string") {
+			// the object is a string
+			resolve = String;
+		} else if (typeof object.length == "number") {
+			// the object is array-like
+			resolve = Array;
+		}
+		resolve.forEach(object, block, context);
+	}
+};
diff --git a/chrome/common/extensions/docs/examples/extensions/benchmark/util/table2CSV.js b/chrome/common/extensions/docs/examples/extensions/benchmark/util/table2CSV.js
new file mode 100644
index 0000000..04a5166
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/benchmark/util/table2CSV.js
@@ -0,0 +1,90 @@
+/*
+This is a small JQuery utility to export HTML table as CSV file.
+
+The author is Kunal Babre and the original script can be found in
+http://www.kunalbabre.com/projects/table2CSV.php. Permissions are
+granted by the author to make changes and redistribute.
+
+Changes made by jning: To avoid exporting the textbox, radio buttons and etc.
+in the table, the parameters rowNum and index in $().find().each() or
+$().filter().find.each() help to ignore non-data cells.
+*/
+
+jQuery.fn.table2CSV = function(rowNum, options) {
+    var options = jQuery.extend({
+        separator: ',',
+        header: [],
+        delivery: 'popup' // popup, value
+    },
+    options);
+
+    var csvData = [];
+    var headerArr = [];
+    var el = this;
+
+    //header
+    var numCols = options.header.length;
+    var tmpRow = []; // construct header avalible array
+
+    if (numCols > 0) {
+        for (var i = 0; i < numCols; i++) {
+            tmpRow[tmpRow.length] = formatData(options.header[i]);
+        }
+    } else {
+        $(el).filter(':visible').find('th').each(function(index) {
+            if (index > 0 && $(this).css('display') != 'none')
+                tmpRow[tmpRow.length] = formatData($(this).html());
+        });
+    }
+
+    row2CSV(tmpRow);
+
+    // actual data
+    $(el).find('tr').each(function(index) {
+        if (index < rowNum + 1) {
+            var tmpRow = [];
+            $(this).filter(':visible').find('td').each(function(index) {
+                if (index > 0 && $(this).css('display') != 'none')
+                    tmpRow[tmpRow.length] = formatData($(this).html());
+            });
+            row2CSV(tmpRow);
+        }
+    });
+    if (options.delivery == 'popup') {
+        var mydata = csvData.join('\n');
+        return popup(mydata);
+    } else {
+        var mydata = csvData.join('\n');
+        return mydata;
+    }
+
+    function row2CSV(tmpRow) {
+        var tmp = tmpRow.join('') // to remove any blank rows
+        // alert(tmp);
+        if (tmpRow.length > 0 && tmp != '') {
+            var mystr = tmpRow.join(options.separator);
+            csvData[csvData.length] = mystr;
+        }
+    }
+    function formatData(input) {
+        // replace " with “
+        var regexp = new RegExp(/["]/g);
+        var output = input.replace(regexp, "“");
+        //HTML
+        var regexp = new RegExp(/\<[^\<]+\>/g);
+        var output = output.replace(regexp, "");
+        if (output == "") return '';
+        return '"' + output + '"';
+    }
+    function popup(data) {
+        var generator = window.open('', 'csv', 'height=400,width=600');
+        generator.document.write('<html><head><title>CSV</title>');
+        generator.document.write('</head><body >');
+        generator.document.write('<textArea cols=70 rows=15 wrap="off" >');
+        generator.document.write(data);
+        generator.document.write('</textArea>');
+        generator.document.write('</body></html>');
+        generator.document.close();
+        return true;
+    }
+};
diff --git a/chrome/common/extensions/docs/examples/extensions/buildbot/bg.js b/chrome/common/extensions/docs/examples/extensions/buildbot/bg.js
new file mode 100644
index 0000000..cc69097
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/buildbot/bg.js
@@ -0,0 +1,80 @@
+// Copyright (c) 2012 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.
+
+var statusURL = "http://chromium-status.appspot.com/current?format=raw";
+
+if (!localStorage.prefs) {
+  // Default to notifications being on.
+  localStorage.prefs = JSON.stringify({ "use_notifications": true });
+}
+
+var lastOpen = null;
+var lastNotification = null;
+function notifyIfStatusChange(open, status) {
+  var prefs = JSON.parse(localStorage.prefs);
+  if (lastOpen && lastOpen != open && prefs.use_notifications) {
+    if (lastNotification) {
+      lastNotification.cancel();
+    }
+    var notification = webkitNotifications.createNotification(
+        chrome.extension.getURL("icon.png"), "Tree is " + open, status);
+    lastNotification = notification;
+    notification.show();
+  }
+  lastOpen = open;
+}
+
+function updateStatus(status) {
+  chrome.browserAction.setTitle({title:status});
+  var open = /open/i;
+  if (open.exec(status)) {
+    notifyIfStatusChange("open", status);
+    //chrome.browserAction.setBadgeText("\u263A");
+    chrome.browserAction.setBadgeText({text:"\u2022"});
+    chrome.browserAction.setBadgeBackgroundColor({color:[0,255,0,255]});
+  } else {
+    notifyIfStatusChange("closed", status);
+    //chrome.browserAction.setBadgeText("\u2639");
+    chrome.browserAction.setBadgeText({text:"\u00D7"});
+    chrome.browserAction.setBadgeBackgroundColor({color:[255,0,0,255]});
+  }
+}
+
+function requestStatus() {
+  requestURL(statusURL, updateStatus);
+  setTimeout(requestStatus, 30000);
+}
+
+function requestURL(url, callback) {
+  //console.log("requestURL: " + url);
+  var xhr = new XMLHttpRequest();
+  try {
+    xhr.onreadystatechange = function(state) {
+      if (xhr.readyState == 4) {
+        if (xhr.status == 200) {
+          var text = xhr.responseText;
+          //console.log(text);
+          callback(text);
+        } else {
+          chrome.browserAction.setBadgeText({text:"?"});
+          chrome.browserAction.setBadgeBackgroundColor({color:[0,0,255,255]});
+        }
+      }
+    }
+
+    xhr.onerror = function(error) {
+      console.log("xhr error: " + JSON.stringify(error));
+      console.dir(error);
+    }
+
+    xhr.open("GET", url, true);
+    xhr.send({});
+  } catch(e) {
+    console.log("exception: " + e);
+  }
+}
+
+window.onload = function() {
+  window.setTimeout(requestStatus, 10);
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/buildbot/chromium.png b/chrome/common/extensions/docs/examples/extensions/buildbot/chromium.png
new file mode 100644
index 0000000..8c07cf8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/buildbot/chromium.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/buildbot/icon.png b/chrome/common/extensions/docs/examples/extensions/buildbot/icon.png
new file mode 100644
index 0000000..691d382
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/buildbot/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/buildbot/manifest.json b/chrome/common/extensions/docs/examples/extensions/buildbot/manifest.json
new file mode 100644
index 0000000..743bcdb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/buildbot/manifest.json
@@ -0,0 +1,22 @@
+{
+  "name": "Chromium Buildbot Monitor",
+  "version": "0.7.7",
+  "description": "Displays the status of the Chromium buildbot in the toolbar.  Click to see more detailed status in a popup.",
+  "icons": { "128": "icon.png" },
+  "background": {
+    "scripts": ["bg.js"]
+  },
+  "permissions": [
+    "notifications",
+    "http://build.chromium.org/",
+    "http://chromium-status.appspot.com/"
+  ],
+  "browser_action": {
+    "default_title": "",
+    "default_icon": "chromium.png",
+    "default_popup": "popup.html"
+  },
+  "options_page": "options.html",
+
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/buildbot/options.html b/chrome/common/extensions/docs/examples/extensions/buildbot/options.html
new file mode 100644
index 0000000..796dd02
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/buildbot/options.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html>
+<head>
+  <title>Chromium Buildbot Monitor Options</title>
+  <script src='options.js'></script>
+</head>
+<body>
+  <h2>Options for Chromium Buildbot Monitor</h2>
+  <br>
+  <label>
+    Use desktop notifications:
+    <input id="notifications" type="checkbox">
+  </label>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/buildbot/options.js b/chrome/common/extensions/docs/examples/extensions/buildbot/options.js
new file mode 100644
index 0000000..e97f346
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/buildbot/options.js
@@ -0,0 +1,17 @@
+// Copyright (c) 2012 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.
+
+function save() {
+  var prefs = JSON.parse(localStorage.prefs);
+  prefs.use_notifications = document.getElementById('notifications').checked;
+  localStorage.prefs = JSON.stringify(prefs);
+}
+
+// Make sure the checkbox checked state gets properly initialized from the
+// saved preference.
+document.addEventListener('DOMContentLoaded', function () {
+  var prefs = JSON.parse(localStorage.prefs);
+  document.getElementById('notifications').checked = prefs.use_notifications;
+  document.getElementById('notifications').addEventListener('click', save);
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/buildbot/popup.html b/chrome/common/extensions/docs/examples/extensions/buildbot/popup.html
new file mode 100644
index 0000000..8d4f6b1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/buildbot/popup.html
@@ -0,0 +1,100 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Chromium Buildbot Monitor Popup</title>
+    <script src='popup.js'></script>
+    <style>
+    body {
+      font-family: sans-serif;
+      font-size: 0.8em;
+      overflow: hidden;
+    }
+
+    #links {
+      background-color: #efefef;
+      border: 1px solid #cccccc;
+      border-radius: 5px;
+      margin-top: 1px;
+      padding: 3px;
+      white-space: nowrap;
+      text-align: center;
+    }
+
+    a {
+      text-decoration: underline;
+      color: #444;
+    }
+
+    a:hover {
+      color: black;
+      cursor: pointer;
+    }
+
+    body.big .bot {
+      -webkit-transition: all .5s ease-out;
+      margin: 20px;
+    }
+
+    body.small .bot {
+      -webkit-transition: all .5s ease-out;
+    }
+
+    .bot {
+      cursor: pointer;
+      border-radius: 5px;
+      margin-top: 1px;
+      padding: 3px;
+      white-space: nowrap;
+    }
+
+    .bot:hover {
+      border: 2px solid black;
+      padding: 2px;
+    }
+
+    .open {
+      color: green;
+    }
+
+    .closed {
+      color: red;
+    }
+
+    .running {
+      background-color:  rgb(255, 252, 108);
+      border: 1px solid rgb(197, 197, 109);
+    }
+
+    .notstarted {
+      border: 1px solid rgb(170, 170, 170);
+    }
+
+    .failure {
+      background-color: rgb(233, 128, 128);
+      border: 1px solid rgb(167, 114, 114);
+    }
+
+    .warnings {
+      background-color: rgb(255, 195, 67);
+      border: 1px solid rgb(194, 157, 70);
+    }
+
+    .success {
+      background-color: rgb(143, 223, 95);
+      border: 1px solid rgb(79, 133, 48);
+    }
+
+    .exception {
+      background-color: rgb(224, 176, 255);
+      border: 1px solid rgb(172, 160, 179);
+    }
+  </style>
+</head>
+<body>
+<div id="links">
+<a href="" id='console'>console</a> -
+<a href="" id='try'>try</a> -
+<a href="" id='fyi'>fyi</a>
+</div>
+<div id="bots">Loading....</div>
+</body>
diff --git a/chrome/common/extensions/docs/examples/extensions/buildbot/popup.js b/chrome/common/extensions/docs/examples/extensions/buildbot/popup.js
new file mode 100644
index 0000000..d127bc6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/buildbot/popup.js
@@ -0,0 +1,154 @@
+// Copyright (c) 2012 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.
+
+var botRoot = "http://build.chromium.org/p/chromium";
+var waterfallURL = botRoot + "/waterfall/bot_status.json?json=1";
+var botList;
+var checkinResults;
+var bots;
+var failures;
+
+function updateBotList(text) {
+  var results = (new RegExp('(.*)<\/body>', 'g')).exec(text);
+  if (!results || results.index < 0) {
+    console.log("Error: couldn't find bot JSON");
+    console.log(text);
+    return;
+  }
+  var data;
+  try {
+    // The build bot returns invalid JSON. Namely it uses single
+    // quotes and includes commas in some invalid locations. We have to
+    // run some regexps across the text to fix it up.
+    var jsonString = results[1].replace(/'/g, '"').replace(/},]/g,'}]');
+    data = JSON.parse(jsonString);
+  } catch (e) {
+    console.dir(e);
+    console.log(text);
+    return;
+  }
+  if (!data) {
+    throw new Error("JSON parse fail: " + results[1]);
+  }
+  botList = data[0];
+  checkinResults = data[1];
+
+  failures = botList.filter(function(bot) {
+    return (bot.color != "success");
+  });
+  displayFailures();
+}
+
+function displayFailures() {
+  bots.innerText = "";
+
+  if (failures.length == 0) {
+    var anchor = document.createElement("a");
+    anchor.addEventListener("click", showConsole);
+    anchor.className = "open";
+    anchor.innerText = "The tree is completely green.";
+    bots.appendChild(anchor);
+    bots.appendChild(document.createTextNode(" (no way!)"));
+  } else {
+    var anchor = document.createElement("a");
+    anchor.addEventListener("click", showFailures);
+    anchor.innerText = "failures:";
+    var div = document.createElement("div");
+    div.appendChild(anchor);
+    bots.appendChild(div);
+
+    failures.forEach(function(bot, i) {
+      var div = document.createElement("div");
+      div.className = "bot " + bot.color;
+      div.addEventListener("click", function() {
+        // Requires closure for each iteration to retain local value of |i|.
+        return function() { showBot(i); }
+      }());
+      div.innerText = bot.title;
+      bots.appendChild(div);
+    });
+  }
+}
+
+function showURL(url) {
+  window.open(url);
+  window.event.stopPropagation();
+}
+
+function showBot(botIndex) {
+  var bot = failures[botIndex];
+  var url = botRoot + "/waterfall/waterfall?builder=" + bot.name;
+  showURL(url);
+}
+
+function showConsole() {
+  var url = botRoot + "/waterfall/console";
+  showURL(url);
+}
+
+function showTry() {
+  var url = botRoot + "/try-server/waterfall";
+  showURL(url);
+}
+
+function showFyi() {
+  var url = botRoot + "/waterfall.fyi/console";
+  showURL(url);
+}
+
+function showFailures() {
+  var url = botRoot +
+      "/waterfall/waterfall?show_events=true&failures_only=true";
+  showURL(url);
+}
+
+function requestURL(url, callback) {
+  console.log("requestURL: " + url);
+  var xhr = new XMLHttpRequest();
+  try {
+    xhr.onreadystatechange = function(state) {
+      if (xhr.readyState == 4) {
+        if (xhr.status == 200) {
+          var text = xhr.responseText;
+          //console.log(text);
+          callback(text);
+        } else {
+          bots.innerText = "Error.";
+        }
+      }
+    }
+
+    xhr.onerror = function(error) {
+      console.log("xhr error: " + JSON.stringify(error));
+      console.dir(error);
+    }
+
+    xhr.open("GET", url, true);
+    xhr.send({});
+  } catch(e) {
+    console.log("exception: " + e);
+  }
+}
+
+function toggle_size() {
+  if (document.body.className == "big") {
+    document.body.className = "small";
+  } else {
+    document.body.className = "big";
+  }
+}
+
+document.addEventListener('DOMContentLoaded', function () {
+  toggle_size();
+
+  bots = document.getElementById("bots");
+
+  // XHR from onload winds up blocking the load, so we put it in a setTimeout.
+  window.setTimeout(requestURL, 0, waterfallURL, updateBotList);
+
+  // Setup event handlers
+  document.querySelector('#console').addEventListener('click', showConsole);
+  document.querySelector('#try').addEventListener('click', showTry);
+  document.querySelector('#fyi').addEventListener('click', showFyi);
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ar/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ar/messages.json
new file mode 100644
index 0000000..3fd46a0
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ar/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (\u062a\u062f\u0639\u0645\u0647\u0627 Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u064a\u0645\u0643\u0646\u0643 \u0628\u0633\u0631\u0639\u0629 \u0627\u0644\u062a\u0639\u0631\u0641 \u0639\u0644\u0649 \u0627\u0644\u0648\u0642\u062a \u0627\u0644\u0645\u062a\u0628\u0642\u064a \u0644\u0643 \u0625\u0644\u0649 \u0623\u0646 \u064a\u062d\u064a\u0646 \u0645\u0648\u0639\u062f \u0627\u062c\u062a\u0645\u0627\u0639\u0643 \u0627\u0644\u062a\u0627\u0644\u064a \u0645\u0646 \u0623\u064a \u062a\u0642\u0648\u064a\u0645 \u0645\u0646 \u062a\u0642\u0627\u0648\u064a\u0645\u0643. \u0627\u0646\u0642\u0631 \u0639\u0644\u0649 \u0627\u0644\u0632\u0631 \u0644\u064a\u062a\u0645 \u0646\u0642\u0644\u0643 \u0625\u0644\u0649 \u0627\u0644\u062a\u0642\u0648\u064a\u0645."},"direction":{"message":"rtl"},"notitle":{"message":"(\u0644\u064a\u0633 \u0647\u0646\u0627\u0643 \u0623\u064a \u0639\u0646\u0648\u0627\u0646)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 \u062f\u0642\u064a\u0642\u0629","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 \u0633\u0627\u0639\u0629","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 \u064a\u0648\u0645","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u062f\u0639\u0645 \u0645\u062a\u0639\u062f\u062f \u0644\u0644\u062a\u0642\u0648\u064a\u0645"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u062a\u0645 \u062d\u0641\u0638 \u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a."},"status_saving":{"message":"\u062c\u0627\u0631\u0650 \u0627\u0644\u062d\u0641\u0638..."},"multicalendartooltip":{"message":"\u0627\u0644\u0631\u062c\u0627\u0621 \u062a\u062d\u062f\u064a\u062f \u0627\u0644\u0645\u0631\u0628\u0639 \u0644\u062a\u0645\u0643\u064a\u0646 \u0627\u0644\u062f\u0639\u0645 \u0627\u0644\u0645\u062a\u0639\u062f\u062f \u0644\u0644\u062a\u0642\u0648\u064a\u0645."},"imagetooltip":{"message":"\u062a\u0642\u0648\u064a\u0645 Google"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/bg/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/bg/messages.json
new file mode 100644
index 0000000..e7dc1a6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/bg/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (by Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u0412\u0438\u0436\u0442\u0435 \u0431\u044a\u0440\u0437\u043e \u043a\u043e\u043b\u043a\u043e \u0432\u0440\u0435\u043c\u0435 \u0432\u0438 \u043e\u0441\u0442\u0430\u0432\u0430 \u0434\u043e \u0441\u043b\u0435\u0434\u0432\u0430\u0449\u0430\u0442\u0430 \u0432\u0438 \u0441\u0440\u0435\u0449\u0430 \u043e\u0442 \u0432\u0441\u0435\u043a\u0438 \u0441\u0432\u043e\u0439 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440. \u041a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u0432\u044a\u0440\u0445\u0443 \u0431\u0443\u0442\u043e\u043d\u0430, \u0437\u0430 \u0434\u0430 \u0433\u043e \u043e\u0442\u0432\u043e\u0440\u0438\u0442\u0435."},"direction":{"message":"ltr"},"notitle":{"message":"(\u0411\u0435\u0437 \u0437\u0430\u0433\u043b\u0430\u0432\u0438\u0435)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 \u043c","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 \u0447","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 \u0434","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u041f\u043e\u0434\u0434\u0440\u044a\u0436\u043a\u0430 \u043d\u0430 \u043d\u044f\u043a\u043e\u043b\u043a\u043e \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0430"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435 \u0441\u0430 \u0437\u0430\u043f\u0430\u0437\u0435\u043d\u0438."},"status_saving":{"message":"\u0417\u0430\u043f\u0430\u0437\u0432\u0430 \u0441\u0435...."},"multicalendartooltip":{"message":"\u041c\u043e\u043b\u044f, \u043f\u043e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u043e\u0442\u043c\u0435\u0442\u043a\u0430 \u0432 \u043a\u0432\u0430\u0434\u0440\u0430\u0442\u0447\u0435\u0442\u043e, \u0437\u0430 \u0434\u0430 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u0442\u0435 \u043f\u043e\u0434\u0434\u0440\u044a\u0436\u043a\u0430\u0442\u0430 \u043d\u0430 \u043d\u044f\u043a\u043e\u043b\u043a\u043e \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0430"},"imagetooltip":{"message":"Google \u041a\u0430\u043b\u0435\u043d\u0434\u0430\u0440"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ca/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ca/messages.json
new file mode 100644
index 0000000..b0ab0c6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ca/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (de Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Comproveu r\u00e0pidament el temps que falta per a la propera reuni\u00f3 a qualsevol dels vostres calendaris. Feu clic al bot\u00f3 per anar-hi."},"direction":{"message":"ltr"},"notitle":{"message":"(Sense t\u00edtol)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 dies","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Compatibilitat amb m\u00faltiples calendaris"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"S'ha desat la configuraci\u00f3."},"status_saving":{"message":"S'est\u00e0 desant..."},"multicalendartooltip":{"message":"Activeu la casella per activar la compatibilitat amb m\u00faltiples calendaris"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/cs/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/cs/messages.json
new file mode 100644
index 0000000..6e31a42
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/cs/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Kontrola Kalend\u00e1\u0159e Google (od spole\u010dnosti Google)"},"title":{"message":"Kontrola Kalend\u00e1\u0159e Google"},"description":{"message":"Umo\u017e\u0148uje rychle zobrazit \u010das, kter\u00fd zb\u00fdv\u00e1 do dal\u0161\u00ed sch\u016fzky napl\u00e1novan\u00e9 v kter\u00e9mkoli z va\u0161ich kalend\u00e1\u0159\u016f. Kliknut\u00edm na tla\u010d\u00edtko p\u0159ejdete do kalend\u00e1\u0159e."},"direction":{"message":"ltr"},"notitle":{"message":"(Bez n\u00e1zvu)"},"optionstitle":{"message":"Kontrola Kalend\u00e1\u0159e Google"},"minutes":{"message":"$1 min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Podpora n\u011bkolika kalend\u00e1\u0159\u016f"},"extensionname":{"message":"Kontrola Kalend\u00e1\u0159e Google"},"status_saved":{"message":"Nastaven\u00ed byla ulo\u017eena."},"status_saving":{"message":"Ukl\u00e1d\u00e1n\u00ed...."},"multicalendartooltip":{"message":"Za\u0161krtnut\u00edm pol\u00ed\u010dka pros\u00edm povolte podporu v\u00edce kalend\u00e1\u0159\u016f"},"imagetooltip":{"message":"Kalend\u00e1\u0159 Google"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/da/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/da/messages.json
new file mode 100644
index 0000000..209b6d9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/da/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (fra Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Tjek hurtigt hvor lang tid der er til n\u00e6ste m\u00f8de i en af dine kalendre. Klik p\u00e5 knappen for at \u00e5bne din kalender."},"direction":{"message":"ltr"},"notitle":{"message":"(Ingen titel)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 t","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Support til flere kalendere"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Indstillingerne er gemt."},"status_saving":{"message":"Gemmer..."},"multicalendartooltip":{"message":"S\u00e6t kryds i feltet for at aktivere support til flere kalendere"},"imagetooltip":{"message":"Google Kalender"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/de/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/de/messages.json
new file mode 100644
index 0000000..3892f39
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/de/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Werfen Sie schnell einen Blick auf die verbleibende Zeit bis zum n\u00e4chsten Termin in einem Ihrer Kalender. Klicken Sie zum \u00d6ffnen Ihres Kalenders auf die Schaltfl\u00e4che."},"direction":{"message":"ltr"},"notitle":{"message":"(Kein Titel)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 Min.","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 Std.","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 Tage","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Unterst\u00fctzung mehrerer Kalender"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Einstellungen gespeichert"},"status_saving":{"message":"Speichern...."},"multicalendartooltip":{"message":"Aktivieren Sie das Kontrollk\u00e4stchen, um die Unterst\u00fctzung mehrerer Kalender einzuschalten."},"imagetooltip":{"message":"Google Kalender"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/el/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/el/messages.json
new file mode 100644
index 0000000..9cd461d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/el/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (\u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u0394\u03b5\u03af\u03c4\u03b5 \u03b3\u03c1\u03ae\u03b3\u03bf\u03c1\u03b1 \u03c0\u03cc\u03c4\u03b5 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b7 \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03b7 \u03c3\u03c5\u03bd\u03ac\u03bd\u03c4\u03b7\u03c3\u03ae \u03c3\u03b1\u03c2 \u03b1\u03c0\u03cc \u03bf\u03c0\u03bf\u03b9\u03bf\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03b7\u03bc\u03b5\u03c1\u03bf\u03bb\u03cc\u03b3\u03b9\u03ac \u03c3\u03b1\u03c2. \u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03bc\u03b5\u03c4\u03b1\u03c6\u03b5\u03c1\u03b8\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03b7\u03bc\u03b5\u03c1\u03bf\u03bb\u03cc\u03b3\u03b9\u03cc \u03c3\u03b1\u03c2."},"direction":{"message":"ltr"},"notitle":{"message":"(\u03a7\u03c9\u03c1\u03af\u03c2 \u03c4\u03af\u03c4\u03bb\u03bf)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1\u03bb","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1\u03c9","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1\u03b7","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u03a5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7 \u03c0\u03bf\u03bb\u03bb\u03ce\u03bd \u03b7\u03bc\u03b5\u03c1\u03bf\u03bb\u03bf\u03b3\u03af\u03c9\u03bd"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u039f\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c4\u03b7\u03ba\u03b1\u03bd."},"status_saving":{"message":"\u0391\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7...."},"multicalendartooltip":{"message":"\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c4\u03bf \u03c0\u03bb\u03b1\u03af\u03c3\u03b9\u03bf \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03ae\u03c1\u03b9\u03be\u03b7 \u03c0\u03bf\u03bb\u03bb\u03ce\u03bd \u03b7\u03bc\u03b5\u03c1\u03bf\u03bb\u03bf\u03b3\u03af\u03c9\u03bd"},"imagetooltip":{"message":"\u0397\u03bc\u03b5\u03c1\u03bf\u03bb\u03cc\u03b3\u03b9\u03bf Google"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/en/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/en/messages.json
new file mode 100644
index 0000000..2b19e00
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/en/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (by Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Quickly see the time until your next meeting from any of your calendars. Click on the button to be taken to your calendar."},"direction":{"message":"ltr"},"notitle":{"message":"(No Title)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Multi Calendar Support"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Settings Saved."},"status_saving":{"message":"Saving...."},"multicalendartooltip":{"message":"Please check the box to enable multiple calendar support"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/en_GB/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/en_GB/messages.json
new file mode 100644
index 0000000..87ef60e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/en_GB/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (by Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Quickly see the time until your next meeting from any of your calendars. Click the button to be taken to your calendar."},"direction":{"message":"ltr"},"notitle":{"message":"(No Title)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Multi-Calendar Support"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Settings Saved."},"status_saving":{"message":"Saving...."},"multicalendartooltip":{"message":"Please tick the box to enable multiple calendar support"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/es/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/es/messages.json
new file mode 100644
index 0000000..03d6010
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/es/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (de Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Consulta r\u00e1pidamente cu\u00e1nto tiempo falta para tu pr\u00f3xima reuni\u00f3n en cualquiera de tus calendarios. Haz clic en el bot\u00f3n para acceder al calendario."},"direction":{"message":"ltr"},"notitle":{"message":"(Sin t\u00edtulo)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Compatibilidad con varios calendarios"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Se ha guardado la configuraci\u00f3n."},"status_saving":{"message":"Guardando...."},"multicalendartooltip":{"message":"Selecciona la casilla para habilitar la compatibilidad con varios calendarios"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/es_419/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/es_419/messages.json
new file mode 100644
index 0000000..e7b2536
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/es_419/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (de Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"F\u00edjate r\u00e1pidamente cu\u00e1nto tiempo falta para tu pr\u00f3xima reuni\u00f3n en alguno de tus calendarios. Haz clic en el bot\u00f3n para que te lleve hasta tu calendario."},"direction":{"message":"ltr"},"notitle":{"message":"(Sin t\u00edtulo)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1\u00a0m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1\u00a0h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Ayuda de Multi Calendar"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Configuraciones guardadas"},"status_saving":{"message":"Guardando...."},"multicalendartooltip":{"message":"Marca el casillero para habilitar la ayuda del calendario m\u00faltiple."},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/et/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/et/messages.json
new file mode 100644
index 0000000..5828ccd
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/et/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Vaadake kiiresti oma j\u00e4rgmise koosolekuni j\u00e4\u00e4nud aega \u00fcksk\u00f5ik millisest oma kalendrist. Kalendrisse minekuks kl\u00f5psake nupul."},"direction":{"message":"ltr"},"notitle":{"message":"(Pealkiri puudub)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 p\u00e4ev(a)","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Multi-Calendari tugi"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Seaded salvestatud."},"status_saving":{"message":"Salvestamine ..."},"multicalendartooltip":{"message":"Mitme kalendri toe lubamiseks m\u00e4rkige ruut"},"imagetooltip":{"message":"Google'i kalender"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fi/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fi/messages.json
new file mode 100644
index 0000000..ddc2088
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fi/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (Googlen tekem\u00e4)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"N\u00e4et nopeasti ajan seuraavaan tapaamiseesi mist\u00e4 tahansa kalenteristasi. Siirryt kalenteriin napsauttamalla painiketta."},"direction":{"message":"ltr"},"notitle":{"message":"(Ei nime\u00e4)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 t","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 pv","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Useiden kalentereiden tuki"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Asetukset tallennettu."},"status_saving":{"message":"Tallennetaan..."},"multicalendartooltip":{"message":"Ota useiden kalentereiden tuki k\u00e4ytt\u00f6\u00f6n valitsemalla valintaruutu"},"imagetooltip":{"message":"Google-kalenteri"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fil/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fil/messages.json
new file mode 100644
index 0000000..16cdeba
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fil/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (mula sa Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Mabilisang tingnan ang oras hanggang sa iyong susunod na pagpupulong mula sa alinman sa iyong mga kalendaryo. Mag-click sa pindutan upang mapunta sa iyong kalendaryo."},"direction":{"message":"ltr"},"notitle":{"message":"(Walang Pamagat)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1o","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1a","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Suporta sa Maramihang Kalendaryo"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Na-save ang Mga Setting."},"status_saving":{"message":"Sine-save...."},"multicalendartooltip":{"message":"Pakilagyan ng check ang kahon upang paganahin ang suporta sa maramihang kalendaryo"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fr/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fr/messages.json
new file mode 100644
index 0000000..f8e413a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/fr/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google\u00a0Calendar Checker (par Google)"},"title":{"message":"Google\u00a0Calendar Checker"},"description":{"message":"V\u00e9rifiez rapidement dans vos agendas le temps qu'il vous reste avant votre prochaine r\u00e9union. Cliquez sur le bouton pour ouvrir l'agenda correspondant."},"direction":{"message":"ltr"},"notitle":{"message":"(Sans titre)"},"optionstitle":{"message":"Google\u00a0Calendar Checker"},"minutes":{"message":"$1\u00a0mn","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1\u00a0h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1\u00a0j","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Prise en charge multi-agendas"},"extensionname":{"message":"Google\u00a0Calendar Checker"},"status_saved":{"message":"Param\u00e8tres enregistr\u00e9s"},"status_saving":{"message":"Enregistrement...."},"multicalendartooltip":{"message":"Cocher la case pour activer la prise en charge multi-agendas"},"imagetooltip":{"message":"Google\u00a0Agenda"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/he/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/he/messages.json
new file mode 100644
index 0000000..09f1654
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/he/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (\u05de\u05d0\u05ea Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u05e8\u05d0\u05d4 \u05d1\u05de\u05d4\u05d9\u05e8\u05d5\u05ea \u05db\u05de\u05d4 \u05d6\u05de\u05df \u05d9\u05e9 \u05dc\u05da \u05e2\u05d3 \u05d4\u05e4\u05d2\u05d9\u05e9\u05d4 \u05d4\u05d1\u05d0\u05d4 \u05e9\u05dc\u05da \u05de\u05db\u05dc \u05dc\u05d5\u05d7 \u05e9\u05e0\u05d4 \u05e9\u05dc\u05da. \u05dc\u05d7\u05e5 \u05e2\u05dc \u05d4\u05dc\u05d7\u05e6\u05df \u05db\u05d3\u05d9 \u05dc\u05e2\u05d1\u05d5\u05e8 \u05dc\u05dc\u05d5\u05d7 \u05d4\u05e9\u05e0\u05d4 \u05e9\u05dc\u05da."},"direction":{"message":"rtl"},"notitle":{"message":"(\u05dc\u05dc\u05d0 \u05db\u05d5\u05ea\u05e8\u05ea)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1\u05d3'","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1\u05e9'","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1\u05d9'","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u05ea\u05de\u05d9\u05db\u05d4 \u05d1\u05d9\u05d5\u05de\u05e0\u05d9\u05dd \u05de\u05e8\u05d5\u05d1\u05d9\u05dd"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u05d4\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05e0\u05e9\u05de\u05e8\u05d5."},"status_saving":{"message":"\u05e9\u05d5\u05de\u05e8...."},"multicalendartooltip":{"message":"\u05e1\u05de\u05df \u05d0\u05ea \u05d4\u05ea\u05d9\u05d1\u05d4 \u05db\u05d3\u05d9 \u05dc\u05d4\u05e4\u05d5\u05da \u05d0\u05ea \u05d4\u05ea\u05de\u05d9\u05db\u05d4 \u05d1\u05d9\u05d5\u05de\u05e0\u05d9\u05dd \u05de\u05e8\u05d5\u05d1\u05d9\u05dd \u05dc\u05e4\u05e2\u05d9\u05dc\u05d4"},"imagetooltip":{"message":"\u05d9\u05d5\u05de\u05df Google"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hi/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hi/messages.json
new file mode 100644
index 0000000..2b19e00
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hi/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (by Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Quickly see the time until your next meeting from any of your calendars. Click on the button to be taken to your calendar."},"direction":{"message":"ltr"},"notitle":{"message":"(No Title)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Multi Calendar Support"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Settings Saved."},"status_saving":{"message":"Saving...."},"multicalendartooltip":{"message":"Please check the box to enable multiple calendar support"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hr/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hr/messages.json
new file mode 100644
index 0000000..f2ab5e5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hr/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (od Googlea)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Brzo pogledajte koliko imate vremena do idu\u0107eg sastanka iz svih svojih kalendara. Kliknite gumb koji \u0107e vas odvesti u va\u0161 kalendar."},"direction":{"message":"ltr"},"notitle":{"message":"(Nema naslova)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Podr\u0161ka za vi\u0161e kalendara"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Postavke su spremljene."},"status_saving":{"message":"Spremanje...."},"multicalendartooltip":{"message":"Uklju\u010dite potvrdni okvir za omogu\u0107avanje podr\u0161ke za vi\u0161e kalendara"},"imagetooltip":{"message":"Google Kalendar"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hu/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hu/messages.json
new file mode 100644
index 0000000..6be01dc
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/hu/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (a Google-t\u00f3l)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Gyorsan megn\u00e9zheti b\u00e1rmelyik napt\u00e1r\u00e1ban, hogy mennyi ideje van m\u00e9g a k\u00f6vetkez\u0151 tal\u00e1lkoz\u00f3ig. Kattintson a gombra a napt\u00e1r megtekint\u00e9s\u00e9hez."},"direction":{"message":"ltr"},"notitle":{"message":"(Nincs c\u00edm)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1p","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1\u00f3","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1nap","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"T\u00f6bb napt\u00e1r t\u00e1mogat\u00e1sa"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Be\u00e1ll\u00edt\u00e1sok elmentve."},"status_saving":{"message":"Ment\u00e9s..."},"multicalendartooltip":{"message":"K\u00e9rj\u00fck, jel\u00f6lje be a jel\u00f6l\u0151n\u00e9gyzetet t\u00f6bb napt\u00e1r t\u00e1mogat\u00e1s\u00e1nak enged\u00e9lyez\u00e9s\u00e9hez"},"imagetooltip":{"message":"Google Napt\u00e1r"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/id/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/id/messages.json
new file mode 100644
index 0000000..37056ba
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/id/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Pemeriksa Google Kalender (oleh Google)"},"title":{"message":"Pemeriksa Google Kalender"},"description":{"message":"Lihat waktu dengan cepat sampai pertemuan berikutnya dari kalender apa pun. Klik tombol untuk diarahkan ke kalender Anda."},"direction":{"message":"ltr"},"notitle":{"message":"(Tanpa Judul)"},"optionstitle":{"message":"Pemeriksa Google Kalender"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1j","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Dukungan Multi-Kalender"},"extensionname":{"message":"Pemeriksa Google Kalender"},"status_saved":{"message":"Setelan Disimpan."},"status_saving":{"message":"Menyimpan..."},"multicalendartooltip":{"message":"Harap periksa kotak untuk mengaktifkan dukungan multi-kalender"},"imagetooltip":{"message":"Google Kalender"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/it/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/it/messages.json
new file mode 100644
index 0000000..90e8daf
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/it/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (di Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Usa uno dei tuoi calendari per controllare rapidamente quanto manca alla prossima riunione. Fai clic sul pulsante per accedere al calendario."},"direction":{"message":"ltr"},"notitle":{"message":"(Nessun titolo)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 g","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Supporto di pi\u00f9 calendari"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Impostazioni salvate."},"status_saving":{"message":"Salvataggio in corso..."},"multicalendartooltip":{"message":"Seleziona la casella per attivare il supporto di pi\u00f9 calendari"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ja/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ja/messages.json
new file mode 100644
index 0000000..30cdf0b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ja/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker\uff08by Google\uff09"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u3069\u306e\u30ab\u30ec\u30f3\u30c0\u30fc\u304b\u3089\u3067\u3082\u6b21\u306e\u4f1a\u8b70\u307e\u3067\u306e\u6642\u9593\u3092\u3059\u3070\u3084\u304f\u30c1\u30a7\u30c3\u30af\u3002\u30dc\u30bf\u30f3\u3092\u30af\u30ea\u30c3\u30af\u3059\u308b\u3068\u30ab\u30ec\u30f3\u30c0\u30fc\u306b\u30a2\u30af\u30bb\u30b9\u3067\u304d\u307e\u3059\u3002"},"direction":{"message":"ltr"},"notitle":{"message":"\uff08\u30bf\u30a4\u30c8\u30eb\u306a\u3057\uff09"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u8907\u6570\u306e\u30ab\u30ec\u30f3\u30c0\u30fc\u306b\u5bfe\u5fdc"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u8a2d\u5b9a\u304c\u4fdd\u5b58\u3055\u308c\u307e\u3057\u305f\u3002"},"status_saving":{"message":"\u4fdd\u5b58\u3057\u3066\u3044\u307e\u3059..."},"multicalendartooltip":{"message":"\u8907\u6570\u306e\u30ab\u30ec\u30f3\u30c0\u30fc\u3092\u4f7f\u7528\u3067\u304d\u308b\u3088\u3046\u306b\u3059\u308b\u306b\u306f\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u30aa\u30f3\u306b\u3057\u3066\u304f\u3060\u3055\u3044"},"imagetooltip":{"message":"Google \u30ab\u30ec\u30f3\u30c0\u30fc"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ko/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ko/messages.json
new file mode 100644
index 0000000..4e381a8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ko/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google \uce98\ub9b0\ub354 \uccb4\ud06c \ub3c4\uc6b0\ubbf8(Google \uc81c\uacf5)"},"title":{"message":"Google \uce98\ub9b0\ub354 \uccb4\ud06c \ub3c4\uc6b0\ubbf8"},"description":{"message":"\uce98\ub9b0\ub354 \uc5b4\ub514\uc5d0\uc11c\ub098 \ub2e4\uc74c \ubaa8\uc784\uae4c\uc9c0 \ub0a8\uc740 \uc2dc\uac04\uc744 \uc2e0\uc18d\ud558\uac8c \uc0b4\ud3b4\ubcfc \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uce98\ub9b0\ub354\ub85c \uc774\ub3d9\ud558\ub824\uba74 \ubc84\ud2bc\uc744 \ud074\ub9ad\ud558\uc138\uc694."},"direction":{"message":"ltr"},"notitle":{"message":"(\uc81c\ubaa9 \uc5c6\uc74c)"},"optionstitle":{"message":"Google \uce98\ub9b0\ub354 \uccb4\ud06c \ub3c4\uc6b0\ubbf8"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\uc5ec\ub7ec \uce98\ub9b0\ub354 \uc9c0\uc6d0"},"extensionname":{"message":"Google \uce98\ub9b0\ub354 \uccb4\ud06c \ub3c4\uc6b0\ubbf8"},"status_saved":{"message":"\uc124\uc815\uc744 \uc800\uc7a5\ud588\uc2b5\ub2c8\ub2e4."},"status_saving":{"message":"\uc800\uc7a5 \uc911..."},"multicalendartooltip":{"message":"\uc5ec\ub7ec \uce98\ub9b0\ub354 \uc9c0\uc6d0\uc744 \uc0ac\uc6a9\ud558\ub3c4\ub85d \uc124\uc815\ud558\ub824\uba74 \ud655\uc778\ub780\uc744 \uc120\ud0dd\ud558\uc138\uc694."},"imagetooltip":{"message":"Google \uce98\ub9b0\ub354"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/lt/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/lt/messages.json
new file mode 100644
index 0000000..aac503f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/lt/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Bet kuriame i\u0161 savo kalendori\u0173 greitai \u017ei\u016br\u0117kite, kiek laiko liko iki kito susitikimo. Jei norite patekti \u012f kalendori\u0173, spustel\u0117kite mygtuk\u0105."},"direction":{"message":"ltr"},"notitle":{"message":"(N\u0117ra pavadinimo)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 min.","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 val.","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d.","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Keli\u0173 kalendori\u0173 palaikymas"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Nustatymai i\u0161saugoti."},"status_saving":{"message":"I\u0161saugoma..."},"multicalendartooltip":{"message":"Kad \u012fgalintum\u0117te keli\u0173 kalendori\u0173 palaikym\u0105, pa\u017eym\u0117kite laukel\u012f"},"imagetooltip":{"message":"\u201eGoogle\u201c kalendorius"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/lv/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/lv/messages.json
new file mode 100644
index 0000000..be33183
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/lv/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (nodro\u0161ina Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Varat \u0101tri skat\u012bt, cik daudz laika atlicis l\u012bdz n\u0101kamajai sapulcei, izmantojot jebkuru no saviem kalend\u0101riem. Lai atv\u0113rtu kalend\u0101ru, nospiediet pogu."},"direction":{"message":"ltr"},"notitle":{"message":"(Bez nosaukuma)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1\u00a0min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1\u00a0h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1\u00a0d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Vair\u0101ku kalend\u0101ru atbalsts"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Iestat\u012bjumi ir saglab\u0101ti."},"status_saving":{"message":"Notiek saglab\u0101\u0161ana..."},"multicalendartooltip":{"message":"L\u016bdzu, atz\u012bm\u0113jiet lodzi\u0146u, lai iesp\u0113jotu vair\u0101ku kalend\u0101ru atbalstu."},"imagetooltip":{"message":"Google kalend\u0101rs"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/nb/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/nb/messages.json
new file mode 100644
index 0000000..666df6e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/nb/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (laget av Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Finn ut raskt hvor lenge det er til neste m\u00f8te fra alle kalendrene dine. Klikk p\u00e5 knappen for \u00e5 g\u00e5 videre til kalenderen."},"direction":{"message":"ltr"},"notitle":{"message":"(Ingen tittel)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 t","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"St\u00f8tte for flere kalendere"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Innstillinger lagret."},"status_saving":{"message":"Lagrer \u2026"},"multicalendartooltip":{"message":"Merk av i ruten for \u00e5 aktivere st\u00f8tte for flere kalendere"},"imagetooltip":{"message":"Google Kalender"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/nl/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/nl/messages.json
new file mode 100644
index 0000000..9ffa222
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/nl/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (van Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Snel in al uw agenda's bekijken hoe lang het nog duurt voordat uw volgende vergadering begint. Klik op de knop om naar uw agenda te gaan."},"direction":{"message":"ltr"},"notitle":{"message":"(Naamloos)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 u","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Ondersteuning voor meerdere agenda's"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Instellingen zijn opgeslagen."},"status_saving":{"message":"Opslaan..."},"multicalendartooltip":{"message":"Vink het selectievakje aan om de ondersteuning voor meerdere agenda's in te schakelen"},"imagetooltip":{"message":"Google Agenda"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pl/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pl/messages.json
new file mode 100644
index 0000000..2a3d471
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pl/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (by Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Szybko sprawd\u017a w dowolnym kalendarzu, ile masz czasu do nast\u0119pnego zebrania. Kliknij przycisk, aby przej\u015b\u0107 do kalendarza."},"direction":{"message":"ltr"},"notitle":{"message":"(Bez tytu\u0142u)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1g","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Obs\u0142uga wielu kalendarzy"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Zapisano ustawienia."},"status_saving":{"message":"Zapisywanie..."},"multicalendartooltip":{"message":"Zaznacz pole, aby w\u0142\u0105czy\u0107 obs\u0142ug\u0119 wielu kalendarzy"},"imagetooltip":{"message":"Kalendarz Google"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pt_BR/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pt_BR/messages.json
new file mode 100644
index 0000000..3a79f1d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pt_BR/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (do Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Veja rapidamente quanto tempo voc\u00ea tem at\u00e9 seu pr\u00f3ximo compromisso. Clique no bot\u00e3o para abrir sua agenda."},"direction":{"message":"ltr"},"notitle":{"message":"(Sem t\u00edtulo)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Suporte para v\u00e1rias agendas"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Configura\u00e7\u00f5es salvas."},"status_saving":{"message":"Salvando..."},"multicalendartooltip":{"message":"Marque a caixa para ativar o suporte para v\u00e1rias agendas"},"imagetooltip":{"message":"Google Agenda"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pt_PT/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pt_PT/messages.json
new file mode 100644
index 0000000..53f249f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/pt_PT/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (do Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Veja rapidamente quanto tempo falta para a sua pr\u00f3xima reuni\u00e3o a partir de qualquer um dos seus calend\u00e1rios. Clique no bot\u00e3o para aceder ao calend\u00e1rio."},"direction":{"message":"ltr"},"notitle":{"message":"(Sem t\u00edtulo)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Suporte para v\u00e1rios calend\u00e1rios"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Defini\u00e7\u00f5es guardadas."},"status_saving":{"message":"A guardar..."},"multicalendartooltip":{"message":"Marque a caixa para permitir o suporte de v\u00e1rios calend\u00e1rios."},"imagetooltip":{"message":"Calend\u00e1rio Google"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ro/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ro/messages.json
new file mode 100644
index 0000000..3540609
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ro/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (de la Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Vede\u0163i rapid timpul p\u00e2n\u0103 la urm\u0103toarea \u00eent\u00e2lnire, din oricare dintre calendarele dvs. Face\u0163i clic pe buton pentru a accesa calendarul."},"direction":{"message":"ltr"},"notitle":{"message":"(F\u0103r\u0103 titlu)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 z","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Asisten\u0163\u0103 pentru mai multe calendare"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Set\u0103rile au fost salvate."},"status_saving":{"message":"Se salveaz\u0103..."},"multicalendartooltip":{"message":"Bifa\u0163i caseta pentru a activa asisten\u0163a pentru mai multe calendare"},"imagetooltip":{"message":"Google Calendar"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ru/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ru/messages.json
new file mode 100644
index 0000000..3436326
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/ru/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043e \u0432 Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u041e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0439\u0442\u0435 \u0432\u0440\u0435\u043c\u044f \u0434\u043e \u0431\u043b\u0438\u0436\u0430\u0439\u0448\u0435\u0433\u043e \u043c\u0435\u0440\u043e\u043f\u0440\u0438\u044f\u0442\u0438\u044f. \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0435\u0439. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443, \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043d\u0443\u0436\u043d\u044b\u0439 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u044c."},"direction":{"message":"ltr"},"notitle":{"message":"(\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1\u043c.","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1\u0447.","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1\u0434.","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u041a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0435\u0439"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b."},"status_saving":{"message":"\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435...."},"multicalendartooltip":{"message":"\u041d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u043f\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0444\u043b\u0430\u0436\u043e\u043a, \u0447\u0442\u043e\u0431\u044b \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u041a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0435\u0439"},"imagetooltip":{"message":"\u041a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u044c Google"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sk/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sk/messages.json
new file mode 100644
index 0000000..65257bf
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sk/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Kontrola Kalend\u00e1ra Google (od spolo\u010dnosti Google)"},"title":{"message":"Kontrola Kalend\u00e1ra Google"},"description":{"message":"V \u013eubovo\u013enom z va\u0161ich kalend\u00e1rov si v r\u00fdchlosti si zobrazte, ko\u013eko \u010dasu m\u00e1te do \u010fal\u0161ej sch\u00f4dzky. Kliknut\u00edm na tla\u010didlo prejdete do svojho kalend\u00e1ra."},"direction":{"message":"ltr"},"notitle":{"message":"(Bez n\u00e1zvu)"},"optionstitle":{"message":"Kontrola Kalend\u00e1ra Google"},"minutes":{"message":"$1 min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Podpora viacer\u00fdch Kalend\u00e1rov"},"extensionname":{"message":"Kontrola Kalend\u00e1ra Google"},"status_saved":{"message":"Nastavenia boli ulo\u017een\u00e9."},"status_saving":{"message":"Prebieha ukladanie...."},"multicalendartooltip":{"message":"Ak chcete povoli\u0165 podporu viacer\u00fdch kalend\u00e1rov, za\u010diarknite toto pol\u00ed\u010dko"},"imagetooltip":{"message":"Kalend\u00e1r Google"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sl/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sl/messages.json
new file mode 100644
index 0000000..09a2113
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sl/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Hitro preverite, koliko je \u0161e do naslednjega sestanka v katerem koli od va\u0161ih koledarjev. Kliknite gumb, da odprete koledar."},"direction":{"message":"ltr"},"notitle":{"message":"(Brez naslova)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Podpora za ve\u010d koledarjev"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Nastavitve shranjene."},"status_saving":{"message":"Shranjevanje ..."},"multicalendartooltip":{"message":"Potrdite polje, da omogo\u010dite podporo za ve\u010d koledarjev."},"imagetooltip":{"message":"Google Koledar"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sr/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sr/messages.json
new file mode 100644
index 0000000..d55c41b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sr/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (\u043e\u0434 Google-\u0430)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u0411\u0440\u0437\u043e \u043f\u043e\u0433\u043b\u0435\u0434\u0430\u0458\u0442\u0435 \u043a\u043e\u043b\u0438\u043a\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0430 \u0438\u043c\u0430\u0442\u0435 \u0434\u043e \u0441\u043b\u0435\u0434\u0435\u045b\u0435\u0433 \u0441\u0430\u0441\u0442\u0430\u043d\u043a\u0430 \u0443 \u0431\u0438\u043b\u043e \u043a\u043e\u043c \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0443. \u041a\u043b\u0438\u043a\u043d\u0438\u0442\u0435 \u043d\u0430 \u0434\u0443\u0433\u043c\u0435 \u0434\u0430 \u0431\u0438\u0441\u0442\u0435 \u043e\u0442\u0432\u043e\u0440\u0438\u043b\u0438 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440."},"direction":{"message":"ltr"},"notitle":{"message":"(\u0411\u0435\u0437 \u043d\u0430\u0441\u043b\u043e\u0432\u0430)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 \u043c\u0438\u043d","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 \u0441","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 \u0434","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u041f\u043e\u0434\u0440\u0448\u043a\u0430 \u0437\u0430 \u0432\u0438\u0448\u0435 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0430"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u041f\u043e\u0434\u0435\u0448\u0430\u0432\u0430\u045a\u0430 \u0441\u0443 \u0441\u0430\u0447\u0443\u0432\u0430\u043d\u0430."},"status_saving":{"message":"\u0427\u0443\u0432\u0430\u045a\u0435...."},"multicalendartooltip":{"message":"\u041f\u043e\u0442\u0432\u0440\u0434\u0438\u0442\u0435 \u0438\u0437\u0431\u043e\u0440 \u0443 \u043f\u043e\u0459\u0443 \u0437\u0430 \u043f\u043e\u0442\u0432\u0440\u0434\u0443 \u0434\u0430 \u0431\u0438\u0441\u0442\u0435 \u043e\u043c\u043e\u0433\u0443\u045b\u0438\u043b\u0438 \u043f\u043e\u0434\u0440\u0448\u043a\u0443 \u0437\u0430 \u0432\u0438\u0448\u0435 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0430"},"imagetooltip":{"message":"Google \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sv/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sv/messages.json
new file mode 100644
index 0000000..edb0193
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/sv/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (fr\u00e5n Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Se snabbt i dina kalendrar hur l\u00e4nge det \u00e4r till n\u00e4sta m\u00f6te. Klicka p\u00e5 knappen s\u00e5 kommer du till kalendern."},"direction":{"message":"ltr"},"notitle":{"message":"(Namnl\u00f6s)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1min","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1tim","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Support f\u00f6r flera kalendrar"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Inst\u00e4llningarna har sparats."},"status_saving":{"message":"Sparar..."},"multicalendartooltip":{"message":"Aktivera support f\u00f6r flera kalendrar genom att markera rutan"},"imagetooltip":{"message":"Google Kalender"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/th/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/th/messages.json
new file mode 100644
index 0000000..c9680f1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/th/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (\u0e42\u0e14\u0e22 Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u0e14\u0e39\u0e40\u0e27\u0e25\u0e32\u0e17\u0e35\u0e48\u0e04\u0e38\u0e13\u0e21\u0e35\u0e01\u0e48\u0e2d\u0e19\u0e16\u0e36\u0e07\u0e01\u0e32\u0e23\u0e1b\u0e23\u0e30\u0e0a\u0e38\u0e21\u0e04\u0e23\u0e31\u0e49\u0e07\u0e15\u0e48\u0e2d\u0e44\u0e1b\u0e44\u0e14\u0e49\u0e08\u0e32\u0e01\u0e1b\u0e0f\u0e34\u0e17\u0e34\u0e19\u0e43\u0e14\u0e46 \u0e01\u0e47\u0e44\u0e14\u0e49\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13 \u0e43\u0e2b\u0e49\u0e04\u0e25\u0e34\u0e01\u0e17\u0e35\u0e48\u0e1b\u0e38\u0e48\u0e21\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e44\u0e1b\u0e17\u0e35\u0e48\u0e1b\u0e0f\u0e34\u0e17\u0e34\u0e19\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13"},"direction":{"message":"ltr"},"notitle":{"message":"(\u0e44\u0e21\u0e48\u0e21\u0e35\u0e0a\u0e37\u0e48\u0e2d)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u0e01\u0e32\u0e23\u0e2a\u0e19\u0e31\u0e1a\u0e2a\u0e19\u0e38\u0e19\u0e1b\u0e0f\u0e34\u0e17\u0e34\u0e19\u0e2b\u0e25\u0e32\u0e22\u0e23\u0e30\u0e1a\u0e1a"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u0e1a\u0e31\u0e19\u0e17\u0e36\u0e01\u0e01\u0e32\u0e23\u0e15\u0e31\u0e49\u0e07\u0e04\u0e48\u0e32\u0e41\u0e25\u0e49\u0e27"},"status_saving":{"message":"\u0e01\u0e33\u0e25\u0e31\u0e07\u0e1a\u0e31\u0e19\u0e17\u0e36\u0e01...."},"multicalendartooltip":{"message":"\u0e42\u0e1b\u0e23\u0e14\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e0a\u0e48\u0e2d\u0e07\u0e17\u0e33\u0e40\u0e04\u0e23\u0e37\u0e48\u0e2d\u0e07\u0e2b\u0e21\u0e32\u0e22 \u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e40\u0e1b\u0e34\u0e14\u0e43\u0e0a\u0e49\u0e07\u0e32\u0e19\u0e01\u0e32\u0e23\u0e2a\u0e19\u0e31\u0e1a\u0e2a\u0e19\u0e38\u0e19\u0e1b\u0e0f\u0e34\u0e17\u0e34\u0e19\u0e2b\u0e25\u0e32\u0e22\u0e23\u0e30\u0e1a\u0e1a"},"imagetooltip":{"message":"Google \u0e1b\u0e0f\u0e34\u0e17\u0e34\u0e19"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/tr/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/tr/messages.json
new file mode 100644
index 0000000..a54371e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/tr/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (Google'dan)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Takvimlerinizden herhangi birine bakarak bir sonraki toplant\u0131n\u0131za ne kadar zaman kald\u0131\u011f\u0131n\u0131 hemen g\u00f6r\u00fcn. Takviminizi a\u00e7mak i\u00e7in d\u00fc\u011fmeyi t\u0131klay\u0131n."},"direction":{"message":"ltr"},"notitle":{"message":"(Ba\u015fl\u0131ks\u0131z)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1s","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1g","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"Birden Fazla Takvim Deste\u011fi"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"Ayarlar Kaydedildi."},"status_saving":{"message":"Kaydediliyor..."},"multicalendartooltip":{"message":"Birden fazla takvim deste\u011fini etkinle\u015ftirmek i\u00e7in l\u00fctfen kutuyu i\u015faretleyin"},"imagetooltip":{"message":"Google Takvim"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/uk/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/uk/messages.json
new file mode 100644
index 0000000..21c7514
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/uk/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (\u0432\u0456\u0434 Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u0428\u0432\u0438\u0434\u043a\u043e \u043f\u0435\u0440\u0435\u0433\u043b\u044f\u0434\u0430\u0439\u0442\u0435 \u0441\u0432\u043e\u0457 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0456, \u0449\u043e\u0431 \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f, \u0441\u043a\u0456\u043b\u044c\u043a\u0438 \u0447\u0430\u0441\u0443 \u0437\u0430\u043b\u0438\u0448\u0438\u043b\u043e\u0441\u044f \u0434\u043e \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u043e\u0457 \u0437\u0443\u0441\u0442\u0440\u0456\u0447\u0456. \u041d\u0430\u0442\u0438\u0441\u043d\u0456\u0442\u044c \u0446\u044e \u043a\u043d\u043e\u043f\u043a\u0443, \u0449\u043e\u0431 \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u0434\u043e \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u044f."},"direction":{"message":"ltr"},"notitle":{"message":"(\u0411\u0435\u0437 \u043d\u0430\u0437\u0432\u0438)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1 \u0445\u0432.","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1 \u0433\u043e\u0434.","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1 \u0434\u043d.","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u041f\u0456\u0434\u0442\u0440\u0438\u043c\u043a\u0430 \u0434\u0435\u043a\u0456\u043b\u044c\u043a\u043e\u0445 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0456\u0432"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043d\u043e."},"status_saving":{"message":"\u0417\u0431\u0435\u0440\u0456\u0433\u0430\u043d\u043d\u044f...."},"multicalendartooltip":{"message":"\u041f\u043e\u0441\u0442\u0430\u0432\u0442\u0435 \u043f\u0440\u0430\u043f\u043e\u0440\u0435\u0446\u044c, \u0449\u043e\u0431 \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u043a\u0443 \u0434\u0435\u043a\u0456\u043b\u044c\u043a\u043e\u0445 \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u0456\u0432"},"imagetooltip":{"message":"\u041a\u0430\u043b\u0435\u043d\u0434\u0430\u0440 Google"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/vi/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/vi/messages.json
new file mode 100644
index 0000000..a54d48b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/vi/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (c\u1ee7a Google)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"Xem nhanh th\u1eddi gian tr\u01b0\u1edbc khi \u0111\u1ebfn cu\u1ed9c h\u1ecdp ti\u1ebfp theo t\u1eeb b\u1ea5t k\u1ef3 l\u1ecbch n\u00e0o c\u1ee7a b\u1ea1n. H\u00e3y nh\u1ea5p v\u00e0o n\u00fat \u0111\u1ec3 \u0111\u01b0\u1ee3c \u0111\u01b0a \u0111\u1ebfn l\u1ecbch c\u1ee7a b\u1ea1n."},"direction":{"message":"ltr"},"notitle":{"message":"(Kh\u00f4ng c\u00f3 ti\u00eau \u0111\u1ec1)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"H\u1ed7 tr\u1ee3 nhi\u1ec1u l\u1ecbch"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u0110\u00e3 l\u01b0u c\u00e0i \u0111\u1eb7t."},"status_saving":{"message":"\u0110ang l\u01b0u...."},"multicalendartooltip":{"message":"Vui l\u00f2ng ch\u1ecdn h\u1ed9p n\u00e0y \u0111\u1ec3 b\u1eadt t\u00ednh n\u0103ng h\u1ed7 tr\u1ee3 nhi\u1ec1u l\u1ecbch"},"imagetooltip":{"message":"L\u1ecbch Google"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/zh_CN/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/zh_CN/messages.json
new file mode 100644
index 0000000..7c2ecea
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/zh_CN/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker\uff08\u7531 Google \u63d0\u4f9b\uff09"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u5feb\u901f\u67e5\u770b\u79bb\u60a8\u7684\u4efb\u610f\u65e5\u5386\u4e2d\u4e0b\u4e00\u6b21\u4f1a\u8bae\u8fd8\u6709\u591a\u957f\u65f6\u95f4\u3002\u60a8\u53ea\u9700\u70b9\u51fb\u8be5\u6309\u94ae\u5373\u53ef\u8fdb\u5165\u81ea\u5df1\u7684\u65e5\u5386\u3002"},"direction":{"message":"ltr"},"notitle":{"message":"\uff08\u65e0\u6807\u9898\uff09"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u591a\u65e5\u5386\u652f\u6301"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u8bbe\u7f6e\u5df2\u4fdd\u5b58\u3002"},"status_saving":{"message":"\u6b63\u5728\u4fdd\u5b58..."},"multicalendartooltip":{"message":"\u8bf7\u9009\u4e2d\u6b64\u6846\u4ee5\u542f\u7528\u591a\u65e5\u5386\u652f\u6301"},"imagetooltip":{"message":"Google \u65e5\u5386"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/_locales/zh_TW/messages.json b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/zh_TW/messages.json
new file mode 100644
index 0000000..5c99aba
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/_locales/zh_TW/messages.json
@@ -0,0 +1 @@
+{"name":{"message":"Google Calendar Checker (\u7531 Google \u63d0\u4f9b)"},"title":{"message":"Google Calendar Checker"},"description":{"message":"\u5f9e\u4efb\u4f55\u65e5\u66c6\u7686\u53ef\u8fc5\u901f\u67e5\u770b\u8ddd\u96e2\u4e0b\u6b21\u6703\u8b70\u9084\u5269\u4e0b\u591a\u5c11\u6642\u9593\u3002\u6309\u4e00\u4e0b\u6309\u9215\u5373\u53ef\u524d\u5f80\u60a8\u7684\u65e5\u66c6\u3002"},"direction":{"message":"ltr"},"notitle":{"message":"(\u7121\u6a19\u984c)"},"optionstitle":{"message":"Google Calendar Checker"},"minutes":{"message":"$1m","placeholders":{"1":{"content":"$1"}}},"hours":{"message":"$1h","placeholders":{"1":{"content":"$1"}}},"days":{"message":"$1d","placeholders":{"1":{"content":"$1"}}},"multicalendartext":{"message":"\u591a\u91cd\u65e5\u66c6\u652f\u63f4"},"extensionname":{"message":"Google Calendar Checker"},"status_saved":{"message":"\u8a2d\u5b9a\u5df2\u5132\u5b58\u3002"},"status_saving":{"message":"\u5132\u5b58\u4e2d...."},"multicalendartooltip":{"message":"\u8acb\u52fe\u9078\u6b64\u65b9\u584a\uff0c\u4ee5\u555f\u7528\u591a\u91cd\u65e5\u66c6\u652f\u63f4\u529f\u80fd"},"imagetooltip":{"message":"Google \u65e5\u66c6"}}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/images/calendar_logo.gif b/chrome/common/extensions/docs/examples/extensions/calendar/images/calendar_logo.gif
new file mode 100644
index 0000000..e082dc9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/images/calendar_logo.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/images/icon-128.gif b/chrome/common/extensions/docs/examples/extensions/calendar/images/icon-128.gif
new file mode 100644
index 0000000..78d34bc
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/images/icon-128.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/images/icon-16.gif b/chrome/common/extensions/docs/examples/extensions/calendar/images/icon-16.gif
new file mode 100644
index 0000000..054e628
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/images/icon-16.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/images/icon-16_bw.gif b/chrome/common/extensions/docs/examples/extensions/calendar/images/icon-16_bw.gif
new file mode 100644
index 0000000..c20d7b8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/images/icon-16_bw.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/javascript/background.js b/chrome/common/extensions/docs/examples/extensions/calendar/javascript/background.js
new file mode 100644
index 0000000..cd7e8db
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/javascript/background.js
@@ -0,0 +1,740 @@
+/**
+ * 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.
+ */
+
+/**
+ * PHASES
+ * 1) Load next event from server refresh every 30 minutes or every time
+ *   you go to calendar or every time you logout drop in a data object.
+ * 2) Display on screen periodically once per minute or on demand.
+ */
+
+// Message shown in badge title when no title is given to an event.
+var MSG_NO_TITLE = chrome.i18n.getMessage('noTitle');
+
+// Time between server polls = 30 minutes.
+var POLL_INTERVAL = 30 * 60 * 1000;
+
+// Redraw interval is 1 min.
+var DRAW_INTERVAL = 60 * 1000;
+
+// The time when we last polled.
+var lastPollTime_ = 0;
+
+// Object for BadgeAnimation
+var badgeAnimation_;
+
+//Object for CanvasAnimation
+var canvasAnimation_;
+
+// Object containing the event.
+var nextEvent_ = null;
+
+// Storing events.
+var eventList = [];
+var nextEvents = [];
+
+// Storing calendars.
+var calendars = [];
+
+var pollUnderProgress = false;
+var defaultAuthor = '';
+var isMultiCalendar = false;
+
+//URL for getting feed of individual calendar support.
+var SINGLE_CALENDAR_SUPPORT_URL = 'https://www.google.com/calendar/feeds' +
+    '/default/private/embed?toolbar=true&max-results=10';
+
+//URL for getting feed of multiple calendar support.
+var MULTIPLE_CALENDAR_SUPPORT_URL = 'https://www.google.com/calendar/feeds' +
+    '/default/allcalendars/full';
+
+//URL for opening Google Calendar in new tab.
+var GOOGLE_CALENDAR_URL = 'http://www.google.com/calendar/render';
+
+//URL for declining invitation of the event.
+var DECLINED_URL = 'http://schemas.google.com/g/2005#event.declined';
+
+//This is used to poll only once per second at most, and delay that if
+//we keep hitting pages that would otherwise force a load.
+var pendingLoadId_ = null;
+
+/**
+ * A "loading" animation displayed while we wait for the first response from
+ * Calendar. This animates the badge text with a dot that cycles from left to
+ * right.
+ * @constructor
+ */
+function BadgeAnimation() {
+  this.timerId_ = 0;
+  this.maxCount_ = 8;  // Total number of states in animation
+  this.current_ = 0;  // Current state
+  this.maxDot_ = 4;  // Max number of dots in animation
+};
+
+/**
+ * Paints the badge text area while loading the data.
+ */
+BadgeAnimation.prototype.paintFrame = function() {
+  var text = '';
+  for (var i = 0; i < this.maxDot_; i++) {
+    text += (i == this.current_) ? '.' : ' ';
+  }
+
+  chrome.browserAction.setBadgeText({text: text});
+  this.current_++;
+  if (this.current_ == this.maxCount_) {
+    this.current_ = 0;
+  }
+};
+
+/**
+ * Starts the animation process.
+ */
+BadgeAnimation.prototype.start = function() {
+  if (this.timerId_) {
+    return;
+  }
+
+  var self = this;
+  this.timerId_ = window.setInterval(function() {
+    self.paintFrame();
+  }, 100);
+};
+
+/**
+ * Stops the animation process.
+ */
+BadgeAnimation.prototype.stop = function() {
+  if (!this.timerId_) {
+    return;
+  }
+
+  window.clearInterval(this.timerId_);
+  this.timerId_ = 0;
+};
+
+/**
+ * Animates the canvas after loading the data from all the calendars. It
+ * rotates the icon and defines the badge text and title.
+ * @constructor
+ */
+function CanvasAnimation() {
+  this.animationFrames_ = 36;  // The number of animation frames
+  this.animationSpeed_ = 10;  // Time between each frame(in ms).
+  this.canvas_ = $('canvas');  // The canvas width + height.
+  this.canvasContext_ = this.canvas_.getContext('2d');  // Canvas context.
+  this.loggedInImage_ = $('logged_in');
+  this.rotation_ = 0;  //Keeps count of rotation angle of extension icon.
+  this.w = this.canvas_.width;  // Setting canvas width.
+  this.h = this.canvas_.height;  // Setting canvas height.
+  this.RED = [208, 0, 24, 255];  //Badge color of extension icon in RGB format.
+  this.BLUE = [0, 24, 208, 255];
+  this.currentBadge_ = null;  // The text in the current badge.
+};
+
+/**
+ * Flips the icon around and draws it.
+ */
+CanvasAnimation.prototype.animate = function() {
+  this.rotation_ += (1 / this.animationFrames_);
+  this.drawIconAtRotation();
+  var self = this;
+  if (this.rotation_ <= 1) {
+    setTimeout(function() {
+      self.animate();
+    }, self.animationSpeed_);
+  } else {
+    this.drawFinal();
+  }
+};
+
+/**
+ * Renders the icon.
+ */
+CanvasAnimation.prototype.drawIconAtRotation = function() {
+  this.canvasContext_.save();
+  this.canvasContext_.clearRect(0, 0, this.w, this.h);
+  this.canvasContext_.translate(Math.ceil(this.w / 2), Math.ceil(this.h / 2));
+  this.canvasContext_.rotate(2 * Math.PI * this.getSector(this.rotation_));
+  this.canvasContext_.drawImage(this.loggedInImage_, -Math.ceil(this.w / 2),
+    -Math.ceil(this.h / 2));
+  this.canvasContext_.restore();
+  chrome.browserAction.setIcon(
+      {imageData: this.canvasContext_.getImageData(0, 0, this.w, this.h)});
+};
+
+/**
+ * Calculates the sector which has to be traversed in a single call of animate
+ * function(360/animationFrames_ = 360/36 = 10 radians).
+ * @param {integer} sector angle to be rotated(in radians).
+ * @return {integer} value in radian of the sector which it has to cover.
+ */
+CanvasAnimation.prototype.getSector = function(sector) {
+  return (1 - Math.sin(Math.PI / 2 + sector * Math.PI)) / 2;
+};
+
+/**
+ * Draws the event icon and determines the badge title and icon title.
+ */
+CanvasAnimation.prototype.drawFinal = function() {
+  badgeAnimation_.stop();
+
+  if (!nextEvent_) {
+    this.showLoggedOut();
+  } else {
+    this.drawIconAtRotation();
+    this.rotation_ = 0;
+
+    var ms = nextEvent_.startTime.getTime() - getCurrentTime();
+    var nextEventMin = ms / (1000 * 60);
+    var bgColor = (nextEventMin < 60) ? this.RED : this.BLUE;
+
+    chrome.browserAction.setBadgeBackgroundColor({color: bgColor});
+    currentBadge_ = this.getBadgeText(nextEvent_);
+    chrome.browserAction.setBadgeText({text: currentBadge_});
+
+    if (nextEvents.length > 0) {
+      var text = '';
+      for (var i = 0, event; event = nextEvents[i]; i++) {
+        text += event.title;
+        if (event.author || event.location) {
+          text += '\n';
+        }
+        if (event.location) {
+          text += event.location + ' ';
+        }
+        if (event.author) {
+          text += event.author;
+        }
+        if (i < (nextEvents.length - 1)) {
+          text += '\n----------\n';
+        }
+      }
+      text = filterSpecialChar(text);
+      chrome.browserAction.setTitle({'title' : text});
+    }
+  }
+  pollUnderProgress = false;
+
+  chrome.extension.sendRequest({
+    message: 'enableSave'
+  }, function() {
+  });
+
+  return;
+};
+
+/**
+ * Shows the user logged out.
+ */
+CanvasAnimation.prototype.showLoggedOut = function() {
+  currentBadge_ = '?';
+  chrome.browserAction.setIcon({path: '../images/icon-16_bw.gif'});
+  chrome.browserAction.setBadgeBackgroundColor({color: [190, 190, 190, 230]});
+  chrome.browserAction.setBadgeText({text: '?'});
+  chrome.browserAction.setTitle({ 'title' : ''});
+};
+
+/**
+ * Gets the badge text.
+ * @param {Object} nextEvent_ next event in the calendar.
+ * @return {String} text Badge text to be shown in extension icon.
+ */
+CanvasAnimation.prototype.getBadgeText = function(nextEvent_) {
+  if (!nextEvent_) {
+    return '';
+  }
+
+  var ms = nextEvent_.startTime.getTime() - getCurrentTime();
+  var nextEventMin = Math.ceil(ms / (1000 * 60));
+
+  var text = '';
+  if (nextEventMin < 60) {
+    text = chrome.i18n.getMessage('minutes', nextEventMin.toString());
+  } else if (nextEventMin < 1440) {
+    text = chrome.i18n.getMessage('hours',
+               Math.round(nextEventMin / 60).toString());
+  } else if (nextEventMin < (1440 * 10)) {
+    text = chrome.i18n.getMessage('days',
+               Math.round(nextEventMin / 60 / 24).toString());
+  }
+  return text;
+};
+
+/**
+ * Provides all the calendar related utils.
+ */
+CalendarManager = {};
+
+/**
+ * Extracts event from the each entry of the calendar.
+ * @param {Object} elem The XML node to extract the event from.
+ * @param {Object} mailId email of the owner of calendar in multiple calendar
+ *     support.
+ * @return {Object} out An object containing the event properties.
+ */
+CalendarManager.extractEvent = function(elem, mailId) {
+  var out = {};
+
+  for (var node = elem.firstChild; node != null; node = node.nextSibling) {
+    if (node.nodeName == 'title') {
+        out.title = node.firstChild ? node.firstChild.nodeValue : MSG_NO_TITLE;
+    } else if (node.nodeName == 'link' &&
+               node.getAttribute('rel') == 'alternate') {
+      out.url = node.getAttribute('href');
+    } else if (node.nodeName == 'gd:where') {
+      out.location = node.getAttribute('valueString');
+    } else if (node.nodeName == 'gd:who') {
+      if (node.firstChild) {
+        if ((!isMultiCalendar) || (isMultiCalendar && mailId &&
+            node.getAttribute('email') == mailId)) {
+          out.attendeeStatus = node.firstChild.getAttribute('value');
+        }
+      }
+    } else if (node.nodeName == 'gd:eventStatus') {
+      out.status = node.getAttribute('value');
+    } else if (node.nodeName == 'gd:when') {
+      var startTimeStr = node.getAttribute('startTime');
+      var endTimeStr = node.getAttribute('endTime');
+
+      startTime = rfc3339StringToDate(startTimeStr);
+      endTime = rfc3339StringToDate(endTimeStr);
+
+      if (startTime == null || endTime == null) {
+        continue;
+      }
+
+      out.isAllDay = (startTimeStr.length <= 11);
+      out.startTime = startTime;
+      out.endTime = endTime;
+    }
+  }
+  return out;
+};
+
+/**
+ * Polls the server to get the feed of the user.
+ */
+CalendarManager.pollServer = function() {
+  if (! pollUnderProgress) {
+    eventList = [];
+    pollUnderProgress = true;
+    pendingLoadId_ = null;
+    calendars = [];
+    lastPollTime_ = getCurrentTime();
+    var url;
+    var xhr = new XMLHttpRequest();
+    try {
+      xhr.onreadystatechange = CalendarManager.genResponseChangeFunc(xhr);
+      xhr.onerror = function(error) {
+        console.log('error: ' + error);
+        nextEvent_ = null;
+        canvasAnimation_.drawFinal();
+      };
+      if (isMultiCalendar) {
+        url = MULTIPLE_CALENDAR_SUPPORT_URL;
+      } else {
+        url = SINGLE_CALENDAR_SUPPORT_URL;
+      }
+
+      xhr.open('GET', url);
+      xhr.send(null);
+    } catch (e) {
+      console.log('ex: ' + e);
+      nextEvent_ = null;
+      canvasAnimation_.drawFinal();
+    }
+  }
+};
+
+/**
+ * Gathers the list of all calendars of a specific user for multiple calendar
+ * support and event entries in single calendar.
+ * @param {xmlHttpRequest} xhr xmlHttpRequest object containing server response.
+ * @return {Object} anonymous function which returns to onReadyStateChange.
+ */
+CalendarManager.genResponseChangeFunc = function(xhr) {
+  return function() {
+    if (xhr.readyState != 4) {
+      return;
+    }
+    if (!xhr.responseXML) {
+      console.log('No responseXML');
+      nextEvent_ = null;
+      canvasAnimation_.drawFinal();
+      return;
+    }
+    if (isMultiCalendar) {
+      var entry_ = xhr.responseXML.getElementsByTagName('entry');
+      if (entry_ && entry_.length > 0) {
+        calendars = [];
+        for (var i = 0, entry; entry = entry_[i]; ++i) {
+          if (!i) {
+            defaultAuthor = entry.querySelector('title').textContent;
+          }
+          // Include only those calendars which are not hidden and selected
+          var isHidden = entry.querySelector('hidden');
+          var isSelected = entry.querySelector('selected');
+          if (isHidden && isHidden.getAttribute('value') == 'false') {
+            if (isSelected && isSelected.getAttribute('value') == 'true') {
+              var calendar_content = entry.querySelector('content');
+              var cal_src = calendar_content.getAttribute('src');
+              cal_src += '?toolbar=true&max-results=10';
+              calendars.push(cal_src);
+            }
+          }
+        }
+        CalendarManager.getCalendarFeed(0);
+        return;
+      }
+    } else {
+      calendars = [];
+      calendars.push(SINGLE_CALENDAR_SUPPORT_URL);
+      CalendarManager.parseCalendarEntry(xhr.responseXML, 0);
+      return;
+    }
+
+    console.error('Error: feed retrieved, but no event found');
+    nextEvent_ = null;
+    canvasAnimation_.drawFinal();
+  };
+};
+
+/**
+ * Retrieves feed for a calendar
+ * @param {integer} calendarId Id of the calendar in array of calendars.
+ */
+CalendarManager.getCalendarFeed = function(calendarId) {
+  var xmlhttp = new XMLHttpRequest();
+  try {
+    xmlhttp.onreadystatechange = CalendarManager.onCalendarResponse(xmlhttp,
+                                     calendarId);
+    xmlhttp.onerror = function(error) {
+      console.log('error: ' + error);
+      nextEvent_ = null;
+      canvasAnimation_.drawFinal();
+    };
+
+    xmlhttp.open('GET', calendars[calendarId]);
+    xmlhttp.send(null);
+  }
+  catch (e) {
+    console.log('ex: ' + e);
+    nextEvent_ = null;
+    canvasAnimation_.drawFinal();
+  }
+};
+
+/**
+ * Gets the event entries of every calendar subscribed in default user calendar.
+ * @param {xmlHttpRequest} xmlhttp xmlHttpRequest containing server response
+ *     for the feed of a specific calendar.
+ * @param {integer} calendarId Variable for storing the no of calendars
+ *     processed.
+ * @return {Object} anonymous function which returns to onReadyStateChange.
+ */
+CalendarManager.onCalendarResponse = function(xmlhttp, calendarId) {
+  return function() {
+    if (xmlhttp.readyState != 4) {
+      return;
+    }
+    if (!xmlhttp.responseXML) {
+      console.log('No responseXML');
+      nextEvent_ = null;
+      canvasAnimation_.drawFinal();
+      return;
+    }
+    CalendarManager.parseCalendarEntry(xmlhttp.responseXML, calendarId);
+  };
+};
+
+/**
+ * Parses events from calendar response XML
+ * @param {string} responseXML Response XML for calendar.
+ * @param {integer} calendarId  Id of the calendar in array of calendars.
+ */
+CalendarManager.parseCalendarEntry = function(responseXML, calendarId) {
+  var entry_ = responseXML.getElementsByTagName('entry');
+  var mailId = null;
+  var author = null;
+
+  if (responseXML.querySelector('author name')) {
+    author = responseXML.querySelector('author name').textContent;
+  }
+  if (responseXML.querySelector('author email')) {
+    mailId = responseXML.querySelector('author email').textContent;
+  }
+
+  if (entry_ && entry_.length > 0) {
+    for (var i = 0, entry; entry = entry_[i]; ++i) {
+     var event_ = CalendarManager.extractEvent(entry, mailId);
+
+      // Get the time from then to now
+      if (event_.startTime) {
+        var t = event_.startTime.getTime() - getCurrentTime();
+        if (t >= 0 && (event_.attendeeStatus != DECLINED_URL)) {
+            if (isMultiCalendar && author) {
+              event_.author = author;
+            }
+            eventList.push(event_);
+        }
+      }
+    }
+  }
+
+  calendarId++;
+  //get the next calendar
+  if (calendarId < calendars.length) {
+    CalendarManager.getCalendarFeed(calendarId);
+  } else {
+    CalendarManager.populateLatestEvent(eventList);
+  }
+};
+
+/**
+ * Fills the event list with the events acquired from the calendar(s).
+ * Parses entire event list and prepares an array of upcoming events.
+ * @param {Array} eventList List of all events.
+ */
+CalendarManager.populateLatestEvent = function(eventList) {
+  nextEvents = [];
+  if (isMultiCalendar) {
+    eventList.sort(sortByDate);
+  }
+
+  //populating next events array.
+  if (eventList.length > 0) {
+    nextEvent_ = eventList[0];
+    nextEvents.push(nextEvent_);
+    var startTime = nextEvent_.startTime.setSeconds(0, 0);
+    for (var i = 1, event; event = eventList[i]; i++) {
+      var time = event.startTime.setSeconds(0, 0);
+      if (time == startTime) {
+        nextEvents.push(event);
+      } else {
+        break;
+      }
+    }
+    if (nextEvents.length > 1 && isMultiCalendar) {
+      nextEvents.sort(sortByAuthor);
+    }
+    canvasAnimation_.animate();
+    return;
+  } else {
+    console.error('Error: feed retrieved, but no event found');
+    nextEvent_ = null;
+    canvasAnimation_.drawFinal();
+  }
+};
+
+var DATE_TIME_REGEX =
+  /^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)\.\d+(\+|-)(\d\d):(\d\d)$/;
+var DATE_TIME_REGEX_Z = /^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)\.\d+Z$/;
+var DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
+
+/**
+* Convert the incoming date into a javascript date.
+* @param {String} rfc3339 The rfc date in string format as following
+*     2006-04-28T09:00:00.000-07:00
+*     2006-04-28T09:00:00.000Z
+*     2006-04-19.
+* @return {Date} The javascript date format of the incoming date.
+*/
+function rfc3339StringToDate(rfc3339) {
+  var parts = DATE_TIME_REGEX.exec(rfc3339);
+
+  // Try out the Z version
+  if (!parts) {
+    parts = DATE_TIME_REGEX_Z.exec(rfc3339);
+  }
+
+  if (parts && parts.length > 0) {
+    var d = new Date();
+    d.setUTCFullYear(parts[1], parseInt(parts[2], 10) - 1, parts[3]);
+    d.setUTCHours(parts[4]);
+    d.setUTCMinutes(parts[5]);
+    d.setUTCSeconds(parts[6]);
+
+    var tzOffsetFeedMin = 0;
+    if (parts.length > 7) {
+      tzOffsetFeedMin = parseInt(parts[8], 10) * 60 + parseInt(parts[9], 10);
+      if (parts[7] != '-') { // This is supposed to be backwards.
+        tzOffsetFeedMin = -tzOffsetFeedMin;
+      }
+    }
+    return new Date(d.getTime() + tzOffsetFeedMin * 60 * 1000);
+  }
+
+  parts = DATE_REGEX.exec(rfc3339);
+  if (parts && parts.length > 0) {
+    return new Date(parts[1], parseInt(parts[2], 10) - 1, parts[3]);
+  }
+  return null;
+};
+
+/**
+ * Sorts all the events by date and time.
+ * @param {object} event_1 Event object.
+ * @param {object} event_2 Event object.
+ * @return {integer} timeDiff Difference in time.
+ */
+function sortByDate(event_1, event_2) {
+  return (event_1.startTime.getTime() - event_2.startTime.getTime());
+};
+
+/**
+ * Sorts all the events by author name.
+ * @param {object} event_1 Event object.
+ * @param {object} event_2 Event object.
+ * @return {integer} nameDiff Difference in default author and others.
+ */
+function sortByAuthor(event_1, event_2) {
+  var nameDiff;
+  if (event_1.author && event_2.author && event_2.author == defaultAuthor) {
+    nameDiff = 1;
+  } else {
+    return 0;
+  }
+  return nameDiff;
+};
+
+/**
+ * Fires once per minute to redraw extension icon.
+ */
+function redraw() {
+  // If the next event just passed, re-poll.
+  if (nextEvent_) {
+    var t = nextEvent_.startTime.getTime() - getCurrentTime();
+    if (t <= 0) {
+      CalendarManager.pollServer();
+      return;
+    }
+  }
+  canvasAnimation_.animate();
+
+  // if 30 minutes have passed re-poll
+  if (getCurrentTime() - lastPollTime_ >= POLL_INTERVAL) {
+    CalendarManager.pollServer();
+  }
+};
+
+/**
+ * Returns the current time in milliseconds.
+ * @return {Number} Current time in milliseconds.
+ */
+function getCurrentTime() {
+  return (new Date()).getTime();
+};
+
+/**
+* Replaces ASCII characters from the title.
+* @param {String} data String containing ASCII code for special characters.
+* @return {String} data ASCII characters replaced with actual characters.
+*/
+function filterSpecialChar(data) {
+  if (data) {
+    data = data.replace(/&lt;/g, '<');
+    data = data.replace(/&gt;/g, '>');
+    data = data.replace(/&amp;/g, '&');
+    data = data.replace(/%7B/g, '{');
+    data = data.replace(/%7D/g, '}');
+    data = data.replace(/&quot;/g, '"');
+    data = data.replace(/&#39;/g, '\'');
+  }
+  return data;
+};
+
+/**
+ * Called from options.js page on saving the settings
+ */
+function onSettingsChange() {
+  isMultiCalendar = JSON.parse(localStorage.multiCalendar);
+  badgeAnimation_.start();
+  CalendarManager.pollServer();
+};
+
+/**
+ * Function runs on updating a tab having url of google applications.
+ * @param {integer} tabId Id of the tab which is updated.
+ * @param {String} changeInfo Gives the information of change in url.
+ * @param {String} tab Gives the url of the tab updated.
+ */
+function onTabUpdated(tabId, changeInfo, tab) {
+  var url = tab.url;
+  if (!url) {
+    return;
+  }
+
+  if ((url.indexOf('www.google.com/calendar/') != -1) ||
+      ((url.indexOf('www.google.com/a/') != -1) &&
+      (url.lastIndexOf('/acs') == url.length - 4)) ||
+      (url.indexOf('www.google.com/accounts/') != -1)) {
+
+    // The login screen isn't helpful
+    if (url.indexOf('https://www.google.com/accounts/ServiceLogin?') == 0) {
+      return;
+    }
+
+    if (pendingLoadId_) {
+      clearTimeout(pendingLoadId_);
+      pendingLoadId_ = null;
+    }
+
+    // try to poll in 2 second [which makes the redirects settle down]
+    pendingLoadId_ = setTimeout(CalendarManager.pollServer, 2000);
+  }
+};
+
+/**
+ * Called when the user clicks on extension icon and opens calendar page.
+ */
+function onClickAction() {
+  chrome.tabs.getAllInWindow(null, function(tabs) {
+    for (var i = 0, tab; tab = tabs[i]; i++) {
+      if (tab.url && isCalendarUrl(tab.url)) {
+        chrome.tabs.update(tab.id, {selected: true});
+        CalendarManager.pollServer();
+        return;
+      }
+    }
+    chrome.tabs.create({url: GOOGLE_CALENDAR_URL});
+    CalendarManager.pollServer();
+  });
+};
+
+/**
+ * Checks whether an instance of Google calendar is already open.
+ * @param {String} url Url of the tab visited.
+ * @return {boolean} true if the url is a Google calendar relative url, false
+ *     otherwise.
+ */
+function isCalendarUrl(url) {
+  return url.indexOf('www.google.com/calendar') != -1 ? true : false;
+};
+
+/**
+ * Initializes everything.
+ */
+function init() {
+  badgeAnimation_ = new BadgeAnimation();
+  canvasAnimation_ = new CanvasAnimation();
+
+  isMultiCalendar = JSON.parse(localStorage.multiCalendar || false);
+
+  chrome.browserAction.setIcon({path: '../images/icon-16.gif'});
+  badgeAnimation_.start();
+  CalendarManager.pollServer();
+  window.setInterval(redraw, DRAW_INTERVAL);
+
+  chrome.tabs.onUpdated.addListener(onTabUpdated);
+
+  chrome.browserAction.onClicked.addListener(function(tab) {
+    onClickAction();
+  });
+};
+
+//Adding listener when body is loaded to call init function.
+window.addEventListener('load', init, false);
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/javascript/options.js b/chrome/common/extensions/docs/examples/extensions/calendar/javascript/options.js
new file mode 100644
index 0000000..6c2030f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/javascript/options.js
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2012 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.
+ */
+
+//Contains true if multiple calendar option is checked, false otherwise.
+var isMultiCalendar;
+
+//adding listener when body is loaded to call init function.
+window.addEventListener('load', init, false);
+
+/**
+ * Sets the value of multiple calendar checkbox based on value from
+ * local storage, and sets up the `save` event handler.
+ */
+function init() {
+  isMultiCalendar = JSON.parse(localStorage.multiCalendar || false);
+  $('multiCalendar').checked = isMultiCalendar;
+  $('multiCalendarText').innerHTML =
+      chrome.i18n.getMessage('multiCalendarText');
+  $('optionsTitle').innerHTML = chrome.i18n.getMessage('optionsTitle');
+  $('imageTooltip').title = chrome.i18n.getMessage('imageTooltip');
+  $('imageTooltip').alt = chrome.i18n.getMessage('imageTooltip');
+  $('multiCalendarText').title = chrome.i18n.getMessage('multiCalendarToolTip');
+  $('multiCalendar').title = chrome.i18n.getMessage('multiCalendarToolTip');
+  $('extensionName').innerHTML = chrome.i18n.getMessage('extensionName');
+  if (chrome.i18n.getMessage('direction') == 'rtl') {
+    document.querySelector('body').style.direction = 'rtl';
+  }
+  document.querySelector('#multiCalendar').addEventListener('click', save);
+};
+
+/**
+ * Saves the value of the checkbox into local storage.
+ */
+function save() {
+  var multiCalendarId = $('multiCalendar');
+  localStorage.multiCalendar = multiCalendarId.checked;
+  if (multiCalendarId) {
+    multiCalendar.disabled = true;
+  }
+  $('status').innerHTML = chrome.i18n.getMessage('status_saving');
+  $('status').style.display = 'block';
+  chrome.extension.getBackgroundPage().onSettingsChange();
+};
+
+/**
+ * Fired when a request is sent from either an extension process or a content
+ * script. Add Listener to enable the save checkbox button on server response.
+ * @param {String} request Request sent by the calling script.
+ * @param {Object} sender Information about the script that sent a message or
+ *     request.
+ * @param {Function} sendResponse Function to call when there is a response.
+ *     The argument should be any JSON-ifiable object, or undefined if there
+ *     is no response.
+ */
+chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
+  if (!request.message)
+    return;
+  switch (request.message) {
+    case 'enableSave':
+      if ($('multiCalendar')) {
+        if ($('multiCalendar').disabled) {
+          $('status').innerHTML = chrome.i18n.getMessage('status_saved');
+          $('status').style.display = 'block';
+          setTimeout("$('status').style.display = 'none'", 1500);
+        }
+        $('multiCalendar').disabled = false;
+      }
+      sendResponse();
+      break;
+  }
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/javascript/util.js b/chrome/common/extensions/docs/examples/extensions/calendar/javascript/util.js
new file mode 100644
index 0000000..1ad5ac6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/javascript/util.js
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) 2010 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.
+ */
+
+/**
+ * Alias for document.getElementById.
+ * @param {string} id The id of the element.
+ * @return {HTMLElement} The html element for the given element id.
+ */
+function $(id) {
+  return document.getElementById(id);
+};
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/manifest.json b/chrome/common/extensions/docs/examples/extensions/calendar/manifest.json
new file mode 100644
index 0000000..60e78b4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/manifest.json
@@ -0,0 +1,22 @@
+{
+  "name": "__MSG_name__",
+  "description": "__MSG_description__",
+  "default_locale":"en",
+  "options_page": "views/options.html",
+  "version": "1.2.2",
+  "background": {
+    "page": "views/background.html"
+  },
+  "permissions": [
+    "tabs", "http://*.google.com/", "https://*.google.com/"
+   ],
+  "browser_action": {
+    "default_title": "__MSG_title__"
+  },
+  "icons": {
+    "128": "images/icon-128.gif",
+    "16":"images/icon-16.gif"
+  },
+
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/views/background.html b/chrome/common/extensions/docs/examples/extensions/calendar/views/background.html
new file mode 100644
index 0000000..b90a41b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/views/background.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2010 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.
+-->
+<html>
+  <head>
+    <script src="../javascript/util.js"></script>
+    <script src="../javascript/background.js"></script>
+  </head>
+  <body>
+    <img id="logged_in" src="../images/icon-16.gif">
+    <canvas id="canvas" width="19" height="19"></canvas>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/calendar/views/options.html b/chrome/common/extensions/docs/examples/extensions/calendar/views/options.html
new file mode 100644
index 0000000..d6cf82d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/calendar/views/options.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2012 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.
+-->
+<html>
+<head>
+  <title id=optionsTitle></title>
+  <script src="../javascript/options.js"></script>
+  <script src="../javascript/util.js"></script>
+  <style>
+    #content {
+      background-color: white;
+      border: 4px solid #B5C7DE;
+      border-radius: 12px;
+      margin: 40px auto 20px;
+      padding: 8px;
+      width: 600px;
+    }
+    .option_row {
+      clear: left;
+      padding: 2.5em 1em 0;
+      text-align:center
+    }
+    #status {
+      background-color: rgb(255, 241, 168);
+      display: none;
+      margin-left: 3px;
+      padding: 1px 2px;
+      text-align: center;
+      font-size: 15px;
+      color: #000;
+    }
+    #multiCalendarText {
+      font-size: 15px;
+      color: #000;
+    }
+    body {
+      background-color: "#ebeff9";
+    }
+    #logo {
+      font-size: 20px;
+      text-align: center;
+    }
+    #extensionName {
+      color: #444;
+    }
+  </style>
+
+</head>
+<body>
+  <div id="content">
+    <div id = "logo">
+      <img src="../images/icon-128.gif" width="48">&nbsp;&nbsp;&nbsp;
+      <img src="../images/calendar_logo.gif" id="imageTooltip" title="" alt="">
+      <br>
+      <label id="extensionName" ></label>
+    </div>
+
+    <div class="option_row">
+      <div>
+        <table cellpadding=1 width=100%>
+          <tr>
+            <td width="15%">
+              &nbsp;
+            </td>
+            <td width="5%">
+              <input type="checkbox" id="multiCalendar" name="multiCalendar" title="">
+            </td>
+            <td width="40%" align="left">
+              <span id="multiCalendarText" title=""></span>
+            </td>
+            <td width="30%">
+              <label id="status"></label>
+            </td>
+            <td width="10%">
+              &nbsp;
+            </td>
+          </tr>
+        </table>
+        <br>
+      </div>
+    </div>
+  </div>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/catblock/background.js b/chrome/common/extensions/docs/examples/extensions/catblock/background.js
new file mode 100644
index 0000000..7fdb601
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/catblock/background.js
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 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.
+
+// Simple extension to replace lolcat images from
+// http://icanhascheezburger.com/ with loldog images instead.
+
+chrome.webRequest.onBeforeRequest.addListener(
+  function(info) {
+    console.log("Cat intercepted: " + info.url);
+    // Redirect the lolcal request to a random loldog URL.
+    var i = Math.round(Math.random() * loldogs.length);
+    return {redirectUrl: loldogs[i]};
+  },
+  // filters
+  {
+    urls: [
+      "https://i.chzbgr.com/*"
+    ],
+    types: ["image"]
+  },
+  // extraInfoSpec
+  ["blocking"]);
diff --git a/chrome/common/extensions/docs/examples/extensions/catblock/loldogs.js b/chrome/common/extensions/docs/examples/extensions/catblock/loldogs.js
new file mode 100644
index 0000000..a8013b1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/catblock/loldogs.js
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 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.
+
+var loldogs = [
+"http://ihasahotdog.files.wordpress.com/2011/08/funny-dog-pictures-yoo-bin-warndid.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/08/funny-dog-pictures-it-juzz-liek-ezploded-or-sumfin.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/08/funny-dog-pictures-cool-story-bro-got-anymore-bacon.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/08/funny-dog-pictures-ohaii-iz-n-ur-livin-room-defyin-ur-gravaties.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/08/cue-puppy-pictures-spike-is-not-sleeping-on-duty-hes-lulling-you-into-a-false-sense-of-security.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/08/funny-dog-pictures-pens-too-mainstream.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/08/funny-dog-pictures-tyrannodoggus-rex.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/08/funny-dog-pictures-sniper-dog-in-position.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/funny-dog-pictures-i-see-wat-yur-doin-must-put-it-on-internet.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/cute-puppy-pictures-fresh-squeed.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/funny-dog-pictures-that-sir-is-not-my-issue.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/funny-dog-pictures-trippin.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/funny-dog-pictures-oh-gurl-diz-iz-my-jam.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/funny-dog-pictures-get-off-teh-lawn-we-haz-no-komment-on-teh-allejed-mailman-insident.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/funny-dog-pictures-sowwy-i-kin-still-feel-da-pea.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/funny-dog-pictures-hang-upz-ai-haz-dem.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/funny-dog-pictures-i-haz-a-happy-cuz-im-going-to-mai-fer-ebber-home.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/funny-dog-pictures-the-guitar-its-a-les-paw.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/funny-dog-pictures-if-you-need-me-ill-just-be-watching-the-game.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/funny-dog-pictures-camouflage-complete.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/cute-puppy-pictures-its-okay-i-tuck-myself-in.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/funny-dog-pictures-transformers-more-than-meets-the-eye.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/cute-puppy-pictures-a-long-night-of-pwnage.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/raised-in-the-woods-sos-he-knew-every-tree-killed-him-a-bear-when-he-was-only-three.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/this-be-ten-times-betteh-den-laska.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/funny-dog-pictures-wut-did-u-say-mai-heering-iz-spotty.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/07/funny-dog-pictures-albark-einstein-discusses-relativity.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/funny-dog-pictures-goggie-ob-teh-week-i-love-you.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/funny-dog-pictures-oh-lord-take-me-now-i-broke-my-chew-toy.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/funny-dog-pictures-i-has-a-safe.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/funny-dog-pictures-corgi-choir.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/funny-dog-pictures-goggie-ob-teh-week-snaaaaacks-i-love-snaaaaacks.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/funny-dog-pictures-porky-pug-bath-club-est.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/funny-dog-pictures-so-wuts-so-tuff-abowt.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/funny-dog-pictures-taek-teh-pitcher-awreddy.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/funny-dog-pictures-hip-um-pot-o-mush-impresshun-iz-good.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/funny-dog-pictures-if-it-touchez-floor-itz-mine-no-secund-rule.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/funny-dog-pictures-pug-life.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/funny-dog-pictures-readz-us-a-storee.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/funny-dog-pictures-green-light-red-light-green-light.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/648800e7-f305-43e8-83ed-06a5325923c8.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/cute-puppy-pictures-why-ai-always-gets-picked-last.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/cute-puppy-pictures-corgagram.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/cute-puppy-pictures-shes-got-a-chicken-to-ride-and-she-dont-care.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/funny-dog-pictures-caring-is-for-the-birds.jpg",
+"http://ihasahotdog.files.wordpress.com/2011/06/funny-dog-pictures-goggie-ob-teh-week-pudge-is-teh-boss.jpg",
+];
diff --git a/chrome/common/extensions/docs/examples/extensions/catblock/manifest.json b/chrome/common/extensions/docs/examples/extensions/catblock/manifest.json
new file mode 100644
index 0000000..e775432
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/catblock/manifest.json
@@ -0,0 +1,12 @@
+{
+  "name": "CatBlock",
+  "version": "1.0",
+  "description": "I can't has cheezburger!",
+  "permissions": ["webRequest", "webRequestBlocking",
+                  "https://i.chzbgr.com/*"],
+  "background": {
+    "scripts": ["loldogs.js", "background.js"]
+  },
+
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/catifier/event_page.js b/chrome/common/extensions/docs/examples/extensions/catifier/event_page.js
new file mode 100644
index 0000000..18b6460
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/catifier/event_page.js
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 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.
+
+// Sample extension to replace all JPEG images (but no PNG/GIF/... images) with
+// lolcat images from http://icanhascheezburger.com/ - except for images on
+// Google.
+
+var RequestMatcher = chrome.declarativeWebRequest.RequestMatcher;
+var IgnoreRules = chrome.declarativeWebRequest.IgnoreRules;
+var RedirectRequest = chrome.declarativeWebRequest.RedirectRequest;
+
+var catImageUrl =
+    'https://i.chzbgr.com/completestore/12/8/23/S__rxG9hIUK4sNuMdTIY9w2.jpg';
+
+// Registers redirect rules assuming that currently no rules are registered by
+// this extension, yet.
+function registerRules() {
+  var redirectRule = {
+    priority: 100,
+    conditions: [
+      // If any of these conditions is fulfilled, the actions are executed.
+      new RequestMatcher({
+        // Both, the url and the resourceType must match.
+        url: {pathSuffix: '.jpg'},
+        resourceType: ['image']
+      }),
+      new RequestMatcher({
+        url: {pathSuffix: '.jpeg'},
+        resourceType: ['image']
+      }),
+    ],
+    actions: [
+      new RedirectRequest({redirectUrl: catImageUrl})
+    ]
+  };
+
+  var exceptionRule = {
+    priority: 1000,
+    conditions: [
+      // We use hostContains to compensate for various top-level domains.
+      new RequestMatcher({url: {hostContains: '.google.'}})
+    ],
+    actions: [
+      new IgnoreRules({lowerPriorityThan: 1000})
+    ]
+  };
+
+  var callback = function() {
+    if (chrome.extension.lastError) {
+      console.error('Error adding rules: ' + chrome.extension.lastError);
+    } else {
+      console.info('Rules successfully installed');
+      chrome.declarativeWebRequest.onRequest.getRules(null,
+          function(rules) {
+            console.info('Now the following rules are registered: ' +
+                         JSON.stringify(rules, null, 2));
+          });
+    }
+  };
+
+  chrome.declarativeWebRequest.onRequest.addRules(
+      [redirectRule, exceptionRule], callback);
+}
+
+function setup() {
+  // This function is also called when the extension has been updated.  Because
+  // registered rules are persisted beyond browser restarts, we remove
+  // previously registered rules before registering new ones.
+  chrome.declarativeWebRequest.onRequest.removeRules(
+    null,
+    function() {
+      if (chrome.extension.lastError) {
+        console.error('Error clearing rules: ' + chrome.extension.lastError);
+      } else {
+        registerRules();
+      }
+    });
+}
+
+// This is triggered when the extension is installed or updated.
+chrome.runtime.onInstalled.addListener(setup);
diff --git a/chrome/common/extensions/docs/examples/extensions/catifier/manifest.json b/chrome/common/extensions/docs/examples/extensions/catifier/manifest.json
new file mode 100644
index 0000000..ba37252
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/catifier/manifest.json
@@ -0,0 +1,12 @@
+{
+  "name": "Catifier",
+  "version": "1.0",
+  "description": "Moar cats!",
+  "permissions": ["declarativeWebRequest", "<all_urls>"],
+  "background": {
+    "scripts": ["event_page.js"],
+    "persistent": false
+  },
+
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/chrome_search/background.js b/chrome/common/extensions/docs/examples/extensions/chrome_search/background.js
new file mode 100644
index 0000000..802920f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/chrome_search/background.js
@@ -0,0 +1,170 @@
+// Copyright (c) 2012 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.
+
+var currentRequest = null;
+
+chrome.omnibox.onInputChanged.addListener(
+  function(text, suggest) {
+    if (currentRequest != null) {
+      currentRequest.onreadystatechange = null;
+      currentRequest.abort();
+      currentRequest = null;
+    }
+
+    updateDefaultSuggestion(text);
+    if (text == '' || text == 'halp')
+      return;
+
+    currentRequest = search(text, function(xml) {
+      var results = [];
+      var entries = xml.getElementsByTagName("entry");
+
+      for (var i = 0, entry; i < 5 && (entry = entries[i]); i++) {
+        var path = entry.getElementsByTagName("file")[0].getAttribute("name");
+        var line =
+            entry.getElementsByTagName("match")[0].getAttribute("lineNumber");
+        var file = path.split("/").pop();
+
+        var description = '<url>' + file + '</url>';
+        if (/^file:/.test(text)) {
+          description += ' <dim>' + path + '</dim>';
+        } else {
+          var content = entry.getElementsByTagName("content")[0].textContent;
+
+          // There can be multiple lines. Kill all the ones except the one that
+          // contains the first match. We can ocassionally fail to find a single
+          // line that matches, so we still handle multiple lines below.
+          var matches = content.split(/\n/);
+          for (var j = 0, match; match = matches[j]; j++) {
+            if (match.indexOf('<b>') > -1) {
+              content = match;
+              break;
+            }
+          }
+
+          // Replace any extraneous whitespace to make it look nicer.
+          content = content.replace(/[\n\t]/g, ' ');
+          content = content.replace(/ {2,}/g, ' ');
+
+          // Codesearch wraps the result in <pre> tags. Remove those if they're
+          // still there.
+          content = content.replace(/<\/?pre>/g, '');
+
+          // Codesearch highlights the matches with 'b' tags. Replaces those
+          // with 'match'.
+          content = content.replace(/<(\/)?b>/g, '<$1match>');
+
+          description += ' ' + content;
+        }
+
+        results.push({
+          content: path + '@' + line,
+          description: description
+        });
+      }
+
+      suggest(results);
+    });
+  }
+);
+
+function resetDefaultSuggestion() {
+  chrome.omnibox.setDefaultSuggestion({
+    description: '<url><match>src:</match></url> Search Chromium source'
+  });
+}
+
+resetDefaultSuggestion();
+
+function updateDefaultSuggestion(text) {
+  var isRegex = /^re:/.test(text);
+  var isFile = /^file:/.test(text);
+  var isHalp = (text == 'halp');
+  var isPlaintext = text.length && !isRegex && !isFile && !isHalp;
+
+  var description = '<match><url>src</url></match><dim> [</dim>';
+  description +=
+      isPlaintext ? ('<match>' + text + '</match>') : 'plaintext-search';
+  description += '<dim> | </dim>';
+  description += isRegex ? ('<match>' + text + '</match>') : 're:regex-search';
+  description += '<dim> | </dim>';
+  description += isFile ? ('<match>' + text + '</match>') : 'file:filename';
+  description += '<dim> | </dim>';
+  description += isHalp ? '<match>halp</match>' : 'halp';
+  description += '<dim> ]</dim>';
+
+  chrome.omnibox.setDefaultSuggestion({
+    description: description
+  });
+}
+
+chrome.omnibox.onInputStarted.addListener(function() {
+  updateDefaultSuggestion('');
+});
+
+chrome.omnibox.onInputCancelled.addListener(function() {
+  resetDefaultSuggestion();
+});
+
+function search(query, callback) {
+  if (query == 'halp')
+    return;
+
+  if (/^re:/.test(query))
+    query = query.substring('re:'.length);
+  else if (/^file:/.test(query))
+    query = 'file:"' + query.substring('file:'.length) + '"';
+  else
+    query = '"' + query + '"';
+
+  var url = "http://code.google.com/p/chromium/source/search?q=" + query;
+
+  var req = new XMLHttpRequest();
+  req.open("GET", url, true);
+  req.setRequestHeader("GData-Version", "2");
+  req.onreadystatechange = function() {
+    if (req.readyState == 4) {
+      callback(req.responseXML);
+    }
+  }
+  req.send(null);
+  return req;
+}
+
+function getUrl(path, line) {
+  var url = "http://code.google.com/codesearch#OAMlx_jo-ck/" + path
+      "&exact_package=chromium&type=cs";
+  if (line)
+    url += "&l=" + line;
+  return url;
+}
+
+function getEntryUrl(entry) {
+  return getUrl(
+      entry.getElementsByTagName("file")[0].getAttribute("name"),
+      entry.getElementsByTagName("match")[0].getAttribute("lineNumber"));
+  return url;
+}
+
+function navigate(url) {
+  chrome.tabs.getSelected(null, function(tab) {
+    chrome.tabs.update(tab.id, {url: url});
+  });
+}
+
+chrome.omnibox.onInputEntered.addListener(function(text) {
+  // TODO(aa): We need a way to pass arbitrary data through. Maybe that is just
+  // URL?
+  if (/@\d+\b/.test(text)) {
+    var chunks = text.split('@');
+    var path = chunks[0];
+    var line = chunks[1];
+    navigate(getUrl(path, line));
+  } else if (text == 'halp') {
+    // TODO(aa)
+  } else {
+    navigate("http://codesearch.google.com/codesearch?" +
+             "vert=chromium&as_q=" + text);
+  }
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/chrome_search/manifest.json b/chrome/common/extensions/docs/examples/extensions/chrome_search/manifest.json
new file mode 100644
index 0000000..c4ddead
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/chrome_search/manifest.json
@@ -0,0 +1,13 @@
+{
+  "background": {
+    "scripts": ["background.js"]
+  },
+  "description": "Add support to the omnibox to search the Chromium source code.",
+  "name": "Chromium Search",
+  "omnibox": { "keyword" : "src" },
+  "permissions": [ "tabs", "http://www.google.com/" ],
+  "version": "6",
+  "minimum_chrome_version": "9",
+
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/email_this_page/background.js b/chrome/common/extensions/docs/examples/extensions/email_this_page/background.js
new file mode 100644
index 0000000..a0fd3c3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/email_this_page/background.js
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 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.
+
+function customMailtoUrl() {
+  if (window.localStorage == null)
+    return "";
+  if (window.localStorage.customMailtoUrl == null)
+    return "";
+  return window.localStorage.customMailtoUrl;
+}
+
+function executeMailto(tab_id, subject, body, selection) {
+  var default_handler = customMailtoUrl().length == 0;
+
+  var action_url = "mailto:?"
+      if (subject.length > 0)
+        action_url += "subject=" + encodeURIComponent(subject) + "&";
+
+  if (body.length > 0) {
+    action_url += "body=" + encodeURIComponent(body);
+
+    // Append the current selection to the end of the text message.
+    if (selection.length > 0) {
+      action_url += encodeURIComponent("\n\n") +
+          encodeURIComponent(selection);
+    }
+  }
+
+  if (!default_handler) {
+    // Custom URL's (such as opening mailto in Gmail tab) should have a
+    // separate tab to avoid clobbering the page you are on.
+    var custom_url = customMailtoUrl();
+    action_url = custom_url.replace("%s", encodeURIComponent(action_url));
+    console.log('Custom url: ' + action_url);
+    chrome.tabs.create({ url: action_url });
+  } else {
+    // Plain vanilla mailto links open up in the same tab to prevent
+    // blank tabs being left behind.
+    console.log('Action url: ' + action_url);
+    chrome.tabs.update(tab_id, { url: action_url });
+  }
+}
+
+chrome.extension.onConnect.addListener(function(port) {
+  var tab = port.sender.tab;
+
+  // This will get called by the content script we execute in
+  // the tab as a result of the user pressing the browser action.
+  port.onMessage.addListener(function(info) {
+    var max_length = 1024;
+    if (info.selection.length > max_length)
+      info.selection = info.selection.substring(0, max_length);
+    executeMailto(tab.id, info.title, tab.url, info.selection);
+  });
+});
+
+// Called when the user clicks on the browser action icon.
+chrome.browserAction.onClicked.addListener(function(tab) {
+  // We can only inject scripts to find the title on pages loaded with http
+  // and https so for all other pages, we don't ask for the title.
+  if (tab.url.indexOf("http:") != 0 &&
+      tab.url.indexOf("https:") != 0) {
+    executeMailto(tab.id, "", tab.url, "");
+  } else {
+    chrome.tabs.executeScript(null, {file: "content_script.js"});
+  }
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/email_this_page/content_script.js b/chrome/common/extensions/docs/examples/extensions/email_this_page/content_script.js
new file mode 100644
index 0000000..fc0ef93
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/email_this_page/content_script.js
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2009 The Chromium Authors. All rights reserved.  Use of this
+ * source code is governed by a BSD-style license that can be found in the
+ * LICENSE file.
+ */
+
+var additionalInfo = {
+  "title": document.title,
+  "selection": window.getSelection().toString()
+};
+
+chrome.extension.connect().postMessage(additionalInfo);
diff --git a/chrome/common/extensions/docs/examples/extensions/email_this_page/email_16x16.png b/chrome/common/extensions/docs/examples/extensions/email_this_page/email_16x16.png
new file mode 100644
index 0000000..155ce5c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/email_this_page/email_16x16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/email_this_page/mail_128x128.png b/chrome/common/extensions/docs/examples/extensions/email_this_page/mail_128x128.png
new file mode 100644
index 0000000..581c97a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/email_this_page/mail_128x128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/email_this_page/manifest.json b/chrome/common/extensions/docs/examples/extensions/email_this_page/manifest.json
new file mode 100644
index 0000000..3801b87
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/email_this_page/manifest.json
@@ -0,0 +1,20 @@
+{
+  "name": "Email this page (by Google)",
+  "description": "This extension adds an email button to the toolbar which allows you to email the page link using your default mail client or Gmail.",
+  "version": "1.2.6",
+  "background": {
+    "scripts": ["background.js"],
+    "persistent": false
+  },
+  "icons": { "128": "mail_128x128.png" },
+  "options_page": "options.html",
+  "permissions": [
+    "tabs", "http://*/*", "https://*/*"
+  ],
+  "browser_action": {
+    "default_title": "Email this page",
+    "default_icon": "email_16x16.png"
+  },
+
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/email_this_page/options.html b/chrome/common/extensions/docs/examples/extensions/email_this_page/options.html
new file mode 100644
index 0000000..a27f7eb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/email_this_page/options.html
@@ -0,0 +1,44 @@
+<!DOCTYPE>
+<!--
+ * Copyright (c) 2012 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.
+-->
+<html>
+<head>
+  <title>Options for the Send as Email extension</title>
+<style>
+#providerSelection {
+  font-family: Helvetica, Arial, sans-serif;
+  font-size: 10pt;
+}
+</style>
+<script src="options.js"></script>
+</head>
+<body>
+<table class="simple">
+<tr>
+  <td rowspan="2" valign="top" align="center" width="80">
+    <img src="mail_128x128.png" width="64" height="64" />
+  </td>
+  <td height="22"></td>
+</tr>
+<tr>
+  <td valign="center">
+    <div id="providerSelection">
+    <strong>Select provider to use when emailing a web page address:</strong>
+    <br /><br />
+    <label>
+      <input id="default" type="radio" name="mailto" value="mailto" checked>
+      Your default mail handler<br>
+    </label>
+
+    <label>
+      <input id="gmail" type="radio" name="mailto" value="gmail">Gmail<br>
+    </label>
+    </div>
+  </td>
+</tr>
+</table>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/email_this_page/options.js b/chrome/common/extensions/docs/examples/extensions/email_this_page/options.js
new file mode 100644
index 0000000..2c682a4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/email_this_page/options.js
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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.
+
+var gmail = "https://mail.google.com/mail/?extsrc=mailto&url=%s";
+
+function toggle(radioButton) {
+  if (window.localStorage == null) {
+    alert('Local storage is required for changing providers');
+    return;
+  }
+  if (document.getElementById('gmail').checked) {
+    window.localStorage.customMailtoUrl = gmail;
+  } else {
+    window.localStorage.customMailtoUrl = "";
+  }
+}
+
+function main() {
+  if (window.localStorage == null) {
+    alert("LocalStorage must be enabled for changing options.");
+    document.getElementById('default').disabled = true;
+    document.getElementById('gmail').disabled = true;
+    return;
+  }
+
+  // Default handler is checked. If we've chosen another provider, we must
+  // change the checkmark.
+  if (window.localStorage.customMailtoUrl == gmail)
+    document.getElementById('gmail').checked = true;
+}
+
+document.addEventListener('DOMContentLoaded', function () {
+  main();
+  document.querySelector('#default').addEventListener('click', toggle);
+  document.querySelector('#gmail').addEventListener('click', toggle);
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/fx/bg.js b/chrome/common/extensions/docs/examples/extensions/fx/bg.js
new file mode 100644
index 0000000..a3b28d8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/fx/bg.js
@@ -0,0 +1,431 @@
+// Copyright (c) 2012 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.
+
+/*
+ * Background page for Chrome Sounds extension.
+ * This tracks various events from Chrome and plays sounds.
+ */
+
+// Map of hostname suffixes or URLs without query params to sounds.
+// Yeah OK, some of these are a little cliche...
+var urlSounds = {
+  "http://www.google.ca/": "canadian-hello.mp3",
+  "about:histograms": "time-passing.mp3",
+  "about:memory": "transform!.mp3",
+  "about:crash": "sadtrombone.mp3",
+  "chrome://extensions/": "beepboop.mp3",
+  "http://www.google.com.au/": "didgeridoo.mp3",
+  "http://www.google.com.my/": "my_subway.mp3",
+  "http://www.google.com/appserve/fiberrfi/": "dialup.mp3",
+  "lively.com": "cricket.mp3",
+  "http://www.google.co.uk/": "mind_the_gap.mp3",
+  "http://news.google.com/": "news.mp3",
+  "http://www.bing.com/": "sonar.mp3",
+};
+
+// Map of query parameter words to sounds.
+// More easy cliches...
+var searchSounds = {
+  "scotland": "bagpipe.mp3",
+  "seattle": "rain.mp3",
+};
+
+// Map of tab numbers to notes on a scale.
+var tabNoteSounds = {
+  "tab0": "mando-1.mp3",
+  "tab1": "mando-2.mp3",
+  "tab2": "mando-3.mp3",
+  "tab3": "mando-4.mp3",
+  "tab4": "mando-5.mp3",
+  "tab5": "mando-6.mp3",
+  "tab6": "mando-7.mp3",
+};
+
+// Map of sounds that play in a continuous loop while an event is happening
+// in the content area (e.g. "keypress" while start and keep looping while
+// the user keeps typing).
+var contentSounds = {
+  "keypress": "typewriter-1.mp3",
+  "resize": "harp-transition-2.mp3",
+  "scroll": "shepard.mp3"
+};
+
+// Map of events to their default sounds
+var eventSounds = {
+  "tabCreated": "conga1.mp3",
+  "tabMoved": "bell-transition.mp3",
+  "tabRemoved": "smash-glass-1.mp3",
+  "tabSelectionChanged": "click.mp3",
+  "tabAttached": "whoosh-15.mp3",
+  "tabDetached": "sword-shrill.mp3",
+  "tabNavigated": "click.mp3",
+  "windowCreated": "bell-small.mp3",
+  "windowFocusChanged": "click.mp3",
+  "bookmarkCreated": "bubble-drop.mp3",
+  "bookmarkMoved": "thud.mp3",
+  "bookmarkRemoved": "explosion-6.mp3",
+  "windowCreatedIncognito": "weird-wind1.mp3",
+  "startup": "whoosh-19.mp3"
+};
+
+var soundLists = [urlSounds, searchSounds, eventSounds, tabNoteSounds,
+    contentSounds];
+
+var sounds = {};
+
+// Map of event names to extension events.
+// Events intentionally skipped:
+// chrome.windows.onRemoved - can't suppress the tab removed that comes first
+var events = {
+  "tabCreated": chrome.tabs.onCreated,
+  "tabMoved": chrome.tabs.onMoved,
+  "tabRemoved": chrome.tabs.onRemoved,
+  "tabSelectionChanged": chrome.tabs.onSelectionChanged,
+  "tabAttached": chrome.tabs.onAttached,
+  "tabDetached": chrome.tabs.onDetached,
+  "tabNavigated": chrome.tabs.onUpdated,
+  "windowCreated": chrome.windows.onCreated,
+  "windowFocusChanged": chrome.windows.onFocusChanged,
+  "bookmarkCreated": chrome.bookmarks.onCreated,
+  "bookmarkMoved": chrome.bookmarks.onMoved,
+  "bookmarkRemoved": chrome.bookmarks.onRemoved
+};
+
+// Map of event name to a validation function that is should return true if
+// the default sound should be played for this event.
+var eventValidator = {
+  "tabCreated": tabCreated,
+  "tabNavigated": tabNavigated,
+  "tabRemoved": tabRemoved,
+  "tabSelectionChanged": tabSelectionChanged,
+  "windowCreated": windowCreated,
+  "windowFocusChanged": windowFocusChanged,
+};
+
+var started = false;
+
+function shouldPlay(id) {
+  // Ignore all events until the startup sound has finished.
+  if (id != "startup" && !started)
+    return false;
+  var val = localStorage.getItem(id);
+  if (val && val != "enabled") {
+    console.log(id + " disabled");
+    return false;
+  }
+  return true;
+}
+
+function didPlay(id) {
+  if (!localStorage.getItem(id))
+    localStorage.setItem(id, "enabled");
+}
+
+function playSound(id, loop) {
+  if (!shouldPlay(id))
+    return;
+
+  var sound = sounds[id];
+  console.log("playsound: " + id);
+  if (sound && sound.src) {
+    if (!sound.paused) {
+      if (sound.currentTime < 0.2) {
+        console.log("ignoring fast replay: " + id + "/" + sound.currentTime);
+        return;
+      }
+      sound.pause();
+      sound.currentTime = 0;
+    }
+    if (loop)
+      sound.loop = loop;
+
+    // Sometimes, when playing multiple times, readyState is HAVE_METADATA.
+    if (sound.readyState == 0) {  // HAVE_NOTHING
+      console.log("bad ready state: " + sound.readyState);
+    } else if (sound.error) {
+      console.log("media error: " + sound.error);
+    } else {
+      didPlay(id);
+      sound.play();
+    }
+  } else {
+    console.log("bad playSound: " + id);
+  }
+}
+
+function stopSound(id) {
+  console.log("stopSound: " + id);
+  var sound = sounds[id];
+  if (sound && sound.src && !sound.paused) {
+    sound.pause();
+    sound.currentTime = 0;
+  }
+}
+
+var base_url = "http://dl.google.com/dl/chrome/extensions/audio/";
+
+function soundLoadError(audio, id) {
+  console.log("failed to load sound: " + id + "-" + audio.src);
+  audio.src = "";
+  if (id == "startup")
+    started = true;
+}
+
+function soundLoaded(audio, id) {
+  console.log("loaded sound: " + id);
+  sounds[id] = audio;
+  if (id == "startup")
+    playSound(id);
+}
+
+// Hack to keep a reference to the objects while we're waiting for them to load.
+var notYetLoaded = {};
+
+function loadSound(file, id) {
+  if (!file.length) {
+    console.log("no sound for " + id);
+    return;
+  }
+  var audio = new Audio();
+  audio.id = id;
+  audio.onerror = function() { soundLoadError(audio, id); };
+  audio.addEventListener("canplaythrough",
+      function() { soundLoaded(audio, id); }, false);
+  if (id == "startup") {
+    audio.addEventListener("ended", function() { started = true; });
+  }
+  audio.src = base_url + file;
+  audio.load();
+  notYetLoaded[id] = audio;
+}
+
+// Remember the last event so that we can avoid multiple events firing
+// unnecessarily (e.g. selection changed due to close).
+var eventsToEat = 0;
+
+function eatEvent(name) {
+  if (eventsToEat > 0) {
+    console.log("ate event: " + name);
+    eventsToEat--;
+    return true;
+  }
+  return false;
+}
+
+function soundEvent(event, name) {
+  if (event) {
+    var validator = eventValidator[name];
+    if (validator) {
+      event.addListener(function() {
+        console.log("handling custom event: " + name);
+
+        // Check this first since the validator may bump the count for future
+        // events.
+        var canPlay = (eventsToEat == 0);
+        if (validator.apply(this, arguments)) {
+          if (!canPlay) {
+            console.log("ate event: " + name);
+            eventsToEat--;
+            return;
+          }
+          playSound(name);
+        }
+      });
+    } else {
+      event.addListener(function() {
+        console.log("handling event: " + name);
+        if (eatEvent(name)) {
+          return;
+        }
+        playSound(name);
+      });
+    }
+  } else {
+    console.log("no event for " + name);
+  }
+}
+
+var navSound;
+
+function stopNavSound() {
+  if (navSound) {
+    stopSound(navSound);
+    navSound = null;
+  }
+}
+
+function playNavSound(id) {
+  stopNavSound();
+  navSound = id;
+  playSound(id);
+}
+
+function tabNavigated(tabId, changeInfo, tab) {
+  // Quick fix to catch the case where the content script doesn't have a chance
+  // to stop itself.
+  stopSound("keypress");
+
+  //console.log(JSON.stringify(changeInfo) + JSON.stringify(tab));
+  if (changeInfo.status != "complete") {
+    return false;
+  }
+  if (eatEvent("tabNavigated")) {
+    return false;
+  }
+
+  console.log(JSON.stringify(tab));
+
+  if (navSound)
+    stopSound(navSound);
+
+  var re = /https?:\/\/([^\/:]*)[^\?]*\??(.*)/i;
+  match = re.exec(tab.url);
+  if (match) {
+    if (match.length == 3) {
+      var query = match[2];
+      var parts = query.split("&");
+      for (var i in parts) {
+        if (parts[i].indexOf("q=") == 0) {
+          var q = decodeURIComponent(parts[i].substring(2));
+          q = q.replace("+", " ");
+          console.log("query == " + q);
+          var words = q.split(" ");
+          for (j in words) {
+            if (searchSounds[words[j]]) {
+              console.log("searchSound: " + words[j]);
+              playNavSound(words[j]);
+              return false;
+            }
+          }
+          break;
+        }
+      }
+    }
+    if (match.length >= 2) {
+      var hostname = match[1];
+      if (hostname) {
+        var parts = hostname.split(".");
+        if (parts.length > 1) {
+          var tld2 = parts.slice(-2).join(".");
+          var tld3 = parts.slice(-3).join(".");
+          var sound = urlSounds[tld2];
+          if (sound) {
+            playNavSound(tld2);
+            return false;
+          }
+          sound = urlSounds[tld3];
+          if (sound) {
+            playNavSound(tld3);
+            return false;
+          }
+        }
+      }
+    }
+  }
+
+  // Now try a direct URL match (without query string).
+  var url = tab.url;
+  var query = url.indexOf("?");
+  if (query > 0) {
+    url = tab.url.substring(0, query);
+  }
+  console.log(tab.url);
+  var sound = urlSounds[url];
+  if (sound) {
+    playNavSound(url);
+    return false;
+  }
+
+  return true;
+}
+
+var selectedTabId = -1;
+
+function tabSelectionChanged(tabId) {
+  selectedTabId = tabId;
+  if (eatEvent("tabSelectionChanged"))
+    return false;
+
+  var count = 7;
+  chrome.tabs.get(tabId, function(tab) {
+    var index = tab.index % count;
+    playSound("tab" + index);
+  });
+  return false;
+}
+
+function tabCreated(tab) {
+  if (eatEvent("tabCreated")) {
+    return false;
+  }
+  eventsToEat++;  // tabNavigated or tabSelectionChanged
+  // TODO - unfortunately, we can't detect whether this tab will get focus, so
+  // we can't decide whether or not to eat a second event.
+  return true;
+}
+
+function tabRemoved(tabId) {
+  if (eatEvent("tabRemoved")) {
+    return false;
+  }
+  if (tabId == selectedTabId) {
+    eventsToEat++;  // tabSelectionChanged
+    stopNavSound();
+  }
+  return true;
+}
+
+function windowCreated(window) {
+  if (eatEvent("windowCreated")) {
+    return false;
+  }
+  eventsToEat += 3;  // tabNavigated, tabSelectionChanged, windowFocusChanged
+  if (window.incognito) {
+    playSound("windowCreatedIncognito");
+    return false;
+  }
+  return true;
+}
+
+var selectedWindowId = -1;
+
+function windowFocusChanged(windowId) {
+  if (windowId == selectedWindowId) {
+    return false;
+  }
+  selectedWindowId = windowId;
+  if (eatEvent("windowFocusChanged")) {
+    return false;
+  }
+  return true;
+}
+
+function contentScriptHandler(request) {
+  if (contentSounds[request.eventName]) {
+    if (request.eventValue == "started") {
+      playSound(request.eventName, true);
+    } else if (request.eventValue == "stopped") {
+      stopSound(request.eventName);
+    } else {
+      playSound(request.eventName);
+    }
+  }
+  console.log("got message: " + JSON.stringify(request));
+}
+
+
+//////////////////////////////////////////////////////
+
+// Listen for messages from content scripts.
+chrome.extension.onRequest.addListener(contentScriptHandler);
+
+// Load the sounds and register event listeners.
+for (var list in soundLists) {
+  for (var id in soundLists[list]) {
+    loadSound(soundLists[list][id], id);
+  }
+}
+for (var name in events) {
+  soundEvent(events[name], name);
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/fx/content.js b/chrome/common/extensions/docs/examples/extensions/fx/content.js
new file mode 100644
index 0000000..b79095c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/fx/content.js
@@ -0,0 +1,62 @@
+/*
+ * Content script for Chrome Sounds.
+ * Tracks in-page events and notifies the background page.
+ */
+
+function sendEvent(event, value) {
+  console.log("sendEvent: " + event + "," + value);
+  chrome.extension.sendRequest({eventName: event, eventValue: value});
+}
+
+// Timers to trigger "stopEvent" for coalescing events.
+var timers = {};
+
+function stopEvent(type) {
+  timers[type] = 0;
+  sendEvent(type, "stopped");
+}
+
+// Automatically coalesces repeating events into a start and a stop event.
+// |validator| is a function which should return true if the event is
+// considered to be a valid event of this type.
+function handleEvent(event, type, validator) {
+  if (validator) {
+    if (!validator(event)) {
+      return;
+    }
+  }
+  var timerId = timers[type];
+  var eventInProgress = (timerId > 0);
+  if (eventInProgress) {
+    clearTimeout(timerId);
+    timers[type] = 0;
+  } else {
+    sendEvent(type, "started");
+  }
+  timers[type] = setTimeout(stopEvent, 300, type);
+}
+
+function listenAndCoalesce(target, type, validator) {
+  target.addEventListener(type, function(event) {
+    handleEvent(event, type, validator);
+  }, true);
+}
+
+listenAndCoalesce(document, "scroll");
+
+// For some reason, "resize" doesn't seem to work with addEventListener.
+if ((window == window.top) && document.body && !document.body.onresize) {
+  document.body.onresize = function(event) {
+    sendEvent("resize", "");
+  };
+}
+
+listenAndCoalesce(document, "keypress", function(event) {
+  if (event.charCode == 13)
+    return false;
+
+  // TODO(erikkay) This doesn't work in gmail's rich text compose window.
+  return event.target.tagName == "TEXTAREA" ||
+         event.target.tagName == "INPUT" ||
+         event.target.isContentEditable;
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/fx/icon.png b/chrome/common/extensions/docs/examples/extensions/fx/icon.png
new file mode 100644
index 0000000..ce610f6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/fx/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/fx/manifest.json b/chrome/common/extensions/docs/examples/extensions/fx/manifest.json
new file mode 100644
index 0000000..962c5ee
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/fx/manifest.json
@@ -0,0 +1,22 @@
+{
+  "name": "Chrome Sounds",
+  "version": "1.2",
+  "description": "Enjoy a more magical and immersive experience when browsing the web using the power of sound.",
+  "background": {
+    "scripts": ["bg.js"]
+  },
+  "options_page": "options.html",
+  "icons": { "128": "icon.png" },
+  "permissions": [
+    "tabs",
+    "bookmarks",
+    "http://*/*",
+    "https://*/*"
+  ],
+  "content_scripts": [ {
+    "matches": ["http://*/*", "https://*/*"],
+    "js": ["content.js"],
+    "all_frames": true
+  }],
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/fx/options.html b/chrome/common/extensions/docs/examples/extensions/fx/options.html
new file mode 100644
index 0000000..2442482
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/fx/options.html
@@ -0,0 +1,31 @@
+<!doctype html>

+<html>

+<head>

+<style>

+body {

+  font-family: sans-serif;

+}

+#attributions {

+  margin-top: 20px;

+  color: #666666;

+  Xfont-size: 10px;

+}

+.sound {

+  cursor: pointer;

+}

+</style>

+<script src="options.js"></script>

+</head>

+<body>

+<div id="sounds"></div>

+<div id="attributions">

+Sounds from:

+<ul>

+<li><a href="http://www.freesound.org">www.freesound.org</a></li>

+<li><a href="http://www.free-samples-n-loops.com/loops.html">www.free-samples-n-loops.com/loops.html</a></li>

+<li>Googlers with microphones.*</li>

+</ul>

+<span style="font-size:10px">* Canadian sound made by actual Canadian.</span>

+</div>

+</body>

+</html>

diff --git a/chrome/common/extensions/docs/examples/extensions/fx/options.js b/chrome/common/extensions/docs/examples/extensions/fx/options.js
new file mode 100644
index 0000000..21d0afa
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/fx/options.js
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 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.
+
+function playSound(id) {
+  console.log(id);
+  chrome.extension.getBackgroundPage().playSound(id, false);
+}
+
+function stopSound(id) {
+  chrome.extension.getBackgroundPage().stopSound(id);
+}
+
+function soundChanged(event) {
+  var key = event.target.name;
+  var checked = event.target.checked;
+  if (checked) {
+    localStorage.setItem(key, "enabled");
+    playSound(event.target.name);
+  } else {
+    localStorage.setItem(key, "disabled");
+    stopSound(event.target.name);
+  }
+}
+
+function showSounds() {
+  var sounds = document.getElementById("sounds");
+  if (!localStorage.length) {
+    sounds.innerText = "";
+    return;
+  }
+  sounds.innerText = "Discovered sounds: (uncheck to disable)";
+  var keys = new Array();
+  for (var key in localStorage) {
+    keys.push(key);
+    console.log(key);
+  }
+  keys.sort();
+  for (var index in keys) {
+    var key = keys[index];
+    var div = document.createElement("div");
+    var check = document.createElement("input");
+    check.type = "checkbox"
+    check.name = key;
+    check.checked = localStorage[key] == "enabled";
+    check.onchange = soundChanged;
+    div.appendChild(check);
+    var text = document.createElement("span");
+    text.id = key;
+    text.innerText = key;
+    text.className = "sound";
+    text.onclick = function(event) { playSound(event.target.id); };
+    div.appendChild(text);
+    sounds.appendChild(div);
+  }
+}
+
+document.addEventListener('DOMContentLoaded', showSounds);
+document.addEventListener('focus', showSounds);
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/README b/chrome/common/extensions/docs/examples/extensions/gdocs/README
new file mode 100644
index 0000000..9c0393b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/README
@@ -0,0 +1,69 @@
+Sample extension to demonstrate integration with an OAuth service.
+
+Overview
+--------
+This sample demonstrates the use of OAuth to authorize against 
+Google's Contacts API inside of an extension.  It implements a library which
+may be reused generically to authorize requests to any 3-legged OAuth API.
+
+Library
+-------
+The library files are:
+ * chrome_ex_oauth.html
+ * chrome_ex_oauth.js
+ * chrome_ex_oauthsimple.js
+
+To use these files, place them in the root of your extension and include both
+.js files in your background page in the following order:
+
+  <script type="text/javascript" src="chrome_ex_oauthsimple.js"></script>
+  <script type="text/javascript" src="chrome_ex_oauth.js"></script>   
+  
+To initialize the API, create a ChromeExOAuth object in the background page:
+
+      var oauth = ChromeExOAuth.initBackgroundPage({
+        'request_url'     :  <OAuth request URL>, 
+        'authorize_url'   :  <OAuth authorize URL>,  
+        'access_url'      :  <OAuth access token URL>,  
+        'consumer_key'    :  <OAuth consumer key>,  
+        'consumer_secret' :  <OAuth consumer secret>,  
+        'scope'           :  <scope parameter for this auth>,
+        'app_name'        :  <application name, not used by all OAuth providers>
+      }); 
+
+Call the authorize() function to redirect the user to the OAuth provider in
+order to obtain an access token.  The client library abstracts most of this 
+process, so all you need to do is pass a callback to the authorize() function
+and a new tab will open and redirect the user.  If the library already has
+stored an access token for the current scope, then no tab will be opened.  In
+either case, the callback will be called with the resulting token and secret.
+
+      oauth.authorize(onAuthorized);
+      
+There is no need to store the token and secret, as this library already stores
+these values in localStorage.  Once the callback you specified is called, you
+can call the sendSignedRequest function to send OAuth-signed requests to the
+API.  The sendSignedRequest call takes an url to fetch, a callback function,
+and an optional parameter object as its arguments.  The callback is passed
+the response text as well as the XMLHttpRequest object which was used to 
+make the request as its arguments.
+  
+      function callback(text, xhr) {
+        //...
+      };
+      
+      function onAuthorized() { 
+        var url = <API url inside of the requested scope>;
+        var request = {
+          'method' : 'GET',
+          'parameters' : {
+             <Any request parameters as key : value pairs>
+          }
+        }
+        oauth.sendSignedRequest(url, callback, request);
+      };
+      oauth.authorize(onAuthorized);
+      
+ 
+
+
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/background.html b/chrome/common/extensions/docs/examples/extensions/gdocs/background.html
new file mode 100644
index 0000000..34478f5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/background.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2010 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.
+ *
+ * Author: Eric Bidelman <ericbidelman@chromium.org>
+-->
+<html>
+  <head>
+    <script type="text/javascript" src="chrome_ex_oauthsimple.js"></script>
+    <script type="text/javascript" src="chrome_ex_oauth.js"></script>
+    <script type="text/javascript">
+      var DOCLIST_SCOPE = 'https://docs.google.com/feeds';
+      var DOCLIST_FEED = DOCLIST_SCOPE + '/default/private/full/';
+      var docs = []; // In memory cache for the user's entire doclist.
+      var refreshRate = localStorage.refreshRate || 300; // 5 min default.
+      var pollIntervalMin = 1000 * refreshRate;
+      var requests = [];
+
+      var oauth = ChromeExOAuth.initBackgroundPage({
+        'request_url': 'https://www.google.com/accounts/OAuthGetRequestToken',
+        'authorize_url': 'https://www.google.com/accounts/OAuthAuthorizeToken',
+        'access_url': 'https://www.google.com/accounts/OAuthGetAccessToken',
+        'consumer_key': 'anonymous',
+        'consumer_secret': 'anonymous',
+        'scope': DOCLIST_SCOPE,
+        'app_name': 'Chrome Extension Sample - Accessing Google Docs with OAuth'
+      });
+
+      function setIcon(opt_badgeObj) {
+        if (opt_badgeObj) {
+          var badgeOpts = {};
+          if (opt_badgeObj && opt_badgeObj.text != undefined) {
+            badgeOpts['text'] = opt_badgeObj.text;
+          }
+          if (opt_badgeObj && opt_badgeObj.tabId) {
+            badgeOpts['tabId'] = opt_badgeObj.tabId;
+          }
+          chrome.browserAction.setBadgeText(badgeOpts);
+        }
+      };
+
+      function clearPendingRequests() {
+        for (var i = 0, req; req = requests[i]; ++i) {
+          window.clearTimeout(req);
+        }
+        requests = [];
+      };
+
+      function logout() {
+        docs = [];
+        setIcon({'text': ''});
+        oauth.clearTokens();
+        clearPendingRequests();
+      };
+    </script>
+  </head>
+  <body>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/chrome_ex_oauth.html b/chrome/common/extensions/docs/examples/extensions/gdocs/chrome_ex_oauth.html
new file mode 100644
index 0000000..61d0ac2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/chrome_ex_oauth.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2010 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.
+ *
+ * Author: Eric Bidelman <ericbidelman@chromium.org>
+-->
+<html>
+  <head>
+    <meta charset="UTF-8">
+    <title>OAuth Redirect Page</title>
+    <style type="text/css">
+      body {
+        font: 16px Arial;
+        color: #333;
+      }
+    </style>
+    <script type="text/javascript" src="chrome_ex_oauthsimple.js"></script>
+    <script type="text/javascript" src="chrome_ex_oauth.js"></script>
+    <script type="text/javascript">
+      function onLoad() {
+        ChromeExOAuth.initCallbackPage();
+      };
+    </script>
+  </head>
+  <body onload="onLoad();">
+    Redirecting...
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/chrome_ex_oauth.js b/chrome/common/extensions/docs/examples/extensions/gdocs/chrome_ex_oauth.js
new file mode 100644
index 0000000..1b62882
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/chrome_ex_oauth.js
@@ -0,0 +1,594 @@
+/**
+ * Copyright (c) 2010 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.
+ */
+
+/**
+ * Constructor - no need to invoke directly, call initBackgroundPage instead.
+ * @constructor
+ * @param {String} url_request_token The OAuth request token URL.
+ * @param {String} url_auth_token The OAuth authorize token URL.
+ * @param {String} url_access_token The OAuth access token URL.
+ * @param {String} consumer_key The OAuth consumer key.
+ * @param {String} consumer_secret The OAuth consumer secret.
+ * @param {String} oauth_scope The OAuth scope parameter.
+ * @param {Object} opt_args Optional arguments.  Recognized parameters:
+ *     "app_name" {String} Name of the current application
+ *     "callback_page" {String} If you renamed chrome_ex_oauth.html, the name
+ *          this file was renamed to.
+ */
+function ChromeExOAuth(url_request_token, url_auth_token, url_access_token,
+                       consumer_key, consumer_secret, oauth_scope, opt_args) {
+  this.url_request_token = url_request_token;
+  this.url_auth_token = url_auth_token;
+  this.url_access_token = url_access_token;
+  this.consumer_key = consumer_key;
+  this.consumer_secret = consumer_secret;
+  this.oauth_scope = oauth_scope;
+  this.app_name = opt_args && opt_args['app_name'] ||
+      "ChromeExOAuth Library";
+  this.key_token = "oauth_token";
+  this.key_token_secret = "oauth_token_secret";
+  this.callback_page = opt_args && opt_args['callback_page'] ||
+      "chrome_ex_oauth.html";
+  this.auth_params = {};
+  if (opt_args && opt_args['auth_params']) {
+    for (key in opt_args['auth_params']) {
+      if (opt_args['auth_params'].hasOwnProperty(key)) {
+        this.auth_params[key] = opt_args['auth_params'][key];
+      }
+    }
+  }
+};
+
+/*******************************************************************************
+ * PUBLIC API METHODS
+ * Call these from your background page.
+ ******************************************************************************/
+
+/**
+ * Initializes the OAuth helper from the background page.  You must call this
+ * before attempting to make any OAuth calls.
+ * @param {Object} oauth_config Configuration parameters in a JavaScript object.
+ *     The following parameters are recognized:
+ *         "request_url" {String} OAuth request token URL.
+ *         "authorize_url" {String} OAuth authorize token URL.
+ *         "access_url" {String} OAuth access token URL.
+ *         "consumer_key" {String} OAuth consumer key.
+ *         "consumer_secret" {String} OAuth consumer secret.
+ *         "scope" {String} OAuth access scope.
+ *         "app_name" {String} Application name.
+ *         "auth_params" {Object} Additional parameters to pass to the
+ *             Authorization token URL.  For an example, 'hd', 'hl', 'btmpl':
+ *             http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth
+ * @return {ChromeExOAuth} An initialized ChromeExOAuth object.
+ */
+ChromeExOAuth.initBackgroundPage = function(oauth_config) {
+  window.chromeExOAuthConfig = oauth_config;
+  window.chromeExOAuth = ChromeExOAuth.fromConfig(oauth_config);
+  window.chromeExOAuthRedirectStarted = false;
+  window.chromeExOAuthRequestingAccess = false;
+
+  var url_match = chrome.extension.getURL(window.chromeExOAuth.callback_page);
+  var tabs = {};
+  chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
+    if (changeInfo.url &&
+        changeInfo.url.substr(0, url_match.length) === url_match &&
+        changeInfo.url != tabs[tabId] &&
+        window.chromeExOAuthRequestingAccess == false) {
+      chrome.tabs.create({ 'url' : changeInfo.url }, function(tab) {
+        tabs[tab.id] = tab.url;
+        chrome.tabs.remove(tabId);
+      });
+    }
+  });
+
+  return window.chromeExOAuth;
+};
+
+/**
+ * Authorizes the current user with the configued API.  You must call this
+ * before calling sendSignedRequest.
+ * @param {Function} callback A function to call once an access token has
+ *     been obtained.  This callback will be passed the following arguments:
+ *         token {String} The OAuth access token.
+ *         secret {String} The OAuth access token secret.
+ */
+ChromeExOAuth.prototype.authorize = function(callback) {
+  if (this.hasToken()) {
+    callback(this.getToken(), this.getTokenSecret());
+  } else {
+    window.chromeExOAuthOnAuthorize = function(token, secret) {
+      callback(token, secret);
+    };
+    chrome.tabs.create({ 'url' :chrome.extension.getURL(this.callback_page) });
+  }
+};
+
+/**
+ * Clears any OAuth tokens stored for this configuration.  Effectively a
+ * "logout" of the configured OAuth API.
+ */
+ChromeExOAuth.prototype.clearTokens = function() {
+  delete localStorage[this.key_token + encodeURI(this.oauth_scope)];
+  delete localStorage[this.key_token_secret + encodeURI(this.oauth_scope)];
+};
+
+/**
+ * Returns whether a token is currently stored for this configuration.
+ * Effectively a check to see whether the current user is "logged in" to
+ * the configured OAuth API.
+ * @return {Boolean} True if an access token exists.
+ */
+ChromeExOAuth.prototype.hasToken = function() {
+  return !!this.getToken();
+};
+
+/**
+ * Makes an OAuth-signed HTTP request with the currently authorized tokens.
+ * @param {String} url The URL to send the request to.  Querystring parameters
+ *     should be omitted.
+ * @param {Function} callback A function to be called once the request is
+ *     completed.  This callback will be passed the following arguments:
+ *         responseText {String} The text response.
+ *         xhr {XMLHttpRequest} The XMLHttpRequest object which was used to
+ *             send the request.  Useful if you need to check response status
+ *             code, etc.
+ * @param {Object} opt_params Additional parameters to configure the request.
+ *     The following parameters are accepted:
+ *         "method" {String} The HTTP method to use.  Defaults to "GET".
+ *         "body" {String} A request body to send.  Defaults to null.
+ *         "parameters" {Object} Query parameters to include in the request.
+ *         "headers" {Object} Additional headers to include in the request.
+ */
+ChromeExOAuth.prototype.sendSignedRequest = function(url, callback,
+                                                     opt_params) {
+  var method = opt_params && opt_params['method'] || 'GET';
+  var body = opt_params && opt_params['body'] || null;
+  var params = opt_params && opt_params['parameters'] || {};
+  var headers = opt_params && opt_params['headers'] || {};
+
+  var signedUrl = this.signURL(url, method, params);
+
+  ChromeExOAuth.sendRequest(method, signedUrl, headers, body, function (xhr) {
+    if (xhr.readyState == 4) {
+      callback(xhr.responseText, xhr);
+    }
+  });
+};
+
+/**
+ * Adds the required OAuth parameters to the given url and returns the
+ * result.  Useful if you need a signed url but don't want to make an XHR
+ * request.
+ * @param {String} method The http method to use.
+ * @param {String} url The base url of the resource you are querying.
+ * @param {Object} opt_params Query parameters to include in the request.
+ * @return {String} The base url plus any query params plus any OAuth params.
+ */
+ChromeExOAuth.prototype.signURL = function(url, method, opt_params) {
+  var token = this.getToken();
+  var secret = this.getTokenSecret();
+  if (!token || !secret) {
+    throw new Error("No oauth token or token secret");
+  }
+
+  var params = opt_params || {};
+
+  var result = OAuthSimple().sign({
+    action : method,
+    path : url,
+    parameters : params,
+    signatures: {
+      consumer_key : this.consumer_key,
+      shared_secret : this.consumer_secret,
+      oauth_secret : secret,
+      oauth_token: token
+    }
+  });
+
+  return result.signed_url;
+};
+
+/**
+ * Generates the Authorization header based on the oauth parameters.
+ * @param {String} url The base url of the resource you are querying.
+ * @param {Object} opt_params Query parameters to include in the request.
+ * @return {String} An Authorization header containing the oauth_* params.
+ */
+ChromeExOAuth.prototype.getAuthorizationHeader = function(url, method,
+                                                          opt_params) {
+  var token = this.getToken();
+  var secret = this.getTokenSecret();
+  if (!token || !secret) {
+    throw new Error("No oauth token or token secret");
+  }
+
+  var params = opt_params || {};
+
+  return OAuthSimple().getHeaderString({
+    action: method,
+    path : url,
+    parameters : params,
+    signatures: {
+      consumer_key : this.consumer_key,
+      shared_secret : this.consumer_secret,
+      oauth_secret : secret,
+      oauth_token: token
+    }
+  });
+};
+
+/*******************************************************************************
+ * PRIVATE API METHODS
+ * Used by the library.  There should be no need to call these methods directly.
+ ******************************************************************************/
+
+/**
+ * Creates a new ChromeExOAuth object from the supplied configuration object.
+ * @param {Object} oauth_config Configuration parameters in a JavaScript object.
+ *     The following parameters are recognized:
+ *         "request_url" {String} OAuth request token URL.
+ *         "authorize_url" {String} OAuth authorize token URL.
+ *         "access_url" {String} OAuth access token URL.
+ *         "consumer_key" {String} OAuth consumer key.
+ *         "consumer_secret" {String} OAuth consumer secret.
+ *         "scope" {String} OAuth access scope.
+ *         "app_name" {String} Application name.
+ *         "auth_params" {Object} Additional parameters to pass to the
+ *             Authorization token URL.  For an example, 'hd', 'hl', 'btmpl':
+ *             http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth
+ * @return {ChromeExOAuth} An initialized ChromeExOAuth object.
+ */
+ChromeExOAuth.fromConfig = function(oauth_config) {
+  return new ChromeExOAuth(
+    oauth_config['request_url'],
+    oauth_config['authorize_url'],
+    oauth_config['access_url'],
+    oauth_config['consumer_key'],
+    oauth_config['consumer_secret'],
+    oauth_config['scope'],
+    {
+      'app_name' : oauth_config['app_name'],
+      'auth_params' : oauth_config['auth_params']
+    }
+  );
+};
+
+/**
+ * Initializes chrome_ex_oauth.html and redirects the page if needed to start
+ * the OAuth flow.  Once an access token is obtained, this function closes
+ * chrome_ex_oauth.html.
+ */
+ChromeExOAuth.initCallbackPage = function() {
+  var background_page = chrome.extension.getBackgroundPage();
+  var oauth_config = background_page.chromeExOAuthConfig;
+  var oauth = ChromeExOAuth.fromConfig(oauth_config);
+  background_page.chromeExOAuthRedirectStarted = true;
+  oauth.initOAuthFlow(function (token, secret) {
+    background_page.chromeExOAuthOnAuthorize(token, secret);
+    background_page.chromeExOAuthRedirectStarted = false;
+    chrome.tabs.getSelected(null, function (tab) {
+      chrome.tabs.remove(tab.id);
+    });
+  });
+};
+
+/**
+ * Sends an HTTP request.  Convenience wrapper for XMLHttpRequest calls.
+ * @param {String} method The HTTP method to use.
+ * @param {String} url The URL to send the request to.
+ * @param {Object} headers Optional request headers in key/value format.
+ * @param {String} body Optional body content.
+ * @param {Function} callback Function to call when the XMLHttpRequest's
+ *     ready state changes.  See documentation for XMLHttpRequest's
+ *     onreadystatechange handler for more information.
+ */
+ChromeExOAuth.sendRequest = function(method, url, headers, body, callback) {
+  var xhr = new XMLHttpRequest();
+  xhr.onreadystatechange = function(data) {
+    callback(xhr, data);
+  }
+  xhr.open(method, url, true);
+  if (headers) {
+    for (var header in headers) {
+      if (headers.hasOwnProperty(header)) {
+        xhr.setRequestHeader(header, headers[header]);
+      }
+    }
+  }
+  xhr.send(body);
+};
+
+/**
+ * Decodes a URL-encoded string into key/value pairs.
+ * @param {String} encoded An URL-encoded string.
+ * @return {Object} An object representing the decoded key/value pairs found
+ *     in the encoded string.
+ */
+ChromeExOAuth.formDecode = function(encoded) {
+  var params = encoded.split("&");
+  var decoded = {};
+  for (var i = 0, param; param = params[i]; i++) {
+    var keyval = param.split("=");
+    if (keyval.length == 2) {
+      var key = ChromeExOAuth.fromRfc3986(keyval[0]);
+      var val = ChromeExOAuth.fromRfc3986(keyval[1]);
+      decoded[key] = val;
+    }
+  }
+  return decoded;
+};
+
+/**
+ * Returns the current window's querystring decoded into key/value pairs.
+ * @return {Object} A object representing any key/value pairs found in the
+ *     current window's querystring.
+ */
+ChromeExOAuth.getQueryStringParams = function() {
+  var urlparts = window.location.href.split("?");
+  if (urlparts.length >= 2) {
+    var querystring = urlparts.slice(1).join("?");
+    return ChromeExOAuth.formDecode(querystring);
+  }
+  return {};
+};
+
+/**
+ * Binds a function call to a specific object.  This function will also take
+ * a variable number of additional arguments which will be prepended to the
+ * arguments passed to the bound function when it is called.
+ * @param {Function} func The function to bind.
+ * @param {Object} obj The object to bind to the function's "this".
+ * @return {Function} A closure that will call the bound function.
+ */
+ChromeExOAuth.bind = function(func, obj) {
+  var newargs = Array.prototype.slice.call(arguments).slice(2);
+  return function() {
+    var combinedargs = newargs.concat(Array.prototype.slice.call(arguments));
+    func.apply(obj, combinedargs);
+  };
+};
+
+/**
+ * Encodes a value according to the RFC3986 specification.
+ * @param {String} val The string to encode.
+ */
+ChromeExOAuth.toRfc3986 = function(val){
+   return encodeURIComponent(val)
+       .replace(/\!/g, "%21")
+       .replace(/\*/g, "%2A")
+       .replace(/'/g, "%27")
+       .replace(/\(/g, "%28")
+       .replace(/\)/g, "%29");
+};
+
+/**
+ * Decodes a string that has been encoded according to RFC3986.
+ * @param {String} val The string to decode.
+ */
+ChromeExOAuth.fromRfc3986 = function(val){
+  var tmp = val
+      .replace(/%21/g, "!")
+      .replace(/%2A/g, "*")
+      .replace(/%27/g, "'")
+      .replace(/%28/g, "(")
+      .replace(/%29/g, ")");
+   return decodeURIComponent(tmp);
+};
+
+/**
+ * Adds a key/value parameter to the supplied URL.
+ * @param {String} url An URL which may or may not contain querystring values.
+ * @param {String} key A key
+ * @param {String} value A value
+ * @return {String} The URL with URL-encoded versions of the key and value
+ *     appended, prefixing them with "&" or "?" as needed.
+ */
+ChromeExOAuth.addURLParam = function(url, key, value) {
+  var sep = (url.indexOf('?') >= 0) ? "&" : "?";
+  return url + sep +
+         ChromeExOAuth.toRfc3986(key) + "=" + ChromeExOAuth.toRfc3986(value);
+};
+
+/**
+ * Stores an OAuth token for the configured scope.
+ * @param {String} token The token to store.
+ */
+ChromeExOAuth.prototype.setToken = function(token) {
+  localStorage[this.key_token + encodeURI(this.oauth_scope)] = token;
+};
+
+/**
+ * Retrieves any stored token for the configured scope.
+ * @return {String} The stored token.
+ */
+ChromeExOAuth.prototype.getToken = function() {
+  return localStorage[this.key_token + encodeURI(this.oauth_scope)];
+};
+
+/**
+ * Stores an OAuth token secret for the configured scope.
+ * @param {String} secret The secret to store.
+ */
+ChromeExOAuth.prototype.setTokenSecret = function(secret) {
+  localStorage[this.key_token_secret + encodeURI(this.oauth_scope)] = secret;
+};
+
+/**
+ * Retrieves any stored secret for the configured scope.
+ * @return {String} The stored secret.
+ */
+ChromeExOAuth.prototype.getTokenSecret = function() {
+  return localStorage[this.key_token_secret + encodeURI(this.oauth_scope)];
+};
+
+/**
+ * Starts an OAuth authorization flow for the current page.  If a token exists,
+ * no redirect is needed and the supplied callback is called immediately.
+ * If this method detects that a redirect has finished, it grabs the
+ * appropriate OAuth parameters from the URL and attempts to retrieve an
+ * access token.  If no token exists and no redirect has happened, then
+ * an access token is requested and the page is ultimately redirected.
+ * @param {Function} callback The function to call once the flow has finished.
+ *     This callback will be passed the following arguments:
+ *         token {String} The OAuth access token.
+ *         secret {String} The OAuth access token secret.
+ */
+ChromeExOAuth.prototype.initOAuthFlow = function(callback) {
+  if (!this.hasToken()) {
+    var params = ChromeExOAuth.getQueryStringParams();
+    if (params['chromeexoauthcallback'] == 'true') {
+      var oauth_token = params['oauth_token'];
+      var oauth_verifier = params['oauth_verifier']
+      this.getAccessToken(oauth_token, oauth_verifier, callback);
+    } else {
+      var request_params = {
+        'url_callback_param' : 'chromeexoauthcallback'
+      }
+      this.getRequestToken(function(url) {
+        window.location.href = url;
+      }, request_params);
+    }
+  } else {
+    callback(this.getToken(), this.getTokenSecret());
+  }
+};
+
+/**
+ * Requests an OAuth request token.
+ * @param {Function} callback Function to call once the authorize URL is
+ *     calculated.  This callback will be passed the following arguments:
+ *         url {String} The URL the user must be redirected to in order to
+ *             approve the token.
+ * @param {Object} opt_args Optional arguments.  The following parameters
+ *     are accepted:
+ *         "url_callback" {String} The URL the OAuth provider will redirect to.
+ *         "url_callback_param" {String} A parameter to include in the callback
+ *             URL in order to indicate to this library that a redirect has
+ *             taken place.
+ */
+ChromeExOAuth.prototype.getRequestToken = function(callback, opt_args) {
+  if (typeof callback !== "function") {
+    throw new Error("Specified callback must be a function.");
+  }
+  var url = opt_args && opt_args['url_callback'] ||
+            window && window.top && window.top.location &&
+            window.top.location.href;
+
+  var url_param = opt_args && opt_args['url_callback_param'] ||
+                  "chromeexoauthcallback";
+  var url_callback = ChromeExOAuth.addURLParam(url, url_param, "true");
+
+  var result = OAuthSimple().sign({
+    path : this.url_request_token,
+    parameters: {
+      "xoauth_displayname" : this.app_name,
+      "scope" : this.oauth_scope,
+      "oauth_callback" : url_callback
+    },
+    signatures: {
+      consumer_key : this.consumer_key,
+      shared_secret : this.consumer_secret
+    }
+  });
+  var onToken = ChromeExOAuth.bind(this.onRequestToken, this, callback);
+  ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken);
+};
+
+/**
+ * Called when a request token has been returned.  Stores the request token
+ * secret for later use and sends the authorization url to the supplied
+ * callback (for redirecting the user).
+ * @param {Function} callback Function to call once the authorize URL is
+ *     calculated.  This callback will be passed the following arguments:
+ *         url {String} The URL the user must be redirected to in order to
+ *             approve the token.
+ * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the
+ *     request token.
+ */
+ChromeExOAuth.prototype.onRequestToken = function(callback, xhr) {
+  if (xhr.readyState == 4) {
+    if (xhr.status == 200) {
+      var params = ChromeExOAuth.formDecode(xhr.responseText);
+      var token = params['oauth_token'];
+      this.setTokenSecret(params['oauth_token_secret']);
+      var url = ChromeExOAuth.addURLParam(this.url_auth_token,
+                                          "oauth_token", token);
+      for (var key in this.auth_params) {
+        if (this.auth_params.hasOwnProperty(key)) {
+          url = ChromeExOAuth.addURLParam(url, key, this.auth_params[key]);
+        }
+      }
+      callback(url);
+    } else {
+      throw new Error("Fetching request token failed. Status " + xhr.status);
+    }
+  }
+};
+
+/**
+ * Requests an OAuth access token.
+ * @param {String} oauth_token The OAuth request token.
+ * @param {String} oauth_verifier The OAuth token verifier.
+ * @param {Function} callback The function to call once the token is obtained.
+ *     This callback will be passed the following arguments:
+ *         token {String} The OAuth access token.
+ *         secret {String} The OAuth access token secret.
+ */
+ChromeExOAuth.prototype.getAccessToken = function(oauth_token, oauth_verifier,
+                                                  callback) {
+  if (typeof callback !== "function") {
+    throw new Error("Specified callback must be a function.");
+  }
+  var bg = chrome.extension.getBackgroundPage();
+  if (bg.chromeExOAuthRequestingAccess == false) {
+    bg.chromeExOAuthRequestingAccess = true;
+
+    var result = OAuthSimple().sign({
+      path : this.url_access_token,
+      parameters: {
+        "oauth_token" : oauth_token,
+        "oauth_verifier" : oauth_verifier
+      },
+      signatures: {
+        consumer_key : this.consumer_key,
+        shared_secret : this.consumer_secret,
+        oauth_secret : this.getTokenSecret(this.oauth_scope)
+      }
+    });
+
+    var onToken = ChromeExOAuth.bind(this.onAccessToken, this, callback);
+    ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken);
+  }
+};
+
+/**
+ * Called when an access token has been returned.  Stores the access token and
+ * access token secret for later use and sends them to the supplied callback.
+ * @param {Function} callback The function to call once the token is obtained.
+ *     This callback will be passed the following arguments:
+ *         token {String} The OAuth access token.
+ *         secret {String} The OAuth access token secret.
+ * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the
+ *     access token.
+ */
+ChromeExOAuth.prototype.onAccessToken = function(callback, xhr) {
+  if (xhr.readyState == 4) {
+    var bg = chrome.extension.getBackgroundPage();
+    if (xhr.status == 200) {
+      var params = ChromeExOAuth.formDecode(xhr.responseText);
+      var token = params["oauth_token"];
+      var secret = params["oauth_token_secret"];
+      this.setToken(token);
+      this.setTokenSecret(secret);
+      bg.chromeExOAuthRequestingAccess = false;
+      callback(token, secret);
+    } else {
+      bg.chromeExOAuthRequestingAccess = false;
+      throw new Error("Fetching access token failed with status " + xhr.status);
+    }
+  }
+};
+
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/chrome_ex_oauthsimple.js b/chrome/common/extensions/docs/examples/extensions/gdocs/chrome_ex_oauthsimple.js
new file mode 100644
index 0000000..af0fe8a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/chrome_ex_oauthsimple.js
@@ -0,0 +1,458 @@
+/* OAuthSimple
+  * A simpler version of OAuth
+  *
+  * author:     jr conlin
+  * mail:       src@anticipatr.com
+  * copyright:  unitedHeroes.net
+  * version:    1.0
+  * url:        http://unitedHeroes.net/OAuthSimple
+  *
+  * Copyright (c) 2009, unitedHeroes.net
+  * All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following conditions are met:
+  *     * Redistributions of source code must retain the above copyright
+  *       notice, this list of conditions and the following disclaimer.
+  *     * Redistributions in binary form must reproduce the above copyright
+  *       notice, this list of conditions and the following disclaimer in the
+  *       documentation and/or other materials provided with the distribution.
+  *     * Neither the name of the unitedHeroes.net nor the
+  *       names of its contributors may be used to endorse or promote products
+  *       derived from this software without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY UNITEDHEROES.NET ''AS IS'' AND ANY
+  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  * DISCLAIMED. IN NO EVENT SHALL UNITEDHEROES.NET BE LIABLE FOR ANY
+  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+var OAuthSimple;
+
+if (OAuthSimple === undefined)
+{
+    /* Simple OAuth
+     *
+     * This class only builds the OAuth elements, it does not do the actual
+     * transmission or reception of the tokens. It does not validate elements
+     * of the token. It is for client use only.
+     *
+     * api_key is the API key, also known as the OAuth consumer key
+     * shared_secret is the shared secret (duh).
+     *
+     * Both the api_key and shared_secret are generally provided by the site
+     * offering OAuth services. You need to specify them at object creation
+     * because nobody <explative>ing uses OAuth without that minimal set of
+     * signatures.
+     *
+     * If you want to use the higher order security that comes from the
+     * OAuth token (sorry, I don't provide the functions to fetch that because
+     * sites aren't horribly consistent about how they offer that), you need to
+     * pass those in either with .setTokensAndSecrets() or as an argument to the
+     * .sign() or .getHeaderString() functions.
+     *
+     * Example:
+       <code>
+        var oauthObject = OAuthSimple().sign({path:'http://example.com/rest/',
+                                              parameters: 'foo=bar&gorp=banana',
+                                              signatures:{
+                                                api_key:'12345abcd',
+                                                shared_secret:'xyz-5309'
+                                             }});
+        document.getElementById('someLink').href=oauthObject.signed_url;
+       </code>
+     *
+     * that will sign as a "GET" using "SHA1-MAC" the url. If you need more than
+     * that, read on, McDuff.
+     */
+
+    /** OAuthSimple creator
+     *
+     * Create an instance of OAuthSimple
+     *
+     * @param api_key {string}       The API Key (sometimes referred to as the consumer key) This value is usually supplied by the site you wish to use.
+     * @param shared_secret (string) The shared secret. This value is also usually provided by the site you wish to use.
+     */
+    OAuthSimple = function (consumer_key,shared_secret)
+    {
+/*        if (api_key == undefined)
+            throw("Missing argument: api_key (oauth_consumer_key) for OAuthSimple. This is usually provided by the hosting site.");
+        if (shared_secret == undefined)
+            throw("Missing argument: shared_secret (shared secret) for OAuthSimple. This is usually provided by the hosting site.");
+*/      this._secrets={};
+        this._parameters={};
+
+        // General configuration options.
+        if (consumer_key !== undefined) {
+            this._secrets['consumer_key'] = consumer_key;
+            }
+        if (shared_secret !== undefined) {
+            this._secrets['shared_secret'] = shared_secret;
+            }
+        this._default_signature_method= "HMAC-SHA1";
+        this._action = "GET";
+        this._nonce_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+
+        this.reset = function() {
+            this._parameters={};
+            this._path=undefined;
+            return this;
+        };
+
+        /** set the parameters either from a hash or a string
+         *
+         * @param {string,object} List of parameters for the call, this can either be a URI string (e.g. "foo=bar&gorp=banana" or an object/hash)
+         */
+        this.setParameters = function (parameters) {
+            if (parameters === undefined) {
+                parameters = {};
+                }
+            if (typeof(parameters) == 'string') {
+                parameters=this._parseParameterString(parameters);
+                }
+            this._parameters = parameters;
+            if (this._parameters['oauth_nonce'] === undefined) {
+                this._getNonce();
+                }
+            if (this._parameters['oauth_timestamp'] === undefined) {
+                this._getTimestamp();
+                }
+            if (this._parameters['oauth_method'] === undefined) {
+                this.setSignatureMethod();
+                }
+            if (this._parameters['oauth_consumer_key'] === undefined) {
+                this._getApiKey();
+                }
+            if(this._parameters['oauth_token'] === undefined) {
+                this._getAccessToken();
+                }
+
+            return this;
+        };
+
+        /** convienence method for setParameters
+         *
+         * @param parameters {string,object} See .setParameters
+         */
+        this.setQueryString = function (parameters) {
+            return this.setParameters(parameters);
+        };
+
+        /** Set the target URL (does not include the parameters)
+         *
+         * @param path {string} the fully qualified URI (excluding query arguments) (e.g "http://example.org/foo")
+         */
+        this.setURL = function (path) {
+            if (path == '') {
+                throw ('No path specified for OAuthSimple.setURL');
+                }
+            this._path = path;
+            return this;
+        };
+
+        /** convienence method for setURL
+         *
+         * @param path {string} see .setURL
+         */
+        this.setPath = function(path){
+            return this.setURL(path);
+        };
+
+        /** set the "action" for the url, (e.g. GET,POST, DELETE, etc.)
+         *
+         * @param action {string} HTTP Action word.
+         */
+        this.setAction = function(action) {
+            if (action === undefined) {
+                action="GET";
+                }
+            action = action.toUpperCase();
+            if (action.match('[^A-Z]')) {
+                throw ('Invalid action specified for OAuthSimple.setAction');
+                }
+            this._action = action;
+            return this;
+        };
+
+        /** set the signatures (as well as validate the ones you have)
+         *
+         * @param signatures {object} object/hash of the token/signature pairs {api_key:, shared_secret:, oauth_token: oauth_secret:}
+         */
+        this.setTokensAndSecrets = function(signatures) {
+            if (signatures)
+            {
+                for (var i in signatures) {
+                    this._secrets[i] = signatures[i];
+                    }
+            }
+            // Aliases
+            if (this._secrets['api_key']) {
+                this._secrets.consumer_key = this._secrets.api_key;
+                }
+            if (this._secrets['access_token']) {
+                this._secrets.oauth_token = this._secrets.access_token;
+                }
+            if (this._secrets['access_secret']) {
+                this._secrets.oauth_secret = this._secrets.access_secret;
+                }
+            // Gauntlet
+            if (this._secrets.consumer_key === undefined) {
+                throw('Missing required consumer_key in OAuthSimple.setTokensAndSecrets');
+                }
+            if (this._secrets.shared_secret === undefined) {
+                throw('Missing required shared_secret in OAuthSimple.setTokensAndSecrets');
+                }
+            if ((this._secrets.oauth_token !== undefined) && (this._secrets.oauth_secret === undefined)) {
+                throw('Missing oauth_secret for supplied oauth_token in OAuthSimple.setTokensAndSecrets');
+                }
+            return this;
+        };
+
+        /** set the signature method (currently only Plaintext or SHA-MAC1)
+         *
+         * @param method {string} Method of signing the transaction (only PLAINTEXT and SHA-MAC1 allowed for now)
+         */
+        this.setSignatureMethod = function(method) {
+            if (method === undefined) {
+                method = this._default_signature_method;
+                }
+            //TODO: accept things other than PlainText or SHA-MAC1
+            if (method.toUpperCase().match(/(PLAINTEXT|HMAC-SHA1)/) === undefined) {
+                throw ('Unknown signing method specified for OAuthSimple.setSignatureMethod');
+                }
+            this._parameters['oauth_signature_method']= method.toUpperCase();
+            return this;
+        };
+
+        /** sign the request
+         *
+         * note: all arguments are optional, provided you've set them using the
+         * other helper functions.
+         *
+         * @param args {object} hash of arguments for the call
+         *                   {action:, path:, parameters:, method:, signatures:}
+         *                   all arguments are optional.
+         */
+        this.sign = function (args) {
+            if (args === undefined) {
+                args = {};
+                }
+            // Set any given parameters
+            if(args['action'] !== undefined) {
+                this.setAction(args['action']);
+                }
+            if (args['path'] !== undefined) {
+                this.setPath(args['path']);
+                }
+            if (args['method'] !== undefined) {
+                this.setSignatureMethod(args['method']);
+                }
+            this.setTokensAndSecrets(args['signatures']);
+            if (args['parameters'] !== undefined){
+            this.setParameters(args['parameters']);
+            }
+            // check the parameters
+            var normParams = this._normalizedParameters();
+            this._parameters['oauth_signature']=this._generateSignature(normParams);
+            return {
+                parameters: this._parameters,
+                signature: this._oauthEscape(this._parameters['oauth_signature']),
+                signed_url: this._path + '?' + this._normalizedParameters(),
+                header: this.getHeaderString()
+            };
+        };
+
+        /** Return a formatted "header" string
+         *
+         * NOTE: This doesn't set the "Authorization: " prefix, which is required.
+         * I don't set it because various set header functions prefer different
+         * ways to do that.
+         *
+         * @param args {object} see .sign
+         */
+        this.getHeaderString = function(args) {
+            if (this._parameters['oauth_signature'] === undefined) {
+                this.sign(args);
+                }
+
+            var result = 'OAuth ';
+            for (var pName in this._parameters)
+            {
+                if (!pName.match(/^oauth/)) {
+                    continue;
+                    }
+                if ((this._parameters[pName]) instanceof Array)
+                {
+                    var pLength = this._parameters[pName].length;
+                    for (var j=0;j<pLength;j++)
+                    {
+                        result += pName +'="'+this._oauthEscape(this._parameters[pName][j])+'" ';
+                    }
+                }
+                else
+                {
+                    result += pName + '="'+this._oauthEscape(this._parameters[pName])+'" ';
+                }
+            }
+            return result;
+        };
+
+        // Start Private Methods.
+
+        /** convert the parameter string into a hash of objects.
+         *
+         */
+        this._parseParameterString = function(paramString){
+            var elements = paramString.split('&');
+            var result={};
+            for(var element=elements.shift();element;element=elements.shift())
+            {
+                var keyToken=element.split('=');
+                var value='';
+                if (keyToken[1]) {
+                    value=decodeURIComponent(keyToken[1]);
+                    }
+                if(result[keyToken[0]]){
+                    if (!(result[keyToken[0]] instanceof Array))
+                    {
+                        result[keyToken[0]] = Array(result[keyToken[0]],value);
+                    }
+                    else
+                    {
+                        result[keyToken[0]].push(value);
+                    }
+                }
+                else
+                {
+                    result[keyToken[0]]=value;
+                }
+            }
+            return result;
+        };
+
+        this._oauthEscape = function(string) {
+            if (string === undefined) {
+                return "";
+                }
+            if (string instanceof Array)
+            {
+                throw('Array passed to _oauthEscape');
+            }
+            return encodeURIComponent(string).replace(/\!/g, "%21").
+            replace(/\*/g, "%2A").
+            replace(/'/g, "%27").
+            replace(/\(/g, "%28").
+            replace(/\)/g, "%29");
+        };
+
+        this._getNonce = function (length) {
+            if (length === undefined) {
+                length=5;
+                }
+            var result = "";
+            var cLength = this._nonce_chars.length;
+            for (var i = 0; i < length;i++) {
+                var rnum = Math.floor(Math.random() *cLength);
+                result += this._nonce_chars.substring(rnum,rnum+1);
+            }
+            this._parameters['oauth_nonce']=result;
+            return result;
+        };
+
+        this._getApiKey = function() {
+            if (this._secrets.consumer_key === undefined) {
+                throw('No consumer_key set for OAuthSimple.');
+                }
+            this._parameters['oauth_consumer_key']=this._secrets.consumer_key;
+            return this._parameters.oauth_consumer_key;
+        };
+
+        this._getAccessToken = function() {
+            if (this._secrets['oauth_secret'] === undefined) {
+                return '';
+                }
+            if (this._secrets['oauth_token'] === undefined) {
+                throw('No oauth_token (access_token) set for OAuthSimple.');
+                }
+            this._parameters['oauth_token'] = this._secrets.oauth_token;
+            return this._parameters.oauth_token;
+        };
+
+        this._getTimestamp = function() {
+            var d = new Date();
+            var ts = Math.floor(d.getTime()/1000);
+            this._parameters['oauth_timestamp'] = ts;
+            return ts;
+        };
+
+        this.b64_hmac_sha1 = function(k,d,_p,_z){
+        // heavily optimized and compressed version of http://pajhome.org.uk/crypt/md5/sha1.js
+        // _p = b64pad, _z = character size; not used here but I left them available just in case
+        if(!_p){_p='=';}if(!_z){_z=8;}function _f(t,b,c,d){if(t<20){return(b&c)|((~b)&d);}if(t<40){return b^c^d;}if(t<60){return(b&c)|(b&d)|(c&d);}return b^c^d;}function _k(t){return(t<20)?1518500249:(t<40)?1859775393:(t<60)?-1894007588:-899497514;}function _s(x,y){var l=(x&0xFFFF)+(y&0xFFFF),m=(x>>16)+(y>>16)+(l>>16);return(m<<16)|(l&0xFFFF);}function _r(n,c){return(n<<c)|(n>>>(32-c));}function _c(x,l){x[l>>5]|=0x80<<(24-l%32);x[((l+64>>9)<<4)+15]=l;var w=[80],a=1732584193,b=-271733879,c=-1732584194,d=271733878,e=-1009589776;for(var i=0;i<x.length;i+=16){var o=a,p=b,q=c,r=d,s=e;for(var j=0;j<80;j++){if(j<16){w[j]=x[i+j];}else{w[j]=_r(w[j-3]^w[j-8]^w[j-14]^w[j-16],1);}var t=_s(_s(_r(a,5),_f(j,b,c,d)),_s(_s(e,w[j]),_k(j)));e=d;d=c;c=_r(b,30);b=a;a=t;}a=_s(a,o);b=_s(b,p);c=_s(c,q);d=_s(d,r);e=_s(e,s);}return[a,b,c,d,e];}function _b(s){var b=[],m=(1<<_z)-1;for(var i=0;i<s.length*_z;i+=_z){b[i>>5]|=(s.charCodeAt(i/8)&m)<<(32-_z-i%32);}return b;}function _h(k,d){var b=_b(k);if(b.length>16){b=_c(b,k.length*_z);}var p=[16],o=[16];for(var i=0;i<16;i++){p[i]=b[i]^0x36363636;o[i]=b[i]^0x5C5C5C5C;}var h=_c(p.concat(_b(d)),512+d.length*_z);return _c(o.concat(h),512+160);}function _n(b){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s='';for(var i=0;i<b.length*4;i+=3){var r=(((b[i>>2]>>8*(3-i%4))&0xFF)<<16)|(((b[i+1>>2]>>8*(3-(i+1)%4))&0xFF)<<8)|((b[i+2>>2]>>8*(3-(i+2)%4))&0xFF);for(var j=0;j<4;j++){if(i*8+j*6>b.length*32){s+=_p;}else{s+=t.charAt((r>>6*(3-j))&0x3F);}}}return s;}function _x(k,d){return _n(_h(k,d));}return _x(k,d);
+        }
+
+
+        this._normalizedParameters = function() {
+            var elements = new Array();
+            var paramNames = [];
+            var ra =0;
+            for (var paramName in this._parameters)
+            {
+                if (ra++ > 1000) {
+                    throw('runaway 1');
+                    }
+                paramNames.unshift(paramName);
+            }
+            paramNames = paramNames.sort();
+            pLen = paramNames.length;
+            for (var i=0;i<pLen; i++)
+            {
+                paramName=paramNames[i];
+                //skip secrets.
+                if (paramName.match(/\w+_secret/)) {
+                    continue;
+                    }
+                if (this._parameters[paramName] instanceof Array)
+                {
+                    var sorted = this._parameters[paramName].sort();
+                    var spLen = sorted.length;
+                    for (var j = 0;j<spLen;j++){
+                        if (ra++ > 1000) {
+                            throw('runaway 1');
+                            }
+                        elements.push(this._oauthEscape(paramName) + '=' +
+                                  this._oauthEscape(sorted[j]));
+                    }
+                    continue;
+                }
+                elements.push(this._oauthEscape(paramName) + '=' +
+                              this._oauthEscape(this._parameters[paramName]));
+            }
+            return elements.join('&');
+        };
+
+        this._generateSignature = function() {
+
+            var secretKey = this._oauthEscape(this._secrets.shared_secret)+'&'+
+                this._oauthEscape(this._secrets.oauth_secret);
+            if (this._parameters['oauth_signature_method'] == 'PLAINTEXT')
+            {
+                return secretKey;
+            }
+            if (this._parameters['oauth_signature_method'] == 'HMAC-SHA1')
+            {
+                var sigString = this._oauthEscape(this._action)+'&'+this._oauthEscape(this._path)+'&'+this._oauthEscape(this._normalizedParameters());
+                return this.b64_hmac_sha1(secretKey,sigString);
+            }
+            return null;
+        };
+
+    return this;
+    };
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/img/docs_spreadsheets-128.gif b/chrome/common/extensions/docs/examples/extensions/gdocs/img/docs_spreadsheets-128.gif
new file mode 100644
index 0000000..e85c4fd
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/img/docs_spreadsheets-128.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/img/docs_spreadsheets-32.gif b/chrome/common/extensions/docs/examples/extensions/gdocs/img/docs_spreadsheets-32.gif
new file mode 100644
index 0000000..da38b31
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/img/docs_spreadsheets-32.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/img/docs_spreadsheets-48.gif b/chrome/common/extensions/docs/examples/extensions/gdocs/img/docs_spreadsheets-48.gif
new file mode 100644
index 0000000..dd54d63
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/img/docs_spreadsheets-48.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/audio.gif b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/audio.gif
new file mode 100644
index 0000000..997d82d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/audio.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/document.gif b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/document.gif
new file mode 100644
index 0000000..f202ab1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/document.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/file.gif b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/file.gif
new file mode 100644
index 0000000..9374dd2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/file.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/folder.gif b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/folder.gif
new file mode 100644
index 0000000..f6bc4f1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/folder.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/form.gif b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/form.gif
new file mode 100644
index 0000000..e7b521d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/form.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/pdf.gif b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/pdf.gif
new file mode 100644
index 0000000..136eb0b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/pdf.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/presentation.gif b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/presentation.gif
new file mode 100644
index 0000000..cede720
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/presentation.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/spreadsheet.gif b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/spreadsheet.gif
new file mode 100644
index 0000000..ec3954f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/spreadsheet.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/trashed.png b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/trashed.png
new file mode 100644
index 0000000..eaee07f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/img/icons/trashed.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/js/jquery-1.4.1.min.js b/chrome/common/extensions/docs/examples/extensions/gdocs/js/jquery-1.4.1.min.js
new file mode 100644
index 0000000..0c7294c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/js/jquery-1.4.1.min.js
@@ -0,0 +1,152 @@
+/*!
+ * jQuery JavaScript Library v1.4.1
+ * http://jquery.com/
+ *
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Mon Jan 25 19:43:33 2010 -0500
+ */
+(function(z,v){function la(){if(!c.isReady){try{r.documentElement.doScroll("left")}catch(a){setTimeout(la,1);return}c.ready()}}function Ma(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,i){var j=a.length;if(typeof b==="object"){for(var n in b)X(a,n,b[n],f,e,d);return a}if(d!==v){f=!i&&f&&c.isFunction(d);for(n=0;n<j;n++)e(a[n],b,f?d.call(a[n],n,e(a[n],b)):d,i);return a}return j?
+e(a[0],b):null}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function ma(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function na(a){var b,d=[],f=[],e=arguments,i,j,n,o,m,s,x=c.extend({},c.data(this,"events").live);if(!(a.button&&a.type==="click")){for(o in x){j=x[o];if(j.live===a.type||j.altLive&&c.inArray(a.type,j.altLive)>-1){i=j.data;i.beforeFilter&&i.beforeFilter[a.type]&&!i.beforeFilter[a.type](a)||f.push(j.selector)}else delete x[o]}i=c(a.target).closest(f,
+a.currentTarget);m=0;for(s=i.length;m<s;m++)for(o in x){j=x[o];n=i[m].elem;f=null;if(i[m].selector===j.selector){if(j.live==="mouseenter"||j.live==="mouseleave")f=c(a.relatedTarget).closest(j.selector)[0];if(!f||f!==n)d.push({elem:n,fn:j})}}m=0;for(s=d.length;m<s;m++){i=d[m];a.currentTarget=i.elem;a.data=i.fn.data;if(i.fn.apply(i.elem,e)===false){b=false;break}}return b}}function oa(a,b){return"live."+(a?a+".":"")+b.replace(/\./g,"`").replace(/ /g,"&")}function pa(a){return!a||!a.parentNode||a.parentNode.nodeType===
+11}function qa(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var i in f)for(var j in f[i])c.event.add(this,i,f[i][j],f[i][j].data)}}})}function ra(a,b,d){var f,e,i;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&a[0].indexOf("<option")<0&&(c.support.checkClone||!sa.test(a[0]))){e=true;if(i=c.fragments[a[0]])if(i!==1)f=i}if(!f){b=b&&b[0]?b[0].ownerDocument||b[0]:r;f=b.createDocumentFragment();
+c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=i?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(ta.concat.apply([],ta.slice(0,b)),function(){d[this]=a});return d}function ua(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Na=z.jQuery,Oa=z.$,r=z.document,S,Pa=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Qa=/^.[^:#\[\.,]*$/,Ra=/\S/,Sa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Ta=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,O=navigator.userAgent,
+va=false,P=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,Q=Array.prototype.slice,wa=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(typeof a==="string")if((d=Pa.exec(a))&&(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:r;if(a=Ta.exec(a))if(c.isPlainObject(b)){a=[r.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=ra([d[1]],
+[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}}else{if(b=r.getElementById(d[2])){if(b.id!==d[2])return S.find(a);this.length=1;this[0]=b}this.context=r;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=r;a=r.getElementsByTagName(a)}else return!b||b.jquery?(b||S).find(a):c(b).find(a);else if(c.isFunction(a))return S.ready(a);if(a.selector!==v){this.selector=a.selector;this.context=a.context}return c.isArray(a)?this.setArray(a):c.makeArray(a,
+this)},selector:"",jquery:"1.4.1",length:0,size:function(){return this.length},toArray:function(){return Q.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){a=c(a||null);a.prevObject=this;a.context=this.context;if(b==="find")a.selector=this.selector+(this.selector?" ":"")+d;else if(b)a.selector=this.selector+"."+b+"("+d+")";return a},setArray:function(a){this.length=0;ba.apply(this,a);return this},each:function(a,b){return c.each(this,
+a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(r,c);else P&&P.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(Q.apply(this,arguments),"slice",Q.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};
+c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,i,j,n;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(i in e){j=a[i];n=e[i];if(a!==n)if(f&&n&&(c.isPlainObject(n)||c.isArray(n))){j=j&&(c.isPlainObject(j)||c.isArray(j))?j:c.isArray(n)?[]:{};a[i]=c.extend(f,j,n)}else if(n!==v)a[i]=n}return a};c.extend({noConflict:function(a){z.$=
+Oa;if(a)z.jQuery=Na;return c},isReady:false,ready:function(){if(!c.isReady){if(!r.body)return setTimeout(c.ready,13);c.isReady=true;if(P){for(var a,b=0;a=P[b++];)a.call(r,c);P=null}c.fn.triggerHandler&&c(r).triggerHandler("ready")}},bindReady:function(){if(!va){va=true;if(r.readyState==="complete")return c.ready();if(r.addEventListener){r.addEventListener("DOMContentLoaded",L,false);z.addEventListener("load",c.ready,false)}else if(r.attachEvent){r.attachEvent("onreadystatechange",L);z.attachEvent("onload",
+c.ready);var a=false;try{a=z.frameElement==null}catch(b){}r.documentElement.doScroll&&a&&la()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,"isPrototypeOf"))return false;var b;for(b in a);return b===v||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;
+return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return z.JSON&&z.JSON.parse?z.JSON.parse(a):(new Function("return "+a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Ra.test(a)){var b=r.getElementsByTagName("head")[0]||
+r.documentElement,d=r.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(r.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,i=a.length,j=i===v||c.isFunction(a);if(d)if(j)for(f in a){if(b.apply(a[f],d)===false)break}else for(;e<i;){if(b.apply(a[e++],d)===false)break}else if(j)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=
+a[0];e<i&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Sa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==
+v;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,i=a.length;e<i;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,i=0,j=a.length;i<j;i++){e=b(a[i],i,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=v}else if(b&&!c.isFunction(b)){d=b;b=v}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},
+uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});O=c.uaMatch(O);if(O.browser){c.browser[O.browser]=true;c.browser.version=O.version}if(c.browser.webkit)c.browser.safari=true;if(wa)c.inArray=function(a,b){return wa.call(b,a)};S=c(r);if(r.addEventListener)L=function(){r.removeEventListener("DOMContentLoaded",
+L,false);c.ready()};else if(r.attachEvent)L=function(){if(r.readyState==="complete"){r.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=r.documentElement,b=r.createElement("script"),d=r.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML="   <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";var e=d.getElementsByTagName("*"),i=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!i)){c.support=
+{leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(i.getAttribute("style")),hrefNormalized:i.getAttribute("href")==="/a",opacity:/^0.55$/.test(i.style.opacity),cssFloat:!!i.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:r.createElement("select").appendChild(r.createElement("option")).selected,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};
+b.type="text/javascript";try{b.appendChild(r.createTextNode("window."+f+"=1;"))}catch(j){}a.insertBefore(b,a.firstChild);if(z[f]){c.support.scriptEval=true;delete z[f]}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function n(){c.support.noCloneEvent=false;d.detachEvent("onclick",n)});d.cloneNode(true).fireEvent("onclick")}d=r.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=r.createDocumentFragment();a.appendChild(d.firstChild);
+c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var n=r.createElement("div");n.style.width=n.style.paddingLeft="1px";r.body.appendChild(n);c.boxModel=c.support.boxModel=n.offsetWidth===2;r.body.removeChild(n).style.display="none"});a=function(n){var o=r.createElement("div");n="on"+n;var m=n in o;if(!m){o.setAttribute(n,"return;");m=typeof o[n]==="function"}return m};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=i=null}})();c.props=
+{"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ua=0,xa={},Va={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==z?xa:a;var f=a[G],e=c.cache;if(!b&&!f)return null;f||(f=++Ua);if(typeof b==="object"){a[G]=f;e=e[f]=c.extend(true,
+{},b)}else e=e[f]?e[f]:typeof d==="undefined"?Va:(e[f]={});if(d!==v){a[G]=f;e[b]=d}return typeof b==="string"?e[b]:e}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==z?xa:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{try{delete a[G]}catch(i){a.removeAttribute&&a.removeAttribute(G)}delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,
+a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===v){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===v&&this.length)f=c.data(this[0],a);return f===v&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);
+return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===v)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||
+a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var ya=/[\n\t]/g,ca=/\s+/,Wa=/\r/g,Xa=/href|src|style/,Ya=/(button|input)/i,Za=/(button|input|object|select|textarea)/i,$a=/^(a|area)$/i,za=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(o){var m=
+c(this);m.addClass(a.call(this,o,m.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className)for(var i=" "+e.className+" ",j=0,n=b.length;j<n;j++){if(i.indexOf(" "+b[j]+" ")<0)e.className+=" "+b[j]}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(o){var m=c(this);m.removeClass(a.call(this,o,m.attr("class")))});if(a&&typeof a==="string"||a===v)for(var b=(a||"").split(ca),
+d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var i=(" "+e.className+" ").replace(ya," "),j=0,n=b.length;j<n;j++)i=i.replace(" "+b[j]+" "," ");e.className=i.substring(1,i.length-1)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var i=c(this);i.toggleClass(a.call(this,e,i.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,i=0,j=c(this),n=b,o=
+a.split(ca);e=o[i++];){n=f?n:!j.hasClass(e);j[n?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(ya," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===v){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||
+{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var i=b?d:0;for(d=b?d+1:e.length;i<d;i++){var j=e[i];if(j.selected){a=c(j).val();if(b)return a;f.push(a)}}return f}if(za.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Wa,"")}return v}var n=c.isFunction(a);return this.each(function(o){var m=c(this),s=a;if(this.nodeType===1){if(n)s=a.call(this,o,m.val());
+if(typeof s==="number")s+="";if(c.isArray(s)&&za.test(this.type))this.checked=c.inArray(m.val(),s)>=0;else if(c.nodeName(this,"select")){var x=c.makeArray(s);c("option",this).each(function(){this.selected=c.inArray(c(this).val(),x)>=0});if(!x.length)this.selectedIndex=-1}else this.value=s}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return v;if(f&&b in c.attrFn)return c(a)[b](d);
+f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==v;b=f&&c.props[b]||b;if(a.nodeType===1){var i=Xa.test(b);if(b in a&&f&&!i){if(e){b==="type"&&Ya.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:Za.test(a.nodeName)||$a.test(a.nodeName)&&a.href?0:v;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=
+""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&i?a.getAttribute(b,2):a.getAttribute(b);return a===null?v:a}return c.style(a,b,d)}});var ab=function(a){return a.replace(/[^\w\s\.\|`]/g,function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==z&&!a.frameElement)a=z;if(!d.guid)d.guid=c.guid++;if(f!==v){d=c.proxy(d);d.data=f}var e=c.data(a,"events")||c.data(a,"events",{}),i=c.data(a,"handle"),j;if(!i){j=
+function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(j.elem,arguments):v};i=c.data(a,"handle",j)}if(i){i.elem=a;b=b.split(/\s+/);for(var n,o=0;n=b[o++];){var m=n.split(".");n=m.shift();if(o>1){d=c.proxy(d);if(f!==v)d.data=f}d.type=m.slice(0).sort().join(".");var s=e[n],x=this.special[n]||{};if(!s){s=e[n]={};if(!x.setup||x.setup.call(a,f,m,d)===false)if(a.addEventListener)a.addEventListener(n,i,false);else a.attachEvent&&a.attachEvent("on"+n,i)}if(x.add)if((m=x.add.call(a,
+d,f,m,s))&&c.isFunction(m)){m.guid=m.guid||d.guid;m.data=m.data||d.data;m.type=m.type||d.type;d=m}s[d.guid]=d;this.global[n]=true}a=null}}},global:{},remove:function(a,b,d){if(!(a.nodeType===3||a.nodeType===8)){var f=c.data(a,"events"),e,i,j;if(f){if(b===v||typeof b==="string"&&b.charAt(0)===".")for(i in f)this.remove(a,i+(b||""));else{if(b.type){d=b.handler;b=b.type}b=b.split(/\s+/);for(var n=0;i=b[n++];){var o=i.split(".");i=o.shift();var m=!o.length,s=c.map(o.slice(0).sort(),ab);s=new RegExp("(^|\\.)"+
+s.join("\\.(?:.*\\.)?")+"(\\.|$)");var x=this.special[i]||{};if(f[i]){if(d){j=f[i][d.guid];delete f[i][d.guid]}else for(var A in f[i])if(m||s.test(f[i][A].type))delete f[i][A];x.remove&&x.remove.call(a,o,j);for(e in f[i])break;if(!e){if(!x.teardown||x.teardown.call(a,o)===false)if(a.removeEventListener)a.removeEventListener(i,c.data(a,"handle"),false);else a.detachEvent&&a.detachEvent("on"+i,c.data(a,"handle"));e=null;delete f[i]}}}}for(e in f)break;if(!e){if(A=c.data(a,"handle"))A.elem=null;c.removeData(a,
+"events");c.removeData(a,"handle")}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();this.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return v;a.result=v;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,
+b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(i){}if(!a.isPropagationStopped()&&f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){d=a.target;var j;if(!(c.nodeName(d,"a")&&e==="click")&&!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()])){try{if(d[e]){if(j=d["on"+e])d["on"+e]=null;this.triggered=true;d[e]()}}catch(n){}if(j)d["on"+e]=j;this.triggered=false}}},handle:function(a){var b,
+d;a=arguments[0]=c.event.fix(a||z.event);a.currentTarget=this;d=a.type.split(".");a.type=d.shift();b=!d.length&&!a.exclusive;var f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)");d=(c.data(this,"events")||{})[a.type];for(var e in d){var i=d[e];if(b||f.test(i.type)){a.handler=i;a.data=i.data;i=i.apply(this,arguments);if(i!==v){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||r;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=r.documentElement;d=r.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
+d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==v)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a,b){c.extend(a,b||{});a.guid+=b.selector+b.live;b.liveProxy=a;c.event.add(this,b.live,na,b)},remove:function(a){if(a.length){var b=
+0,d=new RegExp("(^|\\.)"+a[0]+"(\\.|$)");c.each(c.data(this,"events").live||{},function(){d.test(this.type)&&b++});b<1&&c.event.remove(this,a[0],na)}},special:{}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};
+c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y};var Aa=function(a){for(var b=
+a.relatedTarget;b&&b!==this;)try{b=b.parentNode}catch(d){break}if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}},Ba=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ba:Aa,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ba:Aa)}}});if(!c.support.submitBubbles)c.event.special.submit={setup:function(a,b,d){if(this.nodeName.toLowerCase()!==
+"form"){c.event.add(this,"click.specialSubmit."+d.guid,function(f){var e=f.target,i=e.type;if((i==="submit"||i==="image")&&c(e).closest("form").length)return ma("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit."+d.guid,function(f){var e=f.target,i=e.type;if((i==="text"||i==="password")&&c(e).closest("form").length&&f.keyCode===13)return ma("submit",this,arguments)})}else return false},remove:function(a,b){c.event.remove(this,"click.specialSubmit"+(b?"."+b.guid:""));c.event.remove(this,
+"keypress.specialSubmit"+(b?"."+b.guid:""))}};if(!c.support.changeBubbles){var da=/textarea|input|select/i;function Ca(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d}function ea(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Ca(d);if(a.type!=="focusout"||
+d.type!=="radio")c.data(d,"_change_data",e);if(!(f===v||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}}c.event.special.change={filters:{focusout:ea,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return ea.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return ea.call(this,a)},beforeactivate:function(a){a=
+a.target;a.nodeName.toLowerCase()==="input"&&a.type==="radio"&&c.data(a,"_change_data",Ca(a))}},setup:function(a,b,d){for(var f in T)c.event.add(this,f+".specialChange."+d.guid,T[f]);return da.test(this.nodeName)},remove:function(a,b){for(var d in T)c.event.remove(this,d+".specialChange"+(b?"."+b.guid:""),T[d]);return da.test(this.nodeName)}};var T=c.event.special.change.filters}r.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,
+f)}c.event.special[b]={setup:function(){this.addEventListener(a,d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var i in d)this[b](i,f,d[i],e);return this}if(c.isFunction(f)){e=f;f=v}var j=b==="one"?c.proxy(e,function(n){c(this).unbind(n,j);return e.apply(this,arguments)}):e;return d==="unload"&&b!=="one"?this.one(d,f,e):this.each(function(){c.event.add(this,d,j,f)})}});c.fn.extend({unbind:function(a,
+b){if(typeof a==="object"&&!a.preventDefault){for(var d in a)this.unbind(d,a[d]);return this}return this.each(function(){c.event.remove(this,a,b)})},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+
+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e){var i,j=0;if(c.isFunction(f)){e=f;f=v}for(d=(d||"").split(/\s+/);(i=d[j++])!=null;){i=i==="focus"?"focusin":i==="blur"?"focusout":i==="hover"?d.push("mouseleave")&&"mouseenter":i;b==="live"?c(this.context).bind(oa(i,this.selector),{data:f,selector:this.selector,
+live:i},e):c(this.context).unbind(oa(i,this.selector),e?{guid:e.guid+this.selector+i}:null)}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});z.attachEvent&&!z.addEventListener&&z.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});
+(function(){function a(g){for(var h="",k,l=0;g[l];l++){k=g[l];if(k.nodeType===3||k.nodeType===4)h+=k.nodeValue;else if(k.nodeType!==8)h+=a(k.childNodes)}return h}function b(g,h,k,l,q,p){q=0;for(var u=l.length;q<u;q++){var t=l[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===k){y=l[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=k;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}l[q]=y}}}function d(g,h,k,l,q,p){q=0;for(var u=l.length;q<u;q++){var t=l[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===
+k){y=l[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=k;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(o.filter(h,[t]).length>0){y=t;break}}t=t[g]}l[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,i=Object.prototype.toString,j=false,n=true;[0,0].sort(function(){n=false;return 0});var o=function(g,h,k,l){k=k||[];var q=h=h||r;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||
+typeof g!=="string")return k;for(var p=[],u,t,y,R,H=true,M=w(h),I=g;(f.exec(""),u=f.exec(I))!==null;){I=u[3];p.push(u[1]);if(u[2]){R=u[3];break}}if(p.length>1&&s.exec(g))if(p.length===2&&m.relative[p[0]])t=fa(p[0]+p[1],h);else for(t=m.relative[p[0]]?[h]:o(p.shift(),h);p.length;){g=p.shift();if(m.relative[g])g+=p.shift();t=fa(g,t)}else{if(!l&&p.length>1&&h.nodeType===9&&!M&&m.match.ID.test(p[0])&&!m.match.ID.test(p[p.length-1])){u=o.find(p.shift(),h,M);h=u.expr?o.filter(u.expr,u.set)[0]:u.set[0]}if(h){u=
+l?{expr:p.pop(),set:A(l)}:o.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=u.expr?o.filter(u.expr,u.set):u.set;if(p.length>0)y=A(t);else H=false;for(;p.length;){var D=p.pop();u=D;if(m.relative[D])u=p.pop();else D="";if(u==null)u=h;m.relative[D](y,u,M)}}else y=[]}y||(y=t);y||o.error(D||g);if(i.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))k.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&
+y[g].nodeType===1&&k.push(t[g]);else k.push.apply(k,y);else A(y,k);if(R){o(R,q,k,l);o.uniqueSort(k)}return k};o.uniqueSort=function(g){if(C){j=n;g.sort(C);if(j)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};o.matches=function(g,h){return o(g,null,null,h)};o.find=function(g,h,k){var l,q;if(!g)return[];for(var p=0,u=m.order.length;p<u;p++){var t=m.order[p];if(q=m.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");l=m.find[t](q,
+h,k);if(l!=null){g=g.replace(m.match[t],"");break}}}}l||(l=h.getElementsByTagName("*"));return{set:l,expr:g}};o.filter=function(g,h,k,l){for(var q=g,p=[],u=h,t,y,R=h&&h[0]&&w(h[0]);g&&h.length;){for(var H in m.filter)if((t=m.leftMatch[H].exec(g))!=null&&t[2]){var M=m.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-1)!=="\\"){if(u===p)p=[];if(m.preFilter[H])if(t=m.preFilter[H](t,u,k,p,l,R)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=u[U])!=null;U++)if(D){I=M(D,t,U,u);var Da=
+l^!!I;if(k&&I!=null)if(Da)y=true;else u[U]=false;else if(Da){p.push(D);y=true}}if(I!==v){k||(u=p);g=g.replace(m.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)o.error(g);else break;q=g}return u};o.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var m=o.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
+TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},relative:{"+":function(g,h){var k=typeof h==="string",l=k&&!/\W/.test(h);k=k&&!l;if(l)h=h.toLowerCase();l=0;for(var q=g.length,
+p;l<q;l++)if(p=g[l]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[l]=k||p&&p.nodeName.toLowerCase()===h?p||false:p===h}k&&o.filter(h,g,true)},">":function(g,h){var k=typeof h==="string";if(k&&!/\W/.test(h)){h=h.toLowerCase();for(var l=0,q=g.length;l<q;l++){var p=g[l];if(p){k=p.parentNode;g[l]=k.nodeName.toLowerCase()===h?k:false}}}else{l=0;for(q=g.length;l<q;l++)if(p=g[l])g[l]=k?p.parentNode:p.parentNode===h;k&&o.filter(h,g,true)}},"":function(g,h,k){var l=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=
+h=h.toLowerCase();q=b}q("parentNode",h,l,g,p,k)},"~":function(g,h,k){var l=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,l,g,p,k)}},find:{ID:function(g,h,k){if(typeof h.getElementById!=="undefined"&&!k)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var k=[];h=h.getElementsByName(g[1]);for(var l=0,q=h.length;l<q;l++)h[l].getAttribute("name")===g[1]&&k.push(h[l]);return k.length===0?null:k}},
+TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,k,l,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var u;(u=h[p])!=null;p++)if(u)if(q^(u.className&&(" "+u.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))k||l.push(u);else if(k)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&
+"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,k,l,q,p){h=g[1].replace(/\\/g,"");if(!p&&m.attrMap[h])g[1]=m.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,k,l,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=o(g[3],null,null,h);else{g=o.filter(g[3],h,k,true^q);k||l.push.apply(l,g);return false}else if(m.match.POS.test(g[0])||m.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);
+return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,k){return!!o(k[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===
+g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,h){return h===0},last:function(g,h,k,l){return h===l.length-1},even:function(g,h){return h%2===
+0},odd:function(g,h){return h%2===1},lt:function(g,h,k){return h<k[3]-0},gt:function(g,h,k){return h>k[3]-0},nth:function(g,h,k){return k[3]-0===h},eq:function(g,h,k){return k[3]-0===h}},filter:{PSEUDO:function(g,h,k,l){var q=h[1],p=m.filters[q];if(p)return p(g,k,h,l);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=h[3];k=0;for(l=h.length;k<l;k++)if(h[k]===g)return false;return true}else o.error("Syntax error, unrecognized expression: "+
+q)},CHILD:function(g,h){var k=h[1],l=g;switch(k){case "only":case "first":for(;l=l.previousSibling;)if(l.nodeType===1)return false;if(k==="first")return true;l=g;case "last":for(;l=l.nextSibling;)if(l.nodeType===1)return false;return true;case "nth":k=h[2];var q=h[3];if(k===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var u=0;for(l=p.firstChild;l;l=l.nextSibling)if(l.nodeType===1)l.nodeIndex=++u;p.sizcache=h}g=g.nodeIndex-q;return k===0?g===0:g%k===0&&g/k>=
+0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var k=h[1];g=m.attrHandle[k]?m.attrHandle[k](g):g[k]!=null?g[k]:g.getAttribute(k);k=g+"";var l=h[2];h=h[4];return g==null?l==="!=":l==="="?k===h:l==="*="?k.indexOf(h)>=0:l==="~="?(" "+k+" ").indexOf(h)>=0:!h?k&&g!==false:l==="!="?k!==h:l==="^="?
+k.indexOf(h)===0:l==="$="?k.substr(k.length-h.length)===h:l==="|="?k===h||k.substr(0,h.length+1)===h+"-":false},POS:function(g,h,k,l){var q=m.setFilters[h[2]];if(q)return q(g,k,h,l)}}},s=m.match.POS;for(var x in m.match){m.match[x]=new RegExp(m.match[x].source+/(?![^\[]*\])(?![^\(]*\))/.source);m.leftMatch[x]=new RegExp(/(^(?:.|\r|\n)*?)/.source+m.match[x].source.replace(/\\(\d+)/g,function(g,h){return"\\"+(h-0+1)}))}var A=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};
+try{Array.prototype.slice.call(r.documentElement.childNodes,0)}catch(B){A=function(g,h){h=h||[];if(i.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var k=0,l=g.length;k<l;k++)h.push(g[k]);else for(k=0;g[k];k++)h.push(g[k]);return h}}var C;if(r.documentElement.compareDocumentPosition)C=function(g,h){if(!g.compareDocumentPosition||!h.compareDocumentPosition){if(g==h)j=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===
+h?0:1;if(g===0)j=true;return g};else if("sourceIndex"in r.documentElement)C=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)j=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)j=true;return g};else if(r.createRange)C=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)j=true;return g.ownerDocument?-1:1}var k=g.ownerDocument.createRange(),l=h.ownerDocument.createRange();k.setStart(g,0);k.setEnd(g,0);l.setStart(h,0);l.setEnd(h,0);g=k.compareBoundaryPoints(Range.START_TO_END,
+l);if(g===0)j=true;return g};(function(){var g=r.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var k=r.documentElement;k.insertBefore(g,k.firstChild);if(r.getElementById(h)){m.find.ID=function(l,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(l[1]))?q.id===l[1]||typeof q.getAttributeNode!=="undefined"&&q.getAttributeNode("id").nodeValue===l[1]?[q]:v:[]};m.filter.ID=function(l,q){var p=typeof l.getAttributeNode!=="undefined"&&l.getAttributeNode("id");
+return l.nodeType===1&&p&&p.nodeValue===q}}k.removeChild(g);k=g=null})();(function(){var g=r.createElement("div");g.appendChild(r.createComment(""));if(g.getElementsByTagName("*").length>0)m.find.TAG=function(h,k){k=k.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var l=0;k[l];l++)k[l].nodeType===1&&h.push(k[l]);k=h}return k};g.innerHTML="<a href='#'></a>";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")m.attrHandle.href=function(h){return h.getAttribute("href",
+2)};g=null})();r.querySelectorAll&&function(){var g=o,h=r.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){o=function(l,q,p,u){q=q||r;if(!u&&q.nodeType===9&&!w(q))try{return A(q.querySelectorAll(l),p)}catch(t){}return g(l,q,p,u)};for(var k in g)o[k]=g[k];h=null}}();(function(){var g=r.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===
+0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){m.order.splice(1,0,"CLASS");m.find.CLASS=function(h,k,l){if(typeof k.getElementsByClassName!=="undefined"&&!l)return k.getElementsByClassName(h[1])};g=null}}})();var E=r.compareDocumentPosition?function(g,h){return g.compareDocumentPosition(h)&16}:function(g,h){return g!==h&&(g.contains?g.contains(h):true)},w=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},fa=function(g,h){var k=[],
+l="",q;for(h=h.nodeType?[h]:h;q=m.match.PSEUDO.exec(g);){l+=q[0];g=g.replace(m.match.PSEUDO,"")}g=m.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)o(g,h[q],k);return o.filter(l,k)};c.find=o;c.expr=o.selectors;c.expr[":"]=c.expr.filters;c.unique=o.uniqueSort;c.getText=a;c.isXMLDoc=w;c.contains=E})();var bb=/Until$/,cb=/^(?:parents|prevUntil|prevAll)/,db=/,/;Q=Array.prototype.slice;var Ea=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,i){return!!b.call(e,i,e)===d});else if(b.nodeType)return c.grep(a,
+function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Qa.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;c.find(a,this[f],b);if(f>0)for(var i=d;i<b.length;i++)for(var j=0;j<d;j++)if(b[j]===b[i]){b.splice(i--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=
+0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ea(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ea(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,i={},j;if(f&&a.length){e=0;for(var n=a.length;e<n;e++){j=a[e];i[j]||(i[j]=c.expr.match.POS.test(j)?c(j,b||this.context):j)}for(;f&&f.ownerDocument&&f!==b;){for(j in i){e=i[j];if(e.jquery?e.index(f)>
+-1:c(f).is(e)){d.push({selector:j,elem:f});delete i[j]}}f=f.parentNode}}return d}var o=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(m,s){for(;s&&s.ownerDocument&&s!==b;){if(o?o.index(s)>-1:c(s).is(a))return s;s=s.parentNode}return null})},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),
+a);return this.pushStack(pa(a[0])||pa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},
+nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);bb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):
+e;if((this.length>1||db.test(f))&&cb.test(a))e=e.reverse();return this.pushStack(e,a,Q.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===v||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==
+b&&d.push(a);return d}});var Fa=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ga=/(<([\w:]+)[^>]*?)\/>/g,eb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,Ha=/<([\w:]+)/,fb=/<tbody/i,gb=/<|&\w+;/,sa=/checked\s*(?:[^=]|=\s*.checked.)/i,Ia=function(a,b,d){return eb.test(d)?a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],
+col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==v)return this.empty().append((this[0]&&this[0].ownerDocument||r).createTextNode(a));return c.getText(this)},
+wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?
+d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,
+false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&
+!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Fa,"").replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){qa(this,b);qa(this.find("*"),b.find("*"))}return b},html:function(a){if(a===v)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Fa,""):null;else if(typeof a==="string"&&!/<script/i.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(Ha.exec(a)||
+["",""])[1].toLowerCase()]){a=a.replace(Ga,Ia);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var i=c(this),j=i.html();i.empty().append(function(){return a.call(this,e,j)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,
+b,f))});else a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(s){return c.nodeName(s,"table")?s.getElementsByTagName("tbody")[0]||s.appendChild(s.ownerDocument.createElement("tbody")):s}var e,i,j=a[0],n=[];if(!c.support.checkClone&&arguments.length===3&&typeof j===
+"string"&&sa.test(j))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(j))return this.each(function(s){var x=c(this);a[0]=j.call(this,s,b?x.html():v);x.domManip(a,b,d)});if(this[0]){e=a[0]&&a[0].parentNode&&a[0].parentNode.nodeType===11?{fragment:a[0].parentNode}:ra(a,this,n);if(i=e.fragment.firstChild){b=b&&c.nodeName(i,"tr");for(var o=0,m=this.length;o<m;o++)d.call(b?f(this[o],i):this[o],e.cacheable||this.length>1||o>0?e.fragment.cloneNode(true):e.fragment)}n&&c.each(n,
+Ma)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);for(var e=0,i=d.length;e<i;e++){var j=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),j);f=f.concat(j)}return this.pushStack(f,a,d.selector)}});c.each({remove:function(a,b){if(!a||c.filter(a,[this]).length){if(!b&&this.nodeType===1){c.cleanData(this.getElementsByTagName("*"));c.cleanData([this])}this.parentNode&&
+this.parentNode.removeChild(this)}},empty:function(){for(this.nodeType===1&&c.cleanData(this.getElementsByTagName("*"));this.firstChild;)this.removeChild(this.firstChild)}},function(a,b){c.fn[a]=function(){return this.each(b,arguments)}});c.extend({clean:function(a,b,d,f){b=b||r;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||r;var e=[];c.each(a,function(i,j){if(typeof j==="number")j+="";if(j){if(typeof j==="string"&&!gb.test(j))j=b.createTextNode(j);else if(typeof j===
+"string"){j=j.replace(Ga,Ia);var n=(Ha.exec(j)||["",""])[1].toLowerCase(),o=F[n]||F._default,m=o[0];i=b.createElement("div");for(i.innerHTML=o[1]+j+o[2];m--;)i=i.lastChild;if(!c.support.tbody){m=fb.test(j);n=n==="table"&&!m?i.firstChild&&i.firstChild.childNodes:o[1]==="<table>"&&!m?i.childNodes:[];for(o=n.length-1;o>=0;--o)c.nodeName(n[o],"tbody")&&!n[o].childNodes.length&&n[o].parentNode.removeChild(n[o])}!c.support.leadingWhitespace&&V.test(j)&&i.insertBefore(b.createTextNode(V.exec(j)[0]),i.firstChild);
+j=c.makeArray(i.childNodes)}if(j.nodeType)e.push(j);else e=c.merge(e,j)}});if(d)for(a=0;e[a];a++)if(f&&c.nodeName(e[a],"script")&&(!e[a].type||e[a].type.toLowerCase()==="text/javascript"))f.push(e[a].parentNode?e[a].parentNode.removeChild(e[a]):e[a]);else{e[a].nodeType===1&&e.splice.apply(e,[a+1,0].concat(c.makeArray(e[a].getElementsByTagName("script"))));d.appendChild(e[a])}return e},cleanData:function(a){for(var b=0,d;(d=a[b])!=null;b++){c.event.remove(d);c.removeData(d)}}});var hb=/z-?index|font-?weight|opacity|zoom|line-?height/i,
+Ja=/alpha\([^)]*\)/,Ka=/opacity=([^)]*)/,ga=/float/i,ha=/-([a-z])/ig,ib=/([A-Z])/g,jb=/^-?\d+(?:px)?$/i,kb=/^-?\d/,lb={position:"absolute",visibility:"hidden",display:"block"},mb=["Left","Right"],nb=["Top","Bottom"],ob=r.defaultView&&r.defaultView.getComputedStyle,La=c.support.cssFloat?"cssFloat":"styleFloat",ia=function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===v)return c.curCSS(d,f);if(typeof e==="number"&&!hb.test(f))e+="px";c.style(d,f,e)})};
+c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return v;if((b==="width"||b==="height")&&parseFloat(d)<0)d=v;var f=a.style||a,e=d!==v;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=Ja.test(a)?a.replace(Ja,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Ka.exec(f.filter)[1])/100+"":""}if(ga.test(b))b=La;b=b.replace(ha,ia);if(e)f[b]=d;return f[b]},css:function(a,
+b,d,f){if(b==="width"||b==="height"){var e,i=b==="width"?mb:nb;function j(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(i,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,"border"+this+"Width",true))||0})}a.offsetWidth!==0?j():c.swap(a,lb,j);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&
+a.currentStyle){f=Ka.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ga.test(b))b=La;if(!d&&e&&e[b])f=e[b];else if(ob){if(ga.test(b))b="float";b=b.replace(ib,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ha,ia);f=a.currentStyle[b]||a.currentStyle[d];if(!jb.test(f)&&kb.test(f)){b=e.left;var i=a.runtimeStyle.left;a.runtimeStyle.left=
+a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=i}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var pb=
+J(),qb=/<script(.|\s)*?\/script>/gi,rb=/select|textarea/i,sb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ja=/\?/,tb=/(\?|&)_=.*?(&|$)/,ub=/^(\w+:)?\/\/([^\/?#]+)/,vb=/%20/g;c.fn.extend({_load:c.fn.load,load:function(a,b,d){if(typeof a!=="string")return this._load(a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=
+c.param(b,c.ajaxSettings.traditional);f="POST"}var i=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(j,n){if(n==="success"||n==="notmodified")i.html(e?c("<div />").append(j.responseText.replace(qb,"")).find(e):j.responseText);d&&i.each(d,[j.responseText,n,j])}});return this},serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&
+(this.checked||rb.test(this.nodeName)||sb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,
+b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:z.XMLHttpRequest&&(z.location.protocol!=="file:"||!z.ActiveXObject)?function(){return new z.XMLHttpRequest}:
+function(){try{return new z.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&e.success.call(o,n,j,w);e.global&&f("ajaxSuccess",[w,e])}function d(){e.complete&&e.complete.call(o,w,j);e.global&&f("ajaxComplete",[w,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}
+function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),i,j,n,o=a&&a.context||e,m=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(m==="GET")N.test(e.url)||(e.url+=(ja.test(e.url)?"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||
+N.test(e.url))){i=e.jsonpCallback||"jsonp"+pb++;if(e.data)e.data=(e.data+"").replace(N,"="+i+"$1");e.url=e.url.replace(N,"="+i+"$1");e.dataType="script";z[i]=z[i]||function(q){n=q;b();d();z[i]=v;try{delete z[i]}catch(p){}A&&A.removeChild(B)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===false&&m==="GET"){var s=J(),x=e.url.replace(tb,"$1_="+s+"$2");e.url=x+(x===e.url?(ja.test(e.url)?"&":"?")+"_="+s:"")}if(e.data&&m==="GET")e.url+=(ja.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&
+c.event.trigger("ajaxStart");s=(s=ub.exec(e.url))&&(s[1]&&s[1]!==location.protocol||s[2]!==location.host);if(e.dataType==="script"&&m==="GET"&&s){var A=r.getElementsByTagName("head")[0]||r.documentElement,B=r.createElement("script");B.src=e.url;if(e.scriptCharset)B.charset=e.scriptCharset;if(!i){var C=false;B.onload=B.onreadystatechange=function(){if(!C&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){C=true;b();d();B.onload=B.onreadystatechange=null;A&&B.parentNode&&
+A.removeChild(B)}}}A.insertBefore(B,A.firstChild);return v}var E=false,w=e.xhr();if(w){e.username?w.open(m,e.url,e.async,e.username,e.password):w.open(m,e.url,e.async);try{if(e.data||a&&a.contentType)w.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&w.setRequestHeader("If-Modified-Since",c.lastModified[e.url]);c.etag[e.url]&&w.setRequestHeader("If-None-Match",c.etag[e.url])}s||w.setRequestHeader("X-Requested-With","XMLHttpRequest");w.setRequestHeader("Accept",
+e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(fa){}if(e.beforeSend&&e.beforeSend.call(o,w,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");w.abort();return false}e.global&&f("ajaxSend",[w,e]);var g=w.onreadystatechange=function(q){if(!w||w.readyState===0||q==="abort"){E||d();E=true;if(w)w.onreadystatechange=c.noop}else if(!E&&w&&(w.readyState===4||q==="timeout")){E=true;w.onreadystatechange=c.noop;j=q==="timeout"?"timeout":!c.httpSuccess(w)?
+"error":e.ifModified&&c.httpNotModified(w,e.url)?"notmodified":"success";var p;if(j==="success")try{n=c.httpData(w,e.dataType,e)}catch(u){j="parsererror";p=u}if(j==="success"||j==="notmodified")i||b();else c.handleError(e,w,j,p);d();q==="timeout"&&w.abort();if(e.async)w=null}};try{var h=w.abort;w.abort=function(){w&&h.call(w);g("abort")}}catch(k){}e.async&&e.timeout>0&&setTimeout(function(){w&&!E&&g("timeout")},e.timeout);try{w.send(m==="POST"||m==="PUT"||m==="DELETE"?e.data:null)}catch(l){c.handleError(e,
+w,null,l);d()}e.async||g();return w}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=
+f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b==="json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(j,n){if(c.isArray(n))c.each(n,
+function(o,m){b?f(j,m):d(j+"["+(typeof m==="object"||c.isArray(m)?o:"")+"]",m)});else!b&&n!=null&&typeof n==="object"?c.each(n,function(o,m){d(j+"["+o+"]",m)}):f(j,n)}function f(j,n){n=c.isFunction(n)?n():n;e[e.length]=encodeURIComponent(j)+"="+encodeURIComponent(n)}var e=[];if(b===v)b=c.ajaxSettings.traditional;if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var i in a)d(i,a[i]);return e.join("&").replace(vb,"+")}});var ka={},wb=/toggle|show|hide/,xb=/^([+-]=)?([\d+-.]+)(.*)$/,
+W,ta=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(ka[d])f=ka[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();
+ka[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&
+c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var i=c.extend({},e),j,n=this.nodeType===1&&c(this).is(":hidden"),
+o=this;for(j in a){var m=j.replace(ha,ia);if(j!==m){a[m]=a[j];delete a[j];j=m}if(a[j]==="hide"&&n||a[j]==="show"&&!n)return i.complete.call(this);if((j==="height"||j==="width")&&this.style){i.display=c.css(this,"display");i.overflow=this.style.overflow}if(c.isArray(a[j])){(i.specialEasing=i.specialEasing||{})[j]=a[j][1];a[j]=a[j][0]}}if(i.overflow!=null)this.style.overflow="hidden";i.curAnim=c.extend({},a);c.each(a,function(s,x){var A=new c.fx(o,i,s);if(wb.test(x))A[x==="toggle"?n?"show":"hide":x](a);
+else{var B=xb.exec(x),C=A.cur(true)||0;if(B){x=parseFloat(B[2]);var E=B[3]||"px";if(E!=="px"){o.style[s]=(x||1)+E;C=(x||1)/A.cur(true)*C;o.style[s]=C+E}if(B[1])x=(B[1]==="-="?-1:1)*x+C;A.custom(C,x,E)}else A.custom(C,x,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",
+1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration==="number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,
+b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==
+null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(i){return e.step(i)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop===
+"width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=
+this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=
+c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=
+null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in r.documentElement?function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),
+f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=this[0];if(a)return this.each(function(s){c.offset.setOffset(this,a,s)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=
+b,e=b.ownerDocument,i,j=e.documentElement,n=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var o=b.offsetTop,m=b.offsetLeft;(b=b.parentNode)&&b!==n&&b!==j;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;i=e?e.getComputedStyle(b,null):b.currentStyle;o-=b.scrollTop;m-=b.scrollLeft;if(b===d){o+=b.offsetTop;m+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){o+=parseFloat(i.borderTopWidth)||
+0;m+=parseFloat(i.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&i.overflow!=="visible"){o+=parseFloat(i.borderTopWidth)||0;m+=parseFloat(i.borderLeftWidth)||0}f=i}if(f.position==="relative"||f.position==="static"){o+=n.offsetTop;m+=n.offsetLeft}if(c.offset.supportsFixedPosition&&f.position==="fixed"){o+=Math.max(j.scrollTop,n.scrollTop);m+=Math.max(j.scrollLeft,n.scrollLeft)}return{top:o,left:m}};c.offset={initialize:function(){var a=r.body,b=r.createElement("div"),
+d,f,e,i=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";a.insertBefore(b,a.firstChild);
+d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i;a.removeChild(b);c.offset.initialize=c.noop},
+bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),i=parseInt(c.curCSS(a,"top",true),10)||0,j=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,d,e);d={top:b.top-e.top+i,left:b.left-
+e.left+j};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=
+this.offsetParent||r.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],i;if(!e)return null;if(f!==v)return this.each(function(){if(i=ua(this))i.scrollTo(!a?f:c(i).scrollLeft(),a?f:c(i).scrollTop());else this[d]=f});else return(i=ua(e))?"pageXOffset"in i?i[a?"pageYOffset":"pageXOffset"]:c.support.boxModel&&i.document.documentElement[d]||i.document.body[d]:e[d]}});
+c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(i){var j=c(this);j[d](f.call(this,i,j[d]()))});return"scrollTo"in e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||
+e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===v?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});z.jQuery=z.$=c})(window);
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/manifest.json b/chrome/common/extensions/docs/examples/extensions/gdocs/manifest.json
new file mode 100644
index 0000000..03d1758
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/manifest.json
@@ -0,0 +1,25 @@
+{
+  "name": "Google Document List Viewer",
+  "version": "1.0.2",
+  "icons": {
+    "48": "img/docs_spreadsheets-48.gif",
+    "128": "img/docs_spreadsheets-128.gif"
+  },
+  "description": "Demonstrates how to use OAuth to connect the Google Documents List Data API.",
+  "background": {
+    "page": "background.html"
+  },
+  "options_page": "options.html",
+  "browser_action": {
+    "default_title": "List your Google Docs",
+    "default_icon": "img/docs_spreadsheets-32.gif",
+    "default_popup": "popup.html"
+  },
+  "permissions": [
+    "tabs",
+    "https://docs.google.com/feeds/*",
+    "https://www.google.com/accounts/OAuthGetRequestToken",
+    "https://www.google.com/accounts/OAuthAuthorizeToken",
+    "https://www.google.com/accounts/OAuthGetAccessToken"
+  ]
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/options.html b/chrome/common/extensions/docs/examples/extensions/gdocs/options.html
new file mode 100644
index 0000000..fc5ab5b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/options.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2010 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.
+ *
+ * Author: Eric Bidelman <ericbidelman@chromium.org>
+-->
+<html>
+  <head>
+    <title>Options</title>
+    <script type="text/javascript" src="js/jquery-1.4.1.min.js"></script>
+  </head>
+  <body onload="initUI();">
+    <p><button id="revoke" onclick="logout();">Revoke your OAuth token</button></p>
+    <p>Refresh rate (seconds): <input id="refresh_rate" value="300"></p>
+    <script type="text/javascript">
+      var bgPage = chrome.extension.getBackgroundPage();
+
+      $('#refresh_rate').change(function() {
+        localStorage.refreshRate = $(this).val();
+        bgPage.refreshRate = localStorage.refreshRate;
+        bgPage.pollIntervalMin =  bgPage.refreshRate * 1000;
+      });
+
+      function logout() {
+        bgPage.logout();
+        $('#revoke').get(0).disabled = true;
+      }
+
+      function initUI() {
+        if (!bgPage.oauth.hasToken()) {
+          $('#revoke').get(0).disabled = true;
+        }
+
+        if (localStorage.refreshRate) {
+          $('#refresh_rate').val(localStorage.refreshRate);
+        } else {
+           $('#refresh_rate').val(bgPage.refreshRate);
+        }
+      }
+    </script>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/popup.html b/chrome/common/extensions/docs/examples/extensions/gdocs/popup.html
new file mode 100644
index 0000000..f2d9933
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/popup.html
@@ -0,0 +1,579 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2010 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.
+ *
+ * Author: Eric Bidelman <ericbidelman@chromium.org>
+-->
+<html>
+<head>
+<title>Your Google Documents List</title>
+<script type="text/javascript" src="js/jquery-1.4.1.min.js"></script>
+<style type="text/css">
+body {
+  font: 12px 'Myriad Pro', 'Tw Cen MT', Arial, Verdana, sans-serif;
+  color: #666666;
+  overflow-x: hidden;
+}
+ul {
+  padding: 0;
+  list-style: none;
+}
+li {
+  clear: both;
+  padding: 2px 0;
+}
+li div img {
+  margin: 0 5px;
+  vertical-align: middle;
+}
+li div {
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  overflow: hidden;
+  width: 250px;
+  float: left;
+  padding: 2px 0;
+}
+li span {
+  margin-left: 5px;
+}
+li:hover {
+  background-color: #fffccc;
+}
+a {
+ color: #4E7DC2;
+ text-decoration: none;
+}
+a:hover {
+  color: #880000;
+  text-decoration: underline;
+}
+#butter {
+  color: #fff;
+  background-color: #000033;
+  padding: 5px 20px;
+  border-radius: 15px;
+  width: auto;
+  text-align: center;
+  float: right;
+  display: none;
+}
+#butter.error {
+  background-color: red;
+}
+#new_doc_container {
+  display: none;
+}
+#new_doc_container input[type='text'],textarea {
+  width: 100%;
+}
+#output {
+  width: 375px;
+  clear: both;
+}
+[contenteditable]:hover {
+  outline: 1px dotted #666;
+}
+.star {
+  margin-top: 1px;
+  margin-right: 3px;
+  width: 16px;
+  height: 16px;
+  background: no-repeat url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAGYktHRAA3ADcAN3PbifMAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfZAwkAAh4LdI38AAAC00lEQVQoz42SS2hUZxzFz/d99zF35s7EScxEEzW1mkSlVkQJCNWSakDdxY2P0lKkSMi6G1uqbcVXF12WiiK4EMQixgeoGCMalWZRTYlG0kwNtebtzGQmmZk78937/7sQREWhB87m8DurcwTeo0Pn8xuFEN+A+djetkjnuxj5rvDbUxOLzCDTPkdOtlqU/nrvqYnG/13OZaY+rq3AZ9tb5qkP5qpPZtJTa99m2g92wXg96DjcLT2t6l2z8GVtZTwed23UVspYLFTcsfunWw8UFwaP799CAPDbd5sgdu+7XJPLl+LEosIJmYtjbqh5VWPN5zs3NyViEQszBY1z3UPPe/tHz87Olu4WvPKwEjzthu204TPtW7E0sbnCtSKGlOH51VFn9fIaGYtY0D5x1DHFhjULqlzH2DM6mftC+1TMzpYK/41nrxmaKJxIROt2tS6z41EbAMDMKJUDBlh4RFyfcMWH8xsNANFUzouevTGoh8enw2rZhp0DQ8/S1cxY2rQwbkopUNLExCwCAoghtE9MxMIrB+jsSXrXep9c1MQH1EDPmXTD+h39j5+m52nCwhX1VQ4xCx0wiF8aEIIZONeTzJ6/k7xS8unHS0faBiQAXD7clswHfOh638jIk7EclBQgYjADRIClJEZTedx6ODaeKuifrxzd9uiNnYvSnpa2FTKVhB8whABMQ0IIQAcMQwpIywp5ys69cZJP93eBlJEIlBmujNpQUiAICE8nZlDWAQwlMMe1QYbpBMqobvmhSwB4eZIcpAw7aklt3InGXQt9w2k+/cezzMBUIdNQ5VTsaq6ram6YK+oqHXcoVVwyVaZeAL4BAFoaUlpqdcRS9i9Xk8ULj1NDQUAnMvnyxWyJW+9f+qd9a1NmuQIsO2St0r7/+6uyNEzKBfjr6r/ebSG8P0tanHz4/fq/W37tx82OlSc+OnCvu3Mw+xUz1hHLPtuyfAB4AUJFSguX3LKbAAAAAElFTkSuQmCC) !important;
+}
+.star.selected {
+  background: no-repeat url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAGYktHRAA3ADcAN3PbifMAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfZAwkAAB45Qu9+AAAC1ElEQVQoz42SWUhUYRzFz/+7986dVTPT0SzNFnHBJZHCKGmTAisJgooIIrJ87CGIHopKWp/qoYdAJIIg6qEUy/aiSMoWmpbJyjJCs3SacRZnrvfe7/t6KioKOo+H3+HA4RD+oYMXxpYQ0Q5IeXLXas/FvzHsb+b2tvH8bPaiudrXUZ+rPN2yvc0s+u+wiPdVZHi1xfVzJynZE9h8Huur+ZNpPnAD9KvRdKCbxU13QVXewJFV8+SakrICet8bkO33HZ09AxU7VRF7c2b/XPGDp817Ov2huJahwEr3umRh7gRjTlWRb8O6ZWnZ5ABk8j3a75ih7qDn3HAi434s5eznkkYz3fGwakuxp640vFx1ZXocCneXT7FcFUWSkcYgxyOSVKKlVR8y/W5j6/PB/I0py5ESppV8OZR/VbWEcM+cnMhbscivK66JALIAbkCaYUkiSpKPSG+aoNqaqFpb0+0TCe678niKFRjKcyvFdeuDPf1pWSolZ5ZOTWokDEhrVJIcJfBhEB8haX+TJMZJWsDFx8XGqQcLOkzBWpTgvbPhWXVrXwQ+IkcR8anlBSEXiSiBjwAiBPAICAYB47jwqCh6+kF1V8pW9nUeXhlkAHDpUGNfwtYOXg5og4lwH4BBgA8BPAxIAwDHWFzieu/0L1+T3qNXjzS8AgD1x+wp5hpVHAmnShGAK4C0AMYBLgDBoZENRYPTYI7YbydZuPcahMqyDdLdTpcFkA0IA1YsCdgGQDYcugmTaS6uKFlL9nXRz+aQ8LIsT3RGhf+TD7pAZMiUrY+qI7cHSiPzct6mN1V1Z/pzTCqb9MnbH0uf8XEs9yEAWwUAi2mMaTR7muezfu5eYer4s8XvmLRbB+ITO2K2Xt81WNm8teRmSa5z2OHUeaWVUs//DOsaF9/s9MCx3oa7BDxJWHpbcPect40nL6F9W0NraUvPrROvl20CUGsJ5VmaM2UDwHchqUbp2jdIoAAAAABJRU5ErkJggg==) !important;
+}
+</style>
+</head>
+<body>
+
+<div style="height:15px;">
+  <div style="float:left;">
+    <a href="javascript:void(0);" onclick="gdocs.refreshDocs();return false;">Refresh list</a>,
+    <a href="javascript:void(0);" onclick="$('#new_doc_container').toggle();return false;">New Document</a>
+  </div>
+  <div id="butter">Fetching your docs</div>
+</div>
+<div id="new_doc_container">
+  Create a: <select id="doc_type">
+    <option value="document">document</option>
+    <option value="presentation">presentation</option>
+    <option value="spreadsheet">spreadsheet</option>
+  </select>
+  <input type="text" id="doc_title" placeholder="Enter a title"><br>
+  <textarea id="doc_content" placeholder="Enter document content"></textarea>
+  Star it? <input type="checkbox" id="doc_starred">
+  <button onclick="gdocs.createDoc();" style="float:right;">Create new doc</button>
+</div>
+<div id="output"></div>
+
+<script type="text/javascript">
+// Protected namespaces.
+var util = {};
+var gdocs = {};
+
+var bgPage = chrome.extension.getBackgroundPage();
+var pollIntervalMax = 1000 * 60 * 60;  // 1 hour
+var requestFailureCount = 0;  // used for exponential backoff
+var requestTimeout = 1000 * 2;  // 5 seconds
+
+var DEFAULT_MIMETYPES = {
+  'atom': 'application/atom+xml',
+  'document': 'text/plain',
+  'spreadsheet': 'text/csv',
+  'presentation': 'text/plain',
+  'pdf': 'application/pdf'
+};
+
+// Persistent click handler for star icons.
+$('#doc_type').change(function() {
+  if ($(this).val() === 'presentation') {
+    $('#doc_content').attr('disabled', 'true')
+                     .attr('placeholder', 'N/A for presentations');
+  } else {
+    $('#doc_content').removeAttr('disabled')
+                     .attr('placeholder', 'Enter document content');
+  }
+});
+
+
+// Persistent click handler for changing the title of a document.
+$('[contenteditable="true"]').live('blur', function(index) {
+  var index = $(this).parent().parent().attr('data-index');
+
+  // Only make the XHR if the user chose a new title.
+  if ($(this).text() != bgPage.docs[index].title) {
+    bgPage.docs[index].title = $(this).text();
+    gdocs.updateDoc(bgPage.docs[index]);
+  }
+});
+
+// Persistent click handler for star icons.
+$('.star').live('click', function() {
+  $(this).toggleClass('selected');
+
+  var index = $(this).parent().attr('data-index');
+  bgPage.docs[index].starred = $(this).hasClass('selected');
+  gdocs.updateDoc(bgPage.docs[index]);
+});
+
+/**
+ * Class to compartmentalize properties of a Google document.
+ * @param {Object} entry A JSON representation of a DocList atom entry.
+ * @constructor
+ */
+gdocs.GoogleDoc = function(entry) {
+  this.entry = entry;
+  this.title = entry.title.$t;
+  this.resourceId = entry.gd$resourceId.$t;
+  this.type = gdocs.getCategory(
+    entry.category, 'http://schemas.google.com/g/2005#kind');
+  this.starred = gdocs.getCategory(
+    entry.category, 'http://schemas.google.com/g/2005/labels',
+    'http://schemas.google.com/g/2005/labels#starred') ? true : false;
+  this.link = {
+    'alternate': gdocs.getLink(entry.link, 'alternate').href
+  };
+  this.contentSrc = entry.content.src;
+};
+
+/**
+ * Sets up a future poll for the user's document list.
+ */
+util.scheduleRequest = function() {
+  var exponent = Math.pow(2, requestFailureCount);
+  var delay = Math.min(bgPage.pollIntervalMin * exponent,
+                       pollIntervalMax);
+  delay = Math.round(delay);
+
+  if (bgPage.oauth.hasToken()) {
+    var req = bgPage.window.setTimeout(function() {
+      gdocs.getDocumentList();
+      util.scheduleRequest();
+    }, delay);
+    bgPage.requests.push(req);
+  }
+};
+
+/**
+ * Urlencodes a JSON object of key/value query parameters.
+ * @param {Object} parameters Key value pairs representing URL parameters.
+ * @return {string} query parameters concatenated together.
+ */
+util.stringify = function(parameters) {
+  var params = [];
+  for(var p in parameters) {
+    params.push(encodeURIComponent(p) + '=' +
+                encodeURIComponent(parameters[p]));
+  }
+  return params.join('&');
+};
+
+/**
+ * Creates a JSON object of key/value pairs
+ * @param {string} paramStr A string of Url query parmeters.
+ *    For example: max-results=5&startindex=2&showfolders=true
+ * @return {Object} The query parameters as key/value pairs.
+ */
+util.unstringify = function(paramStr) {
+  var parts = paramStr.split('&');
+
+  var params = {};
+  for (var i = 0, pair; pair = parts[i]; ++i) {
+    var param = pair.split('=');
+    params[decodeURIComponent(param[0])] = decodeURIComponent(param[1]);
+  }
+  return params;
+};
+
+/**
+ * Utility for displaying a message to the user.
+ * @param {string} msg The message.
+ */
+util.displayMsg = function(msg) {
+  $('#butter').removeClass('error').text(msg).show();
+};
+
+/**
+ * Utility for removing any messages currently showing to the user.
+ */
+util.hideMsg = function() {
+  $('#butter').fadeOut(1500);
+};
+
+/**
+ * Utility for displaying an error to the user.
+ * @param {string} msg The message.
+ */
+util.displayError = function(msg) {
+  util.displayMsg(msg);
+  $('#butter').addClass('error');
+};
+
+/**
+ * Returns the correct atom link corresponding to the 'rel' value passed in.
+ * @param {Array<Object>} links A list of atom link objects.
+ * @param {string} rel The rel value of the link to return. For example: 'next'.
+ * @return {string|null} The appropriate link for the 'rel' passed in, or null
+ *     if one is not found.
+ */
+gdocs.getLink = function(links, rel) {
+  for (var i = 0, link; link = links[i]; ++i) {
+    if (link.rel === rel) {
+      return link;
+    }
+  }
+  return null;
+};
+
+/**
+ * Returns the correct atom category corresponding to the scheme/term passed in.
+ * @param {Array<Object>} categories A list of atom category objects.
+ * @param {string} scheme The category's scheme to look up.
+ * @param {opt_term?} An optional term value for the category to look up.
+ * @return {string|null} The appropriate category, or null if one is not found.
+ */
+gdocs.getCategory = function(categories, scheme, opt_term) {
+  for (var i = 0, cat; cat = categories[i]; ++i) {
+    if (opt_term) {
+      if (cat.scheme === scheme && opt_term === cat.term) {
+        return cat;
+      }
+    } else if (cat.scheme === scheme) {
+      return cat;
+    }
+  }
+  return null;
+};
+
+/**
+ * A generic error handler for failed XHR requests.
+ * @param {XMLHttpRequest} xhr The xhr request that failed.
+ * @param {string} textStatus The server's returned status.
+ */
+gdocs.handleError = function(xhr, textStatus) {
+  util.displayError('Failed to fetch docs. Please try again.');
+  ++requestFailureCount;
+};
+
+/**
+ * A helper for constructing the raw Atom xml send in the body of an HTTP post.
+ * @param {XMLHttpRequest} xhr The xhr request that failed.
+ * @param {string} docTitle A title for the document.
+ * @param {string} docType The type of document to create.
+ *     (eg. 'document', 'spreadsheet', etc.)
+ * @param {boolean?} opt_starred Whether the document should be starred.
+ * @return {string} The Atom xml as a string.
+ */
+gdocs.constructAtomXml_ = function(docTitle, docType, opt_starred) {
+  var starred = opt_starred || null;
+
+  var starCat = ['<category scheme="http://schemas.google.com/g/2005/labels" ',
+                 'term="http://schemas.google.com/g/2005/labels#starred" ',
+                 'label="starred"/>'].join('');
+
+  var atom = ["<?xml version='1.0' encoding='UTF-8'?>", 
+              '<entry xmlns="http://www.w3.org/2005/Atom">',
+              '<category scheme="http://schemas.google.com/g/2005#kind"', 
+              ' term="http://schemas.google.com/docs/2007#', docType, '"/>',
+              starred ? starCat : '',
+              '<title>', docTitle, '</title>',
+              '</entry>'].join('');
+  return atom;
+};
+
+/**
+ * A helper for constructing the body of a mime-mutlipart HTTP request.
+ * @param {string} title A title for the new document.
+ * @param {string} docType The type of document to create.
+ *     (eg. 'document', 'spreadsheet', etc.)
+ * @param {string} body The body of the HTTP request.
+ * @param {string} contentType The Content-Type of the (non-Atom) portion of the
+ *     http body.
+ * @param {boolean?} opt_starred Whether the document should be starred.
+ * @return {string} The Atom xml as a string.
+ */
+gdocs.constructContentBody_ = function(title, docType, body, contentType,
+                                       opt_starred) {
+  var body = ['--END_OF_PART\r\n',
+              'Content-Type: application/atom+xml;\r\n\r\n',
+              gdocs.constructAtomXml_(title, docType, opt_starred), '\r\n',
+              '--END_OF_PART\r\n',
+              'Content-Type: ', contentType, '\r\n\r\n',
+              body, '\r\n',
+              '--END_OF_PART--\r\n'].join('');
+  return body;
+};
+
+/**
+ * Creates a new document in Google Docs.
+ */
+gdocs.createDoc = function() {
+  var title = $.trim($('#doc_title').val());
+  if (!title) {
+    alert('Please provide a title');
+    return;
+  }
+  var content = $('#doc_content').val();
+  var starred = $('#doc_starred').is(':checked');
+  var docType = $('#doc_type').val();
+
+  util.displayMsg('Creating doc...');
+
+  var handleSuccess = function(resp, xhr) {
+    bgPage.docs.splice(0, 0, new gdocs.GoogleDoc(JSON.parse(resp).entry));
+
+    gdocs.renderDocList();
+    bgPage.setIcon({'text': bgPage.docs.length.toString()});
+
+    $('#new_doc_container').hide();
+    $('#doc_title').val('');
+    $('#doc_content').val('');
+    util.displayMsg('Document created!');
+    util.hideMsg();
+
+    requestFailureCount = 0;
+  };
+
+  var params = {
+    'method': 'POST',
+    'headers': {
+      'GData-Version': '3.0',
+      'Content-Type': 'multipart/related; boundary=END_OF_PART',
+    },
+    'parameters': {'alt': 'json'},
+    'body': gdocs.constructContentBody_(title, docType, content,
+                                        DEFAULT_MIMETYPES[docType], starred)
+  };
+
+  // Presentation can only be created from binary content. Instead, create a
+  // blank presentation.
+  if (docType === 'presentation') {
+    params['headers']['Content-Type'] = DEFAULT_MIMETYPES['atom'];
+    params['body'] = gdocs.constructAtomXml_(title, docType, starred);
+  }
+
+  bgPage.oauth.sendSignedRequest(bgPage.DOCLIST_FEED, handleSuccess, params);
+};
+
+/**
+ * Updates a document's metadata (title, starred, etc.).
+ * @param {gdocs.GoogleDoc} googleDocObj An object containing the document to
+ *     update.
+ */
+gdocs.updateDoc = function(googleDocObj) {
+  var handleSuccess = function(resp) {
+    util.displayMsg('Updated!');
+    util.hideMsg();
+    requestFailureCount = 0;
+  };
+
+  var params = {
+    'method': 'PUT',
+    'headers': {
+      'GData-Version': '3.0',
+      'Content-Type': 'application/atom+xml',
+      'If-Match': '*'
+    },
+    'body': gdocs.constructAtomXml_(googleDocObj.title, googleDocObj.type,
+                                    googleDocObj.starred)
+  };
+
+  var url = bgPage.DOCLIST_FEED + googleDocObj.resourceId;
+  bgPage.oauth.sendSignedRequest(url, handleSuccess, params);
+};
+
+/**
+ * Deletes a document from the user's document list.
+ * @param {integer} index An index intro the background page's docs array.
+ */
+gdocs.deleteDoc = function(index) {
+  var handleSuccess = function(resp, xhr) {
+    util.displayMsg('Document trashed!');
+    util.hideMsg();
+    requestFailureCount = 0;
+    bgPage.docs.splice(index, 1);
+    bgPage.setIcon({'text': bgPage.docs.length.toString()});
+  }
+
+  var params = {
+    'method': 'DELETE',
+    'headers': {
+      'GData-Version': '3.0',
+      'If-Match': '*'
+    }
+  };
+
+  $('#output li').eq(index).fadeOut('slow');
+
+  bgPage.oauth.sendSignedRequest(
+      bgPage.DOCLIST_FEED + bgPage.docs[index].resourceId,
+      handleSuccess, params);
+};
+
+/**
+ * Callback for processing the JSON feed returned by the DocList API.
+ * @param {string} response The server's response.
+ * @param {XMLHttpRequest} xhr The xhr request that was made.
+ */
+gdocs.processDocListResults = function(response, xhr) {
+  if (xhr.status != 200) {
+    gdocs.handleError(xhr, response);
+    return;
+  } else {
+    requestFailureCount = 0;
+  }
+
+  var data = JSON.parse(response);
+
+  for (var i = 0, entry; entry = data.feed.entry[i]; ++i) {
+    bgPage.docs.push(new gdocs.GoogleDoc(entry));
+  }
+
+  var nextLink = gdocs.getLink(data.feed.link, 'next');
+  if (nextLink) {
+    gdocs.getDocumentList(nextLink.href); // Fetch next page of results.
+  } else {
+    gdocs.renderDocList();
+  }
+};
+
+/**
+ * Presents the in-memory documents that were fetched from the server as HTML.
+ */
+gdocs.renderDocList = function() {
+  util.hideMsg();
+
+  // Construct the iframe's HTML.
+  var html = [];
+  for (var i = 0, doc; doc = bgPage.docs[i]; ++i) {
+    // If we have an arbitrary file, use generic file icon.
+    var type = doc.type.label;
+    if (doc.type.term == 'http://schemas.google.com/docs/2007#file') {
+      type = 'file';
+    }
+
+    var starred = doc.starred ? ' selected' : '';
+    html.push(
+      '<li data-index="', i , '"><div class="star', starred, '"></div>',
+      '<div><img src="img/icons/', type, '.gif">',
+      '<span contenteditable="true" class="doc_title"></span></div>',
+      '<span>[<a href="', doc.link['alternate'],
+      '" target="_new">view</a> | <a href="javascript:void(0);" ',
+      'onclick="gdocs.deleteDoc(',i,
+      ');return false;">delete</a>]','</span></li>');
+  }
+  $('#output').html('<ul>' + html.join('') + '</ul>');
+
+  // Set each span's innerText to be the doc title. We're filling this after
+  // the html has been rendered to the page prevent XSS attacks when using
+  // innerHTML.
+  $('#output li span.doc_title').each(function(i, ul) {
+    $(ul).text(bgPage.docs[i].title);
+  });
+
+  bgPage.setIcon({'text': bgPage.docs.length.toString()});
+};
+
+/**
+ * Fetches the user's document list.
+ * @param {string?} opt_url A url to query the doclist API with. If omitted,
+ *     the main doclist feed uri is used.
+ */
+gdocs.getDocumentList = function(opt_url) {
+  var url = opt_url || null;
+
+  var params = {
+    'headers': {
+      'GData-Version': '3.0'
+    }
+  };
+
+  if (!url) {
+    util.displayMsg('Fetching your docs');
+    bgPage.setIcon({'text': '...'});
+
+    bgPage.docs = []; // Clear document list. We're doing a refresh.
+
+    url = bgPage.DOCLIST_FEED;
+    params['parameters'] = {
+      'alt': 'json',
+      'showfolders': 'true'
+    };
+  } else {
+    util.displayMsg($('#butter').text() + '.');
+
+    var parts = url.split('?');
+    if (parts.length > 1) {
+      url = parts[0]; // Extract base URI. Params are passed in separately.
+      params['parameters'] = util.unstringify(parts[1]);
+    }
+  }
+
+  bgPage.oauth.sendSignedRequest(url, gdocs.processDocListResults, params);
+};
+
+/**
+ * Refreshes the user's document list.
+ */
+gdocs.refreshDocs = function() {
+  bgPage.clearPendingRequests();
+  gdocs.getDocumentList();
+  util.scheduleRequest();
+};
+
+
+bgPage.oauth.authorize(function() {
+  if (!bgPage.docs.length) {
+    gdocs.getDocumentList();
+  } else {
+    gdocs.renderDocList();
+  }
+  util.scheduleRequest();
+});
+</script>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ar/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ar/messages.json
new file mode 100644
index 0000000..87faf64
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ar/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"\u0644\u0639\u0631\u0636 \u0639\u062f\u062f \u0627\u0644\u0631\u0633\u0627\u0626\u0644 \u063a\u064a\u0631 \u0627\u0644\u0645\u0642\u0631\u0648\u0621\u0629 \u0641\u064a \u0627\u0644\u0628\u0631\u064a\u062f \u0627\u0644\u0648\u0627\u0631\u062f \u0641\u064a Google Mail. \u0643\u0645\u0627 \u064a\u0645\u0643\u0646\u0643 \u0627\u0644\u0646\u0642\u0631 \u0639\u0644\u0649 \u0627\u0644\u0632\u0631 \u0644\u0641\u062a\u062d \u0628\u0631\u064a\u062f\u0643 \u0627\u0644\u0648\u0627\u0631\u062f."},"gmailcheck_node_error":{"message":"\u062e\u0637\u0623: \u062a\u0645 \u0627\u0633\u062a\u0631\u062f\u0627\u062f \u0627\u0644\u062e\u0644\u0627\u0635\u0629\u060c \u0648\u0644\u0643\u0646 \u0644\u0645 \u064a\u062a\u0645 \u0627\u0644\u0639\u062b\u0648\u0631 \u0639\u0644\u0649 &lt;fullcount&gt; \u0645\u0646 \u0627\u0644\u0639\u064f\u0642\u062f"},"gmailcheck_exception":{"message":"\u0627\u0633\u062a\u062b\u0646\u0627\u0621: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/bg/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/bg/messages.json
new file mode 100644
index 0000000..0903503
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/bg/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 \u043f\u043e\u0449\u0430\u0442\u0430"},"gmailcheck_description":{"message":"\u041f\u043e\u043a\u0430\u0437\u0432\u0430 \u0431\u0440\u043e\u044f \u043d\u0435\u043f\u0440\u043e\u0447\u0435\u0442\u0435\u043d\u0438 \u0441\u044a\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432\u044a\u0432 \u0432\u0445\u043e\u0434\u044f\u0449\u0430\u0442\u0430 \u0432\u0438 \u043f\u043e\u0449\u0430 \u0432 Google Mail. \u041c\u043e\u0436\u0435\u0442\u0435 \u0441\u044a\u0449\u043e \u0434\u0430 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u0432\u044a\u0440\u0445\u0443 \u0431\u0443\u0442\u043e\u043d\u0430, \u0437\u0430 \u0434\u0430 \u044f \u043e\u0442\u0432\u043e\u0440\u0438\u0442\u0435."},"gmailcheck_node_error":{"message":"\u0413\u0440\u0435\u0448\u043a\u0430: \u0415\u043c\u0438\u0441\u0438\u044f\u0442\u0430 \u0431\u0435 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0430, \u043d\u043e \u043d\u044f\u043c\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d \u0432\u044a\u0437\u0435\u043b &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"\u0438\u0437\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ca/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ca/messages.json
new file mode 100644
index 0000000..6c2b1b2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ca/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Verificador de Google Mail"},"gmailcheck_description":{"message":"Mostra el nombre de missatges no llegits que hi ha a la vostra safata d'entrada de Google Mail. Tamb\u00e9 podeu fer clic al bot\u00f3 per obrir la safata d'entrada."},"gmailcheck_node_error":{"message":"Error: s'ha recuperat el feed, per\u00f2 no s'ha trobat cap node &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"excepci\u00f3: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/cs/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/cs/messages.json
new file mode 100644
index 0000000..394e6ad
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/cs/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Kontrola e-mailu Google"},"gmailcheck_description":{"message":"Zobraz\u00ed po\u010det nep\u0159e\u010dten\u00fdch zpr\u00e1v ve slo\u017ece Doru\u010den\u00e1 po\u0161ta slu\u017eby Google Mail. Kliknut\u00edm na tla\u010d\u00edtko tuto slo\u017eku otev\u0159ete."},"gmailcheck_node_error":{"message":"Chyba: Zdroj byl na\u010dten, nebyl v\u0161ak nalezen \u017e\u00e1dn\u00fd uzel &lt;fullcount&gt;."},"gmailcheck_exception":{"message":"v\u00fdjimka: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/da/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/da/messages.json
new file mode 100644
index 0000000..e1c2f82
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/da/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google E-mail-t\u00e6ller"},"gmailcheck_description":{"message":"Viser antallet af ul\u00e6ste meddelelser i din Google Mail-indbakke. Du kan ogs\u00e5 klikke p\u00e5 knappen for at \u00e5bne din indbakke."},"gmailcheck_node_error":{"message":"Fejl: Feedet blev hentet, men der blev ikke fundet nogen &lt;fullcount&gt;-node"},"gmailcheck_exception":{"message":"undtagelse: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/de/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/de/messages.json
new file mode 100644
index 0000000..6c21886
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/de/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail-Checker"},"gmailcheck_description":{"message":"Zeigt die Anzahl ungelesener Nachrichten in Ihrem Google Mail-Posteingang an. Sie k\u00f6nnen auch auf diese Schaltfl\u00e4che klicken, um Ihren Posteingang zu \u00f6ffnen."},"gmailcheck_node_error":{"message":"Fehler: Feed abgerufen, aber kein &lt;fullcount&gt; Knoten gefunden"},"gmailcheck_exception":{"message":"Ausnahme: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/el/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/el/messages.json
new file mode 100644
index 0000000..7db2714
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/el/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03bf\u03c5 Google Mail"},"gmailcheck_description":{"message":"\u0395\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03b9 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03c4\u03c9\u03bd \u03bc\u03b7 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c3\u03bc\u03ad\u03bd\u03c9\u03bd \u03bc\u03b7\u03bd\u03c5\u03bc\u03ac\u03c4\u03c9\u03bd \u03c3\u03c4\u03b1 \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03b1 \u03c4\u03bf\u03c5 Google Mail \u03c3\u03b1\u03c2. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03b5\u03c0\u03af\u03c3\u03b7\u03c2 \u03bd\u03b1 \u03ba\u03ac\u03bd\u03b5\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03bd\u03bf\u03af\u03be\u03b5\u03c4\u03b5 \u03c4\u03b1 \u03b5\u03b9\u03c3\u03b5\u03c1\u03c7\u03cc\u03bc\u03b5\u03bd\u03ac \u03c3\u03b1\u03c2."},"gmailcheck_node_error":{"message":"\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1: \u03ad\u03b3\u03b9\u03bd\u03b5 \u03b1\u03bd\u03ac\u03ba\u03c4\u03b7\u03c3\u03b7 \u03c1\u03bf\u03ae\u03c2, \u03b1\u03bb\u03bb\u03ac \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c2 &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"\u03b5\u03be\u03b1\u03af\u03c1\u03b5\u03c3\u03b7: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/en/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/en/messages.json
new file mode 100644
index 0000000..063b9ca
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/en/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Displays the number of unread messages in your Google Mail inbox. You can also click the button to open your inbox."},"gmailcheck_node_error":{"message":"Error: feed retrieved, but no &lt;fullcount&gt; node found"},"gmailcheck_exception":{"message":"exception: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/en_GB/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/en_GB/messages.json
new file mode 100644
index 0000000..d3fa3a3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/en_GB/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Displays the number of unread messages in your Google Mail inbox. You can also click the button to open your inbox."},"gmailcheck_node_error":{"message":"Error: Feed retrieved, but no &lt;fullcount&gt; node found"},"gmailcheck_exception":{"message":"exception: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/es/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/es/messages.json
new file mode 100644
index 0000000..4d8364f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/es/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Permite ver el n\u00famero de mensajes sin leer en la bandeja de entrada de Google Mail. Tambi\u00e9n puedes hacer clic en el bot\u00f3n para abrir la bandeja de entrada."},"gmailcheck_node_error":{"message":"Se ha producido un error: el feed se ha recuperado, pero no se ha encontrado ning\u00fan nodo &lt;fullcount&gt;."},"gmailcheck_exception":{"message":"excepci\u00f3n: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/es_419/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/es_419/messages.json
new file mode 100644
index 0000000..54bf270
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/es_419/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Muestra el n\u00famero de mensajes sin leer en tu bandeja de entrada de Google Mail. Tambi\u00e9n puedes hacer clic en el bot\u00f3n para abrir tu bandeja de entrada."},"gmailcheck_node_error":{"message":"Error: se recuper\u00f3 el feed, pero no se encontr\u00f3 el nodo &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"excepci\u00f3n: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/et/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/et/messages.json
new file mode 100644
index 0000000..20b1e84
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/et/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google'i meilikontrollija"},"gmailcheck_description":{"message":"Kuvab Google Maili postkastis olevate lugemata s\u00f5numite arvu. V\u00f5ite kl\u00f5psata ka nupul ja avada postkasti."},"gmailcheck_node_error":{"message":"Viga: voog vastuv\u00f5etud, kuid \u00fchtki &lt;fullcount&gt; s\u00f5lme ei leitud"},"gmailcheck_exception":{"message":"erand: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fi/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fi/messages.json
new file mode 100644
index 0000000..f9da2c1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fi/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google-s\u00e4hk\u00f6postin tarkistus"},"gmailcheck_description":{"message":"N\u00e4ytt\u00e4\u00e4, kuinka monta lukematonta viesti\u00e4 Google Mail -postilaatikossasi on. Voit my\u00f6s avata postilaatikkosi napsauttamalla painiketta."},"gmailcheck_node_error":{"message":"Virhe: sy\u00f6te haettiin, mutta &lt;fullcount&gt;-solmua ei l\u00f6ytynyt"},"gmailcheck_exception":{"message":"poikkeus: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fil/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fil/messages.json
new file mode 100644
index 0000000..1a019a6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fil/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Ipinapakita ang bilang ng mga hindi pa nababasang mensahe sa inbox ng iyong Google Mail. Maaari mo ring i-click ang pindutan upang buksan ang iyong inbox."},"gmailcheck_node_error":{"message":"Error: nakuha ang feed, ngunit walang &lt;fullcount&gt; node na natagpuan"},"gmailcheck_exception":{"message":"pagbubukod: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fr/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fr/messages.json
new file mode 100644
index 0000000..2900819
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/fr/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"V\u00e9rificateur de messages Google"},"gmailcheck_description":{"message":"Affiche le nombre de messages non lus dans votre bo\u00eete de r\u00e9ception Google\u00a0Mail. Vous avez \u00e9galement la possibilit\u00e9 de cliquer sur ce bouton pour ouvrir cette derni\u00e8re."},"gmailcheck_node_error":{"message":"Erreur\u00a0: flux r\u00e9cup\u00e9r\u00e9, mais aucun n\u0153ud &lt;fullcount&gt; trouv\u00e9"},"gmailcheck_exception":{"message":"exception\u00a0: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/he/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/he/messages.json
new file mode 100644
index 0000000..c32e982
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/he/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"\u05de\u05e6\u05d9\u05d2 \u05d0\u05ea \u05de\u05e1\u05e4\u05e8 \u05d4\u05d4\u05d5\u05d3\u05e2\u05d5\u05ea \u05e9\u05dc\u05d0 \u05e0\u05e7\u05e8\u05d0\u05d5 \u05d1\u05ea\u05d9\u05d1\u05ea \u05d4\u05d3\u05d5\u05d0\u05e8 \u05d4\u05e0\u05db\u05e0\u05e1 \u05e9\u05dc\u05da \u05d1-Google Mail. \u05d1\u05e0\u05d5\u05e1\u05e3, \u05ea\u05d5\u05db\u05dc \u05dc\u05dc\u05d7\u05d5\u05e5 \u05e2\u05dc \u05d4\u05dc\u05d7\u05e6\u05df \u05db\u05d3\u05d9 \u05dc\u05e4\u05ea\u05d5\u05d7 \u05d0\u05ea \u05ea\u05d9\u05d1\u05ea \u05d4\u05d3\u05d5\u05d0\u05e8 \u05d4\u05e0\u05db\u05e0\u05e1."},"gmailcheck_node_error":{"message":"\u05e9\u05d2\u05d9\u05d0\u05d4: \u05d4\u05e2\u05d3\u05db\u05d5\u05df \u05d0\u05d5\u05d7\u05d6\u05e8, \u05d0\u05da \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05e6\u05de\u05ea\u05d9\u05dd \u05e9\u05dc &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"\u05d7\u05e8\u05d9\u05d2: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hi/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hi/messages.json
new file mode 100644
index 0000000..0056c56
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hi/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google \u092e\u0947\u0932 \u091c\u093e\u0902\u091a\u0915\u0930\u094d\u0924\u093e"},"gmailcheck_description":{"message":"\u0906\u092a\u0915\u0947 Google \u092e\u0947\u0932 \u0907\u0928\u092c\u0949\u0915\u094d\u0938 \u092e\u0947\u0902 \u0928 \u092a\u095d\u0947 \u0917\u090f \u0938\u0902\u0926\u0947\u0936\u094b\u0902 \u0915\u0940 \u0938\u0902\u0916\u094d\u092f\u093e \u092a\u094d\u0930\u0926\u0930\u094d\u0936\u093f\u0924 \u0915\u0930\u0924\u093e \u0939\u0948. \u0906\u092a \u0905\u092a\u0928\u093e \u0907\u0928\u092c\u0949\u0915\u094d\u0938 \u0916\u094b\u0932\u0928\u0947 \u0915\u0947 \u0932\u093f\u090f \u092c\u091f\u0928 \u0915\u094d\u0932\u093f\u0915 \u092d\u0940 \u0915\u0930 \u0938\u0915\u0924\u0947 \u0939\u0948\u0902."},"gmailcheck_node_error":{"message":"\u0924\u094d\u0930\u0941\u091f\u093f: \u095e\u0940\u0921 \u092a\u0941\u0928\u0930\u094d\u092a\u094d\u0930\u093e\u092a\u094d\u0924 \u0915\u0940 \u0917\u0908, \u0932\u0947\u0915\u093f\u0928 \u0915\u094b\u0908 &lt;fullcount&gt; \u0928\u094b\u0921 \u0928\u0939\u0940\u0902 \u092e\u093f\u0932\u093e"},"gmailcheck_exception":{"message":"\u0905\u092a\u0935\u093e\u0926: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hr/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hr/messages.json
new file mode 100644
index 0000000..94dce1c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hr/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Provjera po\u0161te"},"gmailcheck_description":{"message":"Prikazuje broj nepro\u010ditanih poruka u ulaznom pretincu usluge Google Mail. Mo\u017eete tako\u0111er kliknuti gumb za otvaranje ulazne po\u0161te."},"gmailcheck_node_error":{"message":"Pogre\u0161ka: Feed je dohva\u0107en, ali nije prona\u0111eno \u010dvori\u0161te &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"izuzetak: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hu/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hu/messages.json
new file mode 100644
index 0000000..8b3e6c9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/hu/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Lev\u00e9lfigyel\u0151"},"gmailcheck_description":{"message":"Megjelen\u00edti az olvasatlan \u00fczeneteket a Google Mail be\u00e9rkez\u0151 levelei k\u00f6z\u00f6tt. A gombra kattintva is megnyithatja a be\u00e9rkez\u0151 leveleit."},"gmailcheck_node_error":{"message":"Hiba: h\u00edrcsatorna leh\u00edvva, de a csom\u00f3pont (&lt;fullcount&gt;) hi\u00e1nyzik"},"gmailcheck_exception":{"message":"kiv\u00e9tel: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/id/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/id/messages.json
new file mode 100644
index 0000000..4a8d09c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/id/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Menampilkan jumlah pesan yang belum dibaca dalam kotak masuk Google Mail. Anda juga dapat mengeklik tombol ini untuk membuka kotak masuk."},"gmailcheck_node_error":{"message":"Galat: umpan diperoleh, tetapi tidak ada &lt;fullcount&gt; node yang ditemukan"},"gmailcheck_exception":{"message":"pengecualian: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/it/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/it/messages.json
new file mode 100644
index 0000000..13b6e78
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/it/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Avvisi email"},"gmailcheck_description":{"message":"Visualizza il numero di messaggi da leggere nella posta in arrivo di Google Mail. Puoi anche fare clic sul pulsante per aprire la tua posta in arrivo."},"gmailcheck_node_error":{"message":"Errore: feed recuperato ma non sono stati trovati nodi &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"eccezione: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ja/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ja/messages.json
new file mode 100644
index 0000000..3ce2669
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ja/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Google Mail \u306e\u53d7\u4fe1\u30c8\u30ec\u30a4\u306b\u3042\u308b\u672a\u8aad\u306e\u30e1\u30fc\u30eb\u6570\u3092\u8868\u793a\u3057\u307e\u3059\u3002\u3053\u306e\u30dc\u30bf\u30f3\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u53d7\u4fe1\u30c8\u30ec\u30a4\u3092\u958b\u304f\u3053\u3068\u3082\u3067\u304d\u307e\u3059\u3002"},"gmailcheck_node_error":{"message":"\u30a8\u30e9\u30fc: \u53d6\u5f97\u3057\u305f\u30d5\u30a3\u30fc\u30c9\u306b &lt;fullcount&gt; \u30ce\u30fc\u30c9\u304c\u3042\u308a\u307e\u305b\u3093"},"gmailcheck_exception":{"message":"\u4f8b\u5916: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ko/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ko/messages.json
new file mode 100644
index 0000000..934e49e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ko/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Gmaill \ubc1b\uc740\ud3b8\uc9c0\ud568\uc5d0\uc11c \uc77d\uc9c0 \uc54a\uc740 \uba54\uc77c\uc758 \uc218\ub97c \ub098\ud0c0\ub0c5\ub2c8\ub2e4. \ub610\ud55c \ubc84\ud2bc\uc744 \ud074\ub9ad\ud558\uc5ec \ubc1b\uc740\ud3b8\uc9c0\ud568\uc744 \uc5f4 \uc218\ub3c4 \uc788\uc2b5\ub2c8\ub2e4."},"gmailcheck_node_error":{"message":"\uc624\ub958: \ud53c\ub4dc\ub97c \uac80\uc0c9\ud588\uc73c\ub098 \ucd1d &lt;fullcount&gt;\uac1c\uc758 \ub178\ub4dc\ub97c \ucc3e\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4."},"gmailcheck_exception":{"message":"\uc608\uc678: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/lt/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/lt/messages.json
new file mode 100644
index 0000000..7da57e1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/lt/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"\u201eGoogle\u201c pa\u0161to tikrintuvas"},"gmailcheck_description":{"message":"Pateikiamas \u201eGoogle\u201c pa\u0161to gaut\u0173 lai\u0161k\u0173 aplanke esan\u010di\u0173 neperskaityt\u0173 prane\u0161im\u0173 skai\u010dius. Be to, jei norite atidaryti gaut\u0173 lai\u0161k\u0173 aplank\u0105, galite spustel\u0117ti mygtuk\u0105."},"gmailcheck_node_error":{"message":"Klaida: sklaidos kanalas nuskaitytas, ta\u010diau nerastas joks &lt;fullcount&gt; mazgas"},"gmailcheck_exception":{"message":"i\u0161imtis: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/lv/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/lv/messages.json
new file mode 100644
index 0000000..52f2ca3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/lv/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Tiek r\u0101d\u012bts nelas\u012bto zi\u0146ojumu skaits Google Mail ies\u016btn\u0113. Varat ar\u012b noklik\u0161\u0137in\u0101t uz pogas, lai atv\u0113rtu ies\u016btni."},"gmailcheck_node_error":{"message":"K\u013c\u016bda: pl\u016bsma ir izg\u016bta, ta\u010du netika atrasts neviens &lt;fullcount&gt; mezgls"},"gmailcheck_exception":{"message":"iz\u0146\u0113mums: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/nb/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/nb/messages.json
new file mode 100644
index 0000000..ad8a09d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/nb/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google e-post-sjekker"},"gmailcheck_description":{"message":"Viser antallet uleste meldinger i innboksen for Google Mail. Du kan ogs\u00e5 klikke p\u00e5 knappen for \u00e5 \u00e5pne innboksen."},"gmailcheck_node_error":{"message":"Feil: innmating hentet, men finner ingen &lt;fullcount&gt;-node"},"gmailcheck_exception":{"message":"unntak: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/nl/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/nl/messages.json
new file mode 100644
index 0000000..c40d81f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/nl/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Hiermee wordt het aantal ongelezen berichten in uw Postvak IN van Gmail weergegeven. U kunt ook op de knop klikken om het Postvak IN te openen."},"gmailcheck_node_error":{"message":"Fout: feed opgehaald, maar het knooppunt &lt;fullcount&gt; is niet gevonden"},"gmailcheck_exception":{"message":"uitzondering: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pl/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pl/messages.json
new file mode 100644
index 0000000..d607a06
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pl/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Sprawdzanie poczty Google"},"gmailcheck_description":{"message":"Wy\u015bwietla liczb\u0119 nieprzeczytanych wiadomo\u015bci w Twojej skrzynce odbiorczej Google Mail. Mo\u017cesz te\u017c klikn\u0105\u0107 przycisk, aby otworzy\u0107 swoj\u0105 skrzynk\u0119 odbiorcz\u0105."},"gmailcheck_node_error":{"message":"B\u0142\u0105d: pobrano kana\u0142, ale nie odnaleziono w\u0119z\u0142a &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"wyj\u0105tek: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pt_BR/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pt_BR/messages.json
new file mode 100644
index 0000000..1175d0c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pt_BR/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Verificador de mensagens do Google"},"gmailcheck_description":{"message":"Exibe o n\u00famero de mensagens n\u00e3o lidas na sua Caixa de entrada do Gmail. Voc\u00ea tamb\u00e9m pode clicar no bot\u00e3o para abrir a sua caixa de entrada."},"gmailcheck_node_error":{"message":"Erro: feed recuperado, mas nenhum n\u00f3 &lt;fullcount&gt; encontrado"},"gmailcheck_exception":{"message":"exce\u00e7\u00e3o: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pt_PT/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pt_PT/messages.json
new file mode 100644
index 0000000..e6d8992
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/pt_PT/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Verificador do Google Mail"},"gmailcheck_description":{"message":"Apresenta o n\u00famero de mensagens n\u00e3o lidas existentes na sua caixa de entrada do Google Mail. Pode tamb\u00e9m clicar no bot\u00e3o para abrir a caixa de entrada."},"gmailcheck_node_error":{"message":"Erro: obteve-se o feed, mas n\u00e3o foi encontrado nenhum n\u00f3 &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"excep\u00e7\u00e3o: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ro/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ro/messages.json
new file mode 100644
index 0000000..d0d5838
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ro/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Verificator de e-mail"},"gmailcheck_description":{"message":"Afi\u015feaz\u0103 num\u0103rul mesajelor necitite din folderul Mesaje primite al contului Google Mail. De asemenea, pute\u0163i s\u0103 face\u0163i clic pe buton pentru a deschide folderul Mesaje primite."},"gmailcheck_node_error":{"message":"Eroare: s-a preluat feedul, dar nu s-a g\u0103sit niciun nod &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"excep\u0163ie: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ru/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ru/messages.json
new file mode 100644
index 0000000..dedaef1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/ru/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u043d\u0435\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432 \u043f\u043e\u0447\u0442\u043e\u0432\u043e\u043c \u044f\u0449\u0438\u043a\u0435 Google Mail. \u041c\u043e\u0436\u043d\u043e \u0442\u0430\u043a\u0436\u0435 \u043d\u0430\u0436\u0430\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443, \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043f\u0430\u043f\u043a\u0443 \"\u0412\u0445\u043e\u0434\u044f\u0449\u0438\u0435\"."},"gmailcheck_node_error":{"message":"\u041e\u0448\u0438\u0431\u043a\u0430: \u0444\u0438\u0434 \u0431\u044b\u043b \u043f\u043e\u043b\u0443\u0447\u0435\u043d, \u043d\u043e \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u0443\u0437\u0435\u043b &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"\u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sk/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sk/messages.json
new file mode 100644
index 0000000..13d902a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sk/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Kontrola po\u0161ty Google"},"gmailcheck_description":{"message":"Zobraz\u00ed po\u010det nepre\u010d\u00edtan\u00fdch spr\u00e1v v prie\u010dinku doru\u010denej po\u0161ty v slu\u017ebe Gmail. Kliknut\u00edm na tla\u010didlo prie\u010dinok doru\u010denej po\u0161ty otvor\u00edte."},"gmailcheck_node_error":{"message":"Chyba: informa\u010dn\u00fd kan\u00e1l bol na\u010d\u00edtan\u00fd, nena\u0161iel sa v\u0161ak \u017eiadny uzol &lt;fullcount&gt;."},"gmailcheck_exception":{"message":"v\u00fdnimka: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sl/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sl/messages.json
new file mode 100644
index 0000000..962a8c6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sl/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Preverjevalnik za Google Mail"},"gmailcheck_description":{"message":"Prika\u017ee \u0161tevilo neprebranih sporo\u010dil v nabiralniku storitve Google Mail. Nabiralnik lahko odprete tudi s klikom gumba."},"gmailcheck_node_error":{"message":"Napaka: vir prejet, vendar ni bilo najdeno nobeno vozli\u0161\u010de &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"izjema: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sr/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sr/messages.json
new file mode 100644
index 0000000..a2cfdb4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sr/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google \u043f\u0440\u043e\u0432\u0435\u0440\u0430 \u043f\u043e\u0448\u0442\u0435"},"gmailcheck_description":{"message":"\u041f\u0440\u0438\u043a\u0430\u0437\u0443\u0458\u0435 \u0431\u0440\u043e\u0458 \u043d\u0435\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u0438\u0445 \u043f\u0440\u0438\u043c\u0459\u0435\u043d\u0438\u0445 \u043f\u043e\u0440\u0443\u043a\u0430 Google Mail-\u0430. \u041c\u043e\u0436\u0435\u0442\u0435 \u0438 \u0434\u0430 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 \u0434\u0443\u0433\u043c\u0435 \u0438 \u043e\u0442\u0432\u043e\u0440\u0438\u0442\u0435 \u043f\u0440\u0438\u043c\u0459\u0435\u043d\u0435 \u043f\u043e\u0440\u0443\u043a\u0435."},"gmailcheck_node_error":{"message":"\u0413\u0440\u0435\u0448\u043a\u0430: \u0424\u0438\u0434 \u0458\u0435 \u043f\u0440\u0435\u0443\u0437\u0435\u0442, \u0430\u043b\u0438 \u0447\u0432\u043e\u0440 &lt;fullcount&gt; \u043d\u0438\u0458\u0435 \u043f\u0440\u043e\u043d\u0430\u0452\u0435\u043d"},"gmailcheck_exception":{"message":"\u0438\u0437\u0443\u0437\u0435\u0442\u0430\u043a: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sv/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sv/messages.json
new file mode 100644
index 0000000..b4359f3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/sv/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Visar hur m\u00e5nga ol\u00e4sta meddelanden du har i inkorgen i Google Mail. Du kan ocks\u00e5 klicka p\u00e5 knappen om du vill \u00f6ppna inkorgen."},"gmailcheck_node_error":{"message":"Fel: feeden h\u00e4mtades, men ingen &lt;fullcount&gt;-nod hittades"},"gmailcheck_exception":{"message":"undantag: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/th/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/th/messages.json
new file mode 100644
index 0000000..e5bef6c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/th/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"\u0e41\u0e2a\u0e14\u0e07\u0e08\u0e33\u0e19\u0e27\u0e19\u0e02\u0e2d\u0e07\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21\u0e17\u0e35\u0e48\u0e22\u0e31\u0e07\u0e44\u0e21\u0e48\u0e44\u0e14\u0e49\u0e2d\u0e48\u0e32\u0e19\u0e43\u0e19\u0e01\u0e25\u0e48\u0e2d\u0e07\u0e08\u0e14\u0e2b\u0e21\u0e32\u0e22 Google Mail \u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13 \u0e41\u0e25\u0e30\u0e04\u0e38\u0e13\u0e2a\u0e32\u0e21\u0e32\u0e23\u0e16\u0e04\u0e25\u0e34\u0e01\u0e1b\u0e38\u0e48\u0e21\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e40\u0e1b\u0e34\u0e14\u0e01\u0e25\u0e48\u0e2d\u0e07\u0e08\u0e14\u0e2b\u0e21\u0e32\u0e22\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13\u0e44\u0e14\u0e49\u0e40\u0e0a\u0e48\u0e19\u0e01\u0e31\u0e19"},"gmailcheck_node_error":{"message":"\u0e02\u0e49\u0e2d\u0e1c\u0e34\u0e14\u0e1e\u0e25\u0e32\u0e14: \u0e40\u0e23\u0e35\u0e22\u0e01\u0e04\u0e37\u0e19\u0e1f\u0e35\u0e14\u0e41\u0e25\u0e49\u0e27 \u0e41\u0e15\u0e48\u0e44\u0e21\u0e48\u0e1e\u0e1a\u0e42\u0e2b\u0e19\u0e14 &lt;fullcount&gt;"},"gmailcheck_exception":{"message":"\u0e02\u0e49\u0e2d\u0e22\u0e01\u0e40\u0e27\u0e49\u0e19: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/tr/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/tr/messages.json
new file mode 100644
index 0000000..593057d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/tr/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"Google Mail gelen kutunuzdaki okunmam\u0131\u015f iletilerin say\u0131s\u0131n\u0131 g\u00f6r\u00fcnt\u00fcler. Ayr\u0131ca, d\u00fc\u011fmeyi t\u0131klayarak gelen kutunuzu da a\u00e7abilirsiniz."},"gmailcheck_node_error":{"message":"Hata: yay\u0131n al\u0131nd\u0131, ancak &lt;fullcount&gt; d\u00fc\u011f\u00fcm\u00fc bulunamad\u0131"},"gmailcheck_exception":{"message":"\u00f6zel durum: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/uk/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/uk/messages.json
new file mode 100644
index 0000000..0d196cc
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/uk/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u043f\u043e\u0448\u0442\u0438 Google"},"gmailcheck_description":{"message":"\u0412\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0454 \u043a\u0456\u043b\u044c\u043a\u0456\u0441\u0442\u044c \u043d\u0435\u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u0438\u0445 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c \u0443 \u043f\u0430\u043f\u0446\u0456 \u0437 \u0432\u0445\u0456\u0434\u043d\u0438\u043c\u0438 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f\u043c\u0438 \u0441\u043b\u0443\u0436\u0431\u0438 Google Mail. \u0429\u043e\u0431 \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u0438 \u043f\u0430\u043f\u043a\u0443 \u0437 \u0432\u0445\u0456\u0434\u043d\u0438\u043c\u0438 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f\u043c\u0438, \u043c\u043e\u0436\u043d\u0430 \u0442\u0430\u043a\u043e\u0436 \u043d\u0430\u0442\u0438\u0441\u043d\u0443\u0442\u0438 \u0446\u044e \u043a\u043d\u043e\u043f\u043a\u0443."},"gmailcheck_node_error":{"message":"\u041f\u043e\u043c\u0438\u043b\u043a\u0430: \u043a\u0430\u043d\u0430\u043b \u0432\u0456\u0434\u043d\u043e\u0432\u043b\u0435\u043d\u043e, \u0430\u043b\u0435 \u0436\u043e\u0434\u043d\u043e\u0433\u043e \u0432\u0443\u0437\u043b\u0430 &lt;fullcount&gt; \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e"},"gmailcheck_exception":{"message":"\u0432\u0438\u043d\u044f\u0442\u043e\u043a: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/vi/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/vi/messages.json
new file mode 100644
index 0000000..d26050f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/vi/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Tr\u00ecnh Ki\u1ec3m tra Th\u01b0 c\u1ee7a Google"},"gmailcheck_description":{"message":"Hi\u1ec3n th\u1ecb s\u1ed1 th\u01b0 ch\u01b0a \u0111\u1ecdc trong h\u1ed9p th\u01b0 \u0111\u1ebfn Gmail c\u1ee7a b\u1ea1n. B\u1ea1n c\u0169ng c\u00f3 th\u1ec3 nh\u1ea5p v\u00e0o n\u00fat \u0111\u1ec3 m\u1edf h\u1ed9p th\u01b0 \u0111\u1ebfn c\u1ee7a m\u00ecnh."},"gmailcheck_node_error":{"message":"L\u1ed7i: ngu\u1ed3n c\u1ea5p d\u1eef li\u1ec7u \u0111\u00e3 \u0111\u01b0\u1ee3c truy xu\u1ea5t nh\u01b0ng kh\u00f4ng t\u00ecm th\u1ea5y n\u00fat &lt;fullcount&gt; n\u00e0o"},"gmailcheck_exception":{"message":"ngo\u1ea1i l\u1ec7: $1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/zh_CN/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/zh_CN/messages.json
new file mode 100644
index 0000000..ae6227f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/zh_CN/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"\u663e\u793a Google Mail \u6536\u4ef6\u7bb1\u4e2d\u7684\u672a\u8bfb\u90ae\u4ef6\u6570\u3002\u70b9\u51fb\u8be5\u6309\u94ae\u8fd8\u53ef\u4ee5\u6253\u5f00\u60a8\u7684\u6536\u4ef6\u7bb1\u3002"},"gmailcheck_node_error":{"message":"\u9519\u8bef\uff1a\u7cfb\u7edf\u5df2\u68c0\u7d22\u4f9b\u7a3f\uff0c\u4f46\u672a\u53d1\u73b0 &lt;fullcount&gt; \u8282\u70b9"},"gmailcheck_exception":{"message":"\u5f02\u5e38\uff1a$1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/_locales/zh_TW/messages.json b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/zh_TW/messages.json
new file mode 100644
index 0000000..4c31793
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/_locales/zh_TW/messages.json
@@ -0,0 +1 @@
+{"gmailcheck_name":{"message":"Google Mail Checker"},"gmailcheck_description":{"message":"\u5728 Google Mail \u6536\u4ef6\u5323\u4e2d\u986f\u793a\u672a\u8b80\u90f5\u4ef6\u7684\u6578\u76ee\u3002\u6309\u4e00\u4e0b\u6309\u9215\u4e5f\u53ef\u4ee5\u958b\u555f\u6536\u4ef6\u5323\u3002"},"gmailcheck_node_error":{"message":"\u932f\u8aa4\uff1a\u64f7\u53d6\u5230\u8cc7\u8a0a\u63d0\u4f9b\uff0c\u4f46\u627e\u4e0d\u5230 &lt;fullcount&gt; \u7bc0\u9ede"},"gmailcheck_exception":{"message":"\u4f8b\u5916\u72c0\u6cc1\uff1a$1","placeholders":{"1":{"content":"$1"}}}}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/background.html b/chrome/common/extensions/docs/examples/extensions/gmail/background.html
new file mode 100644
index 0000000..d7aa811
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/background.html
@@ -0,0 +1,3 @@
+<img id="logged_in" src="gmail_logged_in.png">
+<canvas id="canvas" width="19" height="19">
+<script src="background.js"></script>
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/background.js b/chrome/common/extensions/docs/examples/extensions/gmail/background.js
new file mode 100644
index 0000000..fd66dcf
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/background.js
@@ -0,0 +1,351 @@
+// Copyright (c) 2012 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.
+
+var animationFrames = 36;
+var animationSpeed = 10; // ms
+var canvas = document.getElementById('canvas');
+var loggedInImage = document.getElementById('logged_in');
+var canvasContext = canvas.getContext('2d');
+var pollIntervalMin = 5;  // 5 minutes
+var pollIntervalMax = 60;  // 1 hour
+var requestTimeout = 1000 * 2;  // 2 seconds
+var rotation = 0;
+var loadingAnimation = new LoadingAnimation();
+
+// Legacy support for pre-event-pages.
+var oldChromeVersion = !chrome.runtime;
+var requestTimerId;
+
+function getGmailUrl() {
+  return "https://mail.google.com/mail/";
+}
+
+// Identifier used to debug the possibility of multiple instances of the
+// extension making requests on behalf of a single user.
+function getInstanceId() {
+  if (!localStorage.hasOwnProperty("instanceId"))
+    localStorage.instanceId = 'gmc' + parseInt(Date.now() * Math.random(), 10);
+  return localStorage.instanceId;
+}
+
+function getFeedUrl() {
+  // "zx" is a Gmail query parameter that is expected to contain a random
+  // string and may be ignored/stripped.
+  return getGmailUrl() + "feed/atom?zx=" + encodeURIComponent(getInstanceId());
+}
+
+function isGmailUrl(url) {
+  // Return whether the URL starts with the Gmail prefix.
+  return url.indexOf(getGmailUrl()) == 0;
+}
+
+// A "loading" animation displayed while we wait for the first response from
+// Gmail. This animates the badge text with a dot that cycles from left to
+// right.
+function LoadingAnimation() {
+  this.timerId_ = 0;
+  this.maxCount_ = 8;  // Total number of states in animation
+  this.current_ = 0;  // Current state
+  this.maxDot_ = 4;  // Max number of dots in animation
+}
+
+LoadingAnimation.prototype.paintFrame = function() {
+  var text = "";
+  for (var i = 0; i < this.maxDot_; i++) {
+    text += (i == this.current_) ? "." : " ";
+  }
+  if (this.current_ >= this.maxDot_)
+    text += "";
+
+  chrome.browserAction.setBadgeText({text:text});
+  this.current_++;
+  if (this.current_ == this.maxCount_)
+    this.current_ = 0;
+}
+
+LoadingAnimation.prototype.start = function() {
+  if (this.timerId_)
+    return;
+
+  var self = this;
+  this.timerId_ = window.setInterval(function() {
+    self.paintFrame();
+  }, 100);
+}
+
+LoadingAnimation.prototype.stop = function() {
+  if (!this.timerId_)
+    return;
+
+  window.clearInterval(this.timerId_);
+  this.timerId_ = 0;
+}
+
+function updateIcon() {
+  if (!localStorage.hasOwnProperty('unreadCount')) {
+    chrome.browserAction.setIcon({path:"gmail_not_logged_in.png"});
+    chrome.browserAction.setBadgeBackgroundColor({color:[190, 190, 190, 230]});
+    chrome.browserAction.setBadgeText({text:"?"});
+  } else {
+    chrome.browserAction.setIcon({path: "gmail_logged_in.png"});
+    chrome.browserAction.setBadgeBackgroundColor({color:[208, 0, 24, 255]});
+    chrome.browserAction.setBadgeText({
+      text: localStorage.unreadCount != "0" ? localStorage.unreadCount : ""
+    });
+  }
+}
+
+function scheduleRequest() {
+  console.log('scheduleRequest');
+  var randomness = Math.random() * 2;
+  var exponent = Math.pow(2, localStorage.requestFailureCount || 0);
+  var multiplier = Math.max(randomness * exponent, 1);
+  var delay = Math.min(multiplier * pollIntervalMin, pollIntervalMax);
+  delay = Math.round(delay);
+  console.log('Scheduling for: ' + delay);
+
+  if (oldChromeVersion) {
+    if (requestTimerId) {
+      window.clearTimeout(requestTimerId);
+    }
+    requestTimerId = window.setTimeout(onAlarm, delay*60*1000);
+  } else {
+    console.log('Creating alarm');
+    // Use a repeating alarm so that it fires again if there was a problem
+    // setting the next alarm.
+    chrome.alarms.create('refresh', {periodInMinutes: delay});
+  }
+}
+
+// ajax stuff
+function startRequest(params) {
+  // Schedule request immediately. We want to be sure to reschedule, even in the
+  // case where the extension process shuts down while this request is
+  // outstanding.
+  if (params && params.scheduleRequest) scheduleRequest();
+
+  function stopLoadingAnimation() {
+    if (params && params.showLoadingAnimation) loadingAnimation.stop();
+  }
+
+  if (params && params.showLoadingAnimation)
+    loadingAnimation.start();
+
+  getInboxCount(
+    function(count) {
+      stopLoadingAnimation();
+      updateUnreadCount(count);
+    },
+    function() {
+      stopLoadingAnimation();
+      delete localStorage.unreadCount;
+      updateIcon();
+    }
+  );
+}
+
+function getInboxCount(onSuccess, onError) {
+  var xhr = new XMLHttpRequest();
+  var abortTimerId = window.setTimeout(function() {
+    xhr.abort();  // synchronously calls onreadystatechange
+  }, requestTimeout);
+
+  function handleSuccess(count) {
+    localStorage.requestFailureCount = 0;
+    window.clearTimeout(abortTimerId);
+    if (onSuccess)
+      onSuccess(count);
+  }
+
+  var invokedErrorCallback = false;
+  function handleError() {
+    ++localStorage.requestFailureCount;
+    window.clearTimeout(abortTimerId);
+    if (onError && !invokedErrorCallback)
+      onError();
+    invokedErrorCallback = true;
+  }
+
+  try {
+    xhr.onreadystatechange = function() {
+      if (xhr.readyState != 4)
+        return;
+
+      if (xhr.responseXML) {
+        var xmlDoc = xhr.responseXML;
+        var fullCountSet = xmlDoc.evaluate("/gmail:feed/gmail:fullcount",
+            xmlDoc, gmailNSResolver, XPathResult.ANY_TYPE, null);
+        var fullCountNode = fullCountSet.iterateNext();
+        if (fullCountNode) {
+          handleSuccess(fullCountNode.textContent);
+          return;
+        } else {
+          console.error(chrome.i18n.getMessage("gmailcheck_node_error"));
+        }
+      }
+
+      handleError();
+    };
+
+    xhr.onerror = function(error) {
+      handleError();
+    };
+
+    xhr.open("GET", getFeedUrl(), true);
+    xhr.send(null);
+  } catch(e) {
+    console.error(chrome.i18n.getMessage("gmailcheck_exception", e));
+    handleError();
+  }
+}
+
+function gmailNSResolver(prefix) {
+  if(prefix == 'gmail') {
+    return 'http://purl.org/atom/ns#';
+  }
+}
+
+function updateUnreadCount(count) {
+  var changed = localStorage.unreadCount != count;
+  localStorage.unreadCount = count;
+  updateIcon();
+  if (changed)
+    animateFlip();
+}
+
+
+function ease(x) {
+  return (1-Math.sin(Math.PI/2+x*Math.PI))/2;
+}
+
+function animateFlip() {
+  rotation += 1/animationFrames;
+  drawIconAtRotation();
+
+  if (rotation <= 1) {
+    setTimeout(animateFlip, animationSpeed);
+  } else {
+    rotation = 0;
+    updateIcon();
+  }
+}
+
+function drawIconAtRotation() {
+  canvasContext.save();
+  canvasContext.clearRect(0, 0, canvas.width, canvas.height);
+  canvasContext.translate(
+      Math.ceil(canvas.width/2),
+      Math.ceil(canvas.height/2));
+  canvasContext.rotate(2*Math.PI*ease(rotation));
+  canvasContext.drawImage(loggedInImage,
+      -Math.ceil(canvas.width/2),
+      -Math.ceil(canvas.height/2));
+  canvasContext.restore();
+
+  chrome.browserAction.setIcon({imageData:canvasContext.getImageData(0, 0,
+      canvas.width,canvas.height)});
+}
+
+function goToInbox() {
+  console.log('Going to inbox...');
+  chrome.tabs.getAllInWindow(undefined, function(tabs) {
+    for (var i = 0, tab; tab = tabs[i]; i++) {
+      if (tab.url && isGmailUrl(tab.url)) {
+        console.log('Found Gmail tab: ' + tab.url + '. ' +
+                    'Focusing and refreshing count...');
+        chrome.tabs.update(tab.id, {selected: true});
+        startRequest({scheduleRequest:false, showLoadingAnimation:false});
+        return;
+      }
+    }
+    console.log('Could not find Gmail tab. Creating one...');
+    chrome.tabs.create({url: getGmailUrl()});
+  });
+}
+
+function onInit() {
+  console.log('onInit');
+  localStorage.requestFailureCount = 0;  // used for exponential backoff
+  startRequest({scheduleRequest:true, showLoadingAnimation:true});
+  if (!oldChromeVersion) {
+    // TODO(mpcomplete): We should be able to remove this now, but leaving it
+    // for a little while just to be sure the refresh alarm is working nicely.
+    chrome.alarms.create('watchdog', {periodInMinutes:5});
+  }
+}
+
+function onAlarm(alarm) {
+  console.log('Got alarm', alarm);
+  // |alarm| can be undefined because onAlarm also gets called from
+  // window.setTimeout on old chrome versions.
+  if (alarm && alarm.name == 'watchdog') {
+    onWatchdog();
+  } else {
+    startRequest({scheduleRequest:true, showLoadingAnimation:false});
+  }
+}
+
+function onWatchdog() {
+  chrome.alarms.get('refresh', function(alarm) {
+    if (alarm) {
+      console.log('Refresh alarm exists. Yay.');
+    } else {
+      console.log('Refresh alarm doesn\'t exist!? ' +
+                  'Refreshing now and rescheduling.');
+      startRequest({scheduleRequest:true, showLoadingAnimation:false});
+    }
+  });
+}
+
+if (oldChromeVersion) {
+  updateIcon();
+  onInit();
+} else {
+  chrome.runtime.onInstalled.addListener(onInit);
+  chrome.alarms.onAlarm.addListener(onAlarm);
+}
+
+var filters = {
+  // TODO(aa): Cannot use urlPrefix because all the url fields lack the protocol
+  // part. See crbug.com/140238.
+  url: [{urlContains: getGmailUrl().replace(/^https?\:\/\//, '')}]
+};
+
+function onNavigate(details) {
+  if (details.url && isGmailUrl(details.url)) {
+    console.log('Recognized Gmail navigation to: ' + details.url + '.' +
+                'Refreshing count...');
+    startRequest({scheduleRequest:false, showLoadingAnimation:false});
+  }
+}
+if (chrome.webNavigation && chrome.webNavigation.onDOMContentLoaded &&
+    chrome.webNavigation.onReferenceFragmentUpdated) {
+  chrome.webNavigation.onDOMContentLoaded.addListener(onNavigate, filters);
+  chrome.webNavigation.onReferenceFragmentUpdated.addListener(
+      onNavigate, filters);
+} else {
+  chrome.tabs.onUpdated.addListener(function(_, details) {
+    onNavigate(details);
+  });
+}
+
+chrome.browserAction.onClicked.addListener(goToInbox);
+
+if (chrome.runtime && chrome.runtime.onStartup) {
+  chrome.runtime.onStartup.addListener(function() {
+    console.log('Starting browser... updating icon.');
+    startRequest({scheduleRequest:false, showLoadingAnimation:false});
+    updateIcon();
+  });
+} else {
+  // This hack is needed because Chrome 22 does not persist browserAction icon
+  // state, and also doesn't expose onStartup. So the icon always starts out in
+  // wrong state. We don't actually use onStartup except as a clue that we're
+  // in a version of Chrome that has this problem.
+  chrome.windows.onCreated.addListener(function() {
+    console.log('Window created... updating icon.');
+    startRequest({scheduleRequest:false, showLoadingAnimation:false});
+    updateIcon();
+  });
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/gmail_logged_in.png b/chrome/common/extensions/docs/examples/extensions/gmail/gmail_logged_in.png
new file mode 100644
index 0000000..eec9019
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/gmail_logged_in.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/gmail_not_logged_in.png b/chrome/common/extensions/docs/examples/extensions/gmail/gmail_not_logged_in.png
new file mode 100644
index 0000000..ac38fa4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/gmail_not_logged_in.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/icon_128.png b/chrome/common/extensions/docs/examples/extensions/gmail/icon_128.png
new file mode 100644
index 0000000..3a33f40
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/icon_128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/gmail/manifest.json b/chrome/common/extensions/docs/examples/extensions/gmail/manifest.json
new file mode 100644
index 0000000..c181dcd
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/gmail/manifest.json
@@ -0,0 +1,23 @@
+{
+  "background": {
+    "persistent": false,
+    "page": "background.html"
+  },
+  "browser_action": {
+    "default_icon": "gmail_not_logged_in.png"
+  },
+  "default_locale": "en",
+  "description": "__MSG_gmailcheck_description__",
+  "icons": {
+    "128": "icon_128.png"
+  },
+  "name": "__MSG_gmailcheck_name__",
+  "permissions": [
+    "alarms",
+    "tabs",
+    "webNavigation",
+    "*://*.google.com/"
+   ],
+  "version": "4.4.0",
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/imageinfo/NOTICE b/chrome/common/extensions/docs/examples/extensions/imageinfo/NOTICE
new file mode 100644
index 0000000..ecc55f6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/imageinfo/NOTICE
@@ -0,0 +1,5 @@
+This extension uses code from the following JavaScript library:
+
+ImageInfo - A JavaScript library for reading image metadata.
+Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/
+MIT License [http://www.nihilogic.dk/licenses/mit-license.txt]
diff --git a/chrome/common/extensions/docs/examples/extensions/imageinfo/background.js b/chrome/common/extensions/docs/examples/extensions/imageinfo/background.js
new file mode 100644
index 0000000..41147e4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/imageinfo/background.js
@@ -0,0 +1,27 @@
+// 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.
+
+/**
+ * Returns a handler which will open a new window when activated.
+ */
+function getClickHandler() {
+  return function(info, tab) {
+
+    // The srcUrl property is only available for image elements.
+    var url = 'info.html#' + info.srcUrl;
+
+    // Create a new window to the info page.
+    chrome.windows.create({ url: url, width: 520, height: 660 });
+  };
+};
+
+/**
+ * Create a context menu which will only show up for images.
+ */
+chrome.contextMenus.create({
+  "title" : "Get image info",
+  "type" : "normal",
+  "contexts" : ["image"],
+  "onclick" : getClickHandler()
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo-128.png b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo-128.png
new file mode 100644
index 0000000..23a7de3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo-128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo-16.png b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo-16.png
new file mode 100644
index 0000000..ef3bd02
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo-16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo-19.png b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo-19.png
new file mode 100644
index 0000000..afac004
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo-19.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo-48.png b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo-48.png
new file mode 100644
index 0000000..e3e2754
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo-48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo/binaryajax.js b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo/binaryajax.js
new file mode 100644
index 0000000..1f800ad
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo/binaryajax.js
@@ -0,0 +1,235 @@
+

+/*

+ * Binary Ajax 0.1.5

+ * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com, http://blog.nihilogic.dk/

+ * MIT License [http://www.opensource.org/licenses/mit-license.php]

+ */

+

+

+var BinaryFile = function(strData, iDataOffset, iDataLength) {

+  var data = strData;

+  var dataOffset = iDataOffset || 0;

+  var dataLength = 0;

+

+  this.getRawData = function() {

+    return data;

+  }

+

+  if (typeof strData == "string") {

+    dataLength = iDataLength || data.length;

+

+    this.getByteAt = function(iOffset) {

+      return data.charCodeAt(iOffset + dataOffset) & 0xFF;

+    }

+  } else if (typeof strData == "unknown") {

+    dataLength = iDataLength || IEBinary_getLength(data);

+

+    this.getByteAt = function(iOffset) {

+      return IEBinary_getByteAt(data, iOffset + dataOffset);

+    }

+  }

+

+  this.getLength = function() {

+    return dataLength;

+  }

+

+  this.getSByteAt = function(iOffset) {

+    var iByte = this.getByteAt(iOffset);

+    if (iByte > 127)

+      return iByte - 256;

+    else

+      return iByte;

+  }

+

+  this.getShortAt = function(iOffset, bBigEndian) {

+    var iShort = bBigEndian ?

+      (this.getByteAt(iOffset) << 8) + this.getByteAt(iOffset + 1)

+      : (this.getByteAt(iOffset + 1) << 8) + this.getByteAt(iOffset)

+    if (iShort < 0) iShort += 65536;

+    return iShort;

+  }

+  this.getSShortAt = function(iOffset, bBigEndian) {

+    var iUShort = this.getShortAt(iOffset, bBigEndian);

+    if (iUShort > 32767)

+      return iUShort - 65536;

+    else

+      return iUShort;

+  }

+  this.getLongAt = function(iOffset, bBigEndian) {

+    var iByte1 = this.getByteAt(iOffset),

+      iByte2 = this.getByteAt(iOffset + 1),

+      iByte3 = this.getByteAt(iOffset + 2),

+      iByte4 = this.getByteAt(iOffset + 3);

+

+    var iLong = bBigEndian ?

+      (((((iByte1 << 8) + iByte2) << 8) + iByte3) << 8) + iByte4

+      : (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1;

+    if (iLong < 0) iLong += 4294967296;

+    return iLong;

+  }

+  this.getSLongAt = function(iOffset, bBigEndian) {

+    var iULong = this.getLongAt(iOffset, bBigEndian);

+    if (iULong > 2147483647)

+      return iULong - 4294967296;

+    else

+      return iULong;

+  }

+  this.getStringAt = function(iOffset, iLength) {

+    var aStr = [];

+    for (var i=iOffset,j=0;i<iOffset+iLength;i++,j++) {

+      aStr[j] = String.fromCharCode(this.getByteAt(i));

+    }

+    return aStr.join("");

+  }

+

+  this.getCharAt = function(iOffset) {

+    return String.fromCharCode(this.getByteAt(iOffset));

+  }

+  this.toBase64 = function() {

+    return window.btoa(data);

+  }

+  this.fromBase64 = function(strBase64) {

+    data = window.atob(strBase64);

+  }

+}

+

+

+var BinaryAjax = (function() {

+

+  function createRequest() {

+    var oHTTP = null;

+    if (window.XMLHttpRequest) {

+      oHTTP = new XMLHttpRequest();

+    } else if (window.ActiveXObject) {

+      oHTTP = new ActiveXObject("Microsoft.XMLHTTP");

+    }

+    return oHTTP;

+  }

+

+  function getHead(strURL, fncCallback, fncError) {

+    var oHTTP = createRequest();

+    if (oHTTP) {

+      if (fncCallback) {

+        if (typeof(oHTTP.onload) != "undefined") {

+          oHTTP.onload = function() {

+            if (oHTTP.status == "200") {

+              fncCallback(this);

+            } else {

+              if (fncError) fncError();

+            }

+            oHTTP = null;

+          };

+        } else {

+          oHTTP.onreadystatechange = function() {

+            if (oHTTP.readyState == 4) {

+              if (oHTTP.status == "200") {

+                fncCallback(this);

+              } else {

+                if (fncError) fncError();

+              }

+              oHTTP = null;

+            }

+          };

+        }

+      }

+      oHTTP.open("HEAD", strURL, true);

+      oHTTP.send(null);

+    } else {

+      if (fncError) fncError();

+    }

+  }

+

+  function sendRequest(strURL, fncCallback, fncError, aRange, bAcceptRanges, iFileSize) {

+    var oHTTP = createRequest();

+    if (oHTTP) {

+

+      var iDataOffset = 0;

+      if (aRange && !bAcceptRanges) {

+        iDataOffset = aRange[0];

+      }

+      var iDataLen = 0;

+      if (aRange) {

+        iDataLen = aRange[1]-aRange[0]+1;

+      }

+

+      if (fncCallback) {

+        if (typeof(oHTTP.onload) != "undefined") {

+          oHTTP.onload = function() {

+

+            if (oHTTP.status == "200" || oHTTP.status == "206") {

+              this.binaryResponse = new BinaryFile(this.responseText, iDataOffset, iDataLen);

+              this.fileSize = iFileSize || this.getResponseHeader("Content-Length");

+              fncCallback(this);

+            } else {

+              if (fncError) fncError();

+            }

+            oHTTP = null;

+          };

+        } else {

+          oHTTP.onreadystatechange = function() {

+            if (oHTTP.readyState == 4) {

+              if (oHTTP.status == "200" || oHTTP.status == "206") {

+                this.binaryResponse = new BinaryFile(oHTTP.responseBody, iDataOffset, iDataLen);

+                this.fileSize = iFileSize || this.getResponseHeader("Content-Length");

+                fncCallback(this);

+              } else {

+                if (fncError) fncError();

+              }

+              oHTTP = null;

+            }

+          };

+        }

+      }

+      oHTTP.open("GET", strURL, true);

+

+      if (oHTTP.overrideMimeType) oHTTP.overrideMimeType('text/plain; charset=x-user-defined');

+

+      if (aRange && bAcceptRanges) {

+        oHTTP.setRequestHeader("Range", "bytes=" + aRange[0] + "-" + aRange[1]);

+      }

+

+      oHTTP.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 1970 00:00:00 GMT");

+

+      oHTTP.send(null);

+    } else {

+      if (fncError) fncError();

+    }

+  }

+

+  return function(strURL, fncCallback, fncError, aRange) {

+

+    if (aRange) {

+      getHead(

+        strURL,

+        function(oHTTP) {

+          var iLength = parseInt(oHTTP.getResponseHeader("Content-Length"),10);

+          var strAcceptRanges = oHTTP.getResponseHeader("Accept-Ranges");

+

+          var iStart, iEnd;

+          iStart = aRange[0];

+          if (aRange[0] < 0)

+            iStart += iLength;

+          iEnd = iStart + aRange[1] - 1;

+

+          sendRequest(strURL, fncCallback, fncError, [iStart, iEnd], (strAcceptRanges == "bytes"), iLength);

+        }

+      );

+

+    } else {

+      sendRequest(strURL, fncCallback, fncError);

+    }

+  }

+

+}());

+

+

+document.write(

+  "<script type='text/vbscript'>\r\n"

+  + "Function IEBinary_getByteAt(strBinary, iOffset)\r\n"

+  + "	IEBinary_getByteAt = AscB(MidB(strBinary,iOffset+1,1))\r\n"

+  + "End Function\r\n"

+  + "Function IEBinary_getLength(strBinary)\r\n"

+  + "	IEBinary_getLength = LenB(strBinary)\r\n"

+  + "End Function\r\n"

+  + "</script>\r\n"

+);

diff --git a/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo/exif.js b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo/exif.js
new file mode 100644
index 0000000..76a6810
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo/exif.js
@@ -0,0 +1,615 @@
+/*

+ * Javascript EXIF Reader 0.1.2

+ * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com, http://blog.nihilogic.dk/

+ * MIT License [http://www.opensource.org/licenses/mit-license.php]

+ */

+

+

+var EXIF = {};

+

+(function() {

+

+var bDebug = false;

+

+EXIF.Tags = {

+

+  // version tags

+  0x9000 : "ExifVersion",			// EXIF version

+  0xA000 : "FlashpixVersion",		// Flashpix format version

+

+  // colorspace tags

+  0xA001 : "ColorSpace",			// Color space information tag

+

+  // image configuration

+  0xA002 : "PixelXDimension",		// Valid width of meaningful image

+  0xA003 : "PixelYDimension",		// Valid height of meaningful image

+  0x9101 : "ComponentsConfiguration",	// Information about channels

+  0x9102 : "CompressedBitsPerPixel",	// Compressed bits per pixel

+

+  // user information

+  0x927C : "MakerNote",			// Any desired information written by the manufacturer

+  0x9286 : "UserComment",			// Comments by user

+

+  // related file

+  0xA004 : "RelatedSoundFile",		// Name of related sound file

+

+  // date and time

+  0x9003 : "DateTimeOriginal",		// Date and time when the original image was generated

+  0x9004 : "DateTimeDigitized",		// Date and time when the image was stored digitally

+  0x9290 : "SubsecTime",			// Fractions of seconds for DateTime

+  0x9291 : "SubsecTimeOriginal",		// Fractions of seconds for DateTimeOriginal

+  0x9292 : "SubsecTimeDigitized",		// Fractions of seconds for DateTimeDigitized

+

+  // picture-taking conditions

+  0x829A : "ExposureTime",		// Exposure time (in seconds)

+  0x829D : "FNumber",			// F number

+  0x8822 : "ExposureProgram",		// Exposure program

+  0x8824 : "SpectralSensitivity",		// Spectral sensitivity

+  0x8827 : "ISOSpeedRatings",		// ISO speed rating

+  0x8828 : "OECF",			// Optoelectric conversion factor

+  0x9201 : "ShutterSpeedValue",		// Shutter speed

+  0x9202 : "ApertureValue",		// Lens aperture

+  0x9203 : "BrightnessValue",		// Value of brightness

+  0x9204 : "ExposureBias",		// Exposure bias

+  0x9205 : "MaxApertureValue",		// Smallest F number of lens

+  0x9206 : "SubjectDistance",		// Distance to subject in meters

+  0x9207 : "MeteringMode", 		// Metering mode

+  0x9208 : "LightSource",			// Kind of light source

+  0x9209 : "Flash",			// Flash status

+  0x9214 : "SubjectArea",			// Location and area of main subject

+  0x920A : "FocalLength",			// Focal length of the lens in mm

+  0xA20B : "FlashEnergy",			// Strobe energy in BCPS

+  0xA20C : "SpatialFrequencyResponse",	//

+  0xA20E : "FocalPlaneXResolution", 	// Number of pixels in width direction per FocalPlaneResolutionUnit

+  0xA20F : "FocalPlaneYResolution", 	// Number of pixels in height direction per FocalPlaneResolutionUnit

+  0xA210 : "FocalPlaneResolutionUnit", 	// Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution

+  0xA214 : "SubjectLocation",		// Location of subject in image

+  0xA215 : "ExposureIndex",		// Exposure index selected on camera

+  0xA217 : "SensingMethod", 		// Image sensor type

+  0xA300 : "FileSource", 			// Image source (3 == DSC)

+  0xA301 : "SceneType", 			// Scene type (1 == directly photographed)

+  0xA302 : "CFAPattern",			// Color filter array geometric pattern

+  0xA401 : "CustomRendered",		// Special processing

+  0xA402 : "ExposureMode",		// Exposure mode

+  0xA403 : "WhiteBalance",		// 1 = auto white balance, 2 = manual

+  0xA404 : "DigitalZoomRation",		// Digital zoom ratio

+  0xA405 : "FocalLengthIn35mmFilm",	// Equivalent foacl length assuming 35mm film camera (in mm)

+  0xA406 : "SceneCaptureType",		// Type of scene

+  0xA407 : "GainControl",			// Degree of overall image gain adjustment

+  0xA408 : "Contrast",			// Direction of contrast processing applied by camera

+  0xA409 : "Saturation", 			// Direction of saturation processing applied by camera

+  0xA40A : "Sharpness",			// Direction of sharpness processing applied by camera

+  0xA40B : "DeviceSettingDescription",	//

+  0xA40C : "SubjectDistanceRange",	// Distance to subject

+

+  // other tags

+  0xA005 : "InteroperabilityIFDPointer",

+  0xA420 : "ImageUniqueID"		// Identifier assigned uniquely to each image

+};

+

+EXIF.TiffTags = {

+  0x0100 : "ImageWidth",

+  0x0101 : "ImageHeight",

+  0x8769 : "ExifIFDPointer",

+  0x8825 : "GPSInfoIFDPointer",

+  0xA005 : "InteroperabilityIFDPointer",

+  0x0102 : "BitsPerSample",

+  0x0103 : "Compression",

+  0x0106 : "PhotometricInterpretation",

+  0x0112 : "Orientation",

+  0x0115 : "SamplesPerPixel",

+  0x011C : "PlanarConfiguration",

+  0x0212 : "YCbCrSubSampling",

+  0x0213 : "YCbCrPositioning",

+  0x011A : "XResolution",

+  0x011B : "YResolution",

+  0x0128 : "ResolutionUnit",

+  0x0111 : "StripOffsets",

+  0x0116 : "RowsPerStrip",

+  0x0117 : "StripByteCounts",

+  0x0201 : "JPEGInterchangeFormat",

+  0x0202 : "JPEGInterchangeFormatLength",

+  0x012D : "TransferFunction",

+  0x013E : "WhitePoint",

+  0x013F : "PrimaryChromaticities",

+  0x0211 : "YCbCrCoefficients",

+  0x0214 : "ReferenceBlackWhite",

+  0x0132 : "DateTime",

+  0x010E : "ImageDescription",

+  0x010F : "Make",

+  0x0110 : "Model",

+  0x0131 : "Software",

+  0x013B : "Artist",

+  0x8298 : "Copyright"

+}

+

+EXIF.GPSTags = {

+  0x0000 : "GPSVersionID",

+  0x0001 : "GPSLatitudeRef",

+  0x0002 : "GPSLatitude",

+  0x0003 : "GPSLongitudeRef",

+  0x0004 : "GPSLongitude",

+  0x0005 : "GPSAltitudeRef",

+  0x0006 : "GPSAltitude",

+  0x0007 : "GPSTimeStamp",

+  0x0008 : "GPSSatellites",

+  0x0009 : "GPSStatus",

+  0x000A : "GPSMeasureMode",

+  0x000B : "GPSDOP",

+  0x000C : "GPSSpeedRef",

+  0x000D : "GPSSpeed",

+  0x000E : "GPSTrackRef",

+  0x000F : "GPSTrack",

+  0x0010 : "GPSImgDirectionRef",

+  0x0011 : "GPSImgDirection",

+  0x0012 : "GPSMapDatum",

+  0x0013 : "GPSDestLatitudeRef",

+  0x0014 : "GPSDestLatitude",

+  0x0015 : "GPSDestLongitudeRef",

+  0x0016 : "GPSDestLongitude",

+  0x0017 : "GPSDestBearingRef",

+  0x0018 : "GPSDestBearing",

+  0x0019 : "GPSDestDistanceRef",

+  0x001A : "GPSDestDistance",

+  0x001B : "GPSProcessingMethod",

+  0x001C : "GPSAreaInformation",

+  0x001D : "GPSDateStamp",

+  0x001E : "GPSDifferential"

+}

+

+EXIF.StringValues = {

+  ExposureProgram : {

+    0 : "Not defined",

+    1 : "Manual",

+    2 : "Normal program",

+    3 : "Aperture priority",

+    4 : "Shutter priority",

+    5 : "Creative program",

+    6 : "Action program",

+    7 : "Portrait mode",

+    8 : "Landscape mode"

+  },

+  MeteringMode : {

+    0 : "Unknown",

+    1 : "Average",

+    2 : "CenterWeightedAverage",

+    3 : "Spot",

+    4 : "MultiSpot",

+    5 : "Pattern",

+    6 : "Partial",

+    255 : "Other"

+  },

+  LightSource : {

+    0 : "Unknown",

+    1 : "Daylight",

+    2 : "Fluorescent",

+    3 : "Tungsten (incandescent light)",

+    4 : "Flash",

+    9 : "Fine weather",

+    10 : "Cloudy weather",

+    11 : "Shade",

+    12 : "Daylight fluorescent (D 5700 - 7100K)",

+    13 : "Day white fluorescent (N 4600 - 5400K)",

+    14 : "Cool white fluorescent (W 3900 - 4500K)",

+    15 : "White fluorescent (WW 3200 - 3700K)",

+    17 : "Standard light A",

+    18 : "Standard light B",

+    19 : "Standard light C",

+    20 : "D55",

+    21 : "D65",

+    22 : "D75",

+    23 : "D50",

+    24 : "ISO studio tungsten",

+    255 : "Other"

+  },

+  Flash : {

+    0x0000 : "Flash did not fire",

+    0x0001 : "Flash fired",

+    0x0005 : "Strobe return light not detected",

+    0x0007 : "Strobe return light detected",

+    0x0009 : "Flash fired, compulsory flash mode",

+    0x000D : "Flash fired, compulsory flash mode, return light not detected",

+    0x000F : "Flash fired, compulsory flash mode, return light detected",

+    0x0010 : "Flash did not fire, compulsory flash mode",

+    0x0018 : "Flash did not fire, auto mode",

+    0x0019 : "Flash fired, auto mode",

+    0x001D : "Flash fired, auto mode, return light not detected",

+    0x001F : "Flash fired, auto mode, return light detected",

+    0x0020 : "No flash function",

+    0x0041 : "Flash fired, red-eye reduction mode",

+    0x0045 : "Flash fired, red-eye reduction mode, return light not detected",

+    0x0047 : "Flash fired, red-eye reduction mode, return light detected",

+    0x0049 : "Flash fired, compulsory flash mode, red-eye reduction mode",

+    0x004D : "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",

+    0x004F : "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",

+    0x0059 : "Flash fired, auto mode, red-eye reduction mode",

+    0x005D : "Flash fired, auto mode, return light not detected, red-eye reduction mode",

+    0x005F : "Flash fired, auto mode, return light detected, red-eye reduction mode"

+  },

+  SensingMethod : {

+    1 : "Not defined",

+    2 : "One-chip color area sensor",

+    3 : "Two-chip color area sensor",

+    4 : "Three-chip color area sensor",

+    5 : "Color sequential area sensor",

+    7 : "Trilinear sensor",

+    8 : "Color sequential linear sensor"

+  },

+  SceneCaptureType : {

+    0 : "Standard",

+    1 : "Landscape",

+    2 : "Portrait",

+    3 : "Night scene"

+  },

+  SceneType : {

+    1 : "Directly photographed"

+  },

+  CustomRendered : {

+    0 : "Normal process",

+    1 : "Custom process"

+  },

+  WhiteBalance : {

+    0 : "Auto white balance",

+    1 : "Manual white balance"

+  },

+  GainControl : {

+    0 : "None",

+    1 : "Low gain up",

+    2 : "High gain up",

+    3 : "Low gain down",

+    4 : "High gain down"

+  },

+  Contrast : {

+    0 : "Normal",

+    1 : "Soft",

+    2 : "Hard"

+  },

+  Saturation : {

+    0 : "Normal",

+    1 : "Low saturation",

+    2 : "High saturation"

+  },

+  Sharpness : {

+    0 : "Normal",

+    1 : "Soft",

+    2 : "Hard"

+  },

+  SubjectDistanceRange : {

+    0 : "Unknown",

+    1 : "Macro",

+    2 : "Close view",

+    3 : "Distant view"

+  },

+  FileSource : {

+    3 : "DSC"

+  },

+

+  Components : {

+    0 : "",

+    1 : "Y",

+    2 : "Cb",

+    3 : "Cr",

+    4 : "R",

+    5 : "G",

+    6 : "B"

+  }

+}

+

+function addEvent(oElement, strEvent, fncHandler)

+{

+  if (oElement.addEventListener) {

+    oElement.addEventListener(strEvent, fncHandler, false);

+  } else if (oElement.attachEvent) {

+    oElement.attachEvent("on" + strEvent, fncHandler);

+  }

+}

+

+

+function imageHasData(oImg)

+{

+  return !!(oImg.exifdata);

+}

+

+function getImageData(oImg, fncCallback)

+{

+  BinaryAjax(

+    oImg.src,

+    function(oHTTP) {

+      var oEXIF = findEXIFinJPEG(oHTTP.binaryResponse);

+      oImg.exifdata = oEXIF || {};

+      if (fncCallback) fncCallback();

+    }

+  )

+}

+

+function findEXIFinJPEG(oFile) {

+  var aMarkers = [];

+

+  if (oFile.getByteAt(0) != 0xFF || oFile.getByteAt(1) != 0xD8) {

+    return false; // not a valid jpeg

+  }

+

+  var iOffset = 2;

+  var iLength = oFile.getLength();

+  while (iOffset < iLength) {

+    if (oFile.getByteAt(iOffset) != 0xFF) {

+      if (bDebug) console.log("Not a valid marker at offset " + iOffset + ", found: " + oFile.getByteAt(iOffset));

+      return false; // not a valid marker, something is wrong

+    }

+

+    var iMarker = oFile.getByteAt(iOffset+1);

+

+    // we could implement handling for other markers here,

+    // but we're only looking for 0xFFE1 for EXIF data

+

+    if (iMarker == 22400) {

+      if (bDebug) console.log("Found 0xFFE1 marker");

+      return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset+2, true)-2);

+      iOffset += 2 + oFile.getShortAt(iOffset+2, true);

+

+    } else if (iMarker == 225) {

+      // 0xE1 = Application-specific 1 (for EXIF)

+      if (bDebug) console.log("Found 0xFFE1 marker");

+      return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset+2, true)-2);

+

+    } else {

+      iOffset += 2 + oFile.getShortAt(iOffset+2, true);

+    }

+

+  }

+

+}

+

+

+function readTags(oFile, iTIFFStart, iDirStart, oStrings, bBigEnd)

+{

+  var iEntries = oFile.getShortAt(iDirStart, bBigEnd);

+  var oTags = {};

+  for (var i=0;i<iEntries;i++) {

+    var iEntryOffset = iDirStart + i*12 + 2;

+    var strTag = oStrings[oFile.getShortAt(iEntryOffset, bBigEnd)];

+    if (!strTag && bDebug) console.log("Unknown tag: " + oFile.getShortAt(iEntryOffset, bBigEnd));

+    oTags[strTag] = readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd);

+  }

+  return oTags;

+}

+

+

+function readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd)

+{

+  var iType = oFile.getShortAt(iEntryOffset+2, bBigEnd);

+  var iNumValues = oFile.getLongAt(iEntryOffset+4, bBigEnd);

+  var iValueOffset = oFile.getLongAt(iEntryOffset+8, bBigEnd) + iTIFFStart;

+

+  switch (iType) {

+    case 1: // byte, 8-bit unsigned int

+    case 7: // undefined, 8-bit byte, value depending on field

+      if (iNumValues == 1) {

+        return oFile.getByteAt(iEntryOffset + 8, bBigEnd);

+      } else {

+        var iValOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);

+        var aVals = [];

+        for (var n=0;n<iNumValues;n++) {

+          aVals[n] = oFile.getByteAt(iValOffset + n);

+        }

+        return aVals;

+      }

+      break;

+

+    case 2: // ascii, 8-bit byte

+      var iStringOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);

+      return oFile.getStringAt(iStringOffset, iNumValues-1);

+      break;

+

+    case 3: // short, 16 bit int

+      if (iNumValues == 1) {

+        return oFile.getShortAt(iEntryOffset + 8, bBigEnd);

+      } else {

+        var iValOffset = iNumValues > 2 ? iValueOffset : (iEntryOffset + 8);

+        var aVals = [];

+        for (var n=0;n<iNumValues;n++) {

+          aVals[n] = oFile.getShortAt(iValOffset + 2*n, bBigEnd);

+        }

+        return aVals;

+      }

+      break;

+

+    case 4: // long, 32 bit int

+      if (iNumValues == 1) {

+        return oFile.getLongAt(iEntryOffset + 8, bBigEnd);

+      } else {

+        var aVals = [];

+        for (var n=0;n<iNumValues;n++) {

+          aVals[n] = oFile.getLongAt(iValueOffset + 4*n, bBigEnd);

+        }

+        return aVals;

+      }

+      break;

+    case 5:	// rational = two long values, first is numerator, second is denominator

+      if (iNumValues == 1) {

+        return oFile.getLongAt(iValueOffset, bBigEnd) / oFile.getLongAt(iValueOffset+4, bBigEnd);

+      } else {

+        var aVals = [];

+        for (var n=0;n<iNumValues;n++) {

+          aVals[n] = oFile.getLongAt(iValueOffset + 8*n, bBigEnd) / oFile.getLongAt(iValueOffset+4 + 8*n, bBigEnd);

+        }

+        return aVals;

+      }

+      break;

+    case 9: // slong, 32 bit signed int

+      if (iNumValues == 1) {

+        return oFile.getSLongAt(iEntryOffset + 8, bBigEnd);

+      } else {

+        var aVals = [];

+        for (var n=0;n<iNumValues;n++) {

+          aVals[n] = oFile.getSLongAt(iValueOffset + 4*n, bBigEnd);

+        }

+        return aVals;

+      }

+      break;

+    case 10: // signed rational, two slongs, first is numerator, second is denominator

+      if (iNumValues == 1) {

+        return oFile.getSLongAt(iValueOffset, bBigEnd) / oFile.getSLongAt(iValueOffset+4, bBigEnd);

+      } else {

+        var aVals = [];

+        for (var n=0;n<iNumValues;n++) {

+          aVals[n] = oFile.getSLongAt(iValueOffset + 8*n, bBigEnd) / oFile.getSLongAt(iValueOffset+4 + 8*n, bBigEnd);

+        }

+        return aVals;

+      }

+      break;

+  }

+}

+

+

+function readEXIFData(oFile, iStart, iLength)

+{

+  if (oFile.getStringAt(iStart, 4) != "Exif") {

+    if (bDebug) console.log("Not valid EXIF data! " + oFile.getStringAt(iStart, 4));

+    return false;

+  }

+

+  var bBigEnd;

+

+  var iTIFFOffset = iStart + 6;

+

+  // test for TIFF validity and endianness

+  if (oFile.getShortAt(iTIFFOffset) == 0x4949) {

+    bBigEnd = false;

+  } else if (oFile.getShortAt(iTIFFOffset) == 0x4D4D) {

+    bBigEnd = true;

+  } else {

+    if (bDebug) console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)");

+    return false;

+  }

+

+  if (oFile.getShortAt(iTIFFOffset+2, bBigEnd) != 0x002A) {

+    if (bDebug) console.log("Not valid TIFF data! (no 0x002A)");

+    return false;

+  }

+

+  if (oFile.getLongAt(iTIFFOffset+4, bBigEnd) != 0x00000008) {

+    if (bDebug) console.log("Not valid TIFF data! (First offset not 8)", oFile.getShortAt(iTIFFOffset+4, bBigEnd));

+    return false;

+  }

+

+  var oTags = readTags(oFile, iTIFFOffset, iTIFFOffset+8, EXIF.TiffTags, bBigEnd);

+

+  if (oTags.ExifIFDPointer) {

+    var oEXIFTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.ExifIFDPointer, EXIF.Tags, bBigEnd);

+    for (var strTag in oEXIFTags) {

+      switch (strTag) {

+        case "LightSource" :

+        case "Flash" :

+        case "MeteringMode" :

+        case "ExposureProgram" :

+        case "SensingMethod" :

+        case "SceneCaptureType" :

+        case "SceneType" :

+        case "CustomRendered" :

+        case "WhiteBalance" :

+        case "GainControl" :

+        case "Contrast" :

+        case "Saturation" :

+        case "Sharpness" :

+        case "SubjectDistanceRange" :

+        case "FileSource" :

+          oEXIFTags[strTag] = EXIF.StringValues[strTag][oEXIFTags[strTag]];

+          break;

+

+        case "ExifVersion" :

+        case "FlashpixVersion" :

+          oEXIFTags[strTag] = String.fromCharCode(oEXIFTags[strTag][0], oEXIFTags[strTag][1], oEXIFTags[strTag][2], oEXIFTags[strTag][3]);

+          break;

+

+        case "ComponentsConfiguration" :

+          oEXIFTags[strTag] =

+            EXIF.StringValues.Components[oEXIFTags[strTag][0]]

+            + EXIF.StringValues.Components[oEXIFTags[strTag][1]]

+            + EXIF.StringValues.Components[oEXIFTags[strTag][2]]

+            + EXIF.StringValues.Components[oEXIFTags[strTag][3]];

+          break;

+      }

+      oTags[strTag] = oEXIFTags[strTag];

+    }

+  }

+

+  if (oTags.GPSInfoIFDPointer) {

+    var oGPSTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.GPSInfoIFDPointer, EXIF.GPSTags, bBigEnd);

+    for (var strTag in oGPSTags) {

+      switch (strTag) {

+        case "GPSVersionID" :

+          oGPSTags[strTag] = oGPSTags[strTag][0]

+            + "." + oGPSTags[strTag][1]

+            + "." + oGPSTags[strTag][2]

+            + "." + oGPSTags[strTag][3];

+          break;

+      }

+      oTags[strTag] = oGPSTags[strTag];

+    }

+  }

+

+  return oTags;

+}

+

+

+EXIF.getData = function(oImg, fncCallback)

+{

+  if (!oImg.complete) return false;

+  if (!imageHasData(oImg)) {

+    getImageData(oImg, fncCallback);

+  } else {

+    if (fncCallback) fncCallback();

+  }

+  return true;

+}

+

+EXIF.getTag = function(oImg, strTag)

+{

+  if (!imageHasData(oImg)) return;

+  return oImg.exifdata[strTag];

+}

+

+EXIF.pretty = function(oImg)

+{

+  if (!imageHasData(oImg)) return "";

+  var oData = oImg.exifdata;

+  var strPretty = "";

+  for (var a in oData) {

+    if (oData.hasOwnProperty(a)) {

+      if (typeof oData[a] == "object") {

+        strPretty += a + " : [" + oData[a].length + " values]\r\n";

+      } else {

+        strPretty += a + " : " + oData[a] + "\r\n";

+      }

+    }

+  }

+  return strPretty;

+}

+

+EXIF.readFromBinaryFile = function(oFile) {

+  return findEXIFinJPEG(oFile);

+}

+

+function loadAllImages()

+{

+  var aImages = document.getElementsByTagName("img");

+  for (var i=0;i<aImages.length;i++) {

+    if (aImages[i].getAttribute("exif") == "true") {

+      if (!aImages[i].complete) {

+        addEvent(aImages[i], "load",

+          function() {

+            EXIF.getData(this);

+          }

+        );

+      } else {

+        EXIF.getData(aImages[i]);

+      }

+    }

+  }

+}

+

+addEvent(window, "load", loadAllImages);

+

+})();

+

diff --git a/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo/imageinfo.js b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo/imageinfo.js
new file mode 100644
index 0000000..79eb0d2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo/imageinfo.js
@@ -0,0 +1,182 @@
+/*

+ * ImageInfo 0.1.2 - A JavaScript library for reading image metadata.

+ * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/

+ * MIT License [http://www.nihilogic.dk/licenses/mit-license.txt]

+ */

+

+

+var ImageInfo = {};

+

+ImageInfo.useRange = false;

+ImageInfo.range = 10240;

+

+(function() {

+

+  var files = [];

+

+  function readFileData(url, callback) {

+    BinaryAjax(

+      url,

+      function(http) {

+        var tags = readInfoFromData(http.binaryResponse);

+        var mime = http.getResponseHeader("Content-Type");

+

+        tags["mimeType"] = mime;

+        tags["byteSize"] = http.fileSize;

+

+        files[url] = tags;

+        if (callback) callback();

+      },

+      null,

+      ImageInfo.useRange ? [0, ImageInfo.range] : null

+    )

+  }

+

+  function readInfoFromData(data) {

+

+    var offset = 0;

+

+    if (data.getByteAt(0) == 0xFF && data.getByteAt(1) == 0xD8) {

+      return readJPEGInfo(data);

+    }

+    if (data.getByteAt(0) == 0x89 && data.getStringAt(1, 3) == "PNG") {

+      return readPNGInfo(data);

+    }

+    if (data.getStringAt(0,3) == "GIF") {

+      return readGIFInfo(data);

+    }

+    if (data.getByteAt(0) == 0x42 && data.getByteAt(1) == 0x4D) {

+      return readBMPInfo(data);

+    }

+    if (data.getByteAt(0) == 0x00 && data.getByteAt(1) == 0x00) {

+      return readICOInfo(data);

+    }

+

+    return {

+      format : "UNKNOWN"

+    };

+  }

+

+

+  function readPNGInfo(data) {

+    var w = data.getLongAt(16,true);

+    var h = data.getLongAt(20,true);

+

+    var bpc = data.getByteAt(24);

+    var ct = data.getByteAt(25);

+

+    var bpp = bpc;

+    if (ct == 4) bpp *= 2;

+    if (ct == 2) bpp *= 3;

+    if (ct == 6) bpp *= 4;

+

+    var alpha = data.getByteAt(25) >= 4;

+

+    return {

+      format : "PNG",

+      version : "",

+      width : w,

+      height : h,

+      bpp : bpp,

+      alpha : alpha,

+      exif : {}

+    }

+  }

+

+  function readGIFInfo(data) {

+    var version = data.getStringAt(3,3);

+    var w = data.getShortAt(6);

+    var h = data.getShortAt(8);

+

+    var bpp = ((data.getByteAt(10) >> 4) & 7) + 1;

+

+    return {

+      format : "GIF",

+      version : version,

+      width : w,

+      height : h,

+      bpp : bpp,

+      alpha : false,

+      exif : {}

+    }

+  }

+

+  function readJPEGInfo(data) {

+

+    var w = 0;

+    var h = 0;

+    var comps = 0;

+    var len = data.getLength();

+    var offset = 2;

+    while (offset < len) {

+      var marker = data.getShortAt(offset, true);

+      offset += 2;

+      if (marker == 0xFFC0) {

+        h = data.getShortAt(offset + 3, true);

+        w = data.getShortAt(offset + 5, true);

+        comps = data.getByteAt(offset + 7, true)

+        break;

+      } else {

+        offset += data.getShortAt(offset, true)

+      }

+    }

+

+    var exif = {};

+

+    if (typeof EXIF != "undefined" && EXIF.readFromBinaryFile) {

+      exif = EXIF.readFromBinaryFile(data);

+    }

+

+    return {

+      format : "JPEG",

+      version : "",

+      width : w,

+      height : h,

+      bpp : comps * 8,

+      alpha : false,

+      exif : exif

+    }

+  }

+

+  function readBMPInfo(data) {

+    var w = data.getLongAt(18);

+    var h = data.getLongAt(22);

+    var bpp = data.getShortAt(28);

+    return {

+      format : "BMP",

+      version : "",

+      width : w,

+      height : h,

+      bpp : bpp,

+      alpha : false,

+      exif : {}

+    }

+  }

+

+  ImageInfo.loadInfo = function(url, cb) {

+    if (!files[url]) {

+      readFileData(url, cb);

+    } else {

+      if (cb) cb();

+    }

+  }

+

+  ImageInfo.getAllFields = function(url) {

+    if (!files[url]) return null;

+

+    var tags = {};

+    for (var a in files[url]) {

+      if (files[url].hasOwnProperty(a))

+        tags[a] = files[url][a];

+    }

+    return tags;

+  }

+

+  ImageInfo.getField = function(url, field) {

+    if (!files[url]) return null;

+    return files[url][field];

+  }

+

+

+})();

+

diff --git a/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo/readme.txt b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo/readme.txt
new file mode 100644
index 0000000..21865b1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/imageinfo/imageinfo/readme.txt
@@ -0,0 +1,7 @@
+
+ImageInfo - A JavaScript library for reading image metadata.
+Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/
+MIT License [http://www.nihilogic.dk/licenses/mit-license.txt]
+
+For detailed information and code samples please refer to the blog post at:
+http://blog.nihilogic.dk/2008/07/imageinfo-reading-image-info-with-javascript.html
diff --git a/chrome/common/extensions/docs/examples/extensions/imageinfo/info.css b/chrome/common/extensions/docs/examples/extensions/imageinfo/info.css
new file mode 100644
index 0000000..0989945
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/imageinfo/info.css
@@ -0,0 +1,56 @@
+/**
+ * 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.
+ */
+
+body {
+  font: 14px Arial;
+}
+
+h1 {
+  margin: 30px 0 5px 0;
+  padding: 0;
+}
+code {
+  padding: 0;
+  margin: 5px 0;
+  display: block;
+}
+table {
+  border-collapse: collapse;
+  width: 100%;
+  margin: 15px 0;
+}
+td, th {
+  padding: 4px;
+}
+th {
+  text-align: left;
+  width: 130px;
+}
+tr {
+  display: none;
+}
+tr.rendered {
+  display: block;
+}
+tr.rendered:nth-child(odd) {
+  background: #eee;
+}
+#thumbnail {
+  position: fixed;
+  right: 20px;
+  top: 20px;
+  -webkit-box-shadow: 1px 1px 6px #000;
+  border: 4px solid #fff;
+  background: #fff;
+}
+#loader {
+  font: 30px Arial;
+  text-align: center;
+  padding: 100px;
+}
+#exif, #output {
+  display: none;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/imageinfo/info.html b/chrome/common/extensions/docs/examples/extensions/imageinfo/info.html
new file mode 100644
index 0000000..5757f7d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/imageinfo/info.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2010 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.
+-->
+<html
+  <head>
+    <script src="imageinfo/binaryajax.js"></script>
+    <script src="imageinfo/imageinfo.js" ></script>
+    <script src="imageinfo/exif.js" ></script>
+    <link href="info.css" rel="stylesheet" type="text/css"></link>
+    <script src="info.js"></script>
+  </head>
+  <body>
+    <div id="loader">
+      Loading... <img src="loader.gif" />
+    </div>
+    <div id="output">
+      <div id="info">
+        <h1>Image Information</h1>
+        <code id="url"></code>
+        <canvas id="thumbnail"></canvas>
+        <table>
+          <tr>
+            <th>Format</th>
+            <td>format</td>
+          </tr>
+          <tr>
+            <th>Version</th>
+            <td>version</td>
+          </tr>
+          <tr>
+            <th>Width</th>
+            <td>width</td>
+          </tr>
+          <tr>
+            <th>Height</th>
+            <td>height</td>
+          </tr>
+          <tr>
+            <th>Mime Type</th>
+            <td>mimeType</td>
+          </tr>
+          <tr>
+            <th>Size (Bytes)</th>
+            <td>byteSize</td>
+          </tr>
+        </table>
+      </div>
+      <div id="exif">
+        <h2>EXIF Information</h2>
+        <table>
+          <tr>
+            <th>Date</th>
+            <td>DateTime</td>
+          </tr>
+          <tr>
+            <th>Aperture</th>
+            <td>ApertureValue</td>
+          </tr>
+          <tr>
+            <th>Exposure</th>
+            <td>ExposureTime</td>
+          </tr>
+          <tr>
+            <th>Shutter Speed</th>
+            <td>ShutterSpeedValue</td>
+          </tr>
+          <tr>
+            <th>ISO</th>
+            <td>ISOSpeedRatings</td>
+          </tr>
+          <tr>
+            <th>Camera Make</th>
+            <td>Make</td>
+          </tr>
+          <tr>
+            <th>Camera Model</th>
+            <td>Model</td>
+          </tr>
+          <tr>
+            <th>Software</th>
+            <td>Software</td>
+          </tr>
+          <tr>
+            <th>XResolution</th>
+            <td>XResolution</td>
+          </tr>
+          <tr>
+            <th>YResolution</th>
+            <td>YResolution</td>
+          </tr>
+          <tr>
+            <th>Flash</th>
+            <td>Flash</td>
+          </tr>
+        </table>
+      </div>
+    </div>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/imageinfo/info.js b/chrome/common/extensions/docs/examples/extensions/imageinfo/info.js
new file mode 100644
index 0000000..40f3b27
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/imageinfo/info.js
@@ -0,0 +1,145 @@
+// 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.
+
+
+/**
+ * Quick template rendering function.  For each cell passed to it, check
+ * to see if the cell's text content is a key in the supplied data array.
+ * If yes, replace the cell's contents with the corresponding value and
+ * unhide the cell.  If no, then remove the cell's parent (tr) from the
+ * DOM.
+ */
+function renderCells(cells, data) {
+  for (var i = 0; i < cells.length; i++) {
+    var cell = cells[i];
+    var key = cell.innerText;
+    if (data[key]) {
+      cell.innerText = data[key];
+      cell.parentElement.className = "rendered";
+    } else {
+      cell.parentElement.parentElement.removeChild(cell.parentElement);
+    }
+  }
+};
+
+/**
+ * Returns true if the supplies object has no properties.
+ */
+function isEmpty(obj) {
+  for (var key in obj) {
+    if (obj.hasOwnProperty(key)) {
+      return false;
+    }
+  }
+  return true;
+};
+
+/**
+ * Resizes the window to the current dimensions of this page's body.
+ */
+function resizeWindow() {
+  window.setTimeout(function() {
+    chrome.tabs.getCurrent(function (tab) {
+      var newHeight = Math.min(document.body.offsetHeight + 140, 700);
+      chrome.windows.update(tab.windowId, {
+        height: newHeight,
+        width: 520
+      });
+    });
+  }, 150);
+};
+
+/**
+ * Called directly by the background page with information about the
+ * image.  Outputs image data to the DOM.
+ */
+function renderImageInfo(imageinfo) {
+  console.log('imageinfo', imageinfo);
+
+  var divloader = document.querySelector('#loader');
+  var divoutput = document.querySelector('#output');
+  divloader.style.display = "none";
+  divoutput.style.display = "block";
+
+  var divinfo = document.querySelector('#info');
+  var divexif = document.querySelector('#exif');
+
+  // Render general image data.
+  var datacells = divinfo.querySelectorAll('td');
+  renderCells(datacells, imageinfo);
+
+  // If EXIF data exists, unhide the EXIF table and render.
+  if (imageinfo['exif'] && !isEmpty(imageinfo['exif'])) {
+    divexif.style.display = 'block';
+    var exifcells = divexif.querySelectorAll('td');
+    renderCells(exifcells, imageinfo['exif']);
+  }
+};
+
+/**
+ * Renders the URL for the image, trimming if the length is too long.
+ */
+function renderUrl(url) {
+  var divurl = document.querySelector('#url');
+  var urltext = (url.length < 45) ? url : url.substr(0, 42) + '...';
+  var anchor = document.createElement('a');
+  anchor.href = url;
+  anchor.innerText = urltext;
+  divurl.appendChild(anchor);
+};
+
+/**
+ * Renders a thumbnail view of the image.
+ */
+function renderThumbnail(url) {
+  var canvas = document.querySelector('#thumbnail');
+  var context = canvas.getContext('2d');
+
+  canvas.width = 100;
+  canvas.height = 100;
+
+  var image = new Image();
+  image.addEventListener('load', function() {
+    var src_w = image.width;
+    var src_h = image.height;
+    var new_w = canvas.width;
+    var new_h = canvas.height;
+    var ratio = src_w / src_h;
+    if (src_w > src_h) {
+      new_h /= ratio;
+    } else {
+      new_w *= ratio;
+    }
+    canvas.width = new_w;
+    canvas.height = new_h;
+    context.drawImage(image, 0, 0, src_w, src_h, 0, 0, new_w, new_h);
+  });
+  image.src = url;
+};
+
+/**
+ * Returns a function which will handle displaying information about the
+ * image once the ImageInfo class has finished loading.
+ */
+function getImageInfoHandler(url) {
+  return function() {
+    renderUrl(url);
+    renderThumbnail(url);
+    var imageinfo = ImageInfo.getAllFields(url);
+    renderImageInfo(imageinfo);
+    resizeWindow();
+  };
+};
+
+/**
+ * Load the image in question and display it, along with its metadata.
+ */
+document.addEventListener("DOMContentLoaded", function () {
+  // The URL of the image to load is passed on the URL fragment.
+  var imageUrl = window.location.hash.substring(1);
+  if (imageUrl) {
+    // Use the ImageInfo library to load the image and parse it.
+    ImageInfo.loadInfo(imageUrl, getImageInfoHandler(imageUrl));
+  }
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/imageinfo/loader.gif b/chrome/common/extensions/docs/examples/extensions/imageinfo/loader.gif
new file mode 100644
index 0000000..c494f28
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/imageinfo/loader.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/imageinfo/manifest.json b/chrome/common/extensions/docs/examples/extensions/imageinfo/manifest.json
new file mode 100644
index 0000000..ecda1b1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/imageinfo/manifest.json
@@ -0,0 +1,19 @@
+{
+  "name" : "Imageinfo",
+  "version" : "1.0.1",
+  "description" : "Get image info for images, including EXIF data",
+  "background" : { "scripts": ["background.js"] },
+  "permissions" : [
+    "contextMenus",
+    "tabs",
+    "http://*/*",
+    "https://*/*"
+   ],
+  "minimum_chrome_version" : "6.0.0.0",
+  "icons" : {
+    "16" : "imageinfo-16.png",
+    "48" : "imageinfo-48.png",
+    "128" : "imageinfo-128.png"
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/README.txt b/chrome/common/extensions/docs/examples/extensions/irc/README.txt
new file mode 100644
index 0000000..692beb9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/README.txt
@@ -0,0 +1,29 @@
+This directory contains a simple irc app which is a work in progress.
+
+/app - contains the manifest and any additional resources which are to be
+    packaged in a crx.
+
+/servlet - contains the java servlet which will serve the live resources and
+    also proxy the irc traffic between the client and irc servers
+
+/conf - contains configuration files for running the servlet.
+
+This example depends on WebSockets, so it must be run inside a servlet container
+which supports WebSockets.
+
+The following are instructions for setting up a development jetty server to
+host the servlet.
+
+1) Get the jetty 7.x distribution from eclipse.org. Unpack it anywhere. We'll
+   call that directory JETTY_HOME
+2) Delete the contents of JETTY_HOME/webapps.
+3) Copy /conf/irc.xml to JETTY_HOME/contexts, edit the value of resourceBase in
+   irc.xml to point to the contents of /servlet.
+4) Copy jetty.xml and webdefault.xml to JETTY_HOME/etc
+5) Copy the following jars from JETTY_HOME/lib to /servlet/WEB-INF/lib:
+
+  jetty-client, jetty-continuation, jetty-http, jetty-io, jetty-servlets,
+  jetty-util
+  
+6) Compile /servlet/src/org/chromium/IRCProxyWebSocket.java and put the
+   resulting class file in /servlet/WEB-INF/classes
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/app/manifest.json b/chrome/common/extensions/docs/examples/extensions/irc/app/manifest.json
new file mode 100644
index 0000000..9c6b31e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/app/manifest.json
@@ -0,0 +1,11 @@
+{
+  "name": "Chromium IRC App",
+  "version": "0.1",
+  "app": {
+    "launch" : {
+      "url": "http://localhost:8080"
+    },
+    "origins": ["http://localhost:8080"]
+  },
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/conf/irc.xml b/chrome/common/extensions/docs/examples/extensions/irc/conf/irc.xml
new file mode 100644
index 0000000..13441c9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/conf/irc.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0"  encoding="ISO-8859-1"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+  <Set name="contextPath">/</Set>
+  <Set name="resourceBase">file:/D:/eclipse/irc-proxy</Set>
+  <Set name="defaultsDescriptor"><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Set>
+</Configure>
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/conf/jetty.xml b/chrome/common/extensions/docs/examples/extensions/irc/conf/jetty.xml
new file mode 100644
index 0000000..5883d61
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/conf/jetty.xml
@@ -0,0 +1,197 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the Jetty Server                                      -->
+<!--                                                                 -->
+<!-- Documentation of this file format can be found at:              -->
+<!-- http://docs.codehaus.org/display/JETTY/jetty.xml                -->
+<!--                                                                 -->
+<!-- =============================================================== -->
+
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+    <!-- =========================================================== -->
+    <!-- Server Thread Pool                                          -->
+    <!-- =========================================================== -->
+    <Set name="ThreadPool">
+      <!-- Default queued blocking threadpool 
+      -->
+      <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
+        <Set name="minThreads">10</Set>
+        <Set name="maxThreads">200</Set>
+      </New>
+
+      <!-- Optional Java 5 bounded threadpool with job queue 
+      <New class="org.eclipse.thread.concurrent.ThreadPool">
+        <Set name="corePoolSize">50</Set>
+        <Set name="maximumPoolSize">50</Set>
+      </New>
+      -->
+    </Set>
+
+
+
+    <!-- =========================================================== -->
+    <!-- Set connectors                                              -->
+    <!-- =========================================================== -->
+
+    <Call name="addConnector">
+      <Arg>
+          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
+            <Set name="host"><SystemProperty name="jetty.host" /></Set>
+            <Set name="port"><SystemProperty name="jetty.port" default="8080"/></Set>
+            <Set name="maxIdleTime">300000</Set>
+            <Set name="Acceptors">2</Set>
+            <Set name="statsOn">false</Set>
+            <Set name="confidentialPort">8443</Set>
+	    <Set name="lowResourcesConnections">20000</Set>
+	    <Set name="lowResourcesMaxIdleTime">5000</Set>
+          </New>
+      </Arg>
+    </Call>
+
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+    <!-- To add a HTTPS SSL connector                                    -->
+    <!-- mixin jetty-ssl.xml:                                            -->
+    <!--   java -jar start.jar etc/jetty.xml etc/jetty-ssl.xml           -->
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+    
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+    <!-- To add a HTTP blocking connector                                -->
+    <!-- mixin jetty-bio.xml:                                            -->
+    <!--   java -jar start.jar etc/jetty.xml etc/jetty-bio.xml           -->
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+    
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+    <!-- To allow Jetty to be started from xinetd                        -->
+    <!-- mixin jetty-xinetd.xml:                                         -->
+    <!--   java -jar start.jar etc/jetty.xml etc/jetty-xinetd.xml        -->
+    <!--                                                                 -->
+    <!-- See jetty-xinetd.xml for further instructions.                  -->
+    <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+    <!-- =========================================================== -->
+    <!-- Set handler Collection Structure                            --> 
+    <!-- =========================================================== -->
+    <Set name="handler">
+      <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
+        <Set name="handlers">
+         <Array type="org.eclipse.jetty.server.Handler">
+           <Item>
+             <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
+           </Item>
+           <Item>
+             <New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
+           </Item>
+           <Item>
+             <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler"/>
+           </Item>
+         </Array>
+        </Set>
+      </New>
+    </Set>
+    
+    <!-- =========================================================== -->
+    <!-- Configure the context deployer                              -->
+    <!-- A context deployer will deploy contexts described in        -->
+    <!-- configuration files discovered in a directory.              -->
+    <!-- The configuration directory can be scanned for hot          -->
+    <!-- deployments at the configured scanInterval.                 -->
+    <!--                                                             -->
+    <!-- This deployer is configured to deploy contexts configured   -->
+    <!-- in the $JETTY_HOME/contexts directory                       -->
+    <!--                                                             -->
+    <!-- =========================================================== -->
+    <Call name="addBean">
+      <Arg>
+        <New class="org.eclipse.jetty.deploy.ContextDeployer">
+          <Set name="contexts"><Ref id="Contexts"/></Set>
+          <Set name="configurationDir"><SystemProperty name="jetty.home" default="."/>/contexts</Set>
+          <Set name="scanInterval">5</Set>
+          <Call name="setAttribute">
+            <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
+            <Arg>.*/jsp-api-[^/]*\.jar$|.*/jsp-[^/]*\.jar$</Arg>
+          </Call>
+        </New>
+      </Arg>
+    </Call>
+
+    <!-- =========================================================== -->
+    <!-- Configure the webapp deployer.                              -->
+    <!-- A webapp  deployer will deploy standard webapps discovered  -->
+    <!-- in a directory at startup, without the need for additional  -->
+    <!-- configuration files.    It does not support hot deploy or   -->
+    <!-- non standard contexts (see ContextDeployer above).          -->
+    <!--                                                             -->
+    <!-- This deployer is configured to deploy webapps from the      -->
+    <!-- $JETTY_HOME/webapps directory                               -->
+    <!--                                                             -->
+    <!-- Normally only one type of deployer need be used.            -->
+    <!--                                                             -->
+    <!-- =========================================================== -->
+    <Call name="addBean">
+      <Arg>
+        <New class="org.eclipse.jetty.deploy.WebAppDeployer">
+          <Set name="contexts"><Ref id="Contexts"/></Set>
+          <Set name="webAppDir"><SystemProperty name="jetty.home" default="."/>/webapps</Set>
+		  <Set name="parentLoaderPriority">false</Set>
+		  <Set name="extract">true</Set>
+		  <Set name="allowDuplicates">false</Set>
+          <Set name="defaultsDescriptor"><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Set>
+          <Call name="setAttribute">
+            <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
+            <Arg>.*/jsp-api-[^/]*\.jar$|.*/jsp-[^/]*\.jar$</Arg>
+          </Call>
+        </New>
+      </Arg>
+    </Call>
+
+    <!-- =========================================================== -->
+    <!-- Configure Authentication Login Service                      -->
+    <!-- Realms may be configured for the entire server here, or     -->
+    <!-- they can be configured for a specific web app in a context  -->
+    <!-- configuration (see $(jetty.home)/contexts/test.xml for an   -->
+    <!-- example).                                                   -->
+    <!-- =========================================================== -->
+    <Call name="addBean">
+      <Arg>
+        <New class="org.eclipse.jetty.security.HashLoginService">
+          <Set name="name">Test Realm</Set>
+          <Set name="config"><SystemProperty name="jetty.home" default="."/>/etc/realm.properties</Set>
+          <Set name="refreshInterval">0</Set>
+        </New>
+      </Arg>
+    </Call>
+
+    <!-- =========================================================== -->
+    <!-- Configure Request Log                                       -->
+    <!-- Request logs  may be configured for the entire server here, -->
+    <!-- or they can be configured for a specific web app in a       -->
+    <!-- contexts configuration (see $(jetty.home)/contexts/test.xml -->
+    <!-- for an example).                                            -->
+    <!-- =========================================================== -->
+    <Ref id="RequestLog">
+      <Set name="requestLog">
+        <New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
+          <Set name="filename"><SystemProperty name="jetty.home" default="."/>/logs/yyyy_mm_dd.request.log</Set>
+          <Set name="filenameDateFormat">yyyy_MM_dd</Set>
+          <Set name="retainDays">90</Set>
+          <Set name="append">true</Set>
+          <Set name="extended">false</Set>
+          <Set name="logCookies">false</Set>
+          <Set name="LogTimeZone">GMT</Set>
+        </New>
+      </Set>
+    </Ref>
+
+    <!-- =========================================================== -->
+    <!-- extra options                                               -->
+    <!-- =========================================================== -->
+    <Set name="stopAtShutdown">true</Set>
+    <Set name="sendServerVersion">true</Set>
+    <Set name="sendDateHeader">true</Set>
+    <Set name="gracefulShutdown">1000</Set>
+
+</Configure>
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/conf/webdefault.xml b/chrome/common/extensions/docs/examples/extensions/irc/conf/webdefault.xml
new file mode 100644
index 0000000..b52cadd
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/conf/webdefault.xml
@@ -0,0 +1,404 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<!-- ===================================================================== -->
+<!-- This file contains the default descriptor for web applications.       -->
+<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+<!-- The intent of this descriptor is to include jetty specific or common  -->
+<!-- configuration for all webapps.   If a context has a webdefault.xml    -->
+<!-- descriptor, it is applied before the contexts own web.xml file        -->
+<!--                                                                       -->
+<!-- A context may be assigned a default descriptor by:                    -->
+<!--  + Calling WebApplicationContext.setDefaultsDescriptor                -->
+<!--  + Passed an arg to addWebApplications                                -->
+<!--                                                                       -->
+<!-- This file is used both as the resource within the jetty.jar (which is -->
+<!-- used as the default if no explicit defaults descriptor is set) and it -->
+<!-- is copied to the etc directory of the Jetty distro and explicitly     -->
+<!-- by the jetty.xml file.                                                -->
+<!--                                                                       -->
+<!-- ===================================================================== -->
+<web-app 
+   xmlns="http://java.sun.com/xml/ns/javaee" 
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
+   metadata-complete="true"
+   version="2.5"> 
+
+  <description>
+    Default web.xml file.  
+    This file is applied to a Web application before it's own WEB_INF/web.xml file
+  </description>
+
+
+  <!-- ==================================================================== -->
+  <!-- Context params to control Session Cookies                            -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <!-- UNCOMMENT TO ACTIVATE
+  <context-param>
+    <param-name>org.eclipse.jetty.servlet.SessionDomain</param-name>
+    <param-value>127.0.0.1</param-value>
+  </context-param>
+
+  <context-param>
+    <param-name>org.eclipse.jetty.servlet.SessionPath</param-name>
+    <param-value>/</param-value>
+  </context-param>
+
+  <context-param>
+    <param-name>org.eclipse.jetty.servlet.MaxAge</param-name>
+    <param-value>-1</param-value>
+  </context-param>
+  -->
+
+
+  <!-- ==================================================================== -->
+  <!-- The default servlet.                                                 -->
+  <!-- This servlet, normally mapped to /, provides the handling for static -->
+  <!-- content, OPTIONS and TRACE methods for the context.                  -->
+  <!-- The following initParameters are supported:                          -->
+  <!--                                                                      -->
+  <!--   acceptRanges     If true, range requests and responses are         -->
+  <!--                    supported                                         -->
+  <!--                                                                      -->
+  <!--   dirAllowed       If true, directory listings are returned if no    -->
+  <!--                    welcome file is found. Else 403 Forbidden.        -->
+  <!--                                                                      -->
+  <!--   welcomeServlets  If true, attempt to dispatch to welcome files     -->
+  <!--                    that are servlets, if no matching static          -->
+  <!--                    resources can be found.                           -->
+  <!--                                                                      -->
+  <!--   redirectWelcome  If true, redirect welcome file requests           -->
+  <!--                    else use request dispatcher forwards              -->
+  <!--                                                                      -->
+  <!--   gzip             If set to true, then static content will be served--> 
+  <!--                    as gzip content encoded if a matching resource is -->
+  <!--                    found ending with ".gz"                           -->
+  <!--                                                                      -->
+  <!--   resoureBase      Can be set to replace the context resource base   -->
+  <!--                                                                      -->
+  <!--   relativeResourceBase                                               -->
+  <!--                    Set with a pathname relative to the base of the   -->
+  <!--                    servlet context root. Useful for only serving     -->
+  <!--                    static content from only specific subdirectories. -->
+  <!--                                                                      -->
+  <!--   useFileMappedBuffer                                                -->
+  <!--                    If set to true (the default), a  memory mapped    -->
+  <!--                    file buffer will be used to serve static content  -->
+  <!--                    when using an NIO connector. Setting this value   -->
+  <!--                    to false means that a direct buffer will be used  -->
+  <!--                    instead. If you are having trouble with Windows   -->
+  <!--                    file locking, set this to false.                  -->
+  <!--                                                                      -->
+  <!--  cacheControl      If set, all static content will have this value   -->
+  <!--                    set as the cache-control header.                  -->
+  <!--                                                                      -->
+  <!--  maxCacheSize      Maximum size of the static resource cache         -->
+  <!--                                                                      -->
+  <!--  maxCachedFileSize Maximum size of any single file in the cache      -->
+  <!--                                                                      -->
+  <!--  maxCachedFiles    Maximum number of files in the cache              -->
+  <!--                                                                      -->
+  <!--  cacheType         "nio", "bio" or "both" to determine the type(s)   -->
+  <!--                    of resource cache. A bio cached buffer may be used-->
+  <!--                    by nio but is not as efficient as a nio buffer.   -->
+  <!--                    An nio cached buffer may not be used by bio.      -->
+  <!--                                                                      -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <servlet>
+    <servlet-name>default</servlet-name>
+    <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
+    <init-param>
+      <param-name>acceptRanges</param-name>
+      <param-value>true</param-value>
+    </init-param>
+    <init-param>
+      <param-name>dirAllowed</param-name>
+      <param-value>true</param-value>
+    </init-param>
+    <init-param>
+      <param-name>welcomeServlets</param-name>
+ 	    <param-value>false</param-value>
+ 	  </init-param>
+    <init-param>
+      <param-name>redirectWelcome</param-name>
+      <param-value>false</param-value>
+    </init-param>
+    <init-param>
+      <param-name>maxCacheSize</param-name>
+      <param-value>256000000</param-value>
+    </init-param>
+    <init-param>
+      <param-name>maxCachedFileSize</param-name>
+      <param-value>10000000</param-value>
+    </init-param>
+    <init-param>
+      <param-name>maxCachedFiles</param-name>
+      <param-value>1000</param-value>
+    </init-param>
+    <init-param>
+      <param-name>cacheType</param-name>
+      <param-value>both</param-value>
+    </init-param>
+    <init-param>
+      <param-name>gzip</param-name>
+      <param-value>true</param-value>
+    </init-param>
+    <init-param>
+      <param-name>useFileMappedBuffer</param-name>
+      <param-value>false</param-value>
+    </init-param>  
+    <!--
+    <init-param>
+      <param-name>cacheControl</param-name>
+      <param-value>max-age=3600,public</param-value>
+    </init-param>
+    -->
+    <load-on-startup>0</load-on-startup>
+  </servlet> 
+
+  <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
+  
+
+  <!-- ==================================================================== -->
+  <!-- JSP Servlet                                                          -->
+  <!-- This is the jasper JSP servlet from the jakarta project              -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <!-- The JSP page compiler and execution servlet, which is the mechanism  -->
+  <!-- used by Glassfish to support JSP pages.  Traditionally, this servlet -->
+  <!-- is mapped to URL patterh "*.jsp".  This servlet supports the         -->
+  <!-- following initialization parameters (default values are in square    -->
+  <!-- brackets):                                                           -->
+  <!--                                                                      -->
+  <!--   checkInterval       If development is false and reloading is true, -->
+  <!--                       background compiles are enabled. checkInterval -->
+  <!--                       is the time in seconds between checks to see   -->
+  <!--                       if a JSP page needs to be recompiled. [300]    -->
+  <!--                                                                      -->
+  <!--   compiler            Which compiler Ant should use to compile JSP   -->
+  <!--                       pages.  See the Ant documenation for more      -->
+  <!--                       information. [javac]                           -->
+  <!--                                                                      -->
+  <!--   classdebuginfo      Should the class file be compiled with         -->
+  <!--                       debugging information?  [true]                 -->
+  <!--                                                                      -->
+  <!--   classpath           What class path should I use while compiling   -->
+  <!--                       generated servlets?  [Created dynamically      -->
+  <!--                       based on the current web application]          -->
+  <!--                       Set to ? to make the container explicitly set  -->
+  <!--                       this parameter.                                -->
+  <!--                                                                      -->
+  <!--   development         Is Jasper used in development mode (will check -->
+  <!--                       for JSP modification on every access)?  [true] -->
+  <!--                                                                      -->
+  <!--   enablePooling       Determines whether tag handler pooling is      -->
+  <!--                       enabled  [true]                                -->
+  <!--                                                                      -->
+  <!--   fork                Tell Ant to fork compiles of JSP pages so that -->
+  <!--                       a separate JVM is used for JSP page compiles   -->
+  <!--                       from the one Tomcat is running in. [true]      -->
+  <!--                                                                      -->
+  <!--   ieClassId           The class-id value to be sent to Internet      -->
+  <!--                       Explorer when using <jsp:plugin> tags.         -->
+  <!--                       [clsid:8AD9C840-044E-11D1-B3E9-00805F499D93]   -->
+  <!--                                                                      -->
+  <!--   javaEncoding        Java file encoding to use for generating java  -->
+  <!--                       source files. [UTF-8]                          -->
+  <!--                                                                      -->
+  <!--   keepgenerated       Should we keep the generated Java source code  -->
+  <!--                       for each page instead of deleting it? [true]   -->
+  <!--                                                                      -->
+  <!--   logVerbosityLevel   The level of detailed messages to be produced  -->
+  <!--                       by this servlet.  Increasing levels cause the  -->
+  <!--                       generation of more messages.  Valid values are -->
+  <!--                       FATAL, ERROR, WARNING, INFORMATION, and DEBUG. -->
+  <!--                       [WARNING]                                      -->
+  <!--                                                                      -->
+  <!--   mappedfile          Should we generate static content with one     -->
+  <!--                       print statement per input line, to ease        -->
+  <!--                       debugging?  [false]                            -->
+  <!--                                                                      -->
+  <!--                                                                      -->
+  <!--   reloading           Should Jasper check for modified JSPs?  [true] -->
+  <!--                                                                      -->
+  <!--   suppressSmap        Should the generation of SMAP info for JSR45   -->
+  <!--                       debugging be suppressed?  [false]              -->
+  <!--                                                                      -->
+  <!--   dumpSmap            Should the SMAP info for JSR45 debugging be    -->
+  <!--                       dumped to a file? [false]                      -->
+  <!--                       False if suppressSmap is true                  -->
+  <!--                                                                      -->
+  <!--   scratchdir          What scratch directory should we use when      -->
+  <!--                       compiling JSP pages?  [default work directory  -->
+  <!--                       for the current web application]               -->
+  <!--                                                                      -->
+  <!--   tagpoolMaxSize      The maximum tag handler pool size  [5]         -->
+  <!--                                                                      -->
+  <!--   xpoweredBy          Determines whether X-Powered-By response       -->
+  <!--                       header is added by generated servlet  [false]  -->
+  <!--                                                                      -->
+  <!-- If you wish to use Jikes to compile JSP pages:                       -->
+  <!--   Set the init parameter "compiler" to "jikes".  Define              -->
+  <!--   the property "-Dbuild.compiler.emacs=true" when starting Jetty     -->
+  <!--   to cause Jikes to emit error messages in a format compatible with  -->
+  <!--   Jasper.                                                            -->
+  <!--   If you get an error reporting that jikes can't use UTF-8 encoding, -->
+  <!--   try setting the init parameter "javaEncoding" to "ISO-8859-1".     -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <servlet id="jsp">
+    <servlet-name>jsp</servlet-name>
+    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
+    <init-param>
+        <param-name>logVerbosityLevel</param-name>
+        <param-value>DEBUG</param-value>
+    </init-param>
+    <init-param>
+        <param-name>fork</param-name>
+        <param-value>false</param-value>
+    </init-param>
+    <init-param>
+        <param-name>xpoweredBy</param-name>
+        <param-value>false</param-value>
+    </init-param>
+    <!--  
+    <init-param>
+        <param-name>classpath</param-name>
+        <param-value>?</param-value>
+    </init-param>
+    -->
+    <load-on-startup>0</load-on-startup>
+  </servlet>
+
+  <servlet-mapping> 
+    <servlet-name>jsp</servlet-name> 
+    <url-pattern>*.jsp</url-pattern> 
+    <url-pattern>*.jspf</url-pattern>
+    <url-pattern>*.jspx</url-pattern>
+    <url-pattern>*.xsp</url-pattern>
+    <url-pattern>*.JSP</url-pattern> 
+    <url-pattern>*.JSPF</url-pattern>
+    <url-pattern>*.JSPX</url-pattern>
+    <url-pattern>*.XSP</url-pattern>
+  </servlet-mapping>
+  
+  <!-- ==================================================================== -->
+  <!-- Dynamic Servlet Invoker.                                             -->
+  <!-- This servlet invokes anonymous servlets that have not been defined   -->
+  <!-- in the web.xml or by other means. The first element of the pathInfo  -->
+  <!-- of a request passed to the envoker is treated as a servlet name for  -->
+  <!-- an existing servlet, or as a class name of a new servlet.            -->
+  <!-- This servlet is normally mapped to /servlet/*                        -->
+  <!-- This servlet support the following initParams:                       -->
+  <!--                                                                      -->
+  <!--  nonContextServlets       If false, the invoker can only load        -->
+  <!--                           servlets from the contexts classloader.    -->
+  <!--                           This is false by default and setting this  -->
+  <!--                           to true may have security implications.    -->
+  <!--                                                                      -->
+  <!--  verbose                  If true, log dynamic loads                 -->
+  <!--                                                                      -->
+  <!--  *                        All other parameters are copied to the     -->
+  <!--                           each dynamic servlet as init parameters    -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <!-- Uncomment for dynamic invocation
+  <servlet>
+    <servlet-name>invoker</servlet-name>
+    <servlet-class>org.eclipse.jetty.servlet.Invoker</servlet-class>
+    <init-param>
+      <param-name>verbose</param-name>
+      <param-value>false</param-value>
+    </init-param>
+    <init-param>
+      <param-name>nonContextServlets</param-name>
+      <param-value>false</param-value>
+    </init-param>
+    <init-param>
+      <param-name>dynamicParam</param-name>
+      <param-value>anyValue</param-value>
+    </init-param>
+    <load-on-startup>0</load-on-startup>
+  </servlet>
+
+  <servlet-mapping> <servlet-name>invoker</servlet-name> <url-pattern>/servlet/*</url-pattern> </servlet-mapping>
+  -->
+
+
+
+  <!-- ==================================================================== -->
+  <session-config>
+    <session-timeout>30</session-timeout>
+  </session-config>
+
+  <!-- ==================================================================== -->
+  <!-- Default MIME mappings                                                -->
+  <!-- The default MIME mappings are provided by the mime.properties        -->
+  <!-- resource in the org.eclipse.jetty.server.jar file.  Additional or modified  -->
+  <!-- mappings may be specified here                                       -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <!-- UNCOMMENT TO ACTIVATE
+  <mime-mapping>
+    <extension>mysuffix</extension>
+    <mime-type>mymime/type</mime-type>
+  </mime-mapping>
+  -->
+
+  <!-- ==================================================================== -->
+  <welcome-file-list>
+    <welcome-file>index.html</welcome-file>
+    <welcome-file>index.htm</welcome-file>
+    <welcome-file>index.jsp</welcome-file>
+  </welcome-file-list>
+
+  <!-- ==================================================================== -->
+  <locale-encoding-mapping-list>
+    <locale-encoding-mapping><locale>ar</locale><encoding>ISO-8859-6</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>be</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>bg</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>ca</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>cs</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>da</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>de</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>el</locale><encoding>ISO-8859-7</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>en</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>es</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>et</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>fi</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>fr</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>hr</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>hu</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>is</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>it</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>iw</locale><encoding>ISO-8859-8</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>ja</locale><encoding>Shift_JIS</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>ko</locale><encoding>EUC-KR</encoding></locale-encoding-mapping>     
+    <locale-encoding-mapping><locale>lt</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>lv</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>mk</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>nl</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>no</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>pl</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>pt</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>ro</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>ru</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>sh</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>sk</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>sl</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>sq</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>sr</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>sv</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>tr</locale><encoding>ISO-8859-9</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>uk</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>zh</locale><encoding>GB2312</encoding></locale-encoding-mapping>
+    <locale-encoding-mapping><locale>zh_TW</locale><encoding>Big5</encoding></locale-encoding-mapping>   
+  </locale-encoding-mapping-list>
+  
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>Disable TRACE</web-resource-name>
+      <url-pattern>/</url-pattern>
+      <http-method>TRACE</http-method>
+    </web-resource-collection>
+    <auth-constraint/>
+  </security-constraint>
+  
+</web-app>
+
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/WEB-INF/web.xml b/chrome/common/extensions/docs/examples/extensions/irc/servlet/WEB-INF/web.xml
new file mode 100644
index 0000000..151b76d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/WEB-INF/web.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app 
+   xmlns="http://java.sun.com/xml/ns/javaee" 
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
+   version="2.5"> 
+  <display-name>IRC-Proxy</display-name>
+  <context-param>
+    <param-name>org.eclipse.jetty.server.context.ManagedAttributes</param-name>
+    <param-value>org.eclipse.jetty.servlets.ProxyServlet.Logger,org.eclipse.jetty.servlets.ProxyServlet.ThreadPool,org.eclipse.jetty.servlets.ProxyServlet.HttpClient</param-value>
+  </context-param>
+  <servlet>
+    <servlet-name>irc-proxy</servlet-name>
+    <servlet-class>org.chromium.IRCProxyWebSocket</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>irc-proxy</servlet-name>
+    <url-pattern>/ws/*</url-pattern>
+  </servlet-mapping>
+</web-app>
+
+
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/addChannel.html b/chrome/common/extensions/docs/examples/extensions/irc/servlet/addChannel.html
new file mode 100644
index 0000000..c72cfab
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/addChannel.html
@@ -0,0 +1,61 @@
+<html>
+  <head>
+    <link rel="stylesheet" type="text/css" href="styles.css"> 
+    <script src="jstemplate/util.js" type="text/javascript"></script>
+    <script src="jstemplate/jsevalcontext.js" type="text/javascript"></script>
+    <script src="jstemplate/jstemplate.js" type="text/javascript"></script> 
+    <script src="util.js" type="text/javascript"></script> 
+    <script>
+function addChannel() {
+  try {
+    var servers = JSON.parse(localStorage.servers || "[]");
+    var channelName = $F('channel');
+    var serverName = $F('serverSelect')
+    servers.forEach(function(server) {
+      if (server.name == serverName) {
+        server.channels = server.channels || [];
+        server.channels.forEach(function(channel) {
+          if (channel == channelName) {
+            throw channelName + " is already open";
+          }
+        });
+        server.channels.push(channelName);
+      }
+    });
+    
+    localStorage.servers = JSON.stringify(servers);
+    window.opener.syncChannelList();
+    window.opener.joinChannel(serverName, channelName);
+    window.close();
+  } catch (ex) {
+    alert(ex);
+  }
+}
+
+window.onload = function() {
+  var servers = JSON.parse(localStorage.servers || "[]");
+  if (servers.length == 0) {
+    alert("You must first add a server connection");
+    close();
+  }
+
+  jstProcess(new JsEvalContext(servers), $('serverSelect'));
+}
+
+    </script>
+  </head>
+  <body>
+    <div>
+      <select id="serverSelect">
+        <option jsselect="$this" jscontent="name"></option>
+      </select>
+      <input id="channel" type="text" value="#channel">
+    </div>
+    <div>
+    </div>
+    <div>
+      <input type="button" value="Add New Channel"
+             onclick="addChannel();">
+    </div>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/addServer.html b/chrome/common/extensions/docs/examples/extensions/irc/servlet/addServer.html
new file mode 100644
index 0000000..dd6ed2e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/addServer.html
@@ -0,0 +1,54 @@
+<html>
+  <head>
+     <link rel="stylesheet" type="text/css" href="styles.css"> 
+    <script src="util.js" type="text/javascript"></script> 
+    <script>
+function addServer() {
+  try {
+    var servers = JSON.parse(localStorage.servers || "[]");
+    var serverName = $F('serverText');
+  
+    servers.forEach(function(server) {
+      if (server.name == serverName) {
+        throw "Connection to " + serverName + " already established";
+      }
+    });
+
+    var portValue = parseInt($F('serverPort'));
+    if (isNaN(portValue)) {
+      throw $F('serverPort') + " is not a valid port value";
+    }
+
+    var nickValue = $F('nick');
+    var newServer = {
+      name: serverName,
+      port: portValue,
+      nick: nickValue,
+      channels: []
+    };
+    
+    servers.push(newServer);
+    
+    localStorage.servers = JSON.stringify(servers);
+    window.opener.addServerConnection(newServer);
+    close();
+  } catch (ex) {
+    alert(ex);
+  }
+}
+    </script>
+  </head>
+  <body>
+    <div>
+      <input id="serverText" type="text" value="irc.freenode.net">
+      <input id="serverPort" type="text" value="6667">
+    </div>
+    <div>
+      <input id="nick" type="text" value="nick">
+    </div>
+    <div>
+      <input type="button" value="Add New Server"
+             onclick="addServer();">
+    </div>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/index.html b/chrome/common/extensions/docs/examples/extensions/irc/servlet/index.html
new file mode 100644
index 0000000..17fb215
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/index.html
@@ -0,0 +1,412 @@
+<html>
+  <head>
+    <title>ChromiumIRC</title>
+    <link rel="stylesheet" type="text/css" href="styles.css"> 
+    <script src="jstemplate/util.js" type="text/javascript"></script>
+    <script src="jstemplate/jsevalcontext.js" type="text/javascript"></script>
+    <script src="jstemplate/jstemplate.js" type="text/javascript"></script> 
+    <script src="util.js" type="text/javascript"></script>
+    <script lang="JavaScript" src="irc.js"></script>
+    <script>
+
+var ircConnections = {};
+
+// The server & channel configutation data is stored in localStorage.servers.
+// These are setters and getters for this structure.
+function servers() {
+  return JSON.parse(localStorage.servers || "[]");
+}
+function setServers(servers) {
+  localStorage.servers = JSON.stringify(servers);
+}
+
+// Channel list is a sorted list of "server#channel" strings. This maps to
+// channel slides as represented in the UI.
+function channelList() {
+  var channelList = [];
+  servers().forEach(function(server) {
+    server.channels = server.channels || [];
+    server.channels.forEach(function(channel) {
+      channelList.push(server.name + channel);
+    });
+  });
+
+  channelList.sort();
+  return channelList;
+}
+
+window.onload = function() {
+  // Setup notifications.
+  window.onfocus = function() {
+    windowHasFocus = true;
+    clearNotifications();
+  }
+  window.onblur = function() {
+    windowHasFocus = false;
+  }
+
+  syncChannelList();
+
+  // Setup channel navigation and message entry.
+  function handleBodyKeyDown(event) {
+    switch (event.keyCode) {
+      case 37: // left arrow
+        slideTo(-1);
+        break;
+      case 39: // right arrow
+        slideTo(1);
+        break;
+    }
+  }
+  document.body.addEventListener('keydown', handleBodyKeyDown, false);
+
+  // We don't want left & right arrow inside the text entry to move the channel
+  // slides.
+  $('typingDiv').addEventListener('keydown', function(event) {
+    event.stopPropagation();
+  });
+  $('entryText').addEventListener('keydown', function(event) {
+    if (event.keyCode == 13) { // RETURN key.
+      processEntryMessage();
+    }
+  });
+
+  servers().forEach(addServerConnection);
+};
+
+window.onunload = function() {
+  ircConnections.forEach(function(irc) {
+    irc.disconnect();  
+  });
+}
+
+function addServerConnection(server) {
+  var ws = new WebSocket("ws://" + location.host + "/ws");
+  var irc = new IRCConnection(server.name, server.port, server.nick,
+                              ws.send.bind(ws), // sendFunc
+                              ws.close.bind(ws)); // closeFunc
+  ws.onopen = irc.onOpened.bind(irc);
+  ws.onclose = irc.onClosed.bind(irc);
+  ws.onmessage = function(message) {
+    irc.onMessage(message.data);  
+  };
+  irc.onConnect = function(message) {
+    server.channels.forEach(function(channel) {
+      ircConnections[server.name].joinChannel(channel);
+    });
+  };
+  irc.onDisconnect = function(message) {
+  };
+  irc.onText = function(channel, nick, message) {
+    checkForNickReference(server, channel, nick, message);
+    addMessage(server.name, channel, nick, new Date(), message);
+  };
+
+  ircConnections[server.name] = irc;
+}
+
+function joinChannel(serverName, channelName) {
+  ircConnections[serverName].joinChannel(channelName);
+}
+
+function removeChannelListener(channelName) {
+  return function(event) {
+    event.stopPropagation();
+    
+    var servers = servers();
+    servers.forEach(function(server) {
+      if (channelName.indexOf(server.name) == 0) {
+        for (var i = 0; server.channels.length; i++) {
+          if (channelName == server.name + server.channels[i]) {
+            ircConnections[server.name].quitChannel(server.channels[i]);
+            server.channels.splice(i, 1);
+            break;
+          }
+        }
+      }
+    });
+
+    setServers(servers);
+    syncChannelList();
+  };
+}
+
+function syncChannelList() {
+  var channels = channelList();
+  var channelSlides = $('channelSlides');
+  var channelSlideProto = $('channelSlideProto');
+
+  var channelIndex = 0;
+  var slideIndex = 0;
+
+  while(channelIndex < channels.length || 
+        channels.length != channelSlides.children.length) {
+    var channel = channels[channelIndex];
+    var slide = channelSlides.children[slideIndex];
+
+    if (slideIndex == channelSlides.children.length ||
+        channel < slideChannel(slide)) {
+      // Add a new slide.
+      var newSlide = channelSlideProto.cloneNode(true);
+      jstProcess(new JsEvalContext({ name: channel }), newSlide);
+      newSlide.setAttribute("id", "channel-" + channel);
+      newSlide.style.display = "";
+      if (slideIndex == channelSlides.children.length) {
+        channelSlides.appendChild(newSlide);
+      } else {
+        channelSlides.insertBefore(newSlide, slide);
+      }
+      newSlide.addEventListener('click', onClickMoveSlide);
+      childNodeWithClass(newSlide, "removeButton")
+          .addEventListener('click', removeChannelListener(channel));
+      
+      slide = newSlide;
+    } else if (!channel || channel > slideChannel(slide)) {
+      // Delete a removed slide.
+      
+      // If the removed slide is the current slide, we have to pick a new
+      // current slide.
+      if (localStorage.currentSlide == slideChannel(slide)) {
+        if (slide.nextSibling) {
+          localStorage.currentSlide = slideChannel(slide.nextSibling);
+        } else if (channels.length == 0) {
+          localStorage.currentSlide = "";
+        } else if (slideIndex < channelSlides.children.length) {
+          localStorage.currentSlide =
+              slideChannel(channelSlides.children[slideIndex - 1]);
+        }
+      }
+      channelSlides.removeChild(slide);  
+    } else {
+      channelIndex++;
+      slideIndex++;
+    }
+
+    slide.setAttribute("slide", "" + slideIndex - 1);
+  }
+
+  slideTo();
+}
+
+function processEntryMessage() {
+  var message = $('entryText').value;
+  $('entryText').value = "";
+
+  if (!localStorage.currentSlide) {
+    alert('No current channel');
+    return;
+  }
+  
+  var server;
+  var channel;
+  var nick;
+  servers().forEach(function(s) {
+    if (localStorage.currentSlide.indexOf(s.name) == 0) {
+      server = s.name;
+      nick = s.nick;
+      s.channels.forEach(function(c) {
+        if (localStorage.currentSlide == s.name + c) {
+          channel = c;
+        }
+      });
+    }
+  });
+
+  addMessage(server, channel, nick, new Date(), message);
+  ircConnections[server].sendMessage([channel], message);
+}
+
+function addMessage(server, channel, nick, time, body) {
+  messageLine = childNodeWithClass($('channelSlideProto'), "messageLine");
+  var newMessageLine = messageLine.cloneNode(true);
+
+  jstProcess(new JsEvalContext({ 
+    'nick': nick,
+    'time': time,
+    'body': body
+  }), newMessageLine);
+  newMessageLine.style.display = "";
+
+  var messageList =
+      childNodeWithClass($("channel-" + server + channel), "messageList");
+  messageList.appendChild(newMessageLine);
+}
+
+function formatTime(time) {
+  return "";
+}
+
+/**
+ * Slide Navigation. 
+ */
+ 
+// Returns the server#channel string value for a given |slide| element.
+function slideChannel(slide) {
+  return childNodeWithClass(slide, "channel").innerText;
+}
+
+// Handler for clicking on the visible portions of the previous & next slides.
+function onClickMoveSlide() {
+  if (localStorage.currentSlide != slideChannel(this)) {
+    localStorage.currentSlide = slideChannel(this);
+    slideTo();
+  }  
+}
+
+// Handles navigating between the channel slides. If |slideDelta| is given,
+// it should specify the number of slides to move left (negative value) or right
+// positive value. If |slideDelta| is not provided, It ensures that
+// |localStorage.currentSlide| is navigated to.
+function slideTo(slideDelta) {
+  var slide;
+  var slideNumber;
+
+  if (localStorage.currentSlide) {
+    slide = document.getElementById("channel-" + localStorage.currentSlide);
+    if (slide) {
+      slideNumber = parseInt(slide.getAttribute("slide"));
+    }
+  }
+  if (isNaN(slideNumber) || !slide) {
+    slideNumber = 0;
+  }
+  if (typeof(slideDelta) == "number") {
+    slideNumber += slideDelta;
+  }
+
+  var slides = document.getElementsByClassName("channelSlide");
+  if (slideNumber < 0 || slideNumber == slides.length - 1) {
+    return;
+  }
+
+  for (var i = 0; i < slides.length; i++) {
+    var slide = slides[i];
+    var slideIndex = parseInt(slide.getAttribute("slide")) - slideNumber;
+    
+    if (slideIndex <= -2) {
+      slide.className = "channelSlide far-left";
+    }
+    if (slideIndex >= 2) {
+      slide.className = "channelSlide far-right";
+    }
+    
+    switch(slideIndex) {
+      case -1:
+        slide.className = "channelSlide left";
+        break;
+      case 0:
+        slide.className = "channelSlide center";
+        localStorage.currentSlide = slideChannel(slide);
+        break;
+      case 1:
+        slide.className = "channelSlide right";
+        break;
+    }
+  }
+
+  clearNotifications();
+}
+
+/**
+ * Notifications
+ */
+var windowHasFocus = false;
+var notifications = {};
+
+function clearNotifications() {
+  for (property in notifications) {
+    notifications[property].cancel();
+  }
+
+  notifications = {};
+}
+
+function checkForNickReference(server, channel, nick, message) {
+  if (windowHasFocus || !message || message.indexOf(server.nick) < 0) {
+    return;
+  }
+
+  // Notifications will be enabled by the app install. Otherwise, don't notity.
+  if (window.webkitNotifications.checkPermission() != 0) {
+    return;
+  }
+  
+  // Remove a previous notification from the same channel. Show the newer one.
+  if (notifications[server.name + channel]) {
+    notifications[server.name + channel].cancel();
+  }
+
+  var title = "On " + server.name + channel;
+  var icon = "http://www.google.com/favicon.ico";
+  var text = nick + ": " + message;
+  var url = location.protocol + "//" + location.host + "/notification.html";
+  url += "?title=" + encodeURIComponent(title) +
+         "&content=" + encodeURIComponent(text);
+
+  var n = window.webkitNotifications.createHTMLNotification(url);
+  n.ondisplay = function() {};
+  n.onclose = function() {
+    delete notifications[server.name + channel];
+  };
+  n.show();
+
+  notifications[server.name + channel] = n;
+}
+    </script>
+  </head>
+  <body>
+    <!--  TEMPLATES -->
+    <div id="channelSlideProto" style="display:none" class="channelSlide">
+      <div class="channelControls">
+        <div jscontent="name" class="channel">
+        </div>
+        <div class="removeButton">
+          x
+        </div>
+      </div>
+      <div class="channelSlideContainer">
+        <div class="messageListContainer">
+          <div class="messageList">
+            <div jsselect="messages">
+              <div class="messageLine">
+                <div jscontent="nick" class="messageSender"></div>:
+                <div jscontent="body" class="messageBody"></div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="messageListSpacer">.</div>
+      </div>
+    </div>
+    
+    <div id="pageContainer">
+      <div id="headerContainer">
+        <div id="pageControls">
+          <div onclick="window.open('addServer.html');">
+            <div class="addControlLabel">
+              add server
+            </div>
+            <div class="addButton">
+              +
+            </div>
+          </div>
+          <div onclick=" window.open('addChannel.html');">
+            <div class="addControlLabel">
+              add channel
+            </div>
+            <div class="addButton">
+              +
+            </div>
+          </div>
+        </div>
+      </div>
+      <div id="slideContainer">
+        <div id="typingDiv">
+          <input type="text" id="entryText" value="">
+        </div>
+        <div style="" id="channelSlides">
+        </div>
+      </div>
+    </div>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/irc.js b/chrome/common/extensions/docs/examples/extensions/irc/servlet/irc.js
new file mode 100644
index 0000000..b169952
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/irc.js
@@ -0,0 +1,188 @@
+/*

+ * IRCConnection is a simple implementation of the IRC protocol. A small

+ * subset of the IRC commands are implemented. To be functional, IRCConnection

+ * needs some mechanism of transport to be hooked up by:

+ * -Passing in |sendFunc| and |closeFunc| which an IRCConnection to use to send

+ *  an IRC message command and to close the connection respectively.

+ * -Connecting the in-bound functions |onOpened|, |onMessage|, and |onClosed|,

+ *  to the transport so that the IRCConnection can respond to the connection

+ *  being opened, a message being received and the connection being closed.

+ */

+

+function NoOp() {};

+function log(message) { console.log(message); };

+

+function IRCConnection(server, port, nick, sendFunc, closeFunc) {

+  this.server = server;

+  this.port = port;

+  this.nick = nick;

+  this.connected = false;

+  

+  var that = this;

+

+  /**

+   * Client API

+   */

+  this.onConnect = NoOp;

+  this.onDisconnect = NoOp;

+  this.onText = NoOp;

+  this.onNotice = NoOp;

+  this.onNickReferenced = NoOp;

+

+  this.joinChannel = function(channel) {

+    sendCommand(commands.JOIN, [channel], "");

+  };

+

+  this.sendMessage = function(recipient, message) {

+    sendCommand(commands.PRIVMSG, [recipient], message);

+  };

+

+  this.quitChannel = function(channel) {

+    sendCommand(commands.PART, [channel], "");

+  }

+

+  this.disconnect = function(message) {

+    sendCommand(commands.QUIT, [], message);

+    closeFunc();

+  }

+

+  /**

+   * Transport Interface

+   * Whatever transport is used must provide and connect to the following

+   * in-bound events.

+   */

+  this.onOpened = function() {

+    sendFunc(that.server + ":" + that.port);

+    sendCommand(commands.NICK, [this.nick], "");

+    sendCommand(commands.USER,

+                ["chromium-irc-lib", "chromium-ircproxy", "*"],

+                "indigo");

+  };

+

+  this.onMessage = function(message) {

+    log("<< " + message);

+    if (!message || !message.length) {

+      return;

+    }

+

+    var parsed = parseMessage(message);

+

+    // Respond to PING command.

+    if (parsed.command == commands.PING) {

+      sendCommand(commands.PONG, [], parsed.body);

+      return;

+    }

+

+    // Process PRIVMSG.

+    if (parsed.command == commands.PRIVMSG) {

+      if (parsed.body.charCodeAt(0) == 1) {

+        // Ignore CTCP.

+        return;

+      }

+      that.onText(parsed.parameters[0],

+                  parsed.prefix.split("!")[0],

+                  parsed.body);

+      return;

+    }

+

+    // TODO: Other IRC commands.

+    var commandCode = parseInt(parsed.command);

+    if (commandCode == NaN) {

+      return;

+    }

+

+    switch(commandCode) {

+      case 001:  // Server welcome message.

+        that.connected = true;

+        that.onConnect(parsed.body);

+        break;

+      case 002:

+      case 003:

+      case 004:

+      case 005:

+        if (!that.connected) {

+          that.connected = true;

+          that.onConnect();

+        }

+        break;

+      case 433:  // TODO(rafaelw): Nickname in use. 

+        throw "NOT IMPLEMENTED";

+        break;

+      default:

+        break;

+    }

+  }

+

+  this.onClosed = function() {

+    that.connected = false;

+    that.onDisconnect();

+  };

+

+  /**

+   * IRC Implementation

+   * What follows in a minimal implementation of the IRC protocol.

+   * Only |commands| are currently implemented.

+   */

+  var commands = {

+    JOIN: "JOIN",

+    NICK: "NICK",

+    NOTICE: "NOTICE",

+    PART: "PART",

+    PING: "PING",

+    PONG: "PONG",

+    PRIVMSG: "PRIVMSG",

+    QUIT: "QUIT",

+    USER: "USER"

+  };

+

+  function parseMessage(message) {

+    var parsed = {};

+    parsed.prefix = "";

+    parsed.command = "";

+    parsed.parameters = [];

+    parsed.body = "";

+

+    // Trim trailing CRLF.

+    var crlfIndex = message.indexOf("\r\n");

+    if(crlfIndex >= 0) {

+      message = message.substring(0, crlfIndex);

+    }

+

+    // If leading character is ':', the message starts with a prefix.

+    if (message.indexOf(':') == 0) {

+      parsed.prefix = message.substring(1, message.indexOf(" "));

+      message = message.substring(parsed.prefix.length + 2);

+

+      // Forward past extra whitespace.

+      while(message.indexOf(" ") == 0) {

+        message = message.substring(1);

+      }

+    }

+

+    // If there is still a ':', then the message has trailing body.

+    var bodyMarker = message.indexOf(':');

+    if (bodyMarker >= 0) {

+      parsed.body = message.substring(bodyMarker + 1);

+      message = message.substring(0, bodyMarker);

+    }

+

+    parsed.parameters = message.split(" ");

+    parsed.command = parsed.parameters.shift();  // First param is the command.

+

+    return parsed;

+  }

+

+  function sendCommand(command, params, message) {

+    var line = command;

+    if (params && params.length > 0) {

+      line += " " + params.join(" ");

+    }

+    if (message && message.length > 0) {

+      line += " :"  + message;

+    }

+

+    log(">> " + line);

+    line += "\r\n";

+    sendFunc(line);

+  };

+};

diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/jsevalcontext.js b/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/jsevalcontext.js
new file mode 100644
index 0000000..0fc00ff
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/jsevalcontext.js
@@ -0,0 +1,409 @@
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+/**
+ * Author: Steffen Meschkat <mesch@google.com>
+ *
+ * @fileoverview This class is used to evaluate expressions in a local
+ * context. Used by JstProcessor.
+ */
+
+
+/**
+ * Names of special variables defined by the jstemplate evaluation
+ * context. These can be used in js expression in jstemplate
+ * attributes.
+ */
+var VAR_index = '$index';
+var VAR_count = '$count';
+var VAR_this = '$this';
+var VAR_context = '$context';
+var VAR_top = '$top';
+
+
+/**
+ * The name of the global variable which holds the value to be returned if
+ * context evaluation results in an error. 
+ * Use JsEvalContext.setGlobal(GLOB_default, value) to set this.
+ */
+var GLOB_default = '$default';
+
+
+/**
+ * Un-inlined literals, to avoid object creation in IE6. TODO(mesch):
+ * So far, these are only used here, but we could use them thoughout
+ * the code and thus move them to constants.js.
+ */
+var CHAR_colon = ':';
+var REGEXP_semicolon = /\s*;\s*/;
+
+
+/**
+ * See constructor_()
+ * @param {Object|null} opt_data
+ * @param {Object} opt_parent
+ * @constructor
+ */
+function JsEvalContext(opt_data, opt_parent) {
+  this.constructor_.apply(this, arguments);
+}
+
+/**
+ * Context for processing a jstemplate. The context contains a context
+ * object, whose properties can be referred to in jstemplate
+ * expressions, and it holds the locally defined variables.
+ *
+ * @param {Object|null} opt_data The context object. Null if no context.
+ *
+ * @param {Object} opt_parent The parent context, from which local
+ * variables are inherited. Normally the context object of the parent
+ * context is the object whose property the parent object is. Null for the
+ * context of the root object.
+ */
+JsEvalContext.prototype.constructor_ = function(opt_data, opt_parent) {
+  var me = this;
+
+  /**
+   * The context for variable definitions in which the jstemplate
+   * expressions are evaluated. Other than for the local context,
+   * which replaces the parent context, variable definitions of the
+   * parent are inherited. The special variable $this points to data_.
+   *
+   * If this instance is recycled from the cache, then the property is
+   * already initialized.
+   *
+   * @type {Object}
+   */
+  if (!me.vars_) {
+    me.vars_ = {};
+  }
+  if (opt_parent) {
+    // If there is a parent node, inherit local variables from the
+    // parent.
+    copyProperties(me.vars_, opt_parent.vars_);
+  } else {
+    // If a root node, inherit global symbols. Since every parent
+    // chain has a root with no parent, global variables will be
+    // present in the case above too. This means that globals can be
+    // overridden by locals, as it should be.
+    copyProperties(me.vars_, JsEvalContext.globals_);
+  }
+
+  /**
+   * The current context object is assigned to the special variable
+   * $this so it is possible to use it in expressions.
+   * @type Object
+   */
+  me.vars_[VAR_this] = opt_data;
+
+  /**
+   * The entire context structure is exposed as a variable so it can be
+   * passed to javascript invocations through jseval.
+   */
+  me.vars_[VAR_context] = me;
+
+  /**
+   * The local context of the input data in which the jstemplate
+   * expressions are evaluated. Notice that this is usually an Object,
+   * but it can also be a scalar value (and then still the expression
+   * $this can be used to refer to it). Notice this can even be value,
+   * undefined or null. Hence, we have to protect jsexec() from using
+   * undefined or null, yet we want $this to reflect the true value of
+   * the current context. Thus we assign the original value to $this,
+   * above, but for the expression context we replace null and
+   * undefined by the empty string.
+   *
+   * @type {Object|null}
+   */
+  me.data_ = getDefaultObject(opt_data, STRING_empty);
+
+  if (!opt_parent) {
+    // If this is a top-level context, create a variable reference to the data
+    // to allow for  accessing top-level properties of the original context
+    // data from child contexts.
+    me.vars_[VAR_top] = me.data_;
+  }
+};
+
+
+/**
+ * A map of globally defined symbols. Every instance of JsExprContext
+ * inherits them in its vars_.
+ * @type Object
+ */
+JsEvalContext.globals_ = {}
+
+
+/**
+ * Sets a global symbol. It will be available like a variable in every
+ * JsEvalContext instance. This is intended mainly to register
+ * immutable global objects, such as functions, at load time, and not
+ * to add global data at runtime. I.e. the same objections as to
+ * global variables in general apply also here. (Hence the name
+ * "global", and not "global var".)
+ * @param {string} name
+ * @param {Object|null} value
+ */
+JsEvalContext.setGlobal = function(name, value) {
+  JsEvalContext.globals_[name] = value;
+};
+
+
+/**
+ * Set the default value to be returned if context evaluation results in an 
+ * error. (This can occur if a non-existent value was requested). 
+ */
+JsEvalContext.setGlobal(GLOB_default, null);
+
+
+/**
+ * A cache to reuse JsEvalContext instances. (IE6 perf)
+ *
+ * @type Array.<JsEvalContext>
+ */
+JsEvalContext.recycledInstances_ = [];
+
+
+/**
+ * A factory to create a JsEvalContext instance, possibly reusing
+ * one from recycledInstances_. (IE6 perf)
+ *
+ * @param {Object} opt_data
+ * @param {JsEvalContext} opt_parent
+ * @return {JsEvalContext}
+ */
+JsEvalContext.create = function(opt_data, opt_parent) {
+  if (jsLength(JsEvalContext.recycledInstances_) > 0) {
+    var instance = JsEvalContext.recycledInstances_.pop();
+    JsEvalContext.call(instance, opt_data, opt_parent);
+    return instance;
+  } else {
+    return new JsEvalContext(opt_data, opt_parent);
+  }
+};
+
+
+/**
+ * Recycle a used JsEvalContext instance, so we can avoid creating one
+ * the next time we need one. (IE6 perf)
+ *
+ * @param {JsEvalContext} instance
+ */
+JsEvalContext.recycle = function(instance) {
+  for (var i in instance.vars_) {
+    // NOTE(mesch): We avoid object creation here. (IE6 perf)
+    delete instance.vars_[i];
+  }
+  instance.data_ = null;
+  JsEvalContext.recycledInstances_.push(instance);
+};
+
+
+/**
+ * Executes a function created using jsEvalToFunction() in the context
+ * of vars, data, and template.
+ *
+ * @param {Function} exprFunction A javascript function created from
+ * a jstemplate attribute value.
+ *
+ * @param {Element} template DOM node of the template.
+ *
+ * @return {Object|null} The value of the expression from which
+ * exprFunction was created in the current js expression context and
+ * the context of template.
+ */
+JsEvalContext.prototype.jsexec = function(exprFunction, template) {
+  try {
+    return exprFunction.call(template, this.vars_, this.data_);
+  } catch (e) {
+    log('jsexec EXCEPTION: ' + e + ' at ' + template +
+        ' with ' + exprFunction);
+    return JsEvalContext.globals_[GLOB_default];
+  }
+};
+
+
+/**
+ * Clones the current context for a new context object. The cloned
+ * context has the data object as its context object and the current
+ * context as its parent context. It also sets the $index variable to
+ * the given value. This value usually is the position of the data
+ * object in a list for which a template is instantiated multiply.
+ *
+ * @param {Object} data The new context object.
+ *
+ * @param {number} index Position of the new context when multiply
+ * instantiated. (See implementation of jstSelect().)
+ * 
+ * @param {number} count The total number of contexts that were multiply
+ * instantiated. (See implementation of jstSelect().)
+ *
+ * @return {JsEvalContext}
+ */
+JsEvalContext.prototype.clone = function(data, index, count) {
+  var ret = JsEvalContext.create(data, this);
+  ret.setVariable(VAR_index, index);
+  ret.setVariable(VAR_count, count);
+  return ret;
+};
+
+
+/**
+ * Binds a local variable to the given value. If set from jstemplate
+ * jsvalue expressions, variable names must start with $, but in the
+ * API they only have to be valid javascript identifier.
+ *
+ * @param {string} name
+ *
+ * @param {Object?} value
+ */
+JsEvalContext.prototype.setVariable = function(name, value) {
+  this.vars_[name] = value;
+};
+
+
+/**
+ * Returns the value bound to the local variable of the given name, or
+ * undefined if it wasn't set. There is no way to distinguish a
+ * variable that wasn't set from a variable that was set to
+ * undefined. Used mostly for testing.
+ *
+ * @param {string} name
+ *
+ * @return {Object?} value
+ */
+JsEvalContext.prototype.getVariable = function(name) {
+  return this.vars_[name];
+};
+
+
+/**
+ * Evaluates a string expression within the scope of this context
+ * and returns the result.
+ *
+ * @param {string} expr A javascript expression
+ * @param {Element} opt_template An optional node to serve as "this"
+ *
+ * @return {Object?} value
+ */
+JsEvalContext.prototype.evalExpression = function(expr, opt_template) {
+  var exprFunction = jsEvalToFunction(expr);
+  return this.jsexec(exprFunction, opt_template);
+};
+
+
+/**
+ * Uninlined string literals for jsEvalToFunction() (IE6 perf).
+ */
+var STRING_a = 'a_';
+var STRING_b = 'b_';
+var STRING_with = 'with (a_) with (b_) return ';
+
+
+/**
+ * Cache for jsEvalToFunction results.
+ * @type Object
+ */
+JsEvalContext.evalToFunctionCache_ = {};
+
+
+/**
+ * Evaluates the given expression as the body of a function that takes
+ * vars and data as arguments. Since the resulting function depends
+ * only on expr, we cache the result so we save some Function
+ * invocations, and some object creations in IE6.
+ *
+ * @param {string} expr A javascript expression.
+ *
+ * @return {Function} A function that returns the value of expr in the
+ * context of vars and data.
+ */
+function jsEvalToFunction(expr) {
+  if (!JsEvalContext.evalToFunctionCache_[expr]) {
+    try {
+      // NOTE(mesch): The Function constructor is faster than eval().
+      JsEvalContext.evalToFunctionCache_[expr] =
+        new Function(STRING_a, STRING_b, STRING_with + expr);
+    } catch (e) {
+      log('jsEvalToFunction (' + expr + ') EXCEPTION ' + e);
+    }
+  }
+  return JsEvalContext.evalToFunctionCache_[expr];
+}
+
+
+/**
+ * Evaluates the given expression to itself. This is meant to pass
+ * through string attribute values.
+ *
+ * @param {string} expr
+ *
+ * @return {string}
+ */
+function jsEvalToSelf(expr) {
+  return expr;
+}
+
+
+/**
+ * Parses the value of the jsvalues attribute in jstemplates: splits
+ * it up into a map of labels and expressions, and creates functions
+ * from the expressions that are suitable for execution by
+ * JsEvalContext.jsexec(). All that is returned as a flattened array
+ * of pairs of a String and a Function.
+ *
+ * @param {string} expr
+ *
+ * @return {Array}
+ */
+function jsEvalToValues(expr) {
+  // TODO(mesch): It is insufficient to split the values by simply
+  // finding semi-colons, as the semi-colon may be part of a string
+  // constant or escaped.
+  var ret = [];
+  var values = expr.split(REGEXP_semicolon);
+  for (var i = 0, I = jsLength(values); i < I; ++i) {
+    var colon = values[i].indexOf(CHAR_colon);
+    if (colon < 0) {
+      continue;
+    }
+    var label = stringTrim(values[i].substr(0, colon));
+    var value = jsEvalToFunction(values[i].substr(colon + 1));
+    ret.push(label, value);
+  }
+  return ret;
+}
+
+
+/**
+ * Parses the value of the jseval attribute of jstemplates: splits it
+ * up into a list of expressions, and creates functions from the
+ * expressions that are suitable for execution by
+ * JsEvalContext.jsexec(). All that is returned as an Array of
+ * Function.
+ *
+ * @param {string} expr
+ *
+ * @return {Array.<Function>}
+ */
+function jsEvalToExpressions(expr) {
+  var ret = [];
+  var values = expr.split(REGEXP_semicolon);
+  for (var i = 0, I = jsLength(values); i < I; ++i) {
+    if (values[i]) {
+      var value = jsEvalToFunction(values[i]);
+      ret.push(value);
+    }
+  }
+  return ret;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/jstemplate.js b/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/jstemplate.js
new file mode 100644
index 0000000..f5d9f77
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/jstemplate.js
@@ -0,0 +1,1017 @@
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+/**
+ * Author: Steffen Meschkat <mesch@google.com>
+ *
+ * @fileoverview A simple formatter to project JavaScript data into
+ * HTML templates. The template is edited in place. I.e. in order to
+ * instantiate a template, clone it from the DOM first, and then
+ * process the cloned template. This allows for updating of templates:
+ * If the templates is processed again, changed values are merely
+ * updated.
+ *
+ * NOTE(mesch): IE DOM doesn't have importNode().
+ *
+ * NOTE(mesch): The property name "length" must not be used in input
+ * data, see comment in jstSelect_().
+ */
+
+
+/**
+ * Names of jstemplate attributes. These attributes are attached to
+ * normal HTML elements and bind expression context data to the HTML
+ * fragment that is used as template.
+ */
+var ATT_select = 'jsselect';
+var ATT_instance = 'jsinstance';
+var ATT_display = 'jsdisplay';
+var ATT_values = 'jsvalues';
+var ATT_vars = 'jsvars';
+var ATT_eval = 'jseval';
+var ATT_transclude = 'transclude';
+var ATT_content = 'jscontent';
+var ATT_skip = 'jsskip';
+
+
+/**
+ * Name of the attribute that caches a reference to the parsed
+ * template processing attribute values on a template node.
+ */
+var ATT_jstcache = 'jstcache';
+
+
+/**
+ * Name of the property that caches the parsed template processing
+ * attribute values on a template node.
+ */
+var PROP_jstcache = '__jstcache';
+
+
+/**
+ * ID of the element that contains dynamically loaded jstemplates.
+ */
+var STRING_jsts = 'jsts';
+
+
+/**
+ * Un-inlined string literals, to avoid object creation in
+ * IE6.
+ */
+var CHAR_asterisk = '*';
+var CHAR_dollar = '$';
+var CHAR_period = '.';
+var CHAR_ampersand = '&';
+var STRING_div = 'div';
+var STRING_id = 'id';
+var STRING_asteriskzero = '*0';
+var STRING_zero = '0';
+
+
+/**
+ * HTML template processor. Data values are bound to HTML templates
+ * using the attributes transclude, jsselect, jsdisplay, jscontent,
+ * jsvalues. The template is modifed in place. The values of those
+ * attributes are JavaScript expressions that are evaluated in the
+ * context of the data object fragment.
+ *
+ * @param {JsEvalContext} context Context created from the input data
+ * object.
+ *
+ * @param {Element} template DOM node of the template. This will be
+ * processed in place. After processing, it will still be a valid
+ * template that, if processed again with the same data, will remain
+ * unchanged.
+ *
+ * @param {boolean} opt_debugging Optional flag to collect debugging
+ *     information while processing the template.  Only takes effect
+ *     in MAPS_DEBUG.
+ */
+function jstProcess(context, template, opt_debugging) {
+  var processor = new JstProcessor;
+  if (MAPS_DEBUG && opt_debugging) {
+    processor.setDebugging(opt_debugging);
+  }
+  JstProcessor.prepareTemplate_(template);
+
+  /**
+   * Caches the document of the template node, so we don't have to
+   * access it through ownerDocument.
+   * @type Document
+   */
+  processor.document_ = ownerDocument(template);
+
+  processor.run_(bindFully(processor, processor.jstProcessOuter_,
+                           context, template));
+  if (MAPS_DEBUG && opt_debugging) {
+    log('jstProcess:' + '\n' + processor.getLogs().join('\n'));
+  }
+}
+
+
+/**
+ * Internal class used by jstemplates to maintain context.  This is
+ * necessary to process deep templates in Safari which has a
+ * relatively shallow maximum recursion depth of 100.
+ * @class
+ * @constructor
+ */
+function JstProcessor() {
+  if (MAPS_DEBUG) {
+    /**
+     * An array of logging messages.  These are collected during processing
+     * and dumped to the console at the end.
+     * @type Array.<string>
+     */
+    this.logs_ = [];
+  }
+}
+
+
+/**
+ * Counter to generate node ids. These ids will be stored in
+ * ATT_jstcache and be used to lookup the preprocessed js attributes
+ * from the jstcache_. The id is stored in an attribute so it
+ * suvives cloneNode() and thus cloned template nodes can share the
+ * same cache entry.
+ * @type number
+ */
+JstProcessor.jstid_ = 0;
+
+
+/**
+ * Map from jstid to processed js attributes.
+ * @type Object
+ */
+JstProcessor.jstcache_ = {};
+
+/**
+ * The neutral cache entry. Used for all nodes that don't have any
+ * jst attributes. We still set the jsid attribute on those nodes so
+ * we can avoid to look again for all the other jst attributes that
+ * aren't there. Remember: not only the processing of the js
+ * attribute values is expensive and we thus want to cache it. The
+ * access to the attributes on the Node in the first place is
+ * expensive too.
+ */
+JstProcessor.jstcache_[0] = {};
+
+
+/**
+ * Map from concatenated attribute string to jstid.
+ * The key is the concatenation of all jst atributes found on a node
+ * formatted as "name1=value1&name2=value2&...", in the order defined by
+ * JST_ATTRIBUTES. The value is the id of the jstcache_ entry that can
+ * be used for this node. This allows the reuse of cache entries in cases
+ * when a cached entry already exists for a given combination of attribute
+ * values. (For example when two different nodes in a template share the same
+ * JST attributes.)
+ * @type Object
+ */
+JstProcessor.jstcacheattributes_ = {};
+
+
+/**
+ * Map for storing temporary attribute values in prepareNode_() so they don't
+ * have to be retrieved twice. (IE6 perf)
+ * @type Object
+ */
+JstProcessor.attributeValues_ = {};
+
+
+/**
+ * A list for storing non-empty attributes found on a node in prepareNode_().
+ * The array is global since it can be reused - this way there is no need to
+ * construct a new array object for each invocation. (IE6 perf)
+ * @type Array
+ */
+JstProcessor.attributeList_ = [];
+
+
+/**
+ * Prepares the template: preprocesses all jstemplate attributes.
+ *
+ * @param {Element} template
+ */
+JstProcessor.prepareTemplate_ = function(template) {
+  if (!template[PROP_jstcache]) {
+    domTraverseElements(template, function(node) {
+      JstProcessor.prepareNode_(node);
+    });
+  }
+};
+
+
+/**
+ * A list of attributes we use to specify jst processing instructions,
+ * and the functions used to parse their values.
+ *
+ * @type Array.<Array>
+ */
+var JST_ATTRIBUTES = [
+    [ ATT_select, jsEvalToFunction ],
+    [ ATT_display, jsEvalToFunction ],
+    [ ATT_values, jsEvalToValues ],
+    [ ATT_vars, jsEvalToValues ],
+    [ ATT_eval, jsEvalToExpressions ],
+    [ ATT_transclude, jsEvalToSelf ],
+    [ ATT_content, jsEvalToFunction ],
+    [ ATT_skip, jsEvalToFunction ]
+];
+
+
+/**
+ * Prepares a single node: preprocesses all template attributes of the
+ * node, and if there are any, assigns a jsid attribute and stores the
+ * preprocessed attributes under the jsid in the jstcache.
+ *
+ * @param {Element} node
+ *
+ * @return {Object} The jstcache entry. The processed jst attributes
+ * are properties of this object. If the node has no jst attributes,
+ * returns an object with no properties (the jscache_[0] entry).
+ */
+JstProcessor.prepareNode_ = function(node) {
+  // If the node already has a cache property, return it.
+  if (node[PROP_jstcache]) {
+    return node[PROP_jstcache];
+  }
+
+  // If it is not found, we always set the PROP_jstcache property on the node.
+  // Accessing the property is faster than executing getAttribute(). If we
+  // don't find the property on a node that was cloned in jstSelect_(), we
+  // will fall back to check for the attribute and set the property
+  // from cache.
+
+  // If the node has an attribute indexing a cache object, set it as a property
+  // and return it.
+  var jstid = domGetAttribute(node, ATT_jstcache);
+  if (jstid != null) {
+    return node[PROP_jstcache] = JstProcessor.jstcache_[jstid];
+  }
+
+  var attributeValues = JstProcessor.attributeValues_;
+  var attributeList = JstProcessor.attributeList_;
+  attributeList.length = 0;
+
+  // Look for interesting attributes.
+  for (var i = 0, I = jsLength(JST_ATTRIBUTES); i < I; ++i) {
+    var name = JST_ATTRIBUTES[i][0];
+    var value = domGetAttribute(node, name);
+    attributeValues[name] = value;
+    if (value != null) {
+      attributeList.push(name + "=" + value);
+    }
+  }
+
+  // If none found, mark this node to prevent further inspection, and return
+  // an empty cache object.
+  if (attributeList.length == 0) {
+    domSetAttribute(node, ATT_jstcache, STRING_zero);
+    return node[PROP_jstcache] = JstProcessor.jstcache_[0];
+  }
+
+  // If we already have a cache object corresponding to these attributes,
+  // annotate the node with it, and return it.
+  var attstring = attributeList.join(CHAR_ampersand);
+  if (jstid = JstProcessor.jstcacheattributes_[attstring]) {
+    domSetAttribute(node, ATT_jstcache, jstid);
+    return node[PROP_jstcache] = JstProcessor.jstcache_[jstid];
+  }
+
+  // Otherwise, build a new cache object.
+  var jstcache = {};
+  for (var i = 0, I = jsLength(JST_ATTRIBUTES); i < I; ++i) {
+    var att = JST_ATTRIBUTES[i];
+    var name = att[0];
+    var parse = att[1];
+    var value = attributeValues[name];
+    if (value != null) {
+      jstcache[name] = parse(value);
+      if (MAPS_DEBUG) {
+        jstcache.jstAttributeValues = jstcache.jstAttributeValues || {};
+        jstcache.jstAttributeValues[name] = value;
+      }
+    }
+  }
+
+  jstid = STRING_empty + ++JstProcessor.jstid_;
+  domSetAttribute(node, ATT_jstcache, jstid);
+  JstProcessor.jstcache_[jstid] = jstcache;
+  JstProcessor.jstcacheattributes_[attstring] = jstid;
+
+  return node[PROP_jstcache] = jstcache;
+};
+
+
+/**
+ * Runs the given function in our state machine.
+ *
+ * It's informative to view the set of all function calls as a tree:
+ * - nodes are states
+ * - edges are state transitions, implemented as calls to the pending
+ *   functions in the stack.
+ *   - pre-order function calls are downward edges (recursion into call).
+ *   - post-order function calls are upward edges (return from call).
+ * - leaves are nodes which do not recurse.
+ * We represent the call tree as an array of array of calls, indexed as
+ * stack[depth][index].  Here [depth] indexes into the call stack, and
+ * [index] indexes into the call queue at that depth.  We require a call
+ * queue so that a node may branch to more than one child
+ * (which will be called serially), typically due to a loop structure.
+ *
+ * @param {Function} f The first function to run.
+ */
+JstProcessor.prototype.run_ = function(f) {
+  var me = this;
+
+  /**
+   * A stack of queues of pre-order calls.
+   * The inner arrays (constituent queues) are structured as
+   * [ arg2, arg1, method, arg2, arg1, method, ...]
+   * ie. a flattened array of methods with 2 arguments, in reverse order
+   * for efficient push/pop.
+   *
+   * The outer array is a stack of such queues.
+   *
+   * @type Array.<Array>
+   */
+  var calls = me.calls_ = [];
+
+  /**
+   * The index into the queue for each depth. NOTE: Alternative would
+   * be to maintain the queues in reverse order (popping off of the
+   * end) but the repeated calls to .pop() consumed 90% of this
+   * function's execution time.
+   * @type Array.<number>
+   */
+  var queueIndices = me.queueIndices_ = [];
+
+  /**
+   * A pool of empty arrays.  Minimizes object allocation for IE6's benefit.
+   * @type Array.<Array>
+   */
+  var arrayPool = me.arrayPool_ = [];
+
+  f();
+  var queue, queueIndex;
+  var method, arg1, arg2;
+  var temp;
+  while (calls.length) {
+    queue = calls[calls.length - 1];
+    queueIndex = queueIndices[queueIndices.length - 1];
+    if (queueIndex >= queue.length) {
+      me.recycleArray_(calls.pop());
+      queueIndices.pop();
+      continue;
+    }
+
+    // Run the first function in the queue.
+    method = queue[queueIndex++];
+    arg1 = queue[queueIndex++];
+    arg2 = queue[queueIndex++];
+    queueIndices[queueIndices.length - 1] = queueIndex;
+    method.call(me, arg1, arg2);
+  }
+};
+
+
+/**
+ * Pushes one or more functions onto the stack.  These will be run in sequence,
+ * interspersed with any recursive calls that they make.
+ *
+ * This method takes ownership of the given array!
+ *
+ * @param {Array} args Array of method calls structured as
+ *     [ method, arg1, arg2, method, arg1, arg2, ... ]
+ */
+JstProcessor.prototype.push_ = function(args) {
+  this.calls_.push(args);
+  this.queueIndices_.push(0);
+};
+
+
+/**
+ * Enable/disable debugging.
+ * @param {boolean} debugging New state
+ */
+JstProcessor.prototype.setDebugging = function(debugging) {
+  if (MAPS_DEBUG) {
+    this.debugging_ = debugging;
+  }
+};
+
+
+JstProcessor.prototype.createArray_ = function() {
+  if (this.arrayPool_.length) {
+    return this.arrayPool_.pop();
+  } else {
+    return [];
+  }
+};
+
+
+JstProcessor.prototype.recycleArray_ = function(array) {
+  arrayClear(array);
+  this.arrayPool_.push(array);
+};
+
+/**
+ * Implements internals of jstProcess. This processes the two
+ * attributes transclude and jsselect, which replace or multiply
+ * elements, hence the name "outer". The remainder of the attributes
+ * is processed in jstProcessInner_(), below. That function
+ * jsProcessInner_() only processes attributes that affect an existing
+ * node, but doesn't create or destroy nodes, hence the name
+ * "inner". jstProcessInner_() is called through jstSelect_() if there
+ * is a jsselect attribute (possibly for newly created clones of the
+ * current template node), or directly from here if there is none.
+ *
+ * @param {JsEvalContext} context
+ *
+ * @param {Element} template
+ */
+JstProcessor.prototype.jstProcessOuter_ = function(context, template) {
+  var me = this;
+
+  var jstAttributes = me.jstAttributes_(template);
+  if (MAPS_DEBUG && me.debugging_) {
+    me.logState_('Outer', template, jstAttributes.jstAttributeValues);
+  }
+
+  var transclude = jstAttributes[ATT_transclude];
+  if (transclude) {
+    var tr = jstGetTemplate(transclude);
+    if (tr) {
+      domReplaceChild(tr, template);
+      var call = me.createArray_();
+      call.push(me.jstProcessOuter_, context, tr);
+      me.push_(call);
+    } else {
+      domRemoveNode(template);
+    }
+    return;
+  }
+
+  var select = jstAttributes[ATT_select];
+  if (select) {
+    me.jstSelect_(context, template, select);
+  } else {
+    me.jstProcessInner_(context, template);
+  }
+};
+
+
+/**
+ * Implements internals of jstProcess. This processes all attributes
+ * except transclude and jsselect. It is called either from
+ * jstSelect_() for nodes that have a jsselect attribute so that the
+ * jsselect attribute will not be processed again, or else directly
+ * from jstProcessOuter_(). See the comment on jstProcessOuter_() for
+ * an explanation of the name.
+ *
+ * @param {JsEvalContext} context
+ *
+ * @param {Element} template
+ */
+JstProcessor.prototype.jstProcessInner_ = function(context, template) {
+  var me = this;
+
+  var jstAttributes = me.jstAttributes_(template);
+  if (MAPS_DEBUG && me.debugging_) {
+    me.logState_('Inner', template, jstAttributes.jstAttributeValues);
+  }
+
+  // NOTE(mesch): See NOTE on ATT_content why this is a separate
+  // attribute, and not a special value in ATT_values.
+  var display = jstAttributes[ATT_display];
+  if (display) {
+    var shouldDisplay = context.jsexec(display, template);
+    if (MAPS_DEBUG && me.debugging_) {
+      me.logs_.push(ATT_display + ': ' + shouldDisplay + '<br/>');
+    }
+    if (!shouldDisplay) {
+      displayNone(template);
+      return;
+    }
+    displayDefault(template);
+  }
+
+  // NOTE(mesch): jsvars is evaluated before jsvalues, because it's
+  // more useful to be able to use var values in attribute value
+  // expressions than vice versa.
+  var values = jstAttributes[ATT_vars];
+  if (values) {
+    me.jstVars_(context, template, values);
+  }
+
+  values = jstAttributes[ATT_values];
+  if (values) {
+    me.jstValues_(context, template, values);
+  }
+
+  // Evaluate expressions immediately. Useful for hooking callbacks
+  // into jstemplates.
+  //
+  // NOTE(mesch): Evaluation order is sometimes significant, e.g. when
+  // the expression evaluated in jseval relies on the values set in
+  // jsvalues, so it needs to be evaluated *after*
+  // jsvalues. TODO(mesch): This is quite arbitrary, it would be
+  // better if this would have more necessity to it.
+  var expressions = jstAttributes[ATT_eval];
+  if (expressions) {
+    for (var i = 0, I = jsLength(expressions); i < I; ++i) {
+      context.jsexec(expressions[i], template);
+    }
+  }
+
+  var skip = jstAttributes[ATT_skip];
+  if (skip) {
+    var shouldSkip = context.jsexec(skip, template);
+    if (MAPS_DEBUG && me.debugging_) {
+      me.logs_.push(ATT_skip + ': ' + shouldSkip + '<br/>');
+    }
+    if (shouldSkip) return;
+  }
+
+  // NOTE(mesch): content is a separate attribute, instead of just a
+  // special value mentioned in values, for two reasons: (1) it is
+  // fairly common to have only mapped content, and writing
+  // content="expr" is shorter than writing values="content:expr", and
+  // (2) the presence of content actually terminates traversal, and we
+  // need to check for that. Display is a separate attribute for a
+  // reason similar to the second, in that its presence *may*
+  // terminate traversal.
+  var content = jstAttributes[ATT_content];
+  if (content) {
+    me.jstContent_(context, template, content);
+
+  } else {
+    // Newly generated children should be ignored, so we explicitly
+    // store the children to be processed.
+    var queue = me.createArray_();
+    for (var c = template.firstChild; c; c = c.nextSibling) {
+      if (c.nodeType == DOM_ELEMENT_NODE) {
+        queue.push(me.jstProcessOuter_, context, c);
+      }
+    }
+    if (queue.length) me.push_(queue);
+  }
+};
+
+
+/**
+ * Implements the jsselect attribute: evalutes the value of the
+ * jsselect attribute in the current context, with the current
+ * variable bindings (see JsEvalContext.jseval()). If the value is an
+ * array, the current template node is multiplied once for every
+ * element in the array, with the array element being the context
+ * object. If the array is empty, or the value is undefined, then the
+ * current template node is dropped. If the value is not an array,
+ * then it is just made the context object.
+ *
+ * @param {JsEvalContext} context The current evaluation context.
+ *
+ * @param {Element} template The currently processed node of the template.
+ *
+ * @param {Function} select The javascript expression to evaluate.
+ *
+ * @notypecheck FIXME(hmitchell): See OCL6434950. instance and value need
+ * type checks.
+ */
+JstProcessor.prototype.jstSelect_ = function(context, template, select) {
+  var me = this;
+
+  var value = context.jsexec(select, template);
+
+  // Enable reprocessing: if this template is reprocessed, then only
+  // fill the section instance here. Otherwise do the cardinal
+  // processing of a new template.
+  var instance = domGetAttribute(template, ATT_instance);
+
+  var instanceLast = false;
+  if (instance) {
+    if (instance.charAt(0) == CHAR_asterisk) {
+      instance = parseInt10(instance.substr(1));
+      instanceLast = true;
+    } else {
+      instance = parseInt10(/** @type string */(instance));
+    }
+  }
+
+  // The expression value instanceof Array is occasionally false for
+  // arrays, seen in Firefox. Thus we recognize an array as an object
+  // which is not null that has a length property. Notice that this
+  // also matches input data with a length property, so this property
+  // name should be avoided in input data.
+  var multiple = isArray(value);
+  var count = multiple ? jsLength(value) : 1;
+  var multipleEmpty = (multiple && count == 0);
+
+  if (multiple) {
+    if (multipleEmpty) {
+      // For an empty array, keep the first template instance and mark
+      // it last. Remove all other template instances.
+      if (!instance) {
+        domSetAttribute(template, ATT_instance, STRING_asteriskzero);
+        displayNone(template);
+      } else {
+        domRemoveNode(template);
+      }
+
+    } else {
+      displayDefault(template);
+      // For a non empty array, create as many template instances as
+      // are needed. If the template is first processed, as many
+      // template instances are needed as there are values in the
+      // array. If the template is reprocessed, new template instances
+      // are only needed if there are more array values than template
+      // instances. Those additional instances are created by
+      // replicating the last template instance.
+      //
+      // When the template is first processed, there is no jsinstance
+      // attribute. This is indicated by instance === null, except in
+      // opera it is instance === "". Notice also that the === is
+      // essential, because 0 == "", presumably via type coercion to
+      // boolean.
+      if (instance === null || instance === STRING_empty ||
+          (instanceLast && instance < count - 1)) {
+        // A queue of calls to push.
+        var queue = me.createArray_();
+
+        var instancesStart = instance || 0;
+        var i, I, clone;
+        for (i = instancesStart, I = count - 1; i < I; ++i) {
+          var node = domCloneNode(template);
+          domInsertBefore(node, template);
+
+          jstSetInstance(/** @type Element */(node), value, i);
+          clone = context.clone(value[i], i, count);
+
+          queue.push(me.jstProcessInner_, clone, node,
+                     JsEvalContext.recycle, clone, null);
+                     
+        }
+        // Push the originally present template instance last to keep
+        // the order aligned with the DOM order, because the newly
+        // created template instances are inserted *before* the
+        // original instance.
+        jstSetInstance(template, value, i);
+        clone = context.clone(value[i], i, count);
+        queue.push(me.jstProcessInner_, clone, template,
+                   JsEvalContext.recycle, clone, null);
+        me.push_(queue);
+      } else if (instance < count) {
+        var v = value[instance];
+
+        jstSetInstance(template, value, instance);
+        var clone = context.clone(v, instance, count);
+        var queue = me.createArray_();
+        queue.push(me.jstProcessInner_, clone, template,
+                   JsEvalContext.recycle, clone, null);
+        me.push_(queue);
+      } else {
+        domRemoveNode(template);
+      }
+    }
+  } else {
+    if (value == null) {
+      displayNone(template);
+    } else {
+      displayDefault(template);
+      var clone = context.clone(value, 0, 1);
+      var queue = me.createArray_();
+      queue.push(me.jstProcessInner_, clone, template,
+                 JsEvalContext.recycle, clone, null);
+      me.push_(queue);
+    }
+  }
+};
+
+
+/**
+ * Implements the jsvars attribute: evaluates each of the values and
+ * assigns them to variables in the current context. Similar to
+ * jsvalues, except that all values are treated as vars, independent
+ * of their names.
+ *
+ * @param {JsEvalContext} context Current evaluation context.
+ *
+ * @param {Element} template Currently processed template node.
+ *
+ * @param {Array} values Processed value of the jsvalues attribute: a
+ * flattened array of pairs. The second element in the pair is a
+ * function that can be passed to jsexec() for evaluation in the
+ * current jscontext, and the first element is the variable name that
+ * the value returned by jsexec is assigned to.
+ */
+JstProcessor.prototype.jstVars_ = function(context, template, values) {
+  for (var i = 0, I = jsLength(values); i < I; i += 2) {
+    var label = values[i];
+    var value = context.jsexec(values[i+1], template);
+    context.setVariable(label, value);
+  }
+};
+
+
+/**
+ * Implements the jsvalues attribute: evaluates each of the values and
+ * assigns them to variables in the current context (if the name
+ * starts with '$', javascript properties of the current template node
+ * (if the name starts with '.'), or DOM attributes of the current
+ * template node (otherwise). Since DOM attribute values are always
+ * strings, the value is coerced to string in the latter case,
+ * otherwise it's the uncoerced javascript value.
+ *
+ * @param {JsEvalContext} context Current evaluation context.
+ *
+ * @param {Element} template Currently processed template node.
+ *
+ * @param {Array} values Processed value of the jsvalues attribute: a
+ * flattened array of pairs. The second element in the pair is a
+ * function that can be passed to jsexec() for evaluation in the
+ * current jscontext, and the first element is the label that
+ * determines where the value returned by jsexec is assigned to.
+ */
+JstProcessor.prototype.jstValues_ = function(context, template, values) {
+  for (var i = 0, I = jsLength(values); i < I; i += 2) {
+    var label = values[i];
+    var value = context.jsexec(values[i+1], template);
+
+    if (label.charAt(0) == CHAR_dollar) {
+      // A jsvalues entry whose name starts with $ sets a local
+      // variable.
+      context.setVariable(label, value);
+
+    } else if (label.charAt(0) == CHAR_period) {
+      // A jsvalues entry whose name starts with . sets a property of
+      // the current template node. The name may have further dot
+      // separated components, which are translated into namespace
+      // objects. This specifically allows to set properties on .style
+      // using jsvalues. NOTE(mesch): Setting the style attribute has
+      // no effect in IE and hence should not be done anyway.
+      var nameSpaceLabel = label.substr(1).split(CHAR_period);
+      var nameSpaceObject = template;
+      var nameSpaceDepth = jsLength(nameSpaceLabel);
+      for (var j = 0, J = nameSpaceDepth - 1; j < J; ++j) {
+        var jLabel = nameSpaceLabel[j];
+        if (!nameSpaceObject[jLabel]) {
+          nameSpaceObject[jLabel] = {};
+        }
+        nameSpaceObject = nameSpaceObject[jLabel];
+      }
+      nameSpaceObject[nameSpaceLabel[nameSpaceDepth - 1]] = value;
+
+    } else if (label) {
+      // Any other jsvalues entry sets an attribute of the current
+      // template node.
+      if (typeof value == TYPE_boolean) {
+        // Handle boolean values that are set as attributes specially,
+        // according to the XML/HTML convention.
+        if (value) {
+          domSetAttribute(template, label, label);
+        } else {
+          domRemoveAttribute(template, label);
+        }
+      } else {
+        domSetAttribute(template, label, STRING_empty + value);
+      }
+    }
+  }
+};
+
+
+/**
+ * Implements the jscontent attribute. Evalutes the expression in
+ * jscontent in the current context and with the current variables,
+ * and assigns its string value to the content of the current template
+ * node.
+ *
+ * @param {JsEvalContext} context Current evaluation context.
+ *
+ * @param {Element} template Currently processed template node.
+ *
+ * @param {Function} content Processed value of the jscontent
+ * attribute.
+ */
+JstProcessor.prototype.jstContent_ = function(context, template, content) {
+  // NOTE(mesch): Profiling shows that this method costs significant
+  // time. In jstemplate_perf.html, it's about 50%. I tried to replace
+  // by HTML escaping and assignment to innerHTML, but that was even
+  // slower.
+  var value = STRING_empty + context.jsexec(content, template);
+  // Prevent flicker when refreshing a template and the value doesn't
+  // change.
+  if (template.innerHTML == value) {
+    return;
+  }
+  while (template.firstChild) {
+    domRemoveNode(template.firstChild);
+  }
+  var t = domCreateTextNode(this.document_, value);
+  domAppendChild(template, t);
+};
+
+
+/**
+ * Caches access to and parsing of template processing attributes. If
+ * domGetAttribute() is called every time a template attribute value
+ * is used, it takes more than 10% of the time.
+ *
+ * @param {Element} template A DOM element node of the template.
+ *
+ * @return {Object} A javascript object that has all js template
+ * processing attribute values of the node as properties.
+ */
+JstProcessor.prototype.jstAttributes_ = function(template) {
+  if (template[PROP_jstcache]) {
+    return template[PROP_jstcache];
+  }
+
+  var jstid = domGetAttribute(template, ATT_jstcache);
+  if (jstid) {
+    return template[PROP_jstcache] = JstProcessor.jstcache_[jstid];
+  }
+
+  return JstProcessor.prepareNode_(template);
+};
+
+
+/**
+ * Helps to implement the transclude attribute, and is the initial
+ * call to get hold of a template from its ID.
+ *
+ * If the ID is not present in the DOM, and opt_loadHtmlFn is specified, this
+ * function will call that function and add the result to the DOM, before
+ * returning the template.
+ *
+ * @param {string} name The ID of the HTML element used as template.
+ * @param {Function} opt_loadHtmlFn A function which, when called, will return
+ *   HTML that contains an element whose ID is 'name'.
+ *
+ * @return {Element|null} The DOM node of the template. (Only element nodes
+ * can be found by ID, hence it's a Element.)
+ */
+function jstGetTemplate(name, opt_loadHtmlFn) {
+  var doc = document;
+  var section;
+  if (opt_loadHtmlFn) {
+    section = jstLoadTemplateIfNotPresent(doc, name, opt_loadHtmlFn);
+  } else {
+    section = domGetElementById(doc, name);
+  }
+  if (section) {
+    JstProcessor.prepareTemplate_(section);
+    var ret = domCloneElement(section);
+    domRemoveAttribute(ret, STRING_id);
+    return ret;
+  } else {
+    return null;
+  }
+}
+
+/**
+ * This function is the same as 'jstGetTemplate' but, if the template
+ * does not exist, throw an exception.
+ *
+ * @param {string} name The ID of the HTML element used as template.
+ * @param {Function} opt_loadHtmlFn A function which, when called, will return
+ *   HTML that contains an element whose ID is 'name'.
+ *
+ * @return {Element} The DOM node of the template. (Only element nodes
+ * can be found by ID, hence it's a Element.)
+ */
+function jstGetTemplateOrDie(name, opt_loadHtmlFn) {
+  var x = jstGetTemplate(name, opt_loadHtmlFn);
+  check(x !== null);
+  return /** @type Element */(x);
+}
+
+
+/**
+ * If an element with id 'name' is not present in the document, call loadHtmlFn
+ * and insert the result into the DOM.
+ *
+ * @param {Document} doc
+ * @param {string} name
+ * @param {Function} loadHtmlFn A function that returns HTML to be inserted
+ * into the DOM.
+ * @param {string} opt_target The id of a DOM object under which to attach the
+ *   HTML once it's inserted.  An object with this id is created if it does not
+ *   exist.
+ * @return {Element} The node whose id is 'name'
+ */
+function jstLoadTemplateIfNotPresent(doc, name, loadHtmlFn, opt_target) {
+  var section = domGetElementById(doc, name);
+  if (section) {
+    return section;
+  }
+  // Load any necessary HTML and try again.
+  jstLoadTemplate_(doc, loadHtmlFn(), opt_target || STRING_jsts);
+  var section = domGetElementById(doc, name);
+  if (!section) {
+    log("Error: jstGetTemplate was provided with opt_loadHtmlFn, " +
+	"but that function did not provide the id '" + name + "'.");
+  }
+  return /** @type Element */(section);
+}
+
+
+/**
+ * Loads the given HTML text into the given document, so that
+ * jstGetTemplate can find it.
+ *
+ * We append it to the element identified by targetId, which is hidden.
+ * If it doesn't exist, it is created.
+ *
+ * @param {Document} doc The document to create the template in.
+ *
+ * @param {string} html HTML text to be inserted into the document.
+ *
+ * @param {string} targetId The id of a DOM object under which to attach the
+ *   HTML once it's inserted.  An object with this id is created if it does not
+ *   exist.
+ */
+function jstLoadTemplate_(doc, html, targetId) {
+  var existing_target = domGetElementById(doc, targetId);
+  var target;
+  if (!existing_target) {
+    target = domCreateElement(doc, STRING_div);
+    target.id = targetId;
+    displayNone(target);
+    positionAbsolute(target);
+    domAppendChild(doc.body, target);
+  } else {
+    target = existing_target;
+  }
+  var div = domCreateElement(doc, STRING_div);
+  target.appendChild(div);
+  div.innerHTML = html;
+}
+
+
+/**
+ * Sets the jsinstance attribute on a node according to its context.
+ *
+ * @param {Element} template The template DOM node to set the instance
+ * attribute on.
+ *
+ * @param {Array} values The current input context, the array of
+ * values of which the template node will render one instance.
+ *
+ * @param {number} index The index of this template node in values.
+ */
+function jstSetInstance(template, values, index) {
+  if (index == jsLength(values) - 1) {
+    domSetAttribute(template, ATT_instance, CHAR_asterisk + index);
+  } else {
+    domSetAttribute(template, ATT_instance, STRING_empty + index);
+  }
+}
+
+
+/**
+ * Log the current state.
+ * @param {string} caller An identifier for the caller of .log_.
+ * @param {Element} template The template node being processed.
+ * @param {Object} jstAttributeValues The jst attributes of the template node.
+ */
+JstProcessor.prototype.logState_ = function(
+    caller, template, jstAttributeValues) {
+  if (MAPS_DEBUG) {
+    var msg = '<table>';
+    msg += '<caption>' + caller + '</caption>';
+    msg += '<tbody>';
+    if (template.id) {
+      msg += '<tr><td>' + 'id:' + '</td><td>' + template.id + '</td></tr>';
+    }
+    if (template.name) {
+      msg += '<tr><td>' + 'name:' + '</td><td>' + template.name + '</td></tr>';
+    }
+    if (jstAttributeValues) {
+      msg += '<tr><td>' + 'attr:' +
+      '</td><td>' + jsToSource(jstAttributeValues) + '</td></tr>';
+    }
+    msg += '</tbody></table><br/>';
+    this.logs_.push(msg);
+  }
+};
+
+
+/**
+ * Retrieve the processing logs.
+ * @return {Array.<string>} The processing logs.
+ */
+JstProcessor.prototype.getLogs = function() {
+  return this.logs_;
+};
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/util.js b/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/util.js
new file mode 100644
index 0000000..f6c1f46
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/util.js
@@ -0,0 +1,470 @@
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+/**
+ * @fileoverview Miscellaneous constants and functions referenced in
+ * the main source files.
+ *
+ * @author Steffen Meschkat (mesch@google.com)
+ */
+
+var MAPS_DEBUG = false;
+
+function log(msg) {}
+
+// String literals defined globally and not to be inlined. (IE6 perf)
+/** @const */ var STRING_empty = '';
+
+/** @const */ var CSS_display = 'display';
+/** @const */ var CSS_position = 'position';
+
+// Constants for possible values of the typeof operator.
+var TYPE_boolean = 'boolean';
+var TYPE_number = 'number';
+var TYPE_object = 'object';
+var TYPE_string = 'string';
+var TYPE_function = 'function';
+var TYPE_undefined = 'undefined';
+
+
+/**
+ * Wrapper for the eval() builtin function to evaluate expressions and
+ * obtain their value. It wraps the expression in parentheses such
+ * that object literals are really evaluated to objects. Without the
+ * wrapping, they are evaluated as block, and create syntax
+ * errors. Also protects against other syntax errors in the eval()ed
+ * code and returns null if the eval throws an exception.
+ *
+ * @param {string} expr
+ * @return {Object|null}
+ */
+function jsEval(expr) {
+  try {
+    // NOTE(mesch): An alternative idiom would be:
+    //
+    //   eval('(' + expr + ')');
+    //
+    // Note that using the square brackets as below, "" evals to undefined.
+    // The alternative of using parentheses does not work when evaluating
+    // function literals in IE.
+    // e.g. eval("(function() {})") returns undefined, and not a function
+    // object, in IE.
+    return eval('[' + expr + '][0]');
+  } catch (e) {
+    log('EVAL FAILED ' + expr + ': ' + e);
+    return null;
+  }
+}
+
+function jsLength(obj) {
+  return obj.length;
+}
+
+function assert(obj) {}
+
+/**
+ * Copies all properties from second object to the first.  Modifies to.
+ *
+ * @param {Object} to  The target object.
+ * @param {Object} from  The source object.
+ */
+function copyProperties(to, from) {
+  for (var p in from) {
+    to[p] = from[p];
+  }
+}
+
+
+/**
+ * @param {Object|null|undefined} value The possible value to use.
+ * @param {Object} defaultValue The default if the value is not set.
+ * @return {Object} The value, if it is
+ * defined and not null; otherwise the default
+ */
+function getDefaultObject(value, defaultValue) {
+  if (typeof value != TYPE_undefined && value != null) {
+    return /** @type Object */(value);
+  } else {
+    return defaultValue;
+  }
+}
+
+/**
+ * Detect if an object looks like an Array.
+ * Note that instanceof Array is not robust; for example an Array
+ * created in another iframe fails instanceof Array.
+ * @param {Object|null} value Object to interrogate
+ * @return {boolean} Is the object an array?
+ */
+function isArray(value) {
+  return value != null &&
+      typeof value == TYPE_object &&
+      typeof value.length == TYPE_number;
+}
+
+
+/**
+ * Finds a slice of an array.
+ *
+ * @param {Array} array  Array to be sliced.
+ * @param {number} start  The start of the slice.
+ * @param {number} opt_end  The end of the slice (optional).
+ * @return {Array} array  The slice of the array from start to end.
+ */
+function arraySlice(array, start, opt_end) {
+  // Use
+  //   return Function.prototype.call.apply(Array.prototype.slice, arguments);
+  // instead of the simpler
+  //   return Array.prototype.slice.call(array, start, opt_end);
+  // here because of a bug in the FF and IE implementations of
+  // Array.prototype.slice which causes this function to return an empty list
+  // if opt_end is not provided.
+  return Function.prototype.call.apply(Array.prototype.slice, arguments);
+}
+
+
+/**
+ * Jscompiler wrapper for parseInt() with base 10.
+ *
+ * @param {string} s string repersentation of a number.
+ *
+ * @return {number} The integer contained in s, converted on base 10.
+ */
+function parseInt10(s) {
+  return parseInt(s, 10);
+}
+
+
+/**
+ * Clears the array by setting the length property to 0. This usually
+ * works, and if it should turn out not to work everywhere, here would
+ * be the place to implement the browser specific workaround.
+ *
+ * @param {Array} array  Array to be cleared.
+ */
+function arrayClear(array) {
+  array.length = 0;
+}
+
+
+/**
+ * Prebinds "this" within the given method to an object, but ignores all 
+ * arguments passed to the resulting function.
+ * I.e. var_args are all the arguments that method is invoked with when
+ * invoking the bound function.
+ *
+ * @param {Object|null} object  The object that the method call targets.
+ * @param {Function} method  The target method.
+ * @return {Function}  Method with the target object bound to it and curried by
+ *                     the provided arguments.
+ */
+function bindFully(object, method, var_args) {
+  var args = arraySlice(arguments, 2);
+  return function() {
+    return method.apply(object, args);
+  }
+}
+
+// Based on <http://www.w3.org/TR/2000/ REC-DOM-Level-2-Core-20001113/
+// core.html#ID-1950641247>.
+var DOM_ELEMENT_NODE = 1;
+var DOM_ATTRIBUTE_NODE = 2;
+var DOM_TEXT_NODE = 3;
+var DOM_CDATA_SECTION_NODE = 4;
+var DOM_ENTITY_REFERENCE_NODE = 5;
+var DOM_ENTITY_NODE = 6;
+var DOM_PROCESSING_INSTRUCTION_NODE = 7;
+var DOM_COMMENT_NODE = 8;
+var DOM_DOCUMENT_NODE = 9;
+var DOM_DOCUMENT_TYPE_NODE = 10;
+var DOM_DOCUMENT_FRAGMENT_NODE = 11;
+var DOM_NOTATION_NODE = 12;
+
+
+
+function domGetElementById(document, id) {
+  return document.getElementById(id);
+}
+
+/**
+ * Creates a new node in the given document
+ *
+ * @param {Document} doc  Target document.
+ * @param {string} name  Name of new element (i.e. the tag name)..
+ * @return {Element}  Newly constructed element.
+ */
+function domCreateElement(doc, name) {
+  return doc.createElement(name);
+}
+
+/**
+ * Traverses the element nodes in the DOM section underneath the given
+ * node and invokes the given callback as a method on every element
+ * node encountered.
+ *
+ * @param {Element} node  Parent element of the subtree to traverse.
+ * @param {Function} callback  Called on each node in the traversal.
+ */
+function domTraverseElements(node, callback) {
+  var traverser = new DomTraverser(callback);
+  traverser.run(node);
+}
+
+/**
+ * A class to hold state for a dom traversal.
+ * @param {Function} callback  Called on each node in the traversal.
+ * @constructor
+ * @class
+ */
+function DomTraverser(callback) {
+  this.callback_ = callback;
+}
+
+/**
+ * Processes the dom tree in breadth-first order.
+ * @param {Element} root  The root node of the traversal.
+ */
+DomTraverser.prototype.run = function(root) {
+  var me = this;
+  me.queue_ = [ root ];
+  while (jsLength(me.queue_)) {
+    me.process_(me.queue_.shift());
+  }
+}
+
+/**
+ * Processes a single node.
+ * @param {Element} node  The current node of the traversal.
+ */
+DomTraverser.prototype.process_ = function(node) {
+  var me = this;
+
+  me.callback_(node);
+
+  for (var c = node.firstChild; c; c = c.nextSibling) {
+    if (c.nodeType == DOM_ELEMENT_NODE) {
+      me.queue_.push(c);
+    }
+  }
+}
+
+/**
+ * Get an attribute from the DOM.  Simple redirect, exists to compress code.
+ *
+ * @param {Element} node  Element to interrogate.
+ * @param {string} name  Name of parameter to extract.
+ * @return {string|null}  Resulting attribute.
+ */
+function domGetAttribute(node, name) {
+  return node.getAttribute(name);
+  // NOTE(mesch): Neither in IE nor in Firefox, HTML DOM attributes
+  // implement namespaces. All items in the attribute collection have
+  // null localName and namespaceURI attribute values. In IE, we even
+  // encounter DIV elements that don't implement the method
+  // getAttributeNS().
+}
+
+
+/**
+ * Set an attribute in the DOM.  Simple redirect to compress code.
+ *
+ * @param {Element} node  Element to interrogate.
+ * @param {string} name  Name of parameter to set.
+ * @param {string|number} value  Set attribute to this value.
+ */
+function domSetAttribute(node, name, value) {
+  node.setAttribute(name, value);
+}
+
+/**
+ * Remove an attribute from the DOM.  Simple redirect to compress code.
+ *
+ * @param {Element} node  Element to interrogate.
+ * @param {string} name  Name of parameter to remove.
+ */
+function domRemoveAttribute(node, name) {
+  node.removeAttribute(name);
+}
+
+/**
+ * Clone a node in the DOM.
+ *
+ * @param {Node} node  Node to clone.
+ * @return {Node}  Cloned node.
+ */
+function domCloneNode(node) {
+  return node.cloneNode(true);
+  // NOTE(mesch): we never so far wanted to use cloneNode(false),
+  // hence the default.
+}
+
+/**
+ * Clone a element in the DOM.
+ *
+ * @param {Element} element  Element to clone.
+ * @return {Element}  Cloned element.
+ */
+function domCloneElement(element) {
+  return /** @type {Element} */(domCloneNode(element));
+}
+
+/**
+ * Returns the document owner of the given element. In particular,
+ * returns window.document if node is null or the browser does not
+ * support ownerDocument.  If the node is a document itself, returns
+ * itself.
+ *
+ * @param {Node|null|undefined} node  The node whose ownerDocument is required.
+ * @returns {Document}  The owner document or window.document if unsupported.
+ */
+function ownerDocument(node) {
+  if (!node) {
+    return document;
+  } else if (node.nodeType == DOM_DOCUMENT_NODE) {
+    return /** @type Document */(node);
+  } else {
+    return node.ownerDocument || document;
+  }
+}
+
+/**
+ * Creates a new text node in the given document.
+ *
+ * @param {Document} doc  Target document.
+ * @param {string} text  Text composing new text node.
+ * @return {Text}  Newly constructed text node.
+ */
+function domCreateTextNode(doc, text) {
+  return doc.createTextNode(text);
+}
+
+/**
+ * Appends a new child to the specified (parent) node.
+ *
+ * @param {Element} node  Parent element.
+ * @param {Node} child  Child node to append.
+ * @return {Node}  Newly appended node.
+ */
+function domAppendChild(node, child) {
+  return node.appendChild(child);
+}
+
+/**
+ * Sets display to default.
+ *
+ * @param {Element} node  The dom element to manipulate.
+ */
+function displayDefault(node) {
+  node.style[CSS_display] = '';
+}
+
+/**
+ * Sets display to none. Doing this as a function saves a few bytes for
+ * the 'style.display' property and the 'none' literal.
+ *
+ * @param {Element} node  The dom element to manipulate.
+ */
+function displayNone(node) {
+  node.style[CSS_display] = 'none';
+}
+
+
+/**
+ * Sets position style attribute to absolute.
+ *
+ * @param {Element} node  The dom element to manipulate.
+ */
+function positionAbsolute(node) {
+  node.style[CSS_position] = 'absolute';
+}
+
+
+/**
+ * Inserts a new child before a given sibling.
+ *
+ * @param {Node} newChild  Node to insert.
+ * @param {Node} oldChild  Sibling node.
+ * @return {Node}  Reference to new child.
+ */
+function domInsertBefore(newChild, oldChild) {
+  return oldChild.parentNode.insertBefore(newChild, oldChild);
+}
+
+/**
+ * Replaces an old child node with a new child node.
+ *
+ * @param {Node} newChild  New child to append.
+ * @param {Node} oldChild  Old child to remove.
+ * @return {Node}  Replaced node.
+ */
+function domReplaceChild(newChild, oldChild) {
+  return oldChild.parentNode.replaceChild(newChild, oldChild);
+}
+
+/**
+ * Removes a node from the DOM.
+ *
+ * @param {Node} node  The node to remove.
+ * @return {Node}  The removed node.
+ */
+function domRemoveNode(node) {
+  return domRemoveChild(node.parentNode, node);
+}
+
+/**
+ * Remove a child from the specified (parent) node.
+ *
+ * @param {Element} node  Parent element.
+ * @param {Node} child  Child node to remove.
+ * @return {Node}  Removed node.
+ */
+function domRemoveChild(node, child) {
+  return node.removeChild(child);
+}
+
+
+/**
+ * Trim whitespace from begin and end of string.
+ *
+ * @see testStringTrim();
+ *
+ * @param {string} str  Input string.
+ * @return {string}  Trimmed string.
+ */
+function stringTrim(str) {
+  return stringTrimRight(stringTrimLeft(str));
+}
+
+/**
+ * Trim whitespace from beginning of string.
+ *
+ * @see testStringTrimLeft();
+ *
+ * @param {string} str  Input string.
+ * @return {string}  Trimmed string.
+ */
+function stringTrimLeft(str) {
+  return str.replace(/^\s+/, "");
+}
+
+/**
+ * Trim whitespace from end of string.
+ *
+ * @see testStringTrimRight();
+ *
+ * @param {string} str  Input string.
+ * @return {string}  Trimmed string.
+  */
+function stringTrimRight(str) {
+  return str.replace(/\s+$/, "");
+}
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/notification.html b/chrome/common/extensions/docs/examples/extensions/irc/servlet/notification.html
new file mode 100644
index 0000000..e068873
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/notification.html
@@ -0,0 +1,62 @@
+<html>
+  <head>
+    <script src="util.js" type="text/javascript"></script>
+    <style>
+body {
+  margin: 0px;
+  padding: 0px;
+  -webkit-user-select: none;
+}
+
+#notification {
+  width: 300px;
+  height: 50px;
+  position: fixed;
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-box-align: stretch;
+}
+
+#title {
+  display: -webkit-box;
+  -webkit-box-flex: 0;
+  padding: 2px 4px 2px 4px;
+  background-color: #AAAAAA;
+  color: white;
+  font-family: Verdana, sans-serif;
+  font-size: 10px;
+}
+
+#content {
+  display: -webkit-box;
+  -webkit-box-flex: 1;
+  color: #444444;
+  font-family: "Lucida Console", Monospace;
+  font-size: 12px;
+  padding: 4px;
+  text-overflow: ellipsis;
+}
+    </style>
+    <script>
+window.onload = function() {
+  var argString = location.search.substring(location.search.indexOf("?") + 1);
+  var tokens = argString.split("&");
+  var args = {};
+  tokens.forEach(function(token) {
+    var keyVal = token.split("=");
+    args[keyVal[0]] = decodeURIComponent(keyVal[1]);
+  });
+
+  $('title').innerText = args.title;
+  $('content').innerText = args.content;
+}
+    </script>   
+  </head>
+  <body onclick="window.close();">
+    <div id="notification">
+      <div id="title"></div>
+      <div id="content"></div>
+    </div>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/src/org/chromium/IRCProxyWebSocket.java b/chrome/common/extensions/docs/examples/extensions/irc/servlet/src/org/chromium/IRCProxyWebSocket.java
new file mode 100644
index 0000000..522d47a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/src/org/chromium/IRCProxyWebSocket.java
@@ -0,0 +1,116 @@
+package org.chromium;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import javax.net.SocketFactory;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.websocket.WebSocket;
+import org.eclipse.jetty.websocket.WebSocketServlet;
+
+public class IRCProxyWebSocket extends WebSocketServlet {
+
+  private static final long serialVersionUID = 1L;
+
+  private final Set<ChatWebSocket> members_ =
+      new CopyOnWriteArraySet<ChatWebSocket>();
+
+  protected void doGet(HttpServletRequest request, HttpServletResponse response) 
+      throws ServletException ,IOException {
+    getServletContext().getNamedDispatcher("default").forward(request,response);
+  }
+  
+  protected WebSocket doWebSocketConnect(HttpServletRequest request,
+                                         String protocol) {
+    return new ChatWebSocket(); 
+  }
+  
+  class ChatWebSocket implements WebSocket, Runnable {
+    Outbound outbound_;
+    Socket socket_ = null;
+    OutputStreamWriter out_;
+    BufferedReader in_;
+    Thread thread_;
+    byte frame_ = 0;
+
+    public void onConnect(Outbound outbound) {
+      outbound_= outbound;
+    }
+      
+    public void onMessage(byte frame, byte[] data,int offset, int length) {}
+
+    public void onMessage(byte frame, String data) {
+      try {
+        if (socket_ == null) {
+          try {
+            // We assume the client is going to connect and initiate a
+            // connection with the message "server:port".
+            String tokens[] = data.split(":");
+            socket_ = SocketFactory.getDefault().createSocket(tokens[0],
+                Integer.parseInt(tokens[1]));
+            out_ = new OutputStreamWriter(socket_.getOutputStream());
+            InputStreamReader in = new InputStreamReader(
+                socket_.getInputStream());
+            in_ = new BufferedReader(in);
+
+            members_.add(this);
+            thread_ = new Thread(this);
+            thread_.start();
+            
+          } catch (UnknownHostException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+          } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+          } 
+        } else {
+          System.out.print(">> " + data);
+          out_.write(data);
+          out_.flush();
+        }        
+      } catch (IOException e) {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      }
+    }
+
+    public void onDisconnect() {
+      try {
+        socket_.close();
+        thread_.stop();
+      } catch (IOException e) {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      }
+      members_.remove(this);
+    }
+
+    @Override
+    public void run() {
+      while(true) {
+        try {
+           if (in_.ready()) {
+            String line = in_.readLine();
+            System.out.println("<< " + line);
+            outbound_.sendMessage(frame_, line + "\r\n");
+           
+          } else {
+            Thread.sleep(100);
+          }
+        } catch (IOException e) {
+        } catch (InterruptedException e) {
+        }
+      }
+    }
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/styles.css b/chrome/common/extensions/docs/examples/extensions/irc/servlet/styles.css
new file mode 100644
index 0000000..3773dfa
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/styles.css
@@ -0,0 +1,159 @@
+body {

+  margin: 0;

+  padding: 0;

+}

+

+#pageContainer {

+  display: -webkit-box;

+  position: fixed;

+  -webkit-box-orient: vertical;

+  -webkit-box-align: stretch;

+  height: 100%;

+  width: 100%;

+ }

+

+ #headerContainer {

+  display: -webkit-box;

+  height: 32px;

+  -webkit-box-orient: horizontal;

+  -webkit-box-align: stretch;

+ }

+

+#pageControls {

+  position: absolute;

+  right: 0px;

+  font-family: Verdana, sans-serif;

+  font-size: 16px;

+  color: #aaaaaa;

+  padding: 8px;

+}

+

+#pageControls *, .removeButton, .channel, .messageLine * {

+  display: inline-block;

+}

+

+.addControlLabel {

+  margin-left: 20px;

+}

+

+.addButton {

+  background-color: #aaaaaa;

+  color: white;

+}

+

+#slideContainer {

+  display: -webkit-box;

+  -webkit-box-flex: 1;

+  position: relative;

+}

+

+#channelSlides {

+  position: absolute;

+  width: 100%;

+  height: 100%;

+}

+

+.channelSlide {

+  position: absolute;

+  width: 80%;

+  height: 100%;

+  background: -webkit-linear-gradient(#aaa, white);

+  -webkit-transition: margin 0.25s ease-in-out;

+}

+

+.channelSlide.far-left {

+  margin-left: -160%;

+}

+

+.channelSlide.left {

+  margin-left: -75%;

+}

+

+.channelSlide.center {

+  margin-left: 10%;

+}

+

+.channelSlide.right {

+  margin-left: 95%;

+}

+

+.channelSlide.far-right {

+  margin-left: 180%;

+}

+

+.channelControls {

+  position: absolute;

+  z-index: 1;

+  right: 0px;

+  top:0px;

+  color: white;

+  text-align: right;

+  padding: 8px;

+  font-family: "Verdana", sans-serif;

+  font-size: 20px;

+}

+

+.channelControls .removeButton {

+  background-color: white;

+  color: #999999;

+  padding: 0px 6px 4px 6px;

+  height:

+}

+

+.channelSlideContainer {

+  position: relative;

+  display: -webkit-box;

+  -webkit-box-orient: vertical;

+  -webkit-box-align: stretch;

+  height: 100%;

+  width: 100%;

+}

+

+.messageListContainer {

+  overflow: hidden;

+  position: relative;

+  display: -webkit-box;

+  -webkit-box-flex: 1;

+}

+

+.messageListSpacer {

+  display: -webkit-box;

+  -webkit-box-flex: 0;

+  height: 40px;

+  width: 100%;

+}

+

+.messageLine {

+  margin: 6px;

+  color: #999999;

+  font-family: "Lucida Console", Monospace;

+  font-size: 14px;

+}

+

+.messageList {

+  position: absolute;

+  bottom: 0;

+}

+

+#typingDiv {

+  position: fixed;

+  z-index: 4;

+  width: 80%;

+  height: 30px;

+  margin: 10px;

+  margin-left: 10%;

+  bottom: 0px;

+  -webkit-box-shadow: 3px 3px 5px #888;

+}

+

+#entryText {

+  width: 100%;

+  border: 0px;

+  height: 100%;

+  padding-left: 8px;

+  padding-right: 8px;

+  font-family: "Lucida Console", Monospace;

+  color: white;

+  border: 0px;

+  background: #777777;

+}

diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/util.js b/chrome/common/extensions/docs/examples/extensions/irc/servlet/util.js
new file mode 100644
index 0000000..ee1d332
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/util.js
@@ -0,0 +1,19 @@
+function $(el) {

+  return document.getElementById(el);

+}

+

+function $F(el) {

+  return $(el).value;

+}

+

+function bind(obj, func) {

+  return function() {

+    return func.apply(obj, arguments);

+  };

+}

+

+function childNodeWithClass(node, className) {

+  var expression = ".//*[@class='" + className + "']";

+  return document.evaluate(expression, node,

+      null, XPathResult.ANY_TYPE, null).iterateNext();  

+}
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/mappy/background.js b/chrome/common/extensions/docs/examples/extensions/mappy/background.js
new file mode 100644
index 0000000..7fbed30
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/mappy/background.js
@@ -0,0 +1,44 @@
+// 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.
+
+// Global accessor that the popup uses.
+var addresses = {};
+var selectedAddress = null;
+var selectedId = null;
+
+function updateAddress(tabId) {
+  chrome.tabs.sendRequest(tabId, {}, function(address) {
+    addresses[tabId] = address;
+    if (!address) {
+      chrome.pageAction.hide(tabId);
+    } else {
+      chrome.pageAction.show(tabId);
+      if (selectedId == tabId) {
+        updateSelected(tabId);
+      }
+    }
+  });
+}
+
+function updateSelected(tabId) {
+  selectedAddress = addresses[tabId];
+  if (selectedAddress)
+    chrome.pageAction.setTitle({tabId:tabId, title:selectedAddress});
+}
+
+chrome.tabs.onUpdated.addListener(function(tabId, change, tab) {
+  if (change.status == "complete") {
+    updateAddress(tabId);
+  }
+});
+
+chrome.tabs.onSelectionChanged.addListener(function(tabId, info) {
+  selectedId = tabId;
+  updateSelected(tabId);
+});
+
+// Ensure the current selected tab is set up.
+chrome.tabs.getSelected(null, function(tab) {
+  updateAddress(tab.id);
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/mappy/icon.png b/chrome/common/extensions/docs/examples/extensions/mappy/icon.png
new file mode 100644
index 0000000..f48e213
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/mappy/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/mappy/manifest.json b/chrome/common/extensions/docs/examples/extensions/mappy/manifest.json
new file mode 100644
index 0000000..45deedd
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/mappy/manifest.json
@@ -0,0 +1,22 @@
+{
+  "name": "Mappy",
+  "version": "0.6.1",
+  "description": "Finds addresses in the web page you're on and pops up a map window.",
+  "icons": { "128": "icon.png" },
+  "background": { "scripts": ["background.js"] },
+  "content_scripts": [
+    { "matches": ["http://*/*"], "js": ["mappy_content_script.js"] }
+  ],
+  "permissions": [
+    "tabs",
+    "https://maps.google.com/*",
+    "https://maps.googleapis.com/*"
+  ],
+  "page_action": {
+      "default_name": "Display Map",
+      "default_icon": "marker.png",
+      "default_popup": "popup.html"
+  },
+  "manifest_version": 2,
+  "content_security_policy": "default-src 'none'; style-src 'self'; script-src 'self'; connect-src https://maps.googleapis.com; img-src https://maps.google.com"
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/mappy/mappy_content_script.js b/chrome/common/extensions/docs/examples/extensions/mappy/mappy_content_script.js
new file mode 100644
index 0000000..2393a0f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/mappy/mappy_content_script.js
@@ -0,0 +1,52 @@
+// 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.
+
+// The background page is asking us to find an address on the page.
+if (window == top) {
+  chrome.extension.onRequest.addListener(function(req, sender, sendResponse) {
+    sendResponse(findAddress());
+  });
+}
+
+// Search the text nodes for a US-style mailing address.
+// Return null if none is found.
+var findAddress = function() {
+  var found;
+  var re = /(\d+\s+[':.,\s\w]*,\s*[A-Za-z]+\s*\d{5}(-\d{4})?)/m;
+  var node = document.body;
+  var done = false;
+  while (!done) {
+    done = true;
+    for (var i = 0; i < node.childNodes.length; ++i) {
+      var child = node.childNodes[i];
+      if (child.textContent.match(re)) {
+        node = child;
+        found = node;
+        done = false;
+        break;
+      }
+    }
+  }
+  if (found) {
+    var text = "";
+    if (found.childNodes.length) {
+      for (var i = 0; i < found.childNodes.length; ++i) {
+        text += found.childNodes[i].textContent + " ";
+      }
+    } else {
+      text = found.textContent;
+    }
+    var match = re.exec(text);
+    if (match && match.length) {
+      console.log("found: " + match[0]);
+      var trim = /\s{2,}/g;
+      return match[0].replace(trim, " ");
+    } else {
+      console.log("bad initial match: " + found.textContent);
+      console.log("no match in: " + text);
+    }
+  }
+  return null;
+}
+
diff --git a/chrome/common/extensions/docs/examples/extensions/mappy/marker.png b/chrome/common/extensions/docs/examples/extensions/mappy/marker.png
new file mode 100644
index 0000000..83c8889
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/mappy/marker.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/mappy/popup.css b/chrome/common/extensions/docs/examples/extensions/mappy/popup.css
new file mode 100644
index 0000000..8091788
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/mappy/popup.css
@@ -0,0 +1,15 @@
+/**
+ * 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.
+ */
+
+body {
+  margin: 0px;
+  padding: 0px;
+}
+
+#map {
+  width: 512px;
+  height: 512px;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/mappy/popup.html b/chrome/common/extensions/docs/examples/extensions/mappy/popup.html
new file mode 100644
index 0000000..c5d27fb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/mappy/popup.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Popup</title>
+    <link href="popup.css" rel="stylesheet" type="text/css">
+  </head>
+  <body>
+    <img id="map">
+    <script src="popup.js"></script>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/mappy/popup.js b/chrome/common/extensions/docs/examples/extensions/mappy/popup.js
new file mode 100644
index 0000000..45663c3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/mappy/popup.js
@@ -0,0 +1,45 @@
+// 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.
+
+var maps_key = "ABQIAAAATfHumDbW3OmRByfquHd3SRTRERdeAiwZ9EeJWta3L_JZVS0bOBRQeZgr4K0xyVKzUdnnuFl8X9PX0w";
+
+function gclient_geocode(address) {
+  var url = 'https://maps.googleapis.com/maps/api/geocode/json?address=' +
+            encodeURIComponent(address) + '&sensor=false';
+  var request = new XMLHttpRequest();
+
+  request.open('GET', url, true);
+  console.log(url);
+  request.onreadystatechange = function (e) {
+    console.log(request, e);
+    if (request.readyState == 4) {
+      if (request.status == 200) {
+        var json = JSON.parse(request.responseText);
+        var latlng = json.results[0].geometry.location;
+        latlng = latlng.lat + ',' + latlng.lng;
+
+        var src = "https://maps.google.com/staticmap?center=" + latlng +
+                  "&markers=" + latlng + "&zoom=14" +
+                  "&size=512x512&sensor=false&key=" + maps_key;
+        var map = document.getElementById("map");
+
+        map.src = src;
+        map.addEventListener('click', function () {
+          window.close();
+        });
+      } else {
+        console.log('Unable to resolve address into lat/lng');
+      }
+    }
+  };
+  request.send(null);
+}
+
+function map() {
+  var address = chrome.extension.getBackgroundPage().selectedAddress;
+  if (address)
+    gclient_geocode(address);
+}
+
+window.onload = map;
diff --git a/chrome/common/extensions/docs/examples/extensions/maps_app/128.png b/chrome/common/extensions/docs/examples/extensions/maps_app/128.png
new file mode 100644
index 0000000..8148d6c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/maps_app/128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/maps_app/24.png b/chrome/common/extensions/docs/examples/extensions/maps_app/24.png
new file mode 100644
index 0000000..0b4433e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/maps_app/24.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/maps_app/manifest.json b/chrome/common/extensions/docs/examples/extensions/maps_app/manifest.json
new file mode 100644
index 0000000..bb1f2e8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/maps_app/manifest.json
@@ -0,0 +1,15 @@
+{
+  "name": "Google Maps",
+  "version": "3",
+  "icons": { "24": "24.png", "128": "128.png" },
+  "app": {
+    "urls": [
+      "http://maps.google.com/"
+    ],
+    "launch": {
+      "web_url": "http://maps.google.com/"
+    }
+  },
+  "permissions": ["geolocation"],
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/native_messaging/README.txt b/chrome/common/extensions/docs/examples/extensions/native_messaging/README.txt
new file mode 100644
index 0000000..e2f77ac
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/native_messaging/README.txt
@@ -0,0 +1,8 @@
+Before using this extension, you must register the native client (echo.py).
+
+To register the client, you will first need to create a directory called
+"Native Hosts" beside your user data directory
+(http://www.chromium.org/user-experience/user-data-directory). Then, move
+echo.py into this new directory.
+
+The client is written in Python and will need the python executable to run.
diff --git a/chrome/common/extensions/docs/examples/extensions/native_messaging/echo.py b/chrome/common/extensions/docs/examples/extensions/native_messaging/echo.py
new file mode 100755
index 0000000..ffb6cef
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/native_messaging/echo.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+# A simple native client in python.
+# All this client does is echo the text it recieves back at the extension.
+
+import sys
+import struct
+
+MESSAGE_TYPE_SEND_MESSAGE_REQUEST = 0
+MESSAGE_TYPE_SEND_MESSAGE_RESPONSE = 1
+MESSAGE_TYPE_CONNECT = 2
+MESSAGE_TYPE_CONNECT_MESSAGE = 3
+
+def Main():
+  message_number = 0
+
+  while 1:
+    # Read the message type (first 4 bytes).
+    type_bytes = sys.stdin.read(4)
+
+    if len(type_bytes) == 0:
+      break
+
+    message_type = struct.unpack('i', type_bytes)[0]
+
+    # Read the message length (4 bytes).
+    text_length = struct.unpack('i', sys.stdin.read(4))[0]
+
+    # Read the text (JSON object) of the message.
+    text = sys.stdin.read(text_length).decode('utf-8')
+
+    message_number += 1
+
+    response = '{{"id": {0}, "echo": {1}}}'.format(message_number,
+                                                  text).encode('utf-8')
+
+    # Choose the correct message type for the response.
+    if message_type == MESSAGE_TYPE_SEND_MESSAGE_REQUEST:
+      response_type = MESSAGE_TYPE_SEND_MESSAGE_RESPONSE
+    else:
+      response_type = MESSAGE_TYPE_CONNECT_MESSAGE
+
+    try:
+      sys.stdout.write(struct.pack("II", response_type, len(response)))
+      sys.stdout.write(response)
+      sys.stdout.flush()
+    except IOError:
+      break
+
+if __name__ == '__main__':
+  Main()
diff --git a/chrome/common/extensions/docs/examples/extensions/native_messaging/icon-128.png b/chrome/common/extensions/docs/examples/extensions/native_messaging/icon-128.png
new file mode 100644
index 0000000..36dfab7
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/native_messaging/icon-128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/native_messaging/icon-19.png b/chrome/common/extensions/docs/examples/extensions/native_messaging/icon-19.png
new file mode 100644
index 0000000..71a29a4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/native_messaging/icon-19.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/native_messaging/manifest.json b/chrome/common/extensions/docs/examples/extensions/native_messaging/manifest.json
new file mode 100644
index 0000000..cbc9630
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/native_messaging/manifest.json
@@ -0,0 +1,13 @@
+{
+  "name": "Echo Native Message",
+  "version": "1.0",
+  "manifest_version": 2,
+  "description": "Send a message to a native application.",
+  "icons": {
+    "128": "icon-128.png"
+  },
+  "browser_action": {
+    "default_icon": "icon-19.png",
+    "default_popup": "popup.html"
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/native_messaging/popup.html b/chrome/common/extensions/docs/examples/extensions/native_messaging/popup.html
new file mode 100644
index 0000000..33c3aee
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/native_messaging/popup.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2012 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.
+-->
+
+<html>
+  <head>
+    <script src='./popup.js'></script>
+  </head>
+  <body>
+    <input id='input-text' type='text' />
+    <button id='send-native-message'>Connect</button>
+    <div id='response'></div>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/native_messaging/popup.js b/chrome/common/extensions/docs/examples/extensions/native_messaging/popup.js
new file mode 100644
index 0000000..07757e1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/native_messaging/popup.js
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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.
+
+var port = null;
+
+function gotNativeMessage(message) {
+  document.getElementById('response').innerHTML = "<p>Message Number: " +
+                                                  message.id +
+                                                  "</p><p>Message Text: " +
+                                                  JSON.stringify(message.echo) +
+                                                  "</p>";
+}
+
+function sendNativeMessage() {
+  if (!port) {
+    port = chrome.extension.connectNative('echo.py', {"message": "Hi there!"});
+    port.onMessage.addListener(gotNativeMessage);
+    document.getElementById('input-text').style.display = 'block';
+    document.getElementById('send-native-message').innerHTML = 'Send Message';
+  } else {
+    port.postMessage({"message": document.getElementById('input-text').value});
+  }
+}
+
+document.addEventListener('DOMContentLoaded', function () {
+  document.getElementById('input-text').style.display = 'none';
+  document.getElementById('send-native-message').addEventListener(
+      'click', sendNativeMessage);
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/news/_locales/en/messages.json b/chrome/common/extensions/docs/examples/extensions/news/_locales/en/messages.json
new file mode 100644
index 0000000..97e07c1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/_locales/en/messages.json
@@ -0,0 +1,102 @@
+{
+  "extName": {
+    "message": "News Reader (by Google)"
+  },
+  "extDesc": {
+    "message": "Displays the latest stories from Google News in a popup."
+  },
+  "ext_default_title": {
+    "message": "Google News"
+  },
+
+  "1": {
+    "message": "Top Stories"
+  },
+  "n": {
+    "message": "Nation"
+  },
+  "w": {
+    "message": "World"
+  },
+  "b": {
+    "message": "Business"
+  },
+  "t": {
+    "message": "Science/Technology"
+  },
+  "e": {
+    "message": "Entertainment"
+  },
+  "s": {
+    "message": "Sports"
+  },
+  "m": {
+    "message": "Health"
+  },
+  "po": {
+    "message": "Most Popular"
+  },
+
+  "options": {
+    "message": "Options"
+  },
+  "more_stories": {
+    "message": "More stories"
+  },
+
+  "direction": {
+    "message": "ltr"
+  },
+
+  "country": {
+    "message": "Country:"
+  },
+  "topic": {
+    "message": "Topics:"
+  },
+  "save": {
+    "message": "Save"
+  },
+  "saveStatus": {
+    "message": "Options saved"
+  },
+  "storyCount": {
+    "message": "Number of stories:"
+  },
+  "newsOption": {
+    "message": "Google News Options"
+  },
+  "customText": {
+    "message": "Custom Topics:"
+  },
+  "maximumTopics": {
+   "message": "(Maximum $count$)",
+   "placeholders": {
+     "count": {
+       "content": "$1"
+      }
+    }
+  },
+  "submitButton": {
+    "message": "Add"
+  },
+  "deleteTitle": {
+    "message": "Delete"
+  },
+  "invalidChars": {
+    "message": "Invalid character(s)"
+  },
+  "noTopic": {
+    "message": "At least one Topic must be selected"
+  },
+
+  "fetchError": {
+    "message": "Error: Failed to fetch news stories."
+  },
+  "wrongTopic": {
+    "message": "Error: Not a valid feed."
+  },
+  "noStory": {
+    "message": "No story right now"
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/news/css/feed.css b/chrome/common/extensions/docs/examples/extensions/news/css/feed.css
new file mode 100644
index 0000000..49edf8f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/css/feed.css
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2010 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.
+ * 
+ * Sets style of different elements in pop-up page.
+ * 
+ * Author: navneetg@google.com (Navneet Goel).
+ */
+
+body {
+  font-family: arial, sans-serif;
+  font-size: 12px;
+  min-width: 500px;
+  overflow: visible;
+}
+a {
+  color: #0000CC;
+  cursor: pointer;
+  text-decoration: underline;
+}
+#noStories {
+  background-color: rgb(255, 238, 136);
+  font-size: 13px;
+  font-weight: bold;
+  margin-left: 140px;
+  margin-right: 140px;
+  text-align: center;
+}
+.open_box {
+  background-image: url(/images/sprite_arrows.gif);
+  background-position: 0px -24px;
+  clear: left;
+  cursor: pointer;
+  display: block;
+  height: 12px;
+  margin-top: 2px;
+  overflow: hidden;
+  width: 12px;
+  float: left;
+}
+.opened .open_box {
+  background-position: -12px -24px;
+}
+.item {
+  padding: 2px 0;
+}
+.item_title {
+  cursor: pointer;
+  display: block;
+  min-width: 300px;
+  padding: 0 0 0 17px;
+}
+.item_desc {
+  border: none;
+  display: block;
+  height: 0;
+  margin: 0;
+  min-width: 500px;
+  padding: 0;
+  -webkit-transition: height 0.2s ease-out;
+}
+#title {
+  display: block;
+}
+.error {
+  background-color: rgb(255, 238, 136);
+  font-size: 13px;
+  font-weight: bold;
+  margin-left: 125px;
+  margin-right: 125px;
+  text-align: center;
+  white-space: nowrap;
+}
+.more {
+  color: #88C;
+  display: block;
+  margin-left: 385px;
+  padding-right: 10px;
+  padding-top: 5px;
+  text-align: right;
+}
+.topicsLTR {
+  direction: ltr;
+  font-size: 13px;
+  padding-left: 5px;
+}
+.topicsRTL {
+  direction: rtl;
+  font-size: 13px;
+  padding-right: 5px;
+}
+body.rtl #feed {
+  direction: rtl;
+}
+body.rtl .open_box {
+  float: right;
+}
+body.rtl .item_title {
+  padding: 0 17px 0 0;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/news/css/options.css b/chrome/common/extensions/docs/examples/extensions/news/css/options.css
new file mode 100644
index 0000000..91aff7e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/css/options.css
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2010 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.
+ * 
+ * Sets style of different elements in options page.
+ * 
+ * Author: navneetg@google.com (Navneet Goel).
+ */
+
+@CHARSET "UTF-8";
+body {
+  background-color: rgb(235, 239, 249);
+  font-family: arial , sans-serif;
+  font-size: 13px;
+}
+.suppr {
+  background-image: url(/images/delete-icon.png);
+  background-repeat: no-repeat;
+  cursor: pointer;
+  display: inline;
+  float: left;
+  height: 17px;
+  margin-top: 3px;
+  overflow: hidden;
+  width: 14px;
+}
+.checkBoxTopic {
+  float: left;
+  padding-left: 2px;
+  padding-top: 1px;
+}
+.checkBox {
+  float: left;
+}
+.boxAndTopic {
+  overflow: auto;
+  padding-bottom: 5px;
+}
+#invalid_status, #save_status {
+  background-color: rgb(255, 241, 168);
+  font-weight: bold;
+  margin-left: 10px;
+  opacity: 0;
+  padding-bottom: 3px;
+  padding-left: 7px;
+  padding-right: 7px;
+  padding-top: 3px;
+}
+#all_content {
+  background-color: white;
+  border-bottom-left-radius: 12px 12px;
+  border-bottom-right-radius: 12px 12px;
+  border-color: #B5C7DE;
+  border-style: solid;
+  border-top-left-radius: 12px 12px;
+  border-top-right-radius: 12px 12px;
+  border-width: 4px;
+  margin: 40px auto 20px;
+  padding: 12px;
+  width: 600px;
+}
+.col2 {
+  padding-left: 20px;
+}
+body.rtl .col1, body.rtl .col2 {
+  text-align: right;
+}
+body.rtl {
+  direction: rtl;
+}
+body.rtl .col2 {
+  padding-right: 20px;
+}
+body.rtl .checkBox, body.rtl .checkBoxTopic {
+  float: right;
+}
+body.rtl table.contentTable {
+  margin:0 55px 0 0;
+}
+body.rtl #save_div{
+  margin: 0 220px 0 0;
+}
+body.rtl #countryList{
+  margin:0 5px 0 0;
+}
+.col1 {
+  padding-top: 3px;
+  width: 115px;
+}
+.all_rows {
+  height: 35px;
+  vertical-align: top;
+}
+body.rtl .cusTopicsClass {
+  float: right;
+}
+.cusTopicsClass {
+  float: left;
+  width: 225px;
+}
+#save_div {
+  margin-left: 220px;
+}
+#countryList {
+  padding-left: 5px;
+}
+#logo {
+  font-size: 15px;
+  font-weight: bold;
+  text-align: center;
+}
+.noborder {
+  border: 0;
+  font-family: arial, sans-serif;
+  height: 15px;
+  outline: none;
+  overflow: hidden;
+  resize: none;
+  width:205px;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/news/images/buzz.png b/chrome/common/extensions/docs/examples/extensions/news/images/buzz.png
new file mode 100644
index 0000000..c02f12d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/images/buzz.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/news/images/delete-icon.png b/chrome/common/extensions/docs/examples/extensions/news/images/delete-icon.png
new file mode 100644
index 0000000..4087427
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/images/delete-icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/news/images/fb.png b/chrome/common/extensions/docs/examples/extensions/news/images/fb.png
new file mode 100644
index 0000000..6298d4f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/images/fb.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/news/images/news.gif b/chrome/common/extensions/docs/examples/extensions/news/images/news.gif
new file mode 100644
index 0000000..2d8df79
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/images/news.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/news/images/news_action.png b/chrome/common/extensions/docs/examples/extensions/news/images/news_action.png
new file mode 100644
index 0000000..98fe9b2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/images/news_action.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/news/images/news_icon.png b/chrome/common/extensions/docs/examples/extensions/news/images/news_icon.png
new file mode 100644
index 0000000..7d69f79
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/images/news_icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/news/images/sprite_arrows.gif b/chrome/common/extensions/docs/examples/extensions/news/images/sprite_arrows.gif
new file mode 100644
index 0000000..4560faf
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/images/sprite_arrows.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/news/images/twitter.png b/chrome/common/extensions/docs/examples/extensions/news/images/twitter.png
new file mode 100644
index 0000000..278ea7f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/images/twitter.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/news/javascript/feed.js b/chrome/common/extensions/docs/examples/extensions/news/javascript/feed.js
new file mode 100644
index 0000000..f4cc99a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/javascript/feed.js
@@ -0,0 +1,388 @@
+/**
+ * Copyright (c) 2010 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 retrieves news feed and shows news in pop-up
+ * page according to country, topics and no. of stories selected in the
+ * option page.
+ */
+
+// Store value retrieved from locale.
+var moreStoriesLocale = chrome.i18n.getMessage('more_stories') + ' \u00BB ';
+var directionLocale = chrome.i18n.getMessage('direction');
+
+// Feed URL.
+var feedUrl = DEFAULT_NEWS_URL;
+
+//The XMLHttpRequest object that tries to load and parse the feed.
+var req;
+
+/**
+ * Sends request to Google News server
+ */
+function main() {
+  req = new XMLHttpRequest();
+  req.onload = handleResponse;
+  req.onerror = handleError;
+  req.open('GET', feedUrl, true);
+  req.send(null);
+}
+
+/**
+ * Handles feed parsing errors.
+ * @param {String} error The localized error message.
+ */
+function handleFeedParsingFailed(error) {
+  var feed = $('feed');
+  $('noStories').style.display = 'none';
+  feed.className = 'error';
+  feed.innerText = error;
+}
+
+/**
+ * Handles errors during the XMLHttpRequest.
+ */
+function handleError() {
+  handleFeedParsingFailed(chrome.i18n.getMessage('fetchError'));
+  $('topics').style.display = 'none';
+}
+
+/**
+ * Parses the feed response.
+ */
+function handleResponse() {
+  var doc = req.responseXML;
+  if (!doc) {
+    handleFeedParsingFailed(chrome.i18n.getMessage('wrongTopic'));
+    var img = $('title');
+    if(!img.src) {
+      img.src = "/images/news.gif";
+    }
+
+    document.querySelector('body').style.minHeight = 0;
+    return;
+  }
+  buildPreview(doc);
+}
+
+// Stores no. of stories selected in options page.
+var maxFeedItems = (window.localStorage.getItem('count')) ?
+    window.localStorage.getItem('count') : 5;
+
+// Where the more stories link should navigate to.
+var moreStoriesUrl;
+
+/**
+ * Generates news iframe in pop-up page by parsing retrieved feed.
+ * @param {HTMLDocument} doc HTML Document received in feed.
+ */
+function buildPreview(doc) {
+  // Get the link to the feed source.
+  var link = doc.querySelector('link');
+  var parentTag = link.parentNode.tagName;
+  if (parentTag != 'item' && parentTag != 'entry') {
+    moreStoriesUrl = link.textContent;
+  }
+
+  // Setup the title image.
+  var image = doc.querySelector('image');
+  var titleImg;
+
+  // Stores whether language script is Right to Left or not for setting style
+  // of share buttons(Facebook, Twitter and Google Buzz) in iframe.
+  var isRtl = 'lTR';
+
+  if (image) {
+    var url = image.querySelector('url');
+    if (url) {
+      titleImg = url.textContent;
+
+      // Stores URL of title image to be shown on pop-up page.
+      var titleImgUrl = titleImg;
+      var pattern = /ar_/gi;
+      var result = titleImgUrl.match(pattern);
+      if (result != null || titleImgUrl == ISRAEL_IMAGE_URL) {
+        isRtl = 'rTL';
+      }
+    }
+  }
+
+  var img = $('title');
+  if (titleImg) {
+    img.src = titleImg;
+    if (moreStoriesUrl) {
+      $('title_a').addEventListener('click', moreStories);
+    }
+  } else {
+    img.style.display = 'none';
+  }
+
+  // Construct the iframe's HTML.
+  var iframe_src = '<!doctype html><html><head><script>' +
+                   $('iframe_script').textContent + '<' +
+                   '/script><style> ' +
+                   '.rTL {margin-right: 102px; text-align: right;} ' +
+                   '.lTR {margin-left: 102px; text-align: left;} ' +
+                   '</style></head><body onload="frameLoaded();" ' +
+                   'style="padding:0px;margin:0px;">';
+
+  var feed = $('feed');
+  feed.className = '';
+  var entries = doc.getElementsByTagName('entry');
+  if (entries.length == 0) {
+    entries = doc.getElementsByTagName('item');
+  }
+  var count = Math.min(entries.length, maxFeedItems);
+
+  // Stores required height by pop-up page.
+  var minHeight = 19;
+  minHeight = (minHeight * (count - 1)) + 100;
+  document.querySelector('body').style.minHeight = minHeight + 'px';
+  $('feed').innerHTML = '';
+
+  for (var i = 0; i < count; i++) {
+    item = entries.item(i);
+
+    // Grab the title for the feed item.
+    var itemTitle = item.querySelector('title');
+    if (itemTitle) {
+      itemTitle = itemTitle.textContent;
+    } else {
+      itemTitle = 'Unknown title';
+    }
+
+    // Grab the description.
+    var itemDesc = item.querySelector('description');
+    if (!itemDesc) {
+      itemDesc = item.querySelector('summary');
+      if (!itemDesc) {
+        itemDesc = item.querySelector('content');
+      }
+    }
+    if (itemDesc) {
+      itemDesc = itemDesc.childNodes[0].nodeValue;
+
+    } else {
+      itemDesc = '';
+    }
+    var itemLink = item.querySelector('link');
+    if (itemLink) {
+      itemLink = itemLink.textContent;
+    } else {
+      itemLink = 'Unknown itemLink';
+    }
+    var item = document.createElement('div');
+    item.className = 'item';
+    var box = document.createElement('div');
+    box.className = 'open_box';
+    box.addEventListener('click', showDesc);
+    item.appendChild(box);
+
+    var title = document.createElement('a');
+    title.className = 'item_title';
+    title.innerText = itemTitle;
+    title.addEventListener('click', showDesc);
+    item.appendChild(title);
+
+    var desc = document.createElement('iframe');
+    desc.scrolling = 'no';
+    desc.className = 'item_desc';
+    item.appendChild(desc);
+    feed.appendChild(item);
+
+    // Adds share buttons images(Facebook, Twitter and Google Buzz).
+    itemDesc += "<div class = '" + isRtl + "'>";
+    itemDesc += "<a style='cursor: pointer' id='fb' " +
+      "onclick='openNewsShareWindow(this.id,\"" + itemLink + "\")'>" +
+      "<img src='" + chrome.extension.getURL('/images/fb.png') + "'/></a>";
+    itemDesc += " <a style='cursor: pointer' id='twitter' " +
+      "onclick='openNewsShareWindow(this.id,\"" + itemLink + "\")'>" +
+      "<img src='" + chrome.extension.getURL('/images/twitter.png') + "'/></a>";
+    itemDesc += " <a style='cursor: pointer' id='buzz' " +
+      "onclick='openNewsShareWindow(this.id,\"" + itemLink + "\")'>" +
+      "<img src='" + chrome.extension.getURL('/images/buzz.png') + "'/></a>";
+    itemDesc += '</div>';
+
+    // The story body is created as an iframe with a data: URL in order to
+    // isolate it from this page and protect against XSS.  As a data URL, it
+    // has limited privileges and must communicate back using postMessage().
+    desc.src = 'data:text/html;charset=utf-8,' + iframe_src + itemDesc +
+      '</body></html>';
+  }
+  if (moreStoriesUrl && entries.length != 0) {
+    var more = document.createElement('a');
+    more.className = 'more';
+    more.innerText = moreStoriesLocale;
+    more.addEventListener('click', moreStories);
+    feed.appendChild(more);
+  }
+  setStyleByLang(titleImgUrl);
+
+  // Checks whether feed retrieved has news story or not. If not, then shows
+  // error message accordingly.
+  if (entries.length == 0) {
+    $('noStories').innerText = chrome.i18n.getMessage('noStory');
+    $('noStories').style.display = 'block';
+  } else {
+    $('noStories').style.display = 'none';
+  }
+}
+
+/**
+ * Show |url| in a new tab.
+ * @param {String} url The news URL.
+ */
+function showUrl(url) {
+  // Only allow http and https URLs.
+  if (url.indexOf('http:') != 0 && url.indexOf('https:') != 0) {
+    return;
+  }
+  chrome.tabs.create({url: url});
+}
+
+/**
+ * Redirects to Google news site for more stories.
+ * @param {Object} event Onclick event.
+ */
+function moreStories(event) {
+  showUrl(moreStoriesUrl);
+}
+
+/**
+ * Shows description of the news when users clicks on news title.
+ * @param {Object} event Onclick event.
+ */
+function showDesc(event) {
+  var item_ = event.currentTarget.parentNode;
+  var items = document.getElementsByClassName('item');
+  for (var i = 0, item; item = items[i]; i++) {
+    var iframe = item.querySelector('.item_desc');
+    if (item == item_ && item.className == 'item') {
+      item.className = 'item opened';
+      iframe.contentWindow.postMessage('reportHeight', '*');
+    } else {
+      item.className = 'item';
+      iframe.style.height = '0px';
+    }
+  }
+}
+
+/**
+ * Handles messages between different iframes and sets the display of iframe.
+ * @param {Object} e Onmessage event.
+ */
+function iframeMessageHandler(e) {
+  var iframes = document.getElementsByTagName('IFRAME');
+  for (var i = 0, iframe; iframe = iframes[i]; i++) {
+    if (iframe.contentWindow == e.source) {
+      var msg = JSON.parse(e.data);
+      if (msg) {
+        if (msg.type == 'size') {
+          iframe.style.height = msg.size + 'px';
+        } else if (msg.type == 'show') {
+          var url = msg.url;
+          if (url.indexOf('http://news.google.com') == 0) {
+            // If the URL is a redirect URL, strip of the destination and go to
+            // that directly.  This is necessary because the Google news
+            // redirector blocks use of the redirects in this case.
+            var index = url.indexOf('&url=');
+            if (index >= 0) {
+              url = url.substring(index + 5);
+              index = url.indexOf('&');
+              if (index >= 0)
+                url = url.substring(0, index);
+            }
+          }
+          showUrl(url);
+        }
+      }
+      return;
+    }
+  }
+}
+
+/**
+ * Saves last viewed topic by user in local storage on unload of pop-up page.
+ */
+function saveLastTopic() {
+  var topicVal = $('topics').value;
+  window.localStorage.setItem('lastTopic', topicVal);
+}
+
+/**
+ * Sets the URL according to selected topic(or default topic), then retrieves
+ * feed and sets pop-up page.
+ */
+function getNewsByTitle() {
+  var country = window.localStorage.getItem('country');
+  country = (country == 'noCountry' || !country) ? '' : country;
+
+  // Sets direction of topics showed under dropdown in pop-up page according
+  // to set language in browser.
+  $('topics').className = (directionLocale == 'rtl') ? 'topicsRTL' :
+      'topicsLTR';
+
+  var topicVal = $('topics').value;
+
+  // Sets Feed URL in case of custom topic selected.
+  var keywords = JSON.parse(window.localStorage.getItem('keywords'));
+  var isFound = false;
+  if (keywords) {
+    for (i = 0; i < keywords.length; i++) {
+      if (topicVal == keywords[i]) {
+      isFound = true;
+      feedUrl = DEFAULT_NEWS_URL + '&cf=all&ned=' + country + '&q=' + topicVal +
+          '&hl=' + country;
+      break;
+      }
+    }
+  }
+  if (!isFound) {
+    feedUrl = DEFAULT_NEWS_URL + '&cf=all&ned=' + country +
+        '&topic=' + topicVal;
+  }
+  main();
+}
+
+/**
+ * Shows topic list retrieved from local storage(if any),else shows
+ * default topics list.
+ */
+function getTopics() {
+  var topics = JSON.parse(window.localStorage.getItem('topics'));
+  var keywords = JSON.parse(window.localStorage.getItem('keywords'));
+  var element = $('topics');
+
+  // Sets all topics as default list if no list is found from local storage.
+  if (!topics && !keywords) {
+    topics = [' ','n','w','b','t','e','s','m','po'];
+  }
+
+  if (topics) {
+    for (var i = 0; i < (topics.length); i++) {
+      var val = (topics[i] == ' ') ? '1' : topics[i];
+      element.options[element.options.length] = new Option(
+          chrome.i18n.getMessage(val), topics[i]);
+    }
+  }
+
+  // Shows custom topics in list(if any).
+  if (keywords) {
+    for (i = 0; i < (keywords.length); i++) {
+      element.options[element.options.length] = new Option(keywords[i],
+          keywords[i]);
+    }
+  }
+
+  $('option_link').innerText = chrome.i18n.getMessage('options');
+
+  var topicVal = window.localStorage.getItem('lastTopic');
+  if (topicVal) {
+    $('topics').value = topicVal;
+  }
+}
+
+window.addEventListener('message', iframeMessageHandler);
diff --git a/chrome/common/extensions/docs/examples/extensions/news/javascript/options.js b/chrome/common/extensions/docs/examples/extensions/news/javascript/options.js
new file mode 100644
index 0000000..5324a8b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/javascript/options.js
@@ -0,0 +1,395 @@
+/**
+ * Copyright (c) 2010 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 Includes the country selection, topics selection and
+ * selection of no. of news stories to be shown. Include default settings also.
+ * @author navneetg@google.com (Navneet Goel).
+ */
+
+/**
+ * Stores number of selected topics on the options page.
+ */
+var checkCount = 0;
+
+/**
+ * Stores maximum count of custom topics.
+ */
+var MAX_CUS_TOPICS = 10;
+
+/**
+ * Stores temporary added custom topics which are not yet saved.
+ */
+var tempCusTopics = [];
+
+/**
+ * Checks whether ENTER key is pressed or not.
+ */
+function addCustomTopic() {
+  if (window.event.keyCode == 13) {
+    addCusTopic();
+  }
+}
+
+/**
+ * Retrieves and sets last saved country from local storage(if found),
+ * else sets country retrieved from feed.
+ */
+function setCountry() {
+  var country = window.localStorage.getItem('country');
+
+  // If country is not found in localstorage or default value is selected in
+  // drop down menu.
+  if ((!country) || country == 'noCountry') {
+    // XMLHttpRequest object that tries to load the feed for the purpose of
+    // retrieving the country value out of feed.
+    var req = new XMLHttpRequest();
+    req.onload = handleResponse;
+    req.onerror = handleError;
+    req.open('GET', DEFAULT_NEWS_URL, true);
+    req.send(null);
+
+    // Sets country to default Country in dropdown menu.
+    function handleError() {
+      $('countryList').value = 'noCountry';
+    };
+
+    // Handles parsing the feed data got back from XMLHttpRequest.
+    function handleResponse() {
+      // Feed document retrieved from URL.
+      var doc = req.responseXML;
+      if (!doc) {
+        handleError();
+        return;
+      }
+      var imageLink = doc.querySelector('image link');
+      if (imageLink) {
+          // Stores link to set value of country.
+          var newsUrl = imageLink.textContent;
+      }
+
+      // Stores country value
+      $('countryList').value = newsUrl.substring(newsUrl.indexOf('&ned=') + 5,
+          newsUrl.indexOf('&hl='));
+    };
+  } else {
+    $('countryList').value = country;
+  }
+}
+
+/**
+ * Displays various messages to user based on user input.
+ * @param {String} id Id of status element.
+ * @param {Number} timeOut Timeout value of message shown.
+ * @param {String} message Message to be shown.
+ */
+function showUserMessages(id, timeOut, message) {
+  $(id).style.setProperty('-webkit-transition',
+      'opacity 0s ease-in');
+  $(id).style.opacity = 1;
+  $(id).innerText = chrome.i18n.getMessage(message);
+  window.setTimeout(function() {
+    $(id).style.setProperty(
+        '-webkit-transition', 'opacity' + timeOut + 's ease-in');
+    $(id).style.opacity = 0;
+    }, 1E3
+  );
+}
+
+/**
+ * Sets options page CSS according to the browser language(if found), else sets
+ * to default locale.
+ */
+function setOptionPageCSS() {
+  if (chrome.i18n.getMessage('direction') == 'rtl') {
+    document.querySelector('body').className = 'rtl';
+  }
+}
+
+/**
+ * Initializes the options page by retrieving country, topics and count of
+ * stories from local storage if present, else sets to default settings.
+ */
+function initialize() {
+  setOptionPageCSS();
+  setCountry();
+  setCountAndTopicList();
+  setLocalizedTopicList();
+
+  // Adds a custom topic on press of Enter key.
+  $('newKeyword').onkeypress = addCustomTopic;
+}
+
+/**
+ * Retrieves locale values from locale file.
+ */
+function setLocalizedTopicList() {
+  var getI18nMsg = chrome.i18n.getMessage;
+
+  $('top').innerText = getI18nMsg('1');
+  $('nation').innerText = getI18nMsg('n');
+  $('world').innerText = getI18nMsg('w');
+  $('business').innerText = getI18nMsg('b');
+  $('science').innerText = getI18nMsg('t');
+  $('entertainment').innerText = getI18nMsg('e');
+  $('sports').innerText = getI18nMsg('s');
+  $('health').innerText = getI18nMsg('m');
+  $('most').innerText = getI18nMsg('po');
+  $('select_country').innerText = getI18nMsg('country');
+  $('topic').innerText = getI18nMsg('topic');
+  $('save_button').innerText = getI18nMsg('save');
+  $('story_count').innerText = getI18nMsg('storyCount');
+  $('logo').innerHTML = $('logo').innerHTML + getI18nMsg('newsOption');
+  $('custom_text').innerHTML = getI18nMsg('customText') + '<br/>' +
+    getI18nMsg('maximumTopics',[MAX_CUS_TOPICS]);
+  $('submit_button').value = getI18nMsg('submitButton');
+}
+
+/**
+ * Sets topic list and number of stories retrieved from localstorage(if any)
+ * otherwise sets to default.
+ */
+function setCountAndTopicList() {
+  var topicLists = document.getElementsByClassName('checkBox');
+
+  // Retrieves topics list from localStorage.
+  var topics = JSON.parse(window.localStorage.getItem('topics'));
+
+  // Runs if retrieved topic list from local storage contains topics.
+  if (topics) {
+    for (var x = 0, topicList; topicList = topicLists[x]; x++) {
+
+      // Saves whether checkbox is checked or not.
+      var isPresent = false;
+      for (var y = 0; y < topics.length; y++) {
+        if (topics[y] == topicList.value) {
+          topicList.checked = true;
+          isPresent = true;
+          checkCount++;
+          break;
+        }
+      }
+      if (!isPresent) {
+        topicList.checked = false;
+      }
+    }
+  }
+
+  // Retrieves list of custom topics from localstorage(if any) and shows it
+  // in option page.
+  var keywords = JSON.parse(window.localStorage.getItem('keywords'));
+  if (keywords) {
+
+    // Template to store custom topics in a table.
+    var template = [];
+    var title = chrome.i18n.getMessage('deleteTitle');
+    for (var i = 0; i < keywords.length; i++) {
+      checkCount++;
+
+      template.push('<tr style = "height: 22px;">');
+      template.push('<td id = "keyword_value" class = "cusTopicsClass">');
+      template.push('<textarea class="noborder" readonly>');
+      template.push(keywords[i]);
+      template.push('</textarea>');
+      template.push('<td class = "suppr" onclick = "delCusTopic(this)" ');
+        template.push('title="');
+        template.push(title);
+        template.push('">');
+      template.push('</td>');
+      template.push('</tr>');
+    }
+    $('custom_topics').innerHTML = template.join('');
+    if (keywords.length == MAX_CUS_TOPICS) {
+      $('submit_button').disabled = true;
+      $('newKeyword').readOnly = 'readonly';
+    }
+  }
+  // Check all checkboxes(default settings) if no custom topic list and
+  // checkbox topic list from local storage is found.
+  if (!keywords && !topics) {
+    for (var x = 0, topicList; topicList = topicLists[x]; x++) {
+      topicList.checked = true;
+      checkCount++;
+    }
+  }
+
+  // Retrieves saved value of number of stories.
+  var count = window.localStorage.getItem('count');
+
+  // Sets number of stories in dropdown.
+  if (count) {
+    $('storyCount').value = count;
+  }
+}
+
+/**
+ * Saves checked topic list(if any), Custom topics(if any), number of
+ * stories and country value in local storage.
+ */
+function saveTopicsCountry() {
+  var country = $('countryList').value;
+  var topicLists = document.getElementsByClassName('checkBox');
+
+  // Contains selected number of stories.
+  var count = $('storyCount').value;
+
+  // Stores checked topics list.
+  var topicArr = [];
+  for (var i = 0, topicList; topicList = topicLists[i]; i++) {
+    if (topicList.checked) {
+      topicArr.push(topicList.value);
+    }
+  }
+  var keywords = JSON.parse(window.localStorage.getItem('keywords'));
+
+  // Saves custom topics to local storage(if any).
+  if (tempCusTopics.length > 0) {
+    if (keywords) {
+      keywords = keywords.concat(tempCusTopics);
+      window.localStorage.setItem('keywords', JSON.stringify(keywords));
+    } else {
+      window.localStorage.setItem('keywords', JSON.stringify(tempCusTopics));
+    }
+    tempCusTopics.splice(0, tempCusTopics.length);
+  }
+
+  // Saves checkbox topics(if any).
+  if (topicArr.length > 0) {
+    window.localStorage.setItem('topics', JSON.stringify(topicArr));
+  } else {
+    window.localStorage.removeItem('topics');
+  }
+
+  window.localStorage.setItem('count', count);
+  window.localStorage.setItem('country', country);
+
+  showUserMessages('save_status', 0.5, 'saveStatus');
+  $('save_button').disabled = true;
+}
+
+/**
+ * Disables the save button on options page if no topic is selected by the user.
+ * @param {String} id Id of checkbox checked or unchecked.
+ */
+function manageCheckCount(id) {
+  checkCount = ($(id).checked) ? (checkCount + 1) : (checkCount - 1);
+  $('save_button').disabled = (checkCount == 0) ? true : false;
+}
+
+/**
+ * Enables save button if at least one topic is selected.
+ */
+function enableSaveButton() {
+  if (checkCount != 0) {
+    $('save_button').disabled = false;
+  }
+}
+
+/**
+ * Adds new entered custom topic.
+ */
+function addCusTopic() {
+  // Retrieves custom topic list from local storage(if any), else create new
+  // array list.
+  var keywords = JSON.parse(window.localStorage.getItem('keywords') || "[]");
+
+  // Adds topic only if total number of added custom topics are less than 10.
+  if (keywords.length + tempCusTopics.length <= (MAX_CUS_TOPICS - 1)) {
+
+    // Stores new entered value in input textbox.
+    var val = $('newKeyword').value;
+    if (val) {
+      val = val.trim();
+      if (val.length > 0) {
+        var pattern = /,/g;
+
+        // Runs if comma(,) is not present in topic entered.
+        if (val.match(pattern) == null) {
+          checkCount++;
+          tempCusTopics.push(val);
+
+          // Template to store custom topics in a table.
+          var template = [];
+          var title = chrome.i18n.getMessage('deleteTitle');
+
+          template.push('<tr style = "height: 22px;">');
+          template.push('<td id = "keyword_value" class = "cusTopicsClass">');
+          template.push('<textarea class="noborder" readonly>');
+          template.push(val);
+          template.push('</textarea>');
+          template.push('<td class = "suppr" onclick = "delCusTopic(this)" ');
+            template.push('title="');
+            template.push(title);
+            template.push('">');
+          template.push('</td>');
+          template.push('</tr>');
+
+          $('custom_topics').innerHTML += template.join('');
+          enableSaveButton();
+        } else {
+          showUserMessages('invalid_status', 2.5, 'invalidChars');
+        }
+      }
+      $('newKeyword').value = '';
+    }
+  }
+
+  if ((keywords.length + tempCusTopics.length) == (MAX_CUS_TOPICS)) {
+    $('submit_button').disabled = true;
+    $('newKeyword').readOnly = 'readonly';
+  }
+}
+
+/**
+ * Delete custom topic whenever users click on delete icon.
+ * @param {HTMLTableColElement} obj HTML table column element to be deleted.
+ */
+function delCusTopic(obj) {
+  // Deletes only if total number of topics are greater than 1, else shows
+  // error message.
+  if (checkCount > 1) {
+    var value;
+
+    // Extract custom topic value.
+    value = obj.parentNode.querySelector('.cusTopicsClass textarea').value;
+
+    // Removes custom topic element from UI.
+    $('custom_topics').removeChild(obj.parentNode);
+
+    // Removes custom topic element either from temporary array(if topic is
+    // not yet saved) or from saved topic list and saves new list to
+    // local storage.
+    var flag = 0;
+    for (var i = 0; i < tempCusTopics.length; i++) {
+      if (tempCusTopics[i] == value) {
+        tempCusTopics.splice(i, 1);
+        flag = 1;
+        break;
+      }
+    }
+
+    if (flag == 0) {
+      var keywords = JSON.parse(window.localStorage.getItem('keywords'));
+      for (i = 0; i < keywords.length; i++) {
+        if (keywords[i] == value) {
+          keywords.splice(i, 1);
+          break;
+        }
+      }
+      if (keywords.length > 0) {
+        window.localStorage.setItem('keywords', JSON.stringify(keywords));
+      } else {
+        window.localStorage.removeItem('keywords');
+      }
+    }
+
+    checkCount--;
+    $('submit_button').disabled = false;
+  } else {
+    showUserMessages('save_status', 2.5, 'noTopic');
+  }
+  $('newKeyword').readOnly = false;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/news/javascript/util.js b/chrome/common/extensions/docs/examples/extensions/news/javascript/util.js
new file mode 100644
index 0000000..f8e3880
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/javascript/util.js
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2010 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 Defines the constants and most commonly used functions.
+ * @author navneetg@google.com (Navneet Goel).
+ */
+
+/**
+ * Default feed news URL.
+ */
+var DEFAULT_NEWS_URL = 'http://news.google.com/news?output=rss';
+
+/**
+ * Image URL of Israel country.
+ */
+var ISRAEL_IMAGE_URL = 'http://www.gstatic.com/news/img/logo/iw_il/news.gif';
+
+/**
+ * Alias for getElementById.
+ * @param {String} elementId Element id of the HTML element to be fetched.
+ * @return {Element} Element corresponding to the element id.
+ */
+function $(elementId) {
+  return document.getElementById(elementId);
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/news/manifest.json b/chrome/common/extensions/docs/examples/extensions/news/manifest.json
new file mode 100644
index 0000000..a64df9c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/manifest.json
@@ -0,0 +1,20 @@
+{
+  "name": "__MSG_extName__",
+  "version": "2.0",
+  "description": "__MSG_extDesc__",
+  "icons": { "128": "images/news_icon.png" },
+  "default_locale":"en",
+  "browser_action": {
+    "default_title": "__MSG_ext_default_title__",
+    "default_icon": "images/news_action.png",
+    "default_popup": "views/feed.html"
+  },
+  "permissions": [
+    "tabs",
+    "http://news.google.com/*"
+  ],
+  "options_page": "views/options.html",
+  "background": {
+    "page": "views/background.html"
+  }
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/news/views/background.html b/chrome/common/extensions/docs/examples/extensions/news/views/background.html
new file mode 100644
index 0000000..c97e977
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/views/background.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2010 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 Contains script for running at the background to open up the
+options page when the extension is reloaded.
+-->
+
+<html>
+  <body>
+    <script>
+      //Retrieves value from local storage(if found).
+      var newsFlag = window.localStorage.getItem('newsFlag');
+
+      //Runs if extension installation is done.
+      if(!newsFlag) {
+        var optionsPageURL = chrome.extension.getURL('/views/options.html');
+        chrome.tabs.create({url: optionsPageURL});
+        window.localStorage.setItem('newsFlag','1');
+      }
+    </script>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/news/views/feed.html b/chrome/common/extensions/docs/examples/extensions/news/views/feed.html
new file mode 100644
index 0000000..43d147b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/views/feed.html
@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2010 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 serves as the pop-up page for showing news 
+according to the settings saved in options page otherwise shows default
+settings.
+@author navneetg@google.com (Navneet Goel).
+-->
+
+<html>
+<head>
+<script src = "/javascript/util.js"></script>
+<link rel = "stylesheet" href = "/css/feed.css"/>
+
+<script id = "iframe_script">
+
+/**
+ * Facebook share URL.
+ */
+var FB_SHARE_URL = "http://www.facebook.com/sharer.php?u=";
+
+/**
+ * Twitter share URL.
+ */
+var TWITTER_SHARE_URL = "http://twitter.com/share?&url=";
+
+/**
+ * Buzz share URL.
+ */
+var BUZZ_SHARE_URL = "http://www.google.com/buzz/post?&url=";
+
+/**
+ * Opens new window either of facebook, twitter or google buzz.
+ * @param {String} id Specifies whether to share news on Facebook, Google Buzz
+ *     or Twitter.
+ * @param {String} url Contains URL of the News to be shared.
+ */
+function openNewsShareWindow(id, url) {
+  var newsUrl = url.substring(url.indexOf('&url=') + 5);
+  var openUrl;
+  switch (id) {
+     case 'fb':
+       openUrl = FB_SHARE_URL;
+       break;
+     case 'buzz':
+       openUrl = BUZZ_SHARE_URL;
+       break;
+     case 'twitter':
+       openUrl = TWITTER_SHARE_URL;
+       break;
+   }
+  window.open(openUrl + newsUrl, '_blank',
+      'resizable=0,scrollbars=0,width=690,height=415');
+}
+
+/**
+ * Checks language in image url retrieved from feed and sets style of 
+ * title and openbox in pop-up page(if url is found), otherwise sets
+ * to default styling.
+ */
+function setStyleByLang(titleImgUrl) {
+  var openBoxes = document.getElementsByClassName('open_box');
+  var itemTitles = document.getElementsByClassName('item_title');
+
+  if (titleImgUrl != 'NULL') {
+    var pattern = /ar_/gi;
+    var result = titleImgUrl.match(pattern);
+    if (result != null || titleImgUrl == ISRAEL_IMAGE_URL) {
+      document.querySelector('body').className = 'rtl';
+    }
+  }
+}
+
+/**
+ * Reports the height.
+ */
+function reportHeight() {
+  var msg = JSON.stringify({type:"size", size:document.body.offsetHeight});
+  parent.postMessage(msg, "*");
+}
+
+/**
+ * Initialize the iframe body.
+ */
+function frameLoaded() {
+  var links = document.getElementsByTagName("A");
+  for (var i = 0, link; link = links[i]; i++) {
+    var class = link.className;
+    if (class != "item_title" && class != "open_box") {
+      link.addEventListener("click", showStory);
+    }
+  }
+  window.addEventListener("message", messageHandler);
+}
+
+/**
+ * Redirects to Google news site according to clicked URL.
+ * @param {Object} event Onclick event.
+ */
+function showStory(event) {
+  var href = event.currentTarget.href;
+  parent.postMessage(JSON.stringify({type:"show", url:href}), "*");
+  event.preventDefault();
+}
+
+/**
+ * Handles message.
+ * @param {Object} event Onmessage event.
+ */
+function messageHandler(event) {
+  reportHeight();
+}
+</script>
+<script src = "/javascript/feed.js"></script>
+</head>
+
+<body onload = "getTopics();getNewsByTitle();" onunload = "saveLastTopic();">
+
+<div style = "margin-bottom: 15px;">
+  <div style = "float: right;">
+    <div style = "float: right; font-size: 11px">
+      <a id = "option_link" onclick = "chrome.tabs.create({url: '/views/options.html', selected: true})">
+      </a>
+    </div>
+    <div style = "margin-top: 27px">
+      <select id = "topics" onchange = "getNewsByTitle();" style = "display: inline;">
+      </select>
+    </div>
+  </div>
+  <a id = "title_a">
+    <img id = "title" style = "padding-top: 5px;">
+  </a>
+</div>
+
+<div id = "feed">
+</div>
+
+<div id = "noStories">
+</div>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/news/views/options.html b/chrome/common/extensions/docs/examples/extensions/news/views/options.html
new file mode 100644
index 0000000..6bf99b3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news/views/options.html
@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2010 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 serves as the option page of the extension for
+customizing the settings such as setting country, topics and number of
+news stories to be shown.
+@author navneetg@google.com (Navneet Goel).
+-->
+
+<html>
+  <head>
+    <script src = "/javascript/util.js"></script>
+    <meta http-equiv = "Content-Type" content = "text/html; charset = UTF-8">
+    <link rel = "stylesheet" href = "/css/options.css"/>
+    <script src = "/javascript/options.js"></script>
+  </head>
+  <body onload = "initialize();">
+    <div id = "all_content">
+      <div id = "logo">
+        <img id = "title_image" src = "/images/news.gif"/>
+        <br/>
+      </div>
+      <br/><br/>
+
+      <table class = "contentTable" style = "margin-left: 55px;" width = "96%">
+        <tr class = "all_rows">
+          <td class = "col1"><b id = "select_country"></b></td>
+          <td class = "col2">
+            <select id = "countryList" onchange = "enableSaveButton();">
+              <option value = "noCountry">-Select-</option>
+              <option value = "es_ar">Argentina</option>
+              <option value = "au">Australia</option>
+              <option value = "nl_be">België</option>
+              <option value = "fr_be">Belgique</option>
+              <option value = "en_bw">Botswana</option>
+              <option value = "pt-BR_br">Brasil</option>
+              <option value = "ca">Canada English</option>
+              <option value = "fr_ca">Canada Français</option>
+              <option value = "cs_cz">Česká republika</option>
+              <option value = "es_cl">Chile</option>
+              <option value = "es_co">Colombia</option>
+              <option value = "es_cu">Cuba</option>
+              <option value = "de">Deutschland</option>
+              <option value = "es">España</option>
+              <option value = "es_us">Estados Unidos</option>
+              <option value = "en_et">Ethiopia</option>
+              <option value = "fr">France</option>
+              <option value = "en_gh">Ghana</option>
+              <option value = "in">India</option>
+              <option value = "en_ie">Ireland</option>
+              <option value = "en_il">Israel English</option>
+              <option value = "it">Italia</option>
+              <option value = "en_ke">Kenya</option>
+              <option value = "hu_hu">Magyarország</option>
+              <option value = "en_my">Malaysia</option>
+              <option value = "es_mx">México</option>
+              <option value = "en_na">Namibia</option>
+              <option value = "nl_nl">Nederland</option>
+              <option value = "nz">New Zealand</option>
+              <option value = "en_ng">Nigeria</option>
+              <option value = "no_no">Norge</option>
+              <option value = "de_at">Österreich</option>
+              <option value = "en_pk">Pakistan</option>
+              <option value = "es_pe">Perú</option>
+              <option value = "en_ph">Philippines</option>
+              <option value = "pl_pl">Polska</option>
+              <option value = "pt-PT_pt">Portugal</option>
+              <option value = "de_ch">Schweiz</option>
+              <option value = "fr_sn">Sénégal</option>
+              <option value = "en_sg">Singapore</option>
+              <option value = "en_za">South Africa</option>
+              <option value = "fr_ch">Suisse</option>
+              <option value = "sv_se">Sverige</option>
+              <option value = "en_tz">Tanzania</option>
+              <option value = "tr_tr">Türkiye</option>
+              <option value = "uk">U.K.</option>
+              <option value = "us">U.S.</option>
+              <option value = "en_ug">Uganda</option>
+              <option value = "es_ve">Venezuela</option>
+              <option value = "vi_vn">Việt Nam (Vietnam)</option>
+              <option value = "en_zw">Zimbabwe</option>
+              <option value = "el_gr">Ελλάδα (Greece)</option>
+              <option value = "ru_ru">Россия (Russia)</option>
+              <option value = "ru_ua">Украина / русский (Ukraine)</option>
+              <option value = "uk_ua">Україна / українська (Ukraine)</option>
+              <option value = "iw_il">ישראל (Israel)</option>
+              <option value = "ar_ae">الإمارات (UAE)</option>
+              <option value = "ar_sa">السعودية (KSA)</option>
+              <option value = "ar_me">العالم العربي (Arabic)</option>
+              <option value = "ar_lb">لبنان (Lebanon)</option>
+              <option value = "ar_eg">مصر (Egypt)</option>
+              <option value = "hi_in">हिन्दी (India)</option>
+              <option value = "ta_in">தமிழ்(India)</option>
+              <option value = "te_in">తెలుగు (India)</option>
+              <option value = "ml_in">മലയാളം (India)</option>
+              <option value = "kr">한국 (Korea)</option>
+              <option value = "cn">中国版 (China)</option>
+              <option value = "tw">台灣版 (Taiwan)</option>
+              <option value = "jp">日本 (Japan)</option>
+              <option value = "hk">香港版 (Hong Kong)</option>
+            </select>
+          </td>
+        </tr>
+        <tr class = "all_rows">
+          <td class = "col1"><b id = "story_count"></b></td>
+          <td class = "col2">
+            <select id = "storyCount" style = "padding-left: 3px;" onchange = "enableSaveButton();">
+              <option value = "1">1</option>
+              <option value = "2">2</option>
+              <option value = "3">3</option>
+              <option value = "4">4</option>
+              <option value = "5" selected = "selected">5</option>
+              <option value = "6">6</option>
+              <option value = "7">7</option>
+              <option value = "8">8</option>
+              <option value = "9">9</option>
+              <option value = "10">10</option>
+            </select>
+          </td>
+        </tr>
+        <tr class = "all_rows">
+          <td class = "col1">
+            <div id = "topic" style = "font-weight: bold;"></div>
+          </td>
+          <td class = "col2">
+            <div>
+              <div class = "boxAndTopic"><input class = "checkBox" type = "checkbox" value = " " id = "check11" onchange = "manageCheckCount(this.id)"/><div class = "checkBoxTopic" id = "top"></div><br/></div>
+              <div class = "boxAndTopic"><input class = "checkBox" type = "checkbox" value = "n" id = "check13" onchange = "manageCheckCount(this.id)"/><div class = "checkBoxTopic" id = "nation"></div><br/></div>
+              <div class = "boxAndTopic"><input class = "checkBox" type = "checkbox" value = "w" id = "check14" onchange = "manageCheckCount(this.id)"/><div class = "checkBoxTopic" id = "world"></div><br/></div>
+              <div class = "boxAndTopic"><input class = "checkBox" type = "checkbox" value = "b" id = "check15" onchange = "manageCheckCount(this.id)"/><div class = "checkBoxTopic" id = "business"></div><br/></div>
+              <div class = "boxAndTopic"><input class = "checkBox" type = "checkbox" value = "t" id = "check16" onchange = "manageCheckCount(this.id)"/><div class = "checkBoxTopic" id = "science"></div><br/></div>
+              <div class = "boxAndTopic"><input class = "checkBox" type = "checkbox" value = "e" id = "check17" onchange = "manageCheckCount(this.id)"/><div class = "checkBoxTopic" id = "entertainment"></div><br/></div>
+              <div class = "boxAndTopic"><input class = "checkBox" type = "checkbox" value = "s" id = "check18" onchange = "manageCheckCount(this.id)"/><div class = "checkBoxTopic" id = "sports"></div><br/></div>
+              <div class = "boxAndTopic"><input class = "checkBox" type = "checkbox" value = "m" id = "check19" onchange = "manageCheckCount(this.id)"/><div class = "checkBoxTopic" id = "health"></div><br/></div>
+              <div class = "boxAndTopic"><input class = "checkBox" type = "checkbox" value = "po" id = "check20" onchange = "manageCheckCount(this.id)"/><div class = "checkBoxTopic" id = "most"></div><br/></div>
+            </div>
+          </td>
+        </tr>
+        <tr class = "all_rows">
+          <td class = "col1">
+            <div id = "custom_text" style = "font-weight: bold;"></div>
+          </td>
+          <td class = "col2">
+            <input id = "newKeyword" type = "text" maxlength = "20" style = "width: 205px;">
+            <input id = "submit_button" type = "submit" onclick = "addCusTopic()" style = "width: 45px;">
+            <span id = "invalid_status"></span>
+            <table>
+              <tbody id = "custom_topics"></tbody>
+            </table>
+          </td>
+        </tr>
+      </table>
+      <br/>
+      <div id = "save_div">
+        <button id = "save_button" type = "button" disabled = "disabled" onclick = "saveTopicsCountry()" style = "width: 80px;">
+        </button>
+        <span id = "save_status"></span>
+      </div>
+    </div>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/news_a11y/feed.css b/chrome/common/extensions/docs/examples/extensions/news_a11y/feed.css
new file mode 100644
index 0000000..8755421
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_a11y/feed.css
@@ -0,0 +1,74 @@
+/**
+ * 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.
+ */
+
+body {
+  font-family: helvetica, arial, sans-serif;
+  font-size: 12px;
+  overflow: hidden;
+}
+
+a {
+  color:#0000CC;
+  text-decoration: underline;
+  cursor: pointer;
+}
+
+.open_box {
+  display: block;
+  overflow: hidden;
+  margin-right: 4px;
+  margin-top: 2px;
+  height: 12px;
+  width: 12px;
+  float: left;
+  clear: left;
+  background-image: url(sprite_arrows.gif);
+  background-position: 0px -24px;
+  cursor: pointer;
+}
+
+.opened .open_box {
+  background-position:-12px -24px;
+}
+
+.item {
+  padding: 2px 0px;
+}
+
+.item_title {
+  display: block;
+  min-width: 300px;
+  padding-left: 15px;
+  cursor: pointer;
+}
+
+.item_desc {
+  min-width: 500px;
+  height: 0px;
+  display: block;
+  border: none;
+  padding: 0px;
+  margin: 0px;
+  -webkit-transition: height 0.2s ease-out;
+}
+
+#title {
+  display: block;
+  margin-left: auto;
+}
+
+.error {
+  white-space: nowrap;
+  color: red;
+}
+
+.more {
+  display: block;
+  text-align: right;
+  padding-top: 20px;
+  padding-right: 10px;
+  color: #88C;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/news_a11y/feed.html b/chrome/common/extensions/docs/examples/extensions/news_a11y/feed.html
new file mode 100644
index 0000000..4451240
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_a11y/feed.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <link href="feed.css" rel="stylesheet" type="text/css">
+    <script src="feed.js"></script>
+  </head>
+  <body>
+    <a id="title_a" tabIndex="0"><img id='title' alt="Google News logo"></a>
+    <div id="feed"></div>
+  </body>
+</html>
+
diff --git a/chrome/common/extensions/docs/examples/extensions/news_a11y/feed.js b/chrome/common/extensions/docs/examples/extensions/news_a11y/feed.js
new file mode 100644
index 0000000..db3476c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_a11y/feed.js
@@ -0,0 +1,277 @@
+// 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.
+
+// Feed
+var feedUrl = 'http://news.google.com/?output=rss';
+
+// The XMLHttpRequest object that tries to load and parse the feed.
+var req;
+
+function main() {
+  req = new XMLHttpRequest();
+  req.onload = handleResponse;
+  req.onerror = handleError;
+  req.open('GET', feedUrl, true);
+  req.send(null);
+}
+
+// Handles feed parsing errors.
+function handleFeedParsingFailed(error) {
+  var feed = document.getElementById('feed');
+  feed.className = 'error';
+  feed.innerText = 'Error: ' + error;
+}
+
+// Handles errors during the XMLHttpRequest.
+function handleError() {
+  handleFeedParsingFailed('Failed to fetch RSS feed.');
+}
+
+// Handles parsing the feed data we got back from XMLHttpRequest.
+function handleResponse() {
+  var doc = req.responseXML;
+  if (!doc) {
+    handleFeedParsingFailed('Not a valid feed.');
+    return;
+  }
+  buildPreview(doc);
+}
+
+// The maximum number of feed items to show in the preview.
+var maxFeedItems = 5;
+
+// Where the more stories link should navigate to.
+var moreStoriesUrl;
+
+function buildPreview(doc) {
+  // Get the link to the feed source.
+  var link = doc.getElementsByTagName('link');
+  var parentTag = link[0].parentNode.tagName;
+  if (parentTag != 'item' && parentTag != 'entry') {
+    moreStoriesUrl = link[0].textContent;
+  }
+
+  // Setup the title image.
+  var images = doc.getElementsByTagName('image');
+  var titleImg;
+  if (images.length != 0) {
+    var urls = images[0].getElementsByTagName('url');
+    if (urls.length != 0) {
+      titleImg = urls[0].textContent;
+    }
+  }
+  var img = document.getElementById('title');
+  // Listen for mouse and key events
+  if (titleImg) {
+    img.src = titleImg;
+    if (moreStoriesUrl) {
+      document.getElementById('title_a').addEventListener('click',
+          moreStories);
+      document.getElementById('title_a').addEventListener('keydown',
+                                         function(event) {
+                                           if (event.keyCode == 13) {
+                                             moreStories(event);
+                                           }});
+    }
+  } else {
+    img.style.display = 'none';
+  }
+
+  // Construct the iframe's HTML.
+  var iframe_src = '<!doctype html><html><head><script ' +
+      'src="chrome-extension://ldglnfnokeifbcaeppacaejckagballg/' +
+      'feed_iframe.js"><' + '/script><link href="chrome-extension://ldglnf' +
+      'nokeifbcaeppacaejckagballg/feed_iframe.css" rel="stylesheet" ' +
+      'type="text/css"></head><body>';
+
+  var feed = document.getElementById('feed');
+  // Set ARIA role indicating the feed element has a tree structure
+  feed.setAttribute('role', 'tree');
+
+  var entries = doc.getElementsByTagName('entry');
+  if (entries.length == 0) {
+    entries = doc.getElementsByTagName('item');
+  }
+  var count = Math.min(entries.length, maxFeedItems);
+  for (var i = 0; i < count; i++) {
+    item = entries.item(i);
+
+    // Grab the title for the feed item.
+    var itemTitle = item.getElementsByTagName('title')[0];
+    if (itemTitle) {
+      itemTitle = itemTitle.textContent;
+    } else {
+      itemTitle = 'Unknown title';
+    }
+
+    // Grab the description.
+    var itemDesc = item.getElementsByTagName('description')[0];
+    if (!itemDesc) {
+      itemDesc = item.getElementsByTagName('summary')[0];
+      if (!itemDesc) {
+        itemDesc = item.getElementsByTagName('content')[0];
+      }
+    }
+    if (itemDesc) {
+      itemDesc = itemDesc.childNodes[0].nodeValue;
+    } else {
+      itemDesc = '';
+    }
+
+    var item = document.createElement('div');
+    item.className = 'item';
+    var box = document.createElement('div');
+    box.className = 'open_box';
+    box.addEventListener('click', showDesc);
+    // Disable focusing on box image separately from rest of tree item
+    box.tabIndex = -1;
+    item.appendChild(box);
+
+    var title = document.createElement('a');
+    title.className = 'item_title';
+    // Give title an ID for use with ARIA
+    title.id = 'item' + i;
+    title.innerText = itemTitle;
+    title.addEventListener('click', showDesc);
+    title.addEventListener('keydown', keyHandlerShowDesc);
+    // Update aria-activedescendant property in response to focus change
+    // within the tree
+    title.addEventListener('focus', function(event) {
+                                      feed.setAttribute(
+                                        'aria-activedescendant', this.id);
+                                    });
+    // Enable keyboard focus on the item title element
+    title.tabIndex = 0;
+    // Set ARIA role role indicating that the title element is a node in the
+    // tree structure
+    title.setAttribute('role', 'treeitem');
+    // Set the ARIA state indicating this tree item is currently collapsed.
+    title.setAttribute('aria-expanded', 'false');
+    // Set ARIA property indicating that all items are at the same hierarchical
+    // level (no nesting)
+    title.setAttribute('aria-level', '1');
+    item.appendChild(title);
+
+    var desc = document.createElement('iframe');
+    desc.scrolling = 'no';
+    desc.className = 'item_desc';
+    // Disable keyboard focus on elements in iFrames that have not been
+    // displayed yet
+    desc.tabIndex = -1;
+
+    // The story body is created as an iframe with a data: URL in order to
+    // isolate it from this page and protect against XSS.  As a data URL, it
+    // has limited privileges and must communicate back using postMessage().
+    desc.src='data:text/html,' + iframe_src + itemDesc + '</body></html>';
+
+    item.appendChild(desc);
+    feed.appendChild(item);
+  }
+
+  if (moreStoriesUrl) {
+    var more = document.createElement('a');
+    more.className = 'more';
+    more.innerText = 'More stories \u00BB';
+    more.tabIndex = 0;
+    more.addEventListener('click', moreStories);
+    more.addEventListener('keydown', function(event) {
+                                       if (event.keyCode == 13) {
+                                         moreStories(event);
+                                       }});
+    feed.appendChild(more);
+  }
+}
+
+// Show |url| in a new tab.
+function showUrl(url) {
+  // Only allow http and https URLs.
+  if (url.indexOf('http:') != 0 && url.indexOf('https:') != 0) {
+    return;
+  }
+  chrome.tabs.create({url: url});
+}
+
+function moreStories(event) {
+  showUrl(moreStoriesUrl);
+}
+
+function keyHandlerShowDesc(event) {
+// Display content under heading when spacebar or right-arrow pressed
+// Hide content when spacebar pressed again or left-arrow pressed
+// Move to next heading when down-arrow pressed
+// Move to previous heading when up-arrow pressed
+  if (event.keyCode == 32) {
+    showDesc(event);
+  } else if ((this.parentNode.className == 'item opened') &&
+           (event.keyCode == 37)) {
+    showDesc(event);
+  } else if ((this.parentNode.className == 'item') && (event.keyCode == 39)) {
+    showDesc(event);
+  } else if (event.keyCode == 40) {
+    if (this.parentNode.nextSibling) {
+      this.parentNode.nextSibling.children[1].focus();
+    }
+  } else if (event.keyCode == 38) {
+    if (this.parentNode.previousSibling) {
+      this.parentNode.previousSibling.children[1].focus();
+    }
+  }
+}
+
+function showDesc(event) {
+  var item = event.currentTarget.parentNode;
+  var items = document.getElementsByClassName('item');
+  for (var i = 0; i < items.length; i++) {
+    var iframe = items[i].getElementsByClassName('item_desc')[0];
+    if (items[i] == item && items[i].className == 'item') {
+      items[i].className = 'item opened';
+      iframe.contentWindow.postMessage('reportHeight', '*');
+      // Set the ARIA state indicating the tree item is currently expanded.
+      items[i].getElementsByClassName('item_title')[0].
+        setAttribute('aria-expanded', 'true');
+      iframe.tabIndex = 0;
+    } else {
+      items[i].className = 'item';
+      iframe.style.height = '0px';
+      // Set the ARIA state indicating the tree item is currently collapsed.
+      items[i].getElementsByClassName('item_title')[0].
+        setAttribute('aria-expanded', 'false');
+      iframe.tabIndex = -1;
+    }
+  }
+}
+
+function iframeMessageHandler(e) {
+  // Only listen to messages from one of our own iframes.
+  var iframes = document.getElementsByTagName('IFRAME');
+  for (var i = 0; i < iframes.length; i++) {
+    if (iframes[i].contentWindow == e.source) {
+      var msg = JSON.parse(e.data);
+      if (msg) {
+        if (msg.type == 'size') {
+          iframes[i].style.height = msg.size + 'px';
+        } else if (msg.type == 'show') {
+          var url = msg.url;
+          if (url.indexOf('http://news.google.com') == 0) {
+            // If the URL is a redirect URL, strip of the destination and go to
+            // that directly.  This is necessary because the Google news
+            // redirector blocks use of the redirects in this case.
+            var index = url.indexOf('&url=');
+            if (index >= 0) {
+              url = url.substring(index + 5);
+              index = url.indexOf('&');
+              if (index >= 0)
+                url = url.substring(0, index);
+            }
+          }
+          showUrl(url);
+        }
+      }
+      return;
+    }
+  }
+}
+
+window.addEventListener('message', iframeMessageHandler);
+document.addEventListener('DOMContentLoaded', main);
diff --git a/chrome/common/extensions/docs/examples/extensions/news_a11y/feed_iframe.css b/chrome/common/extensions/docs/examples/extensions/news_a11y/feed_iframe.css
new file mode 100644
index 0000000..159417f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_a11y/feed_iframe.css
@@ -0,0 +1,10 @@
+/**
+ * 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.
+ */
+
+body {
+  margin: 0;
+  padding: 0;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/news_a11y/feed_iframe.js b/chrome/common/extensions/docs/examples/extensions/news_a11y/feed_iframe.js
new file mode 100644
index 0000000..0fcb590
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_a11y/feed_iframe.js
@@ -0,0 +1,31 @@
+// 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.
+
+function reportHeight() {
+  var msg = JSON.stringify({type:"size", size:document.body.offsetHeight});
+  parent.postMessage(msg, "*");
+}
+
+function frameLoaded() {
+  var links = document.getElementsByTagName("A");
+  for (i = 0; i < links.length; i++) {
+    var c = links[i].className;
+    if (c != "item_title" && c != "open_box") {
+      links[i].addEventListener("click", showStory);
+    }
+  }
+  window.addEventListener("message", messageHandler);
+}
+
+function showStory(event) {
+  var href = event.currentTarget.href;
+  parent.postMessage(JSON.stringify({type:"show", url:href}), "*");
+  event.preventDefault();
+}
+
+function messageHandler(event) {
+  reportHeight();
+}
+
+document.addEventListener('DOMContentLoaded', frameLoaded);
diff --git a/chrome/common/extensions/docs/examples/extensions/news_a11y/manifest.json b/chrome/common/extensions/docs/examples/extensions/news_a11y/manifest.json
new file mode 100644
index 0000000..496ad4f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_a11y/manifest.json
@@ -0,0 +1,17 @@
+{
+  "name": "News Reader",
+  "version": "1.1",
+  "description": "Displays the first 5 items from the 'Google News - top news' RSS feed in a popup.",
+  "icons": { "128": "news_icon.png" },
+  "browser_action": {
+    "default_title": "Google News",
+    "default_icon": "news_action.png",
+    "default_popup": "feed.html"
+  },
+  "permissions": [
+    "tabs",
+    "http://news.google.com/*"
+  ],
+  "manifest_version": 2,
+  "content_security_policy": "img-src 'self' http://* https://*; script-src 'self'; connect-src http://news.google.com; frame-src data:"
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/news_a11y/news_action.png b/chrome/common/extensions/docs/examples/extensions/news_a11y/news_action.png
new file mode 100644
index 0000000..98fe9b2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_a11y/news_action.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/news_a11y/news_icon.png b/chrome/common/extensions/docs/examples/extensions/news_a11y/news_icon.png
new file mode 100644
index 0000000..7d69f79
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_a11y/news_icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/news_a11y/sprite_arrows.gif b/chrome/common/extensions/docs/examples/extensions/news_a11y/sprite_arrows.gif
new file mode 100644
index 0000000..4560faf
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_a11y/sprite_arrows.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/en/messages.json b/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/en/messages.json
new file mode 100644
index 0000000..9f5ea5d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/en/messages.json
@@ -0,0 +1,63 @@
+{
+  "name": {
+    "message": "News Reader",
+    "description": "Extension name in manifest."
+  },
+  "description": {
+    "message": "Displays the first 5 items from the '$Google$ News - top news' RSS feed in a popup.",
+    "description": "Extension description in manifest.",
+    "placeholders": {
+      "google": {
+        "content": "Google",
+        "example": "Google"
+      }
+    }
+  },
+  "default_title": {
+    "message": "$Google$ News",
+    "description": "Extension browser action tooltip text in manifest.",
+    "placeholders": {
+      "google": {
+        "content": "Google",
+        "example": "Google"
+      }
+    }
+  },
+  "unknown_title": {
+    "message": "Unknown title",
+    "description": "Unknown news title."
+  },
+  "error": {
+    "message": "Error: $error$",
+    "description": "Generic error template. Expects error parameter to be passed in.",
+    "placeholders": {
+      "error": {
+        "content": "$1",
+        "example": "Failed to fetch RSS feed."
+      }
+    }
+  },
+  "failed_to_fetch_rss": {
+    "message": "Failed to fetch RSS feed.",
+    "description": "User visible error message."
+  },
+  "not_a_valid_feed": {
+    "message": "Not a valid feed.",
+    "description": "User visible error message."
+  },
+  "more_stories": {
+    "message": "To $Google$ News \u00BB",
+    "description": "Link name to more Google News.",
+    "placeholders": {
+      "google": {
+        "content": "Google",
+        "example": "Google"
+      }
+    }
+  },
+  "newsUrl": {
+    "message": "http://news.google.com",
+    "description": "Url to Google News."
+  }
+}
+
diff --git a/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/es/messages.json b/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/es/messages.json
new file mode 100644
index 0000000..2093149
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/es/messages.json
@@ -0,0 +1,63 @@
+{
+  "name": {
+    "message": "Lector de noticias",
+    "description": "Nombre de la extensión en el manifiesto."
+  },
+  "description": {
+    "message": "Muestra los primeros 5 eventos de '$Google$ noticias - destacados' RSS feed en una ventana.",
+    "description": "Descripción de la extensión en el manifiesto.",
+    "placeholders": {
+      "google": {
+        "content": "Google",
+        "example": "Google"
+      }
+    }
+  },
+  "default_title": {
+    "message": "$Google$ noticias",
+    "description": "Texto de la accion de menú de la extension en el manifiesto.",
+    "placeholders": {
+      "google": {
+        "content": "Google",
+        "example": "Google"
+      }
+    }
+  },
+  "unknown_title": {
+    "message": "Título desconocido",
+    "description": "Noticia con título desconocido."
+  },
+  "error": {
+    "message": "Error: $error$",
+    "description": "Plantilla de error genérico. Hace falta pasar un parámetro de error.",
+    "placeholders": {
+      "error": {
+        "content": "$1",
+        "example": "Fallo al capturar el RSS feed."
+      }
+    }
+  },
+  "failed_to_fetch_rss": {
+    "message": "Fallo al capturar el RSS feed.",
+    "description": "Mensaje de error visible para el usuario."
+  },
+  "not_a_valid_feed": {
+    "message": "Feed no válido.",
+    "description": "Mensaje de error visible para el usuario."
+  },
+  "more_stories": {
+    "message": "Ir a $Google$ noticias \u00BB",
+    "description": "Nombre del enlace a Google noticias.",
+    "placeholders": {
+      "google": {
+        "content": "Google",
+        "example": "Google"
+      }
+    }
+  },
+  "newsUrl": {
+    "message": "http://news.google.es",
+    "description": "Dirección de Google News."
+  }
+}
+
diff --git a/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/sr/messages.json b/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/sr/messages.json
new file mode 100644
index 0000000..7d43d7a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_i18n/_locales/sr/messages.json
@@ -0,0 +1,59 @@
+{
+  "name": {
+    "message": "Читач вести",
+    "description": "Назив екстензије у манифесту."
+  },
+  "description": {
+    "message": "Приказује првих 5 вести са '$Google$ Вести - главне вести' у прозорчићу.",
+    "description": "Опис екстензије у манифесту.",
+    "placeholders": {
+      "google": {
+        "content": "Google",
+        "example": "Google"
+      }
+    }
+  },
+  "default_title": {
+    "message": "$Google$ Вести",
+    "description": "Назив дугмета екстензије.",
+    "placeholders": {
+      "google": {
+        "content": "Google",
+        "example": "Google"
+      }
+    }
+  },
+  "unknown_title": {
+    "message": "Непознат наслов",
+    "description": "Непознат наслов вести."
+  },
+  "error": {
+    "message": "Грешка - $error$",
+    "description": "Општи облик грешке.",
+    "placeholders": {
+      "error": {
+        "content": "$1",
+        "example": "фид је недоступан."
+      }
+    }
+  },
+  "failed_to_fetch_rss": {
+    "message": "фид је недоступан.",
+    "description": "Порука грешке коју види корисник када је фид недоступан."
+  },
+  "not_a_valid_feed": {
+    "message": "неисправан фид.",
+    "description": "Порука грешке коју види корисник када је фид неисправан."
+  },
+  "more_stories": {
+    "message": "Ка $Google$ Вестима \u00BB",
+    "description": "Назив везе ка још вести.",
+    "placeholders": {
+      "google": {
+        "content": "Google",
+        "example": "Google"
+      }
+    }
+  }
+}
+
diff --git a/chrome/common/extensions/docs/examples/extensions/news_i18n/feed.html b/chrome/common/extensions/docs/examples/extensions/news_i18n/feed.html
new file mode 100644
index 0000000..902b0de
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_i18n/feed.html
@@ -0,0 +1,310 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+body {
+  font-family: helvetica, arial, sans-serif;
+  font-size: 12px;
+  overflow: hidden;
+}
+
+a {
+  color:#0000CC;
+  text-decoration: underline;
+  cursor: pointer;
+}
+
+.open_box {
+  display: block;
+  overflow: hidden;
+  margin-right: 4px;
+  margin-top: 2px;
+  height: 12px;
+  width: 12px;
+  float: left;
+  clear: left;
+  background-image: url(sprite_arrows.gif);
+  background-position: 0px -24px;
+  cursor: pointer;
+}
+
+.opened .open_box {
+  background-position:-12px -24px;
+}
+
+.item {
+  padding: 2px 0px;
+}
+
+.item_title {
+  display: block;
+  min-width: 300px;
+  padding-left: 15px;
+  cursor: pointer;
+}
+
+.item_desc {
+  min-width: 500px;
+  height: 0px;
+  display: block;
+  border: none;
+  padding: 0px;
+  margin: 0px;
+  -webkit-transition: height 0.2s ease-out;
+}
+
+#title {
+  display: block;
+  margin-left: auto;
+}
+
+.error {
+  white-space: nowrap;
+  color: red;
+}
+
+.more {
+  display: block;
+  text-align: right;
+  padding-top: 20px;
+  padding-right: 10px;
+  color: #88C;
+}
+
+</style>
+<script id="iframe_script">
+function reportHeight() {
+  var msg = JSON.stringify({type:"size", size:document.body.offsetHeight});
+  parent.postMessage(msg, "*");
+}
+
+function frameLoaded() {
+  var links = document.getElementsByTagName("A");
+  for (i = 0; i < links.length; i++) {
+    var class = links[i].className;
+    if (class != "item_title" && class != "open_box") {
+      links[i].addEventListener("click", showStory);
+    }
+  }
+  window.addEventListener("message", messageHandler);
+}
+
+function showStory(event) {
+  var href = event.currentTarget.href;
+  parent.postMessage(JSON.stringify({type:"show", url:href}), "*");
+  event.preventDefault();
+}
+
+function messageHandler(event) {
+  reportHeight();
+}
+
+</script>
+<script>
+// Feed URL.
+var feedUrl = chrome.i18n.getMessage('newsUrl') + '/?output=rss';
+
+// The XMLHttpRequest object that tries to load and parse the feed.
+var req;
+
+function main() {
+  req = new XMLHttpRequest();
+  req.onload = handleResponse;
+  req.onerror = handleError;
+  req.open("GET", feedUrl, true);
+  req.send(null);
+}
+
+// Handles feed parsing errors.
+function handleFeedParsingFailed(error) {
+  var feed = document.getElementById("feed");
+  feed.className = "error";
+  feed.innerText = chrome.i18n.getMessage("error", error);
+}
+
+// Handles errors during the XMLHttpRequest.
+function handleError() {
+  handleFeedParsingFailed(chrome.i18n.getMessage('failed_to_fetch_rss'));
+}
+
+// Handles parsing the feed data we got back from XMLHttpRequest.
+function handleResponse() {
+  var doc = req.responseXML;
+  if (!doc) {
+    handleFeedParsingFailed(chrome.i18n.getMessage('not_a_valid_feed'));
+    return;
+  }
+  buildPreview(doc);
+}
+
+// The maximum number of feed items to show in the preview.
+var maxFeedItems = 5;
+
+// Where the more stories link should navigate to.
+var moreStoriesUrl;
+
+function buildPreview(doc) {
+  // Get the link to the feed source.
+  var link = doc.getElementsByTagName("link");
+  var parentTag = link[0].parentNode.tagName;
+  if (parentTag != "item" && parentTag != "entry") {
+    moreStoriesUrl = link[0].textContent;
+  }
+
+  // Setup the title image.
+  var images = doc.getElementsByTagName("image");
+  var titleImg;
+  if (images.length != 0) {
+    var urls = images[0].getElementsByTagName("url");
+    if (urls.length != 0) {
+      titleImg = urls[0].textContent;
+    }
+  }
+  var img = document.getElementById("title");
+  if (titleImg) {
+    img.src = titleImg;
+    if (moreStoriesUrl) {
+      document.getElementById("title_a").addEventListener("click", moreStories);
+    }
+  } else {
+    img.style.display = "none";
+  }
+
+  // Construct the iframe's HTML.
+  var iframe_src = "<!doctype html><html><head><script>" +
+                   document.getElementById("iframe_script").textContent + "<" +
+                   "/script></head><body onload='frameLoaded();' " +
+                   "style='padding:0px;margin:0px;'>";
+
+  var feed = document.getElementById("feed");
+  var entries = doc.getElementsByTagName('entry');
+  if (entries.length == 0) {
+    entries = doc.getElementsByTagName('item');
+  }
+  var count = Math.min(entries.length, maxFeedItems);
+  for (var i = 0; i < count; i++) {
+    item = entries.item(i);
+
+    // Grab the title for the feed item.
+    var itemTitle = item.getElementsByTagName('title')[0];
+    if (itemTitle) {
+      itemTitle = itemTitle.textContent;
+    } else {
+      itemTitle = chrome.i18n.getMessage("unknown_title");
+    }
+
+    // Grab the description.
+    var itemDesc = item.getElementsByTagName('description')[0];
+    if (!itemDesc) {
+      itemDesc = item.getElementsByTagName('summary')[0];
+      if (!itemDesc) {
+        itemDesc = item.getElementsByTagName('content')[0];
+      }
+    }
+    if (itemDesc) {
+      itemDesc = itemDesc.childNodes[0].nodeValue;
+    } else {
+      itemDesc = '';
+    }
+
+    var item = document.createElement("div");
+    item.className = "item";
+    var box = document.createElement("div");
+    box.className = "open_box";
+    box.addEventListener("click", showDesc);
+    item.appendChild(box);
+
+    var title = document.createElement("a");
+    title.className = "item_title";
+    title.innerText = itemTitle;
+    title.addEventListener("click", showDesc);
+    item.appendChild(title);
+
+    var desc = document.createElement("iframe");
+    desc.scrolling = "no";
+    desc.className = "item_desc";
+    item.appendChild(desc);
+    feed.appendChild(item);
+
+    // The story body is created as an iframe with a data: URL in order to
+    // isolate it from this page and protect against XSS.  As a data URL, it
+    // has limited privileges and must communicate back using postMessage().
+    desc.src="data:text/html," + iframe_src + itemDesc + "</body></html>";
+  }
+
+  if (moreStoriesUrl) {
+    var more = document.createElement("a");
+    more.className = "more";
+    more.innerText = chrome.i18n.getMessage("more_stories");
+    more.addEventListener("click", moreStories);
+    feed.appendChild(more);
+  }
+}
+
+// Show |url| in a new tab.
+function showUrl(url) {
+  // Only allow http and https URLs.
+  if (url.indexOf("http:") != 0 && url.indexOf("https:") != 0) {
+    return;
+  }
+  chrome.tabs.create({url: url});
+}
+
+function moreStories(event) {
+  showUrl(moreStoriesUrl);
+}
+
+function showDesc(event) {
+  var item = event.currentTarget.parentNode;
+  var items = document.getElementsByClassName("item");
+  for (var i = 0; i < items.length; i++) {
+    var iframe = items[i].getElementsByClassName("item_desc")[0];
+    if (items[i] == item && items[i].className == "item") {
+      items[i].className = "item opened";
+      iframe.contentWindow.postMessage("reportHeight", "*");
+    } else {
+      items[i].className = "item";
+      iframe.style.height = "0px";
+    }
+  }
+}
+
+function iframeMessageHandler(e) {
+  // Only listen to messages from one of our own iframes.
+  var iframes = document.getElementsByTagName("IFRAME");
+  for (var i = 0; i < iframes.length; i++) {
+    if (iframes[i].contentWindow == e.source) {
+      var msg = JSON.parse(e.data);
+      if (msg) {
+        if (msg.type == "size") {
+          iframes[i].style.height = msg.size + "px";
+        } else if (msg.type == "show") {
+          var url = msg.url;
+          if (url.indexOf(chrome.i18n.getMessage('newsUrl')) == 0) {
+            // If the URL is a redirect URL, strip of the destination and go to
+            // that directly.  This is necessary because the Google news
+            // redirector blocks use of the redirects in this case.
+            var index = url.indexOf("&url=");
+            if (index >= 0) {
+              url = url.substring(index + 5);
+              index = url.indexOf("&");
+              if (index >= 0)
+                url = url.substring(0, index);
+            }
+          }
+          showUrl(url);
+        }
+      }
+      return;
+    }
+  }
+}
+
+window.addEventListener("message", iframeMessageHandler);
+</script>
+</head>
+<body onload="main();">
+<a id="title_a"><img id='title'></a>
+<div id="feed"></div>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/news_i18n/manifest.json b/chrome/common/extensions/docs/examples/extensions/news_i18n/manifest.json
new file mode 100644
index 0000000..32b4cc0
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_i18n/manifest.json
@@ -0,0 +1,17 @@
+{
+  "name": "__MSG_name__",
+  "version": "1.1",
+  "description": "__MSG_description__",
+  "icons": { "128": "news_icon.png" },
+  "browser_action": {
+    "default_title": "__MSG_default_title__",
+    "default_icon": "news_action.png",
+    "default_popup": "feed.html"
+  },
+  "permissions": [
+    "tabs",
+    "http://news.google.com/*",
+    "http://news.google.es/*"
+  ],
+  "default_locale": "en"
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/news_i18n/news_action.png b/chrome/common/extensions/docs/examples/extensions/news_i18n/news_action.png
new file mode 100644
index 0000000..98fe9b2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_i18n/news_action.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/news_i18n/news_icon.png b/chrome/common/extensions/docs/examples/extensions/news_i18n/news_icon.png
new file mode 100644
index 0000000..7d69f79
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_i18n/news_icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/news_i18n/sprite_arrows.gif b/chrome/common/extensions/docs/examples/extensions/news_i18n/sprite_arrows.gif
new file mode 100644
index 0000000..4560faf
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/news_i18n/sprite_arrows.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/oauth_contacts/NOTICE b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/NOTICE
new file mode 100644
index 0000000..8166d26
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/NOTICE
@@ -0,0 +1,49 @@
+This extension uses code from the following two JavaScript libraries:
+
+http://unitedheroes.net/OAuthSimple/js/OAuthSimple.js
+=====================================================
+/* OAuthSimple
+  * A simpler version of OAuth
+  *
+  * author:     jr conlin
+  * mail:       src@anticipatr.com
+  * copyright:  unitedHeroes.net
+  * version:    1.0 
+  * url:        http://unitedHeroes.net/OAuthSimple
+  *
+  * Copyright (c) 2009, unitedHeroes.net
+  * All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following conditions are met:
+  *     * Redistributions of source code must retain the above copyright
+  *       notice, this list of conditions and the following disclaimer.
+  *     * Redistributions in binary form must reproduce the above copyright
+  *       notice, this list of conditions and the following disclaimer in the
+  *       documentation and/or other materials provided with the distribution.
+  *     * Neither the name of the unitedHeroes.net nor the
+  *       names of its contributors may be used to endorse or promote products
+  *       derived from this software without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY UNITEDHEROES.NET ''AS IS'' AND ANY
+  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  * DISCLAIMED. IN NO EVENT SHALL UNITEDHEROES.NET BE LIABLE FOR ANY
+  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ 
+ http://pajhome.org.uk/crypt/md5/sha1.js
+ =======================================
+ /*
+ * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
+ * in FIPS PUB 180-1
+ * Version 2.1a Copyright Paul Johnston 2000 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for details.
+ */
diff --git a/chrome/common/extensions/docs/examples/extensions/oauth_contacts/README b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/README
new file mode 100644
index 0000000..9c0393b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/README
@@ -0,0 +1,69 @@
+Sample extension to demonstrate integration with an OAuth service.
+
+Overview
+--------
+This sample demonstrates the use of OAuth to authorize against 
+Google's Contacts API inside of an extension.  It implements a library which
+may be reused generically to authorize requests to any 3-legged OAuth API.
+
+Library
+-------
+The library files are:
+ * chrome_ex_oauth.html
+ * chrome_ex_oauth.js
+ * chrome_ex_oauthsimple.js
+
+To use these files, place them in the root of your extension and include both
+.js files in your background page in the following order:
+
+  <script type="text/javascript" src="chrome_ex_oauthsimple.js"></script>
+  <script type="text/javascript" src="chrome_ex_oauth.js"></script>   
+  
+To initialize the API, create a ChromeExOAuth object in the background page:
+
+      var oauth = ChromeExOAuth.initBackgroundPage({
+        'request_url'     :  <OAuth request URL>, 
+        'authorize_url'   :  <OAuth authorize URL>,  
+        'access_url'      :  <OAuth access token URL>,  
+        'consumer_key'    :  <OAuth consumer key>,  
+        'consumer_secret' :  <OAuth consumer secret>,  
+        'scope'           :  <scope parameter for this auth>,
+        'app_name'        :  <application name, not used by all OAuth providers>
+      }); 
+
+Call the authorize() function to redirect the user to the OAuth provider in
+order to obtain an access token.  The client library abstracts most of this 
+process, so all you need to do is pass a callback to the authorize() function
+and a new tab will open and redirect the user.  If the library already has
+stored an access token for the current scope, then no tab will be opened.  In
+either case, the callback will be called with the resulting token and secret.
+
+      oauth.authorize(onAuthorized);
+      
+There is no need to store the token and secret, as this library already stores
+these values in localStorage.  Once the callback you specified is called, you
+can call the sendSignedRequest function to send OAuth-signed requests to the
+API.  The sendSignedRequest call takes an url to fetch, a callback function,
+and an optional parameter object as its arguments.  The callback is passed
+the response text as well as the XMLHttpRequest object which was used to 
+make the request as its arguments.
+  
+      function callback(text, xhr) {
+        //...
+      };
+      
+      function onAuthorized() { 
+        var url = <API url inside of the requested scope>;
+        var request = {
+          'method' : 'GET',
+          'parameters' : {
+             <Any request parameters as key : value pairs>
+          }
+        }
+        oauth.sendSignedRequest(url, callback, request);
+      };
+      oauth.authorize(onAuthorized);
+      
+ 
+
+
diff --git a/chrome/common/extensions/docs/examples/extensions/oauth_contacts/background.js b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/background.js
new file mode 100644
index 0000000..2eef084
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/background.js
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 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.
+
+var oauth = ChromeExOAuth.initBackgroundPage({
+  'request_url' : 'https://www.google.com/accounts/OAuthGetRequestToken',
+  'authorize_url' : 'https://www.google.com/accounts/OAuthAuthorizeToken',
+  'access_url' : 'https://www.google.com/accounts/OAuthGetAccessToken',
+  'consumer_key' : 'anonymous',
+  'consumer_secret' : 'anonymous',
+  'scope' : 'http://www.google.com/m8/feeds/',
+  'app_name' : 'Sample - OAuth Contacts'
+});
+
+var contacts = null;
+
+function setIcon() {
+  if (oauth.hasToken()) {
+    chrome.browserAction.setIcon({ 'path' : 'img/icon-19-on.png'});
+  } else {
+    chrome.browserAction.setIcon({ 'path' : 'img/icon-19-off.png'});
+  }
+};
+
+function onContacts(text, xhr) {
+  contacts = [];
+  var data = JSON.parse(text);
+  for (var i = 0, entry; entry = data.feed.entry[i]; i++) {
+    var contact = {
+      'name' : entry['title']['$t'],
+      'id' : entry['id']['$t'],
+      'emails' : []
+    };
+
+    if (entry['gd$email']) {
+      var emails = entry['gd$email'];
+      for (var j = 0, email; email = emails[j]; j++) {
+        contact['emails'].push(email['address']);
+      }
+    }
+
+    if (!contact['name']) {
+      contact['name'] = contact['emails'][0] || "<Unknown>";
+    }
+    contacts.push(contact);
+  }
+
+  chrome.tabs.create({ 'url' : 'contacts.html'});
+};
+
+function getContacts() {
+  oauth.authorize(function() {
+    console.log("on authorize");
+    setIcon();
+    var url = "http://www.google.com/m8/feeds/contacts/default/full";
+    oauth.sendSignedRequest(url, onContacts, {
+      'parameters' : {
+        'alt' : 'json',
+        'max-results' : 100
+      }
+    });
+  });
+};
+
+function logout() {
+  oauth.clearTokens();
+  setIcon();
+};
+
+setIcon();
+chrome.browserAction.onClicked.addListener(getContacts);
diff --git a/chrome/common/extensions/docs/examples/extensions/oauth_contacts/chrome_ex_oauth.html b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/chrome_ex_oauth.html
new file mode 100644
index 0000000..4b03a3a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/chrome_ex_oauth.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2009 The Chromium Authors. All rights reserved.  Use of this
+ * source code is governed by a BSD-style license that can be found in the
+ * LICENSE file.
+-->
+<html>
+  <head>
+    <title>OAuth Redirect Page</title>
+    <style type="text/css">
+      body {
+        font: 16px Arial;
+        color: #333;
+      }
+    </style>
+    <script type="text/javascript" src="chrome_ex_oauthsimple.js"></script>
+    <script type="text/javascript" src="chrome_ex_oauth.js"></script>
+    <script type="text/javascript" src="onload.js"></script>
+  </head>
+  <body>
+    Redirecting...
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/oauth_contacts/chrome_ex_oauth.js b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/chrome_ex_oauth.js
new file mode 100644
index 0000000..7e13710
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/chrome_ex_oauth.js
@@ -0,0 +1,593 @@
+/**
+ * Copyright (c) 2010 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.
+ */
+
+/**
+ * Constructor - no need to invoke directly, call initBackgroundPage instead.
+ * @constructor
+ * @param {String} url_request_token The OAuth request token URL.
+ * @param {String} url_auth_token The OAuth authorize token URL.
+ * @param {String} url_access_token The OAuth access token URL.
+ * @param {String} consumer_key The OAuth consumer key.
+ * @param {String} consumer_secret The OAuth consumer secret.
+ * @param {String} oauth_scope The OAuth scope parameter.
+ * @param {Object} opt_args Optional arguments.  Recognized parameters:
+ *     "app_name" {String} Name of the current application
+ *     "callback_page" {String} If you renamed chrome_ex_oauth.html, the name
+ *          this file was renamed to.
+ */
+function ChromeExOAuth(url_request_token, url_auth_token, url_access_token,
+                       consumer_key, consumer_secret, oauth_scope, opt_args) {
+  this.url_request_token = url_request_token;
+  this.url_auth_token = url_auth_token;
+  this.url_access_token = url_access_token;
+  this.consumer_key = consumer_key;
+  this.consumer_secret = consumer_secret;
+  this.oauth_scope = oauth_scope;
+  this.app_name = opt_args && opt_args['app_name'] ||
+      "ChromeExOAuth Library";
+  this.key_token = "oauth_token";
+  this.key_token_secret = "oauth_token_secret";
+  this.callback_page = opt_args && opt_args['callback_page'] ||
+      "chrome_ex_oauth.html";
+  this.auth_params = {};
+  if (opt_args && opt_args['auth_params']) {
+    for (key in opt_args['auth_params']) {
+      if (opt_args['auth_params'].hasOwnProperty(key)) {
+        this.auth_params[key] = opt_args['auth_params'][key];
+      }
+    }
+  }
+};
+
+/*******************************************************************************
+ * PUBLIC API METHODS
+ * Call these from your background page.
+ ******************************************************************************/
+
+/**
+ * Initializes the OAuth helper from the background page.  You must call this
+ * before attempting to make any OAuth calls.
+ * @param {Object} oauth_config Configuration parameters in a JavaScript object.
+ *     The following parameters are recognized:
+ *         "request_url" {String} OAuth request token URL.
+ *         "authorize_url" {String} OAuth authorize token URL.
+ *         "access_url" {String} OAuth access token URL.
+ *         "consumer_key" {String} OAuth consumer key.
+ *         "consumer_secret" {String} OAuth consumer secret.
+ *         "scope" {String} OAuth access scope.
+ *         "app_name" {String} Application name.
+ *         "auth_params" {Object} Additional parameters to pass to the
+ *             Authorization token URL.  For an example, 'hd', 'hl', 'btmpl':
+ *             http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth
+ * @return {ChromeExOAuth} An initialized ChromeExOAuth object.
+ */
+ChromeExOAuth.initBackgroundPage = function(oauth_config) {
+  window.chromeExOAuthConfig = oauth_config;
+  window.chromeExOAuth = ChromeExOAuth.fromConfig(oauth_config);
+  window.chromeExOAuthRedirectStarted = false;
+  window.chromeExOAuthRequestingAccess = false;
+
+  var url_match = chrome.extension.getURL(window.chromeExOAuth.callback_page);
+  var tabs = {};
+  chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
+    if (changeInfo.url &&
+        changeInfo.url.substr(0, url_match.length) === url_match &&
+        changeInfo.url != tabs[tabId] &&
+        window.chromeExOAuthRequestingAccess == false) {
+      chrome.tabs.create({ 'url' : changeInfo.url }, function(tab) {
+        tabs[tab.id] = tab.url;
+        chrome.tabs.remove(tabId);
+      });
+    }
+  });
+
+  return window.chromeExOAuth;
+};
+
+/**
+ * Authorizes the current user with the configued API.  You must call this
+ * before calling sendSignedRequest.
+ * @param {Function} callback A function to call once an access token has
+ *     been obtained.  This callback will be passed the following arguments:
+ *         token {String} The OAuth access token.
+ *         secret {String} The OAuth access token secret.
+ */
+ChromeExOAuth.prototype.authorize = function(callback) {
+  if (this.hasToken()) {
+    callback(this.getToken(), this.getTokenSecret());
+  } else {
+    window.chromeExOAuthOnAuthorize = function(token, secret) {
+      callback(token, secret);
+    };
+    chrome.tabs.create({ 'url' :chrome.extension.getURL(this.callback_page) });
+  }
+};
+
+/**
+ * Clears any OAuth tokens stored for this configuration.  Effectively a
+ * "logout" of the configured OAuth API.
+ */
+ChromeExOAuth.prototype.clearTokens = function() {
+  delete localStorage[this.key_token + encodeURI(this.oauth_scope)];
+  delete localStorage[this.key_token_secret + encodeURI(this.oauth_scope)];
+};
+
+/**
+ * Returns whether a token is currently stored for this configuration.
+ * Effectively a check to see whether the current user is "logged in" to
+ * the configured OAuth API.
+ * @return {Boolean} True if an access token exists.
+ */
+ChromeExOAuth.prototype.hasToken = function() {
+  return !!this.getToken();
+};
+
+/**
+ * Makes an OAuth-signed HTTP request with the currently authorized tokens.
+ * @param {String} url The URL to send the request to.  Querystring parameters
+ *     should be omitted.
+ * @param {Function} callback A function to be called once the request is
+ *     completed.  This callback will be passed the following arguments:
+ *         responseText {String} The text response.
+ *         xhr {XMLHttpRequest} The XMLHttpRequest object which was used to
+ *             send the request.  Useful if you need to check response status
+ *             code, etc.
+ * @param {Object} opt_params Additional parameters to configure the request.
+ *     The following parameters are accepted:
+ *         "method" {String} The HTTP method to use.  Defaults to "GET".
+ *         "body" {String} A request body to send.  Defaults to null.
+ *         "parameters" {Object} Query parameters to include in the request.
+ *         "headers" {Object} Additional headers to include in the request.
+ */
+ChromeExOAuth.prototype.sendSignedRequest = function(url, callback,
+                                                     opt_params) {
+  var method = opt_params && opt_params['method'] || 'GET';
+  var body = opt_params && opt_params['body'] || null;
+  var params = opt_params && opt_params['parameters'] || {};
+  var headers = opt_params && opt_params['headers'] || {};
+
+  var signedUrl = this.signURL(url, method, params);
+
+  ChromeExOAuth.sendRequest(method, signedUrl, headers, body, function (xhr) {
+    if (xhr.readyState == 4) {
+      callback(xhr.responseText, xhr);
+    }
+  });
+};
+
+/**
+ * Adds the required OAuth parameters to the given url and returns the
+ * result.  Useful if you need a signed url but don't want to make an XHR
+ * request.
+ * @param {String} method The http method to use.
+ * @param {String} url The base url of the resource you are querying.
+ * @param {Object} opt_params Query parameters to include in the request.
+ * @return {String} The base url plus any query params plus any OAuth params.
+ */
+ChromeExOAuth.prototype.signURL = function(url, method, opt_params) {
+  var token = this.getToken();
+  var secret = this.getTokenSecret();
+  if (!token || !secret) {
+    throw new Error("No oauth token or token secret");
+  }
+
+  var params = opt_params || {};
+
+  var result = OAuthSimple().sign({
+    action : method,
+    path : url,
+    parameters : params,
+    signatures: {
+      consumer_key : this.consumer_key,
+      shared_secret : this.consumer_secret,
+      oauth_secret : secret,
+      oauth_token: token
+    }
+  });
+
+  return result.signed_url;
+};
+
+/**
+ * Generates the Authorization header based on the oauth parameters.
+ * @param {String} url The base url of the resource you are querying.
+ * @param {Object} opt_params Query parameters to include in the request.
+ * @return {String} An Authorization header containing the oauth_* params.
+ */
+ChromeExOAuth.prototype.getAuthorizationHeader = function(url, method,
+                                                          opt_params) {
+  var token = this.getToken();
+  var secret = this.getTokenSecret();
+  if (!token || !secret) {
+    throw new Error("No oauth token or token secret");
+  }
+
+  var params = opt_params || {};
+
+  return OAuthSimple().getHeaderString({
+    action: method,
+    path : url,
+    parameters : params,
+    signatures: {
+      consumer_key : this.consumer_key,
+      shared_secret : this.consumer_secret,
+      oauth_secret : secret,
+      oauth_token: token
+    }
+  });
+};
+
+/*******************************************************************************
+ * PRIVATE API METHODS
+ * Used by the library.  There should be no need to call these methods directly.
+ ******************************************************************************/
+
+/**
+ * Creates a new ChromeExOAuth object from the supplied configuration object.
+ * @param {Object} oauth_config Configuration parameters in a JavaScript object.
+ *     The following parameters are recognized:
+ *         "request_url" {String} OAuth request token URL.
+ *         "authorize_url" {String} OAuth authorize token URL.
+ *         "access_url" {String} OAuth access token URL.
+ *         "consumer_key" {String} OAuth consumer key.
+ *         "consumer_secret" {String} OAuth consumer secret.
+ *         "scope" {String} OAuth access scope.
+ *         "app_name" {String} Application name.
+ *         "auth_params" {Object} Additional parameters to pass to the
+ *             Authorization token URL.  For an example, 'hd', 'hl', 'btmpl':
+ *             http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth
+ * @return {ChromeExOAuth} An initialized ChromeExOAuth object.
+ */
+ChromeExOAuth.fromConfig = function(oauth_config) {
+  return new ChromeExOAuth(
+    oauth_config['request_url'],
+    oauth_config['authorize_url'],
+    oauth_config['access_url'],
+    oauth_config['consumer_key'],
+    oauth_config['consumer_secret'],
+    oauth_config['scope'],
+    {
+      'app_name' : oauth_config['app_name'],
+      'auth_params' : oauth_config['auth_params']
+    }
+  );
+};
+
+/**
+ * Initializes chrome_ex_oauth.html and redirects the page if needed to start
+ * the OAuth flow.  Once an access token is obtained, this function closes
+ * chrome_ex_oauth.html.
+ */
+ChromeExOAuth.initCallbackPage = function() {
+  var background_page = chrome.extension.getBackgroundPage();
+  var oauth_config = background_page.chromeExOAuthConfig;
+  var oauth = ChromeExOAuth.fromConfig(oauth_config);
+  background_page.chromeExOAuthRedirectStarted = true;
+  oauth.initOAuthFlow(function (token, secret) {
+    background_page.chromeExOAuthOnAuthorize(token, secret);
+    background_page.chromeExOAuthRedirectStarted = false;
+    chrome.tabs.getSelected(null, function (tab) {
+      chrome.tabs.remove(tab.id);
+    });
+  });
+};
+
+/**
+ * Sends an HTTP request.  Convenience wrapper for XMLHttpRequest calls.
+ * @param {String} method The HTTP method to use.
+ * @param {String} url The URL to send the request to.
+ * @param {Object} headers Optional request headers in key/value format.
+ * @param {String} body Optional body content.
+ * @param {Function} callback Function to call when the XMLHttpRequest's
+ *     ready state changes.  See documentation for XMLHttpRequest's
+ *     onreadystatechange handler for more information.
+ */
+ChromeExOAuth.sendRequest = function(method, url, headers, body, callback) {
+  var xhr = new XMLHttpRequest();
+  xhr.onreadystatechange = function(data) {
+    callback(xhr, data);
+  }
+  xhr.open(method, url, true);
+  if (headers) {
+    for (var header in headers) {
+      if (headers.hasOwnProperty(header)) {
+        xhr.setRequestHeader(header, headers[header]);
+      }
+    }
+  }
+  xhr.send(body);
+};
+
+/**
+ * Decodes a URL-encoded string into key/value pairs.
+ * @param {String} encoded An URL-encoded string.
+ * @return {Object} An object representing the decoded key/value pairs found
+ *     in the encoded string.
+ */
+ChromeExOAuth.formDecode = function(encoded) {
+  var params = encoded.split("&");
+  var decoded = {};
+  for (var i = 0, param; param = params[i]; i++) {
+    var keyval = param.split("=");
+    if (keyval.length == 2) {
+      var key = ChromeExOAuth.fromRfc3986(keyval[0]);
+      var val = ChromeExOAuth.fromRfc3986(keyval[1]);
+      decoded[key] = val;
+    }
+  }
+  return decoded;
+};
+
+/**
+ * Returns the current window's querystring decoded into key/value pairs.
+ * @return {Object} A object representing any key/value pairs found in the
+ *     current window's querystring.
+ */
+ChromeExOAuth.getQueryStringParams = function() {
+  var urlparts = window.location.href.split("?");
+  if (urlparts.length >= 2) {
+    var querystring = urlparts.slice(1).join("?");
+    return ChromeExOAuth.formDecode(querystring);
+  }
+  return {};
+};
+
+/**
+ * Binds a function call to a specific object.  This function will also take
+ * a variable number of additional arguments which will be prepended to the
+ * arguments passed to the bound function when it is called.
+ * @param {Function} func The function to bind.
+ * @param {Object} obj The object to bind to the function's "this".
+ * @return {Function} A closure that will call the bound function.
+ */
+ChromeExOAuth.bind = function(func, obj) {
+  var newargs = Array.prototype.slice.call(arguments).slice(2);
+  return function() {
+    var combinedargs = newargs.concat(Array.prototype.slice.call(arguments));
+    func.apply(obj, combinedargs);
+  };
+};
+
+/**
+ * Encodes a value according to the RFC3986 specification.
+ * @param {String} val The string to encode.
+ */
+ChromeExOAuth.toRfc3986 = function(val){
+   return encodeURIComponent(val)
+       .replace(/\!/g, "%21")
+       .replace(/\*/g, "%2A")
+       .replace(/'/g, "%27")
+       .replace(/\(/g, "%28")
+       .replace(/\)/g, "%29");
+};
+
+/**
+ * Decodes a string that has been encoded according to RFC3986.
+ * @param {String} val The string to decode.
+ */
+ChromeExOAuth.fromRfc3986 = function(val){
+  var tmp = val
+      .replace(/%21/g, "!")
+      .replace(/%2A/g, "*")
+      .replace(/%27/g, "'")
+      .replace(/%28/g, "(")
+      .replace(/%29/g, ")");
+   return decodeURIComponent(tmp);
+};
+
+/**
+ * Adds a key/value parameter to the supplied URL.
+ * @param {String} url An URL which may or may not contain querystring values.
+ * @param {String} key A key
+ * @param {String} value A value
+ * @return {String} The URL with URL-encoded versions of the key and value
+ *     appended, prefixing them with "&" or "?" as needed.
+ */
+ChromeExOAuth.addURLParam = function(url, key, value) {
+  var sep = (url.indexOf('?') >= 0) ? "&" : "?";
+  return url + sep +
+         ChromeExOAuth.toRfc3986(key) + "=" + ChromeExOAuth.toRfc3986(value);
+};
+
+/**
+ * Stores an OAuth token for the configured scope.
+ * @param {String} token The token to store.
+ */
+ChromeExOAuth.prototype.setToken = function(token) {
+  localStorage[this.key_token + encodeURI(this.oauth_scope)] = token;
+};
+
+/**
+ * Retrieves any stored token for the configured scope.
+ * @return {String} The stored token.
+ */
+ChromeExOAuth.prototype.getToken = function() {
+  return localStorage[this.key_token + encodeURI(this.oauth_scope)];
+};
+
+/**
+ * Stores an OAuth token secret for the configured scope.
+ * @param {String} secret The secret to store.
+ */
+ChromeExOAuth.prototype.setTokenSecret = function(secret) {
+  localStorage[this.key_token_secret + encodeURI(this.oauth_scope)] = secret;
+};
+
+/**
+ * Retrieves any stored secret for the configured scope.
+ * @return {String} The stored secret.
+ */
+ChromeExOAuth.prototype.getTokenSecret = function() {
+  return localStorage[this.key_token_secret + encodeURI(this.oauth_scope)];
+};
+
+/**
+ * Starts an OAuth authorization flow for the current page.  If a token exists,
+ * no redirect is needed and the supplied callback is called immediately.
+ * If this method detects that a redirect has finished, it grabs the
+ * appropriate OAuth parameters from the URL and attempts to retrieve an
+ * access token.  If no token exists and no redirect has happened, then
+ * an access token is requested and the page is ultimately redirected.
+ * @param {Function} callback The function to call once the flow has finished.
+ *     This callback will be passed the following arguments:
+ *         token {String} The OAuth access token.
+ *         secret {String} The OAuth access token secret.
+ */
+ChromeExOAuth.prototype.initOAuthFlow = function(callback) {
+  if (!this.hasToken()) {
+    var params = ChromeExOAuth.getQueryStringParams();
+    if (params['chromeexoauthcallback'] == 'true') {
+      var oauth_token = params['oauth_token'];
+      var oauth_verifier = params['oauth_verifier']
+      this.getAccessToken(oauth_token, oauth_verifier, callback);
+    } else {
+      var request_params = {
+        'url_callback_param' : 'chromeexoauthcallback'
+      }
+      this.getRequestToken(function(url) {
+        window.location.href = url;
+      }, request_params);
+    }
+  } else {
+    callback(this.getToken(), this.getTokenSecret());
+  }
+};
+
+/**
+ * Requests an OAuth request token.
+ * @param {Function} callback Function to call once the authorize URL is
+ *     calculated.  This callback will be passed the following arguments:
+ *         url {String} The URL the user must be redirected to in order to
+ *             approve the token.
+ * @param {Object} opt_args Optional arguments.  The following parameters
+ *     are accepted:
+ *         "url_callback" {String} The URL the OAuth provider will redirect to.
+ *         "url_callback_param" {String} A parameter to include in the callback
+ *             URL in order to indicate to this library that a redirect has
+ *             taken place.
+ */
+ChromeExOAuth.prototype.getRequestToken = function(callback, opt_args) {
+  if (typeof callback !== "function") {
+    throw new Error("Specified callback must be a function.");
+  }
+  var url = opt_args && opt_args['url_callback'] ||
+            window && window.top && window.top.location &&
+            window.top.location.href;
+
+  var url_param = opt_args && opt_args['url_callback_param'] ||
+                  "chromeexoauthcallback";
+  var url_callback = ChromeExOAuth.addURLParam(url, url_param, "true");
+
+  var result = OAuthSimple().sign({
+    path : this.url_request_token,
+    parameters: {
+      "xoauth_displayname" : this.app_name,
+      "scope" : this.oauth_scope,
+      "oauth_callback" : url_callback
+    },
+    signatures: {
+      consumer_key : this.consumer_key,
+      shared_secret : this.consumer_secret
+    }
+  });
+  var onToken = ChromeExOAuth.bind(this.onRequestToken, this, callback);
+  ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken);
+};
+
+/**
+ * Called when a request token has been returned.  Stores the request token
+ * secret for later use and sends the authorization url to the supplied
+ * callback (for redirecting the user).
+ * @param {Function} callback Function to call once the authorize URL is
+ *     calculated.  This callback will be passed the following arguments:
+ *         url {String} The URL the user must be redirected to in order to
+ *             approve the token.
+ * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the
+ *     request token.
+ */
+ChromeExOAuth.prototype.onRequestToken = function(callback, xhr) {
+  if (xhr.readyState == 4) {
+    if (xhr.status == 200) {
+      var params = ChromeExOAuth.formDecode(xhr.responseText);
+      var token = params['oauth_token'];
+      this.setTokenSecret(params['oauth_token_secret']);
+      var url = ChromeExOAuth.addURLParam(this.url_auth_token,
+                                          "oauth_token", token);
+      for (var key in this.auth_params) {
+        if (this.auth_params.hasOwnProperty(key)) {
+          url = ChromeExOAuth.addURLParam(url, key, this.auth_params[key]);
+        }
+      }
+      callback(url);
+    } else {
+      throw new Error("Fetching request token failed. Status " + xhr.status);
+    }
+  }
+};
+
+/**
+ * Requests an OAuth access token.
+ * @param {String} oauth_token The OAuth request token.
+ * @param {String} oauth_verifier The OAuth token verifier.
+ * @param {Function} callback The function to call once the token is obtained.
+ *     This callback will be passed the following arguments:
+ *         token {String} The OAuth access token.
+ *         secret {String} The OAuth access token secret.
+ */
+ChromeExOAuth.prototype.getAccessToken = function(oauth_token, oauth_verifier,
+                                                  callback) {
+  if (typeof callback !== "function") {
+    throw new Error("Specified callback must be a function.");
+  }
+  var bg = chrome.extension.getBackgroundPage();
+  if (bg.chromeExOAuthRequestingAccess == false) {
+    bg.chromeExOAuthRequestingAccess = true;
+
+    var result = OAuthSimple().sign({
+      path : this.url_access_token,
+      parameters: {
+        "oauth_token" : oauth_token,
+        "oauth_verifier" : oauth_verifier
+      },
+      signatures: {
+        consumer_key : this.consumer_key,
+        shared_secret : this.consumer_secret,
+        oauth_secret : this.getTokenSecret(this.oauth_scope)
+      }
+    });
+
+    var onToken = ChromeExOAuth.bind(this.onAccessToken, this, callback);
+    ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken);
+  }
+};
+
+/**
+ * Called when an access token has been returned.  Stores the access token and
+ * access token secret for later use and sends them to the supplied callback.
+ * @param {Function} callback The function to call once the token is obtained.
+ *     This callback will be passed the following arguments:
+ *         token {String} The OAuth access token.
+ *         secret {String} The OAuth access token secret.
+ * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the
+ *     access token.
+ */
+ChromeExOAuth.prototype.onAccessToken = function(callback, xhr) {
+  if (xhr.readyState == 4) {
+    var bg = chrome.extension.getBackgroundPage();
+    if (xhr.status == 200) {
+      var params = ChromeExOAuth.formDecode(xhr.responseText);
+      var token = params["oauth_token"];
+      var secret = params["oauth_token_secret"];
+      this.setToken(token);
+      this.setTokenSecret(secret);
+      bg.chromeExOAuthRequestingAccess = false;
+      callback(token, secret);
+    } else {
+      bg.chromeExOAuthRequestingAccess = false;
+      throw new Error("Fetching access token failed with status " + xhr.status);
+    }
+  }
+};
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/oauth_contacts/chrome_ex_oauthsimple.js b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/chrome_ex_oauthsimple.js
new file mode 100644
index 0000000..af0fe8a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/chrome_ex_oauthsimple.js
@@ -0,0 +1,458 @@
+/* OAuthSimple
+  * A simpler version of OAuth
+  *
+  * author:     jr conlin
+  * mail:       src@anticipatr.com
+  * copyright:  unitedHeroes.net
+  * version:    1.0
+  * url:        http://unitedHeroes.net/OAuthSimple
+  *
+  * Copyright (c) 2009, unitedHeroes.net
+  * All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following conditions are met:
+  *     * Redistributions of source code must retain the above copyright
+  *       notice, this list of conditions and the following disclaimer.
+  *     * Redistributions in binary form must reproduce the above copyright
+  *       notice, this list of conditions and the following disclaimer in the
+  *       documentation and/or other materials provided with the distribution.
+  *     * Neither the name of the unitedHeroes.net nor the
+  *       names of its contributors may be used to endorse or promote products
+  *       derived from this software without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY UNITEDHEROES.NET ''AS IS'' AND ANY
+  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  * DISCLAIMED. IN NO EVENT SHALL UNITEDHEROES.NET BE LIABLE FOR ANY
+  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+var OAuthSimple;
+
+if (OAuthSimple === undefined)
+{
+    /* Simple OAuth
+     *
+     * This class only builds the OAuth elements, it does not do the actual
+     * transmission or reception of the tokens. It does not validate elements
+     * of the token. It is for client use only.
+     *
+     * api_key is the API key, also known as the OAuth consumer key
+     * shared_secret is the shared secret (duh).
+     *
+     * Both the api_key and shared_secret are generally provided by the site
+     * offering OAuth services. You need to specify them at object creation
+     * because nobody <explative>ing uses OAuth without that minimal set of
+     * signatures.
+     *
+     * If you want to use the higher order security that comes from the
+     * OAuth token (sorry, I don't provide the functions to fetch that because
+     * sites aren't horribly consistent about how they offer that), you need to
+     * pass those in either with .setTokensAndSecrets() or as an argument to the
+     * .sign() or .getHeaderString() functions.
+     *
+     * Example:
+       <code>
+        var oauthObject = OAuthSimple().sign({path:'http://example.com/rest/',
+                                              parameters: 'foo=bar&gorp=banana',
+                                              signatures:{
+                                                api_key:'12345abcd',
+                                                shared_secret:'xyz-5309'
+                                             }});
+        document.getElementById('someLink').href=oauthObject.signed_url;
+       </code>
+     *
+     * that will sign as a "GET" using "SHA1-MAC" the url. If you need more than
+     * that, read on, McDuff.
+     */
+
+    /** OAuthSimple creator
+     *
+     * Create an instance of OAuthSimple
+     *
+     * @param api_key {string}       The API Key (sometimes referred to as the consumer key) This value is usually supplied by the site you wish to use.
+     * @param shared_secret (string) The shared secret. This value is also usually provided by the site you wish to use.
+     */
+    OAuthSimple = function (consumer_key,shared_secret)
+    {
+/*        if (api_key == undefined)
+            throw("Missing argument: api_key (oauth_consumer_key) for OAuthSimple. This is usually provided by the hosting site.");
+        if (shared_secret == undefined)
+            throw("Missing argument: shared_secret (shared secret) for OAuthSimple. This is usually provided by the hosting site.");
+*/      this._secrets={};
+        this._parameters={};
+
+        // General configuration options.
+        if (consumer_key !== undefined) {
+            this._secrets['consumer_key'] = consumer_key;
+            }
+        if (shared_secret !== undefined) {
+            this._secrets['shared_secret'] = shared_secret;
+            }
+        this._default_signature_method= "HMAC-SHA1";
+        this._action = "GET";
+        this._nonce_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+
+        this.reset = function() {
+            this._parameters={};
+            this._path=undefined;
+            return this;
+        };
+
+        /** set the parameters either from a hash or a string
+         *
+         * @param {string,object} List of parameters for the call, this can either be a URI string (e.g. "foo=bar&gorp=banana" or an object/hash)
+         */
+        this.setParameters = function (parameters) {
+            if (parameters === undefined) {
+                parameters = {};
+                }
+            if (typeof(parameters) == 'string') {
+                parameters=this._parseParameterString(parameters);
+                }
+            this._parameters = parameters;
+            if (this._parameters['oauth_nonce'] === undefined) {
+                this._getNonce();
+                }
+            if (this._parameters['oauth_timestamp'] === undefined) {
+                this._getTimestamp();
+                }
+            if (this._parameters['oauth_method'] === undefined) {
+                this.setSignatureMethod();
+                }
+            if (this._parameters['oauth_consumer_key'] === undefined) {
+                this._getApiKey();
+                }
+            if(this._parameters['oauth_token'] === undefined) {
+                this._getAccessToken();
+                }
+
+            return this;
+        };
+
+        /** convienence method for setParameters
+         *
+         * @param parameters {string,object} See .setParameters
+         */
+        this.setQueryString = function (parameters) {
+            return this.setParameters(parameters);
+        };
+
+        /** Set the target URL (does not include the parameters)
+         *
+         * @param path {string} the fully qualified URI (excluding query arguments) (e.g "http://example.org/foo")
+         */
+        this.setURL = function (path) {
+            if (path == '') {
+                throw ('No path specified for OAuthSimple.setURL');
+                }
+            this._path = path;
+            return this;
+        };
+
+        /** convienence method for setURL
+         *
+         * @param path {string} see .setURL
+         */
+        this.setPath = function(path){
+            return this.setURL(path);
+        };
+
+        /** set the "action" for the url, (e.g. GET,POST, DELETE, etc.)
+         *
+         * @param action {string} HTTP Action word.
+         */
+        this.setAction = function(action) {
+            if (action === undefined) {
+                action="GET";
+                }
+            action = action.toUpperCase();
+            if (action.match('[^A-Z]')) {
+                throw ('Invalid action specified for OAuthSimple.setAction');
+                }
+            this._action = action;
+            return this;
+        };
+
+        /** set the signatures (as well as validate the ones you have)
+         *
+         * @param signatures {object} object/hash of the token/signature pairs {api_key:, shared_secret:, oauth_token: oauth_secret:}
+         */
+        this.setTokensAndSecrets = function(signatures) {
+            if (signatures)
+            {
+                for (var i in signatures) {
+                    this._secrets[i] = signatures[i];
+                    }
+            }
+            // Aliases
+            if (this._secrets['api_key']) {
+                this._secrets.consumer_key = this._secrets.api_key;
+                }
+            if (this._secrets['access_token']) {
+                this._secrets.oauth_token = this._secrets.access_token;
+                }
+            if (this._secrets['access_secret']) {
+                this._secrets.oauth_secret = this._secrets.access_secret;
+                }
+            // Gauntlet
+            if (this._secrets.consumer_key === undefined) {
+                throw('Missing required consumer_key in OAuthSimple.setTokensAndSecrets');
+                }
+            if (this._secrets.shared_secret === undefined) {
+                throw('Missing required shared_secret in OAuthSimple.setTokensAndSecrets');
+                }
+            if ((this._secrets.oauth_token !== undefined) && (this._secrets.oauth_secret === undefined)) {
+                throw('Missing oauth_secret for supplied oauth_token in OAuthSimple.setTokensAndSecrets');
+                }
+            return this;
+        };
+
+        /** set the signature method (currently only Plaintext or SHA-MAC1)
+         *
+         * @param method {string} Method of signing the transaction (only PLAINTEXT and SHA-MAC1 allowed for now)
+         */
+        this.setSignatureMethod = function(method) {
+            if (method === undefined) {
+                method = this._default_signature_method;
+                }
+            //TODO: accept things other than PlainText or SHA-MAC1
+            if (method.toUpperCase().match(/(PLAINTEXT|HMAC-SHA1)/) === undefined) {
+                throw ('Unknown signing method specified for OAuthSimple.setSignatureMethod');
+                }
+            this._parameters['oauth_signature_method']= method.toUpperCase();
+            return this;
+        };
+
+        /** sign the request
+         *
+         * note: all arguments are optional, provided you've set them using the
+         * other helper functions.
+         *
+         * @param args {object} hash of arguments for the call
+         *                   {action:, path:, parameters:, method:, signatures:}
+         *                   all arguments are optional.
+         */
+        this.sign = function (args) {
+            if (args === undefined) {
+                args = {};
+                }
+            // Set any given parameters
+            if(args['action'] !== undefined) {
+                this.setAction(args['action']);
+                }
+            if (args['path'] !== undefined) {
+                this.setPath(args['path']);
+                }
+            if (args['method'] !== undefined) {
+                this.setSignatureMethod(args['method']);
+                }
+            this.setTokensAndSecrets(args['signatures']);
+            if (args['parameters'] !== undefined){
+            this.setParameters(args['parameters']);
+            }
+            // check the parameters
+            var normParams = this._normalizedParameters();
+            this._parameters['oauth_signature']=this._generateSignature(normParams);
+            return {
+                parameters: this._parameters,
+                signature: this._oauthEscape(this._parameters['oauth_signature']),
+                signed_url: this._path + '?' + this._normalizedParameters(),
+                header: this.getHeaderString()
+            };
+        };
+
+        /** Return a formatted "header" string
+         *
+         * NOTE: This doesn't set the "Authorization: " prefix, which is required.
+         * I don't set it because various set header functions prefer different
+         * ways to do that.
+         *
+         * @param args {object} see .sign
+         */
+        this.getHeaderString = function(args) {
+            if (this._parameters['oauth_signature'] === undefined) {
+                this.sign(args);
+                }
+
+            var result = 'OAuth ';
+            for (var pName in this._parameters)
+            {
+                if (!pName.match(/^oauth/)) {
+                    continue;
+                    }
+                if ((this._parameters[pName]) instanceof Array)
+                {
+                    var pLength = this._parameters[pName].length;
+                    for (var j=0;j<pLength;j++)
+                    {
+                        result += pName +'="'+this._oauthEscape(this._parameters[pName][j])+'" ';
+                    }
+                }
+                else
+                {
+                    result += pName + '="'+this._oauthEscape(this._parameters[pName])+'" ';
+                }
+            }
+            return result;
+        };
+
+        // Start Private Methods.
+
+        /** convert the parameter string into a hash of objects.
+         *
+         */
+        this._parseParameterString = function(paramString){
+            var elements = paramString.split('&');
+            var result={};
+            for(var element=elements.shift();element;element=elements.shift())
+            {
+                var keyToken=element.split('=');
+                var value='';
+                if (keyToken[1]) {
+                    value=decodeURIComponent(keyToken[1]);
+                    }
+                if(result[keyToken[0]]){
+                    if (!(result[keyToken[0]] instanceof Array))
+                    {
+                        result[keyToken[0]] = Array(result[keyToken[0]],value);
+                    }
+                    else
+                    {
+                        result[keyToken[0]].push(value);
+                    }
+                }
+                else
+                {
+                    result[keyToken[0]]=value;
+                }
+            }
+            return result;
+        };
+
+        this._oauthEscape = function(string) {
+            if (string === undefined) {
+                return "";
+                }
+            if (string instanceof Array)
+            {
+                throw('Array passed to _oauthEscape');
+            }
+            return encodeURIComponent(string).replace(/\!/g, "%21").
+            replace(/\*/g, "%2A").
+            replace(/'/g, "%27").
+            replace(/\(/g, "%28").
+            replace(/\)/g, "%29");
+        };
+
+        this._getNonce = function (length) {
+            if (length === undefined) {
+                length=5;
+                }
+            var result = "";
+            var cLength = this._nonce_chars.length;
+            for (var i = 0; i < length;i++) {
+                var rnum = Math.floor(Math.random() *cLength);
+                result += this._nonce_chars.substring(rnum,rnum+1);
+            }
+            this._parameters['oauth_nonce']=result;
+            return result;
+        };
+
+        this._getApiKey = function() {
+            if (this._secrets.consumer_key === undefined) {
+                throw('No consumer_key set for OAuthSimple.');
+                }
+            this._parameters['oauth_consumer_key']=this._secrets.consumer_key;
+            return this._parameters.oauth_consumer_key;
+        };
+
+        this._getAccessToken = function() {
+            if (this._secrets['oauth_secret'] === undefined) {
+                return '';
+                }
+            if (this._secrets['oauth_token'] === undefined) {
+                throw('No oauth_token (access_token) set for OAuthSimple.');
+                }
+            this._parameters['oauth_token'] = this._secrets.oauth_token;
+            return this._parameters.oauth_token;
+        };
+
+        this._getTimestamp = function() {
+            var d = new Date();
+            var ts = Math.floor(d.getTime()/1000);
+            this._parameters['oauth_timestamp'] = ts;
+            return ts;
+        };
+
+        this.b64_hmac_sha1 = function(k,d,_p,_z){
+        // heavily optimized and compressed version of http://pajhome.org.uk/crypt/md5/sha1.js
+        // _p = b64pad, _z = character size; not used here but I left them available just in case
+        if(!_p){_p='=';}if(!_z){_z=8;}function _f(t,b,c,d){if(t<20){return(b&c)|((~b)&d);}if(t<40){return b^c^d;}if(t<60){return(b&c)|(b&d)|(c&d);}return b^c^d;}function _k(t){return(t<20)?1518500249:(t<40)?1859775393:(t<60)?-1894007588:-899497514;}function _s(x,y){var l=(x&0xFFFF)+(y&0xFFFF),m=(x>>16)+(y>>16)+(l>>16);return(m<<16)|(l&0xFFFF);}function _r(n,c){return(n<<c)|(n>>>(32-c));}function _c(x,l){x[l>>5]|=0x80<<(24-l%32);x[((l+64>>9)<<4)+15]=l;var w=[80],a=1732584193,b=-271733879,c=-1732584194,d=271733878,e=-1009589776;for(var i=0;i<x.length;i+=16){var o=a,p=b,q=c,r=d,s=e;for(var j=0;j<80;j++){if(j<16){w[j]=x[i+j];}else{w[j]=_r(w[j-3]^w[j-8]^w[j-14]^w[j-16],1);}var t=_s(_s(_r(a,5),_f(j,b,c,d)),_s(_s(e,w[j]),_k(j)));e=d;d=c;c=_r(b,30);b=a;a=t;}a=_s(a,o);b=_s(b,p);c=_s(c,q);d=_s(d,r);e=_s(e,s);}return[a,b,c,d,e];}function _b(s){var b=[],m=(1<<_z)-1;for(var i=0;i<s.length*_z;i+=_z){b[i>>5]|=(s.charCodeAt(i/8)&m)<<(32-_z-i%32);}return b;}function _h(k,d){var b=_b(k);if(b.length>16){b=_c(b,k.length*_z);}var p=[16],o=[16];for(var i=0;i<16;i++){p[i]=b[i]^0x36363636;o[i]=b[i]^0x5C5C5C5C;}var h=_c(p.concat(_b(d)),512+d.length*_z);return _c(o.concat(h),512+160);}function _n(b){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s='';for(var i=0;i<b.length*4;i+=3){var r=(((b[i>>2]>>8*(3-i%4))&0xFF)<<16)|(((b[i+1>>2]>>8*(3-(i+1)%4))&0xFF)<<8)|((b[i+2>>2]>>8*(3-(i+2)%4))&0xFF);for(var j=0;j<4;j++){if(i*8+j*6>b.length*32){s+=_p;}else{s+=t.charAt((r>>6*(3-j))&0x3F);}}}return s;}function _x(k,d){return _n(_h(k,d));}return _x(k,d);
+        }
+
+
+        this._normalizedParameters = function() {
+            var elements = new Array();
+            var paramNames = [];
+            var ra =0;
+            for (var paramName in this._parameters)
+            {
+                if (ra++ > 1000) {
+                    throw('runaway 1');
+                    }
+                paramNames.unshift(paramName);
+            }
+            paramNames = paramNames.sort();
+            pLen = paramNames.length;
+            for (var i=0;i<pLen; i++)
+            {
+                paramName=paramNames[i];
+                //skip secrets.
+                if (paramName.match(/\w+_secret/)) {
+                    continue;
+                    }
+                if (this._parameters[paramName] instanceof Array)
+                {
+                    var sorted = this._parameters[paramName].sort();
+                    var spLen = sorted.length;
+                    for (var j = 0;j<spLen;j++){
+                        if (ra++ > 1000) {
+                            throw('runaway 1');
+                            }
+                        elements.push(this._oauthEscape(paramName) + '=' +
+                                  this._oauthEscape(sorted[j]));
+                    }
+                    continue;
+                }
+                elements.push(this._oauthEscape(paramName) + '=' +
+                              this._oauthEscape(this._parameters[paramName]));
+            }
+            return elements.join('&');
+        };
+
+        this._generateSignature = function() {
+
+            var secretKey = this._oauthEscape(this._secrets.shared_secret)+'&'+
+                this._oauthEscape(this._secrets.oauth_secret);
+            if (this._parameters['oauth_signature_method'] == 'PLAINTEXT')
+            {
+                return secretKey;
+            }
+            if (this._parameters['oauth_signature_method'] == 'HMAC-SHA1')
+            {
+                var sigString = this._oauthEscape(this._action)+'&'+this._oauthEscape(this._path)+'&'+this._oauthEscape(this._normalizedParameters());
+                return this.b64_hmac_sha1(secretKey,sigString);
+            }
+            return null;
+        };
+
+    return this;
+    };
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/oauth_contacts/contacts.html b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/contacts.html
new file mode 100644
index 0000000..10f29e8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/contacts.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2009 The Chromium Authors. All rights reserved.  Use of this
+ * source code is governed by a BSD-style license that can be found in the
+ * LICENSE file.
+-->
+<html>
+  <head>
+    <title>Your Google Contacts List</title>
+    <style type="text/css">
+      body {
+        font: 14px Arial;
+      }
+      p {
+        font-weight: bold;
+      }
+    </style>
+  </head>
+  <body>
+    <h1>Your Google Contacts List</h1>
+    <h2>Listing the first 100 results of a standard query to
+      <a href="http://code.google.com/apis/contacts/">Google's
+      Contacts API</a></h2>
+    <button id="clear">Click here to clear your OAuth token</button>
+    <div id="output">
+    </div>
+    <script src="contacts.js"></script>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/oauth_contacts/contacts.js b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/contacts.js
new file mode 100644
index 0000000..225cd9b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/contacts.js
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 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.
+
+var contacts = chrome.extension.getBackgroundPage().contacts;
+var output = document.getElementById('output');
+for (var i = 0, contact; contact = contacts[i]; i++) {
+  var div = document.createElement('div');
+  var pName = document.createElement('p');
+  var ulEmails = document.createElement('ul');
+
+  pName.innerText = contact['name'];
+  div.appendChild(pName);
+
+  for (var j = 0, email; email = contact['emails'][j]; j++) {
+    var liEmail = document.createElement('li');
+    liEmail.innerText = email;
+    ulEmails.appendChild(liEmail);
+  }
+
+  div.appendChild(ulEmails);
+  output.appendChild(div);
+}
+
+function logout() {
+  chrome.extension.getBackgroundPage().logout();
+  window.close();
+}
+
+document.addEventListener('DOMContentLoaded', function () {
+  document.querySelector('#clear').addEventListener('click', logout);
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/oauth_contacts/img/icon-128.png b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/img/icon-128.png
new file mode 100644
index 0000000..d04874d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/img/icon-128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/oauth_contacts/img/icon-19-off.png b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/img/icon-19-off.png
new file mode 100644
index 0000000..123cbf7
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/img/icon-19-off.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/oauth_contacts/img/icon-19-on.png b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/img/icon-19-on.png
new file mode 100644
index 0000000..4451534
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/img/icon-19-on.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/oauth_contacts/img/icon-32.png b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/img/icon-32.png
new file mode 100644
index 0000000..6e05a2b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/img/icon-32.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/oauth_contacts/img/icon-48.png b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/img/icon-48.png
new file mode 100644
index 0000000..93960b9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/img/icon-48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/oauth_contacts/manifest.json b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/manifest.json
new file mode 100644
index 0000000..5771b42
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/manifest.json
@@ -0,0 +1,26 @@
+{
+  "name": "Sample - OAuth Contacts",
+  "version": "1.0.6",
+  "icons": { "48": "img/icon-48.png",
+            "128": "img/icon-128.png" },
+  "description": "Uses OAuth to connect to Google's contacts service and display a list of your contacts.",
+  "background": {
+    "scripts": [
+      "chrome_ex_oauthsimple.js",
+      "chrome_ex_oauth.js",
+      "background.js"
+    ]
+  },
+  "browser_action": {
+    "default_title": "",
+    "default_icon": "img/icon-19-off.png"
+  },
+  "permissions": [
+    "tabs",
+    "http://www.google.com/m8/feeds/*",
+    "https://www.google.com/accounts/OAuthGetRequestToken",
+    "https://www.google.com/accounts/OAuthAuthorizeToken",
+    "https://www.google.com/accounts/OAuthGetAccessToken"
+  ],
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/oauth_contacts/onload.js b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/onload.js
new file mode 100644
index 0000000..83ba522
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/oauth_contacts/onload.js
@@ -0,0 +1,7 @@
+// Copyright (c) 2012 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.
+
+window.onload = function() {
+  ChromeExOAuth.initCallbackPage();
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/_locales/en/messages.json b/chrome/common/extensions/docs/examples/extensions/plugin_settings/_locales/en/messages.json
new file mode 100644
index 0000000..95524bd
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/_locales/en/messages.json
@@ -0,0 +1,23 @@
+{
+  "extName": {
+    "message": "Per-plugin content settings"
+  },
+  "extDescription": {
+    "message": "Customize your content setting for different plug-ins."
+  },
+  "patternColumnHeader": {
+    "message": "Hostname Pattern"
+  },
+  "settingColumnHeader": {
+    "message": "Behavior"
+  },
+  "allowRule": {
+    "message": "Allow"
+  },
+  "blockRule": {
+    "message": "Block"
+  },
+  "addNewPattern": {
+    "message": "Add a new hostname pattern"
+  }
+}
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/bunny128.png b/chrome/common/extensions/docs/examples/extensions/plugin_settings/bunny128.png
new file mode 100644
index 0000000..917371d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/bunny128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/bunny48.png b/chrome/common/extensions/docs/examples/extensions/plugin_settings/bunny48.png
new file mode 100644
index 0000000..30434e4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/bunny48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/css/plugin_list.css b/chrome/common/extensions/docs/examples/extensions/plugin_settings/css/plugin_list.css
new file mode 100644
index 0000000..225bd3c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/css/plugin_list.css
@@ -0,0 +1,85 @@
+/*
+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.
+*/
+
+body {
+  font-family: Helvetica, sans-serif;
+  background-color: white;
+  color: black;
+  margin: 10px;
+}
+
+.plugin-list {
+  border: 1px solid #d9d9d9;
+  border-radius: 2px;
+  width: 517px;
+}
+
+.plugin-list > li {
+  padding: 3px;
+}
+
+.plugin-name {
+  display: inline-block;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  font-weight: bold;
+}
+
+.num-rules:before {
+  content: ' ';
+}
+
+.plugin-show-details .num-rules {
+  display: none;
+}
+
+.plugin-description {
+  display: inline-block;
+  -webkit-padding-start: 7px;
+  width: auto;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  font-size: 95%;
+}
+
+.plugin-details {
+  -webkit-transition: height .5s ease-in-out;
+  background: #f5f8f8;
+  border: 1px solid #b2b2b2;
+  border-radius: 5px;
+  padding: 5px;
+  height: 0;
+  width: 500px;
+  opacity: 0;
+}
+
+.plugin-measure-details .plugin-details {
+  -webkit-transition: none;
+  height: auto;
+  visibility: hidden;
+}
+
+li.plugin-show-details {
+  height: auto;
+}
+
+.plugin-show-details .plugin-description {
+  height: auto;
+}
+
+.plugin-show-details .plugin-details {
+  opacity: 1;
+  height: auto;
+}
+
+.column-headers {
+  -webkit-margin-start: 17px;
+  display: -webkit-box;
+}
+
+.column-headers > div {
+  font-weight: bold;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/css/rule_list.css b/chrome/common/extensions/docs/examples/extensions/plugin_settings/css/rule_list.css
new file mode 100644
index 0000000..b67157b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/css/rule_list.css
@@ -0,0 +1,33 @@
+/*
+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.
+*/
+
+.rule-pattern {
+  -webkit-box-flex: 1;
+  -webkit-margin-end: 10px;
+  -webkit-margin-start: 14px;
+}
+
+.rule-behavior {
+  display: inline-block;
+  width: 120px;
+}
+
+select.rule-behavior {
+  vertical-align: middle;
+}
+
+.rule-list {
+  border: 1px solid #d9d9d9;
+  border-radius: 2px;
+}
+
+.pattern-column-header {
+  -webkit-box-flex: 1;
+}
+
+.setting-column-header {
+  width: 145px;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/button.css b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/button.css
new file mode 100644
index 0000000..5564f42
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/button.css
@@ -0,0 +1,43 @@
+button,
+input[type='button'],
+input[type='submit'] {
+  -webkit-border-radius: 2px;
+  -webkit-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;
+  font-size: inherit;
+  margin-bottom: 0px;
+  min-width: 4em;
+  padding: 3px 12px 3px 12px;
+}
+
+button:hover,
+input[type='button']:hover,
+input[type='submit']:hover {
+  -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.2);
+  background: #ebebeb -webkit-linear-gradient(#fefefe, #f8f8f8 40%, #e9e9e9);
+  border-color: #999;
+  color: #222;
+}
+
+button:active,
+input[type='button']:active,
+input[type='submit']:active {
+  -webkit-box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.2);
+  background: #ebebeb -webkit-linear-gradient(#f4f4f4, #efefef 40%, #dcdcdc);
+  color: #333;
+}
+
+button[disabled],
+input[type='button'][disabled],
+input[type='submit'][disabled],
+button[disabled]:hover,
+input[type='button'][disabled]:hover,
+input[type='submit'][disabled]:hover {
+  -webkit-box-shadow: none;
+  background: -webkit-linear-gradient(#fafafa, #f4f4f4 40%, #e5e5e5);
+  border-color: #aaa;
+  color: #888;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/chrome_shared.css b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/chrome_shared.css
new file mode 100644
index 0000000..fa70aa2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/chrome_shared.css
@@ -0,0 +1,257 @@
+/* Styles common to WebUI pages that share the options pages style */
+body {
+  cursor: default;
+  font-size: 13px;
+}
+
+a:link {
+  color: rgb(63, 110, 194);
+}
+
+a:active {
+  color: rgb(37, 64, 113);
+}
+
+#navbar-content-title {
+  -webkit-padding-end: 24px;
+  -webkit-user-select: none;
+  color: #53637d;
+  cursor: pointer;
+  font-size: 200%;
+  font-weight: normal;
+  margin: 0;
+  padding-bottom: 14px;
+  padding-top: 13px;
+  text-align: end;
+  text-shadow: white 0 1px 2px;
+}
+
+#main-content {
+  display: -webkit-box;
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+}
+
+#navbar {
+  margin: 0;
+}
+
+#navbar-container {
+  -webkit-border-end: 1px solid #c6c9ce;
+  background: -webkit-linear-gradient(rgba(234, 238, 243, 0.2), #eaeef3),
+              -webkit-linear-gradient(left, #eaeef3, #eaeef3 97%, #d3d7db);
+  position: fixed;
+  bottom: 0;
+  /* We set both left and right for the sake of RTL. */
+  left: 0;
+  right: 0;
+  top: 0;
+  width: 216px;
+  z-index: 2;
+}
+
+html[dir='rtl'] #navbar-container {
+  background: -webkit-linear-gradient(rgba(234, 238, 243, 0), #EAEEF3),
+              -webkit-linear-gradient(right, #EAEEF3, #EAEEF3 97%, #D3D7DB);
+}
+
+html.hide-menu #navbar-container {
+  display: none;
+}
+
+#navbar-container > ul {
+  -webkit-user-select: none;
+  list-style-type: none;
+  margin: 0;
+  padding: 0;
+}
+
+.navbar-item {
+  border-bottom: 1px solid transparent;
+  border-top: 1px solid transparent;
+  color: #426dc9;
+  cursor: pointer;
+  display: block;
+  font-size: 105%;
+  outline: none;
+  padding: 7px 0;
+  text-align: end;
+  text-shadow: white 0 1px 1px;
+  -webkit-padding-end: 24px;
+}
+
+.navbar-item:focus {
+  border-bottom: 1px solid #8faad9;
+  border-top: 1px solid #8faad9;
+}
+
+.navbar-item-selected {
+  -webkit-box-shadow: 0px 1px 0px #f7f7f7;
+  background: -webkit-linear-gradient(left, #bbcee9, #bbcee9 97%, #aabedc);
+  border-bottom: 1px solid #8faad9;
+  border-top: 1px solid #8faad9;
+  color: black;
+  text-shadow: #bbcee9 0 1px 1px;
+}
+
+#mainview {
+  -webkit-box-align: stretch;
+  -webkit-padding-start: 216px;
+  margin: 0;
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  z-index: 1;
+}
+
+html.hide-menu #mainview {
+  -webkit-padding-start: 0;
+}
+
+#mainview-content {
+  min-height: 100%;
+  position: relative;
+}
+
+#page-container {
+  box-sizing: border-box;
+  max-width: 888px;
+  min-width: 600px;
+  padding: 0 24px;
+}
+
+div.checkbox,
+div.radio {
+  margin: 5px 0;
+  color: #444;
+}
+
+div.disabled {
+  color: #888;
+}
+
+/* TEXT */
+input[type='password'],
+input[type='text'],
+input[type='url'],
+input:not([type]) {
+  -webkit-border-radius: 2px;
+  border: 1px solid #aaa;
+  font-size: inherit;
+  padding: 3px;
+}
+
+/* CHECKBOX, RADIO */
+input[type=checkbox],
+input[type=radio] {
+  margin-left: 0;
+  margin-right: 0;
+  position: relative;
+  top: 1px;
+}
+
+/* Checkbox and radio buttons have different sizes on different platforms. The
+ * following rules have platform specific tweaks.
+ * TODO(arv): Test the vertical position on Linux and CrOS as well.
+ */
+
+label > input[type=checkbox],
+label > input[type=radio] {
+  opacity: 0.7;
+  margin-top: 1px;
+}
+
+html[os=mac] label > input[type=checkbox],
+html[os=mac] label > input[type=radio] {
+  margin-top: 2px;
+}
+
+html[os=chromeos] label > input[type=checkbox],
+html[os=chromeos] label > input[type=radio] {
+  top: 2px;
+}
+
+/* Checkbox and radio hover visuals.
+ * Their appearance when checked is set to be the same.
+ */
+label:hover > input[type=checkbox]:not([disabled]),
+label:hover > input[type=radio]:not([disabled]),
+label > input:not([disabled]):checked {
+  opacity: 1;
+}
+
+label:hover > input[type=checkbox]:not([disabled]) ~ span,
+label:hover > input[type=radio]:not([disabled]) ~ span,
+label > input:not([disabled]):checked ~ span {
+  color: #222;
+}
+
+/* This will 'disable' the label associated with any input whose next sibling is
+ * the span containing the label (usually a checkbox or radio).
+ */
+label > input[disabled] ~ span {
+  color: #888;
+}
+
+/* Elements that need to be LTR even in an RTL context, but should align
+ * right. (Namely, URLs, search engine names, etc.)
+ */
+html[dir='rtl'] .weakrtl {
+  direction: ltr;
+  text-align: right;
+}
+
+/* Input fields in search engine table need to be weak-rtl. Since those input
+ * fields are generated for all cr.ListItem elements (and we only want weakrtl
+ * on some), the class needs to be on the enclosing div.
+ */
+html[dir='rtl'] div.weakrtl input {
+    direction: ltr;
+    text-align: right;
+}
+
+html[dir='rtl'] .favicon-cell.weakrtl {
+  -webkit-padding-end: 22px;
+  -webkit-padding-start: 0;
+}
+
+/* weakrtl for selection drop downs needs to account for the fact that
+ * Webkit does not honor the text-align attribute for the select element.
+ * (See Webkit bug #40216)
+ */
+html[dir='rtl'] select.weakrtl {
+  direction: rtl;
+}
+
+html[dir='rtl'] select.weakrtl option {
+  direction: ltr;
+}
+
+/* WebKit does not honor alignment for text specified via placeholder attrib.
+ * This CSS is a workaround. Please remove once WebKit bug is fixed.
+ * https://bugs.webkit.org/show_bug.cgi?id=63367
+ */
+html[dir='rtl'] input.weakrtl::-webkit-input-placeholder,
+html[dir='rtl'] .weakrtl input::-webkit-input-placeholder {
+  direction: rtl;
+}
+
+.page h1 {
+  -webkit-padding-end: 24px;
+  -webkit-user-select: none;
+  border-bottom: 1px solid #eeeeee;
+  color: #53637d;
+  font-size: 200%;
+  font-weight: normal;
+  margin: 0;
+  padding-bottom: 4px;
+  padding-top: 13px;
+  text-shadow: white 0 1px 2px;
+}
+
+
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/list.css b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/list.css
new file mode 100644
index 0000000..75d2a92
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/list.css
@@ -0,0 +1,89 @@
+
+list,
+grid {
+  display: block;
+  outline: none;
+  overflow: auto;
+  position: relative; /* Make sure that item offsets are relative to the
+                         list. */
+}
+
+list > *,
+grid > * {
+  -webkit-user-select: none;
+  background-color: rgba(255,255,255,0);
+  border: 1px solid rgba(255,255,255,0); /* transparent white */
+  border-radius: 2px;
+  cursor: default;
+  line-height: 20px;
+  margin: -1px 0;
+  overflow: hidden;
+  padding: 0px 3px;
+  position: relative; /* to allow overlap */
+  text-overflow: ellipsis;
+  white-space: pre;
+}
+
+list > * {
+  display: block;
+}
+
+grid > * {
+  display: inline-block;
+}
+
+list > [lead],
+grid > [lead] {
+  border-color: transparent;
+}
+
+list:focus > [lead],
+grid:focus > [lead] {
+  border-color: hsl(214, 91%, 65%);
+  z-index: 2;
+}
+
+list > [anchor],
+grid > [anchor] {
+
+}
+
+list:not([disabled]) > :hover,
+grid:not([disabled]) > :hover {
+  border-color: hsl(214, 91%, 85%);
+  z-index: 1;
+  background-color: hsl(214, 91%, 97%);
+}
+
+list > [selected],
+grid > [selected] {
+  border-color: hsl(0, 0%, 85%);
+  background-color: hsl(0,0%,90%);
+  z-index: 2;
+  background-image: -webkit-linear-gradient(rgba(255, 255, 255, 0.8),
+                                            rgba(255, 255, 255, 0));
+}
+
+list:focus > [selected],
+grid:focus > [selected] {
+  background-color: hsl(214,91%,89%);
+  border-color: hsl(214, 91%, 65%);
+}
+
+list:focus > [lead][selected],
+list > [selected]:hover,
+grid:focus > [lead][selected],
+grid > [selected]:hover {
+  background-color: hsl(214, 91%, 87%);
+  border-color: hsl(214, 91%, 65%);
+}
+
+list > .spacer,
+grid > .spacer {
+  border: 0;
+  box-sizing: border-box;
+  display: block;
+  overflow: hidden;
+  visibility: hidden;
+  margin: 0;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/select.css b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/select.css
new file mode 100644
index 0000000..ba951e5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/css/select.css
@@ -0,0 +1,51 @@
+/* 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.
+ *
+ * This is the generic select css used on various WebUI implementations.
+ */
+
+select {
+  -webkit-appearance: button;
+  -webkit-border-radius: 2px;
+  -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+  -webkit-padding-end: 20px;
+  -webkit-padding-start: 2px;
+  -webkit-user-select: none;
+  background-image: url("../images/select.png"),
+                    -webkit-linear-gradient(#fafafa, #f4f4f4 40%, #e5e5e5);
+  background-position: center right;
+  background-repeat: no-repeat;
+  border: 1px solid #aaa;
+  color: #555;
+  font-size: inherit;
+  margin: 0;
+  overflow: hidden;
+  padding-top: 2px;
+  padding-bottom: 2px;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+html[dir='rtl'] select {
+  background-position: center left;
+}
+
+
+select:disabled {
+  color: graytext;
+}
+
+select:enabled:hover {
+  -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+  background-image: url("../images/select.png"),
+                    -webkit-linear-gradient(#fefefe, #f8f8f8 40%, #e9e9e9);
+  color: #333;
+}
+
+select:enabled:active {
+  -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
+  background-image: url("../images/select.png"),
+                    -webkit-linear-gradient(#f4f4f4, #efefef 40%, #dcdcdc);
+  color: #444;
+}
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/images/select.png b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/images/select.png
new file mode 100644
index 0000000..c20d304
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/images/select.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr.js
new file mode 100644
index 0000000..52e2524
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr.js
@@ -0,0 +1,374 @@
+// 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.
+
+const cr = (function() {
+
+  /**
+   * Whether we are using a Mac or not.
+   * @type {boolean}
+   */
+  const isMac = /Mac/.test(navigator.platform);
+
+  /**
+   * Whether this is on the Windows platform or not.
+   * @type {boolean}
+   */
+  const isWindows = /Win/.test(navigator.platform);
+
+  /**
+   * Whether this is on chromeOS or not.
+   * @type {boolean}
+   */
+  const isChromeOS = /CrOS/.test(navigator.userAgent);
+
+  /**
+   * Whether this is on vanilla Linux (not chromeOS).
+   * @type {boolean}
+   */
+  const isLinux = /Linux/.test(navigator.userAgent);
+
+  /**
+   * Whether this uses GTK or not.
+   * @type {boolean}
+   */
+  const isGTK = /GTK/.test(chrome.toolkit);
+
+  /**
+   * Whether this uses the views toolkit or not.
+   * @type {boolean}
+   */
+  const isViews = /views/.test(chrome.toolkit);
+
+  /**
+   * Sets the os and toolkit attributes in the <html> element so that platform
+   * specific css rules can be applied.
+   */
+  function enablePlatformSpecificCSSRules() {
+    if (isMac)
+      doc.documentElement.setAttribute('os', 'mac');
+    if (isWindows)
+      doc.documentElement.setAttribute('os', 'windows');
+    if (isChromeOS)
+      doc.documentElement.setAttribute('os', 'chromeos');
+    if (isLinux)
+      doc.documentElement.setAttribute('os', 'linux');
+    if (isGTK)
+      doc.documentElement.setAttribute('toolkit', 'gtk');
+    if (isViews)
+      doc.documentElement.setAttribute('toolkit', 'views');
+  }
+
+  /**
+   * Builds an object structure for the provided namespace path,
+   * ensuring that names that already exist are not overwritten. For
+   * example:
+   * "a.b.c" -> a = {};a.b={};a.b.c={};
+   * @param {string} name Name of the object that this file defines.
+   * @param {*=} opt_object The object to expose at the end of the path.
+   * @param {Object=} opt_objectToExportTo The object to add the path to;
+   *     default is {@code window}.
+   * @private
+   */
+  function exportPath(name, opt_object, opt_objectToExportTo) {
+    var parts = name.split('.');
+    var cur = opt_objectToExportTo || window /* global */;
+
+    for (var part; parts.length && (part = parts.shift());) {
+      if (!parts.length && opt_object !== undefined) {
+        // last part and we have an object; use it
+        cur[part] = opt_object;
+      } else if (part in cur) {
+        cur = cur[part];
+      } else {
+        cur = cur[part] = {};
+      }
+    }
+    return cur;
+  };
+
+  // cr.Event is called CrEvent in here to prevent naming conflicts. We also
+  // store the original Event in case someone does a global alias of cr.Event.
+  const DomEvent = Event;
+
+  /**
+   * Creates a new event to be used with cr.EventTarget or DOM EventTarget
+   * objects.
+   * @param {string} type The name of the event.
+   * @param {boolean=} opt_bubbles Whether the event bubbles. Default is false.
+   * @param {boolean=} opt_preventable Whether the default action of the event
+   *     can be prevented.
+   * @constructor
+   * @extends {DomEvent}
+   */
+  function CrEvent(type, opt_bubbles, opt_preventable) {
+    var e = cr.doc.createEvent('Event');
+    e.initEvent(type, !!opt_bubbles, !!opt_preventable);
+    e.__proto__ = CrEvent.prototype;
+    return e;
+  }
+
+  CrEvent.prototype = {
+    __proto__: DomEvent.prototype
+  };
+
+  /**
+   * Fires a property change event on the target.
+   * @param {EventTarget} target The target to dispatch the event on.
+   * @param {string} propertyName The name of the property that changed.
+   * @param {*} newValue The new value for the property.
+   * @param {*} oldValue The old value for the property.
+   */
+  function dispatchPropertyChange(target, propertyName, newValue, oldValue) {
+    var e = new CrEvent(propertyName + 'Change');
+    e.propertyName = propertyName;
+    e.newValue = newValue;
+    e.oldValue = oldValue;
+    target.dispatchEvent(e);
+  }
+
+  /**
+   * The kind of property to define in {@code defineProperty}.
+   * @enum {number}
+   */
+  const PropertyKind = {
+    /**
+     * Plain old JS property where the backing data is stored as a "private"
+     * field on the object.
+     */
+    JS: 'js',
+
+    /**
+     * The property backing data is stored as an attribute on an element.
+     */
+    ATTR: 'attr',
+
+    /**
+     * The property backing data is stored as an attribute on an element. If the
+     * element has the attribute then the value is true.
+     */
+    BOOL_ATTR: 'boolAttr'
+  };
+
+  /**
+   * Helper function for defineProperty that returns the getter to use for the
+   * property.
+   * @param {string} name
+   * @param {cr.PropertyKind} kind
+   * @return {function():*} The getter for the property.
+   */
+  function getGetter(name, kind) {
+    switch (kind) {
+      case PropertyKind.JS:
+        var privateName = name + '_';
+        return function() {
+          return this[privateName];
+        };
+      case PropertyKind.ATTR:
+        return function() {
+          return this.getAttribute(name);
+        };
+      case PropertyKind.BOOL_ATTR:
+        return function() {
+          return this.hasAttribute(name);
+        };
+    }
+  }
+
+  /**
+   * Helper function for defineProperty that returns the setter of the right
+   * kind.
+   * @param {string} name The name of the property we are defining the setter
+   *     for.
+   * @param {cr.PropertyKind} kind The kind of property we are getting the
+   *     setter for.
+   * @param {function(*):void} opt_setHook A function to run after the property
+   *     is set, but before the propertyChange event is fired.
+   * @return {function(*):void} The function to use as a setter.
+   */
+  function getSetter(name, kind, opt_setHook) {
+    switch (kind) {
+      case PropertyKind.JS:
+        var privateName = name + '_';
+        return function(value) {
+          var oldValue = this[privateName];
+          if (value !== oldValue) {
+            this[privateName] = value;
+            if (opt_setHook)
+              opt_setHook.call(this, value, oldValue);
+            dispatchPropertyChange(this, name, value, oldValue);
+          }
+        };
+
+      case PropertyKind.ATTR:
+        return function(value) {
+          var oldValue = this[name];
+          if (value !== oldValue) {
+            if (value == undefined)
+              this.removeAttribute(name);
+            else
+              this.setAttribute(name, value);
+            if (opt_setHook)
+              opt_setHook.call(this, value, oldValue);
+            dispatchPropertyChange(this, name, value, oldValue);
+          }
+        };
+
+      case PropertyKind.BOOL_ATTR:
+        return function(value) {
+          var oldValue = this[name];
+          if (value !== oldValue) {
+            if (value)
+              this.setAttribute(name, name);
+            else
+              this.removeAttribute(name);
+            if (opt_setHook)
+              opt_setHook.call(this, value, oldValue);
+            dispatchPropertyChange(this, name, value, oldValue);
+          }
+        };
+    }
+  }
+
+  /**
+   * Defines a property on an object. When the setter changes the value a
+   * property change event with the type {@code name + 'Change'} is fired.
+   * @param {!Object} obj The object to define the property for.
+   * @param {string} name The name of the property.
+   * @param {cr.PropertyKind=} opt_kind What kind of underlying storage to use.
+   * @param {function(*):void} opt_setHook A function to run after the
+   *     property is set, but before the propertyChange event is fired.
+   */
+  function defineProperty(obj, name, opt_kind, opt_setHook) {
+    if (typeof obj == 'function')
+      obj = obj.prototype;
+
+    var kind = opt_kind || PropertyKind.JS;
+
+    if (!obj.__lookupGetter__(name)) {
+      obj.__defineGetter__(name, getGetter(name, kind));
+    }
+
+    if (!obj.__lookupSetter__(name)) {
+      obj.__defineSetter__(name, getSetter(name, kind, opt_setHook));
+    }
+  }
+
+  /**
+   * Counter for use with createUid
+   */
+  var uidCounter = 1;
+
+  /**
+   * @return {number} A new unique ID.
+   */
+  function createUid() {
+    return uidCounter++;
+  }
+
+  /**
+   * Returns a unique ID for the item. This mutates the item so it needs to be
+   * an object
+   * @param {!Object} item The item to get the unique ID for.
+   * @return {number} The unique ID for the item.
+   */
+  function getUid(item) {
+    if (item.hasOwnProperty('uid'))
+      return item.uid;
+    return item.uid = createUid();
+  }
+
+  /**
+   * Dispatches a simple event on an event target.
+   * @param {!EventTarget} target The event target to dispatch the event on.
+   * @param {string} type The type of the event.
+   * @param {boolean=} opt_bubbles Whether the event bubbles or not.
+   * @param {boolean=} opt_cancelable Whether the default action of the event
+   *     can be prevented.
+   * @return {boolean} If any of the listeners called {@code preventDefault}
+   *     during the dispatch this will return false.
+   */
+  function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) {
+    var e = new cr.Event(type, opt_bubbles, opt_cancelable);
+    return target.dispatchEvent(e);
+  }
+
+  /**
+   * @param {string} name
+   * @param {!Function} fun
+   */
+  function define(name, fun) {
+    var obj = exportPath(name);
+    var exports = fun();
+    for (var propertyName in exports) {
+      // Maybe we should check the prototype chain here? The current usage
+      // pattern is always using an object literal so we only care about own
+      // properties.
+      var propertyDescriptor = Object.getOwnPropertyDescriptor(exports,
+                                                               propertyName);
+      if (propertyDescriptor)
+        Object.defineProperty(obj, propertyName, propertyDescriptor);
+    }
+  }
+
+  /**
+   * Document used for various document related operations.
+   * @type {!Document}
+   */
+  var doc = document;
+
+
+  /**
+   * Allows you to run func in the context of a different document.
+   * @param {!Document} document The document to use.
+   * @param {function():*} func The function to call.
+   */
+  function withDoc(document, func) {
+    var oldDoc = doc;
+    doc = document;
+    try {
+      func();
+    } finally {
+      doc = oldDoc;
+    }
+  }
+
+  /**
+   * Adds a {@code getInstance} static method that always return the same
+   * instance object.
+   * @param {!Function} ctor The constructor for the class to add the static
+   *     method to.
+   */
+  function addSingletonGetter(ctor) {
+    ctor.getInstance = function() {
+      return ctor.instance_ || (ctor.instance_ = new ctor());
+    };
+  }
+
+  return {
+    addSingletonGetter: addSingletonGetter,
+    isChromeOS: isChromeOS,
+    isMac: isMac,
+    isWindows: isWindows,
+    isLinux: isLinux,
+    isViews: isViews,
+    enablePlatformSpecificCSSRules: enablePlatformSpecificCSSRules,
+    define: define,
+    defineProperty: defineProperty,
+    PropertyKind: PropertyKind,
+    createUid: createUid,
+    getUid: getUid,
+    dispatchSimpleEvent: dispatchSimpleEvent,
+    dispatchPropertyChange: dispatchPropertyChange,
+
+    /**
+     * The document that we are currently using.
+     * @type {!Document}
+     */
+    get doc() {
+      return doc;
+    },
+    withDoc: withDoc,
+    Event: CrEvent
+  };
+})();
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/event_target.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/event_target.js
new file mode 100644
index 0000000..5bcb41d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/event_target.js
@@ -0,0 +1,104 @@
+// Copyright (c) 2010 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 contains an implementation of the EventTarget interface
+ * as defined by DOM Level 2 Events.
+ */
+
+cr.define('cr', function() {
+
+  /**
+   * Creates a new EventTarget. This class implements the DOM level 2
+   * EventTarget interface and can be used wherever those are used.
+   * @constructor
+   */
+  function EventTarget() {
+  }
+
+  EventTarget.prototype = {
+
+    /**
+     * Adds an event listener to the target.
+     * @param {string} type The name of the event.
+     * @param {!Function|{handleEvent:Function}} handler The handler for the
+     *     event. This is called when the event is dispatched.
+     */
+    addEventListener: function(type, handler) {
+      if (!this.listeners_)
+        this.listeners_ = Object.create(null);
+      if (!(type in this.listeners_)) {
+        this.listeners_[type] = [handler];
+      } else {
+        var handlers = this.listeners_[type];
+        if (handlers.indexOf(handler) < 0)
+          handlers.push(handler);
+      }
+    },
+
+    /**
+     * Removes an event listener from the target.
+     * @param {string} type The name of the event.
+     * @param {!Function|{handleEvent:Function}} handler The handler for the
+     *     event.
+     */
+    removeEventListener: function(type, handler) {
+      if (!this.listeners_)
+        return;
+      if (type in this.listeners_) {
+        var handlers = this.listeners_[type];
+        var index = handlers.indexOf(handler);
+        if (index >= 0) {
+          // Clean up if this was the last listener.
+          if (handlers.length == 1)
+            delete this.listeners_[type];
+          else
+            handlers.splice(index, 1);
+        }
+      }
+    },
+
+    /**
+     * Dispatches an event and calls all the listeners that are listening to
+     * the type of the event.
+     * @param {!cr.event.Event} event The event to dispatch.
+     * @return {boolean} Whether the default action was prevented. If someone
+     *     calls preventDefault on the event object then this returns false.
+     */
+    dispatchEvent: function(event) {
+      if (!this.listeners_)
+        return true;
+
+      // Since we are using DOM Event objects we need to override some of the
+      // properties and methods so that we can emulate this correctly.
+      var self = this;
+      event.__defineGetter__('target', function() {
+        return self;
+      });
+      event.preventDefault = function() {
+        this.returnValue = false;
+      };
+
+      var type = event.type;
+      var prevented = 0;
+      if (type in this.listeners_) {
+        // Clone to prevent removal during dispatch
+        var handlers = this.listeners_[type].concat();
+        for (var i = 0, handler; handler = handlers[i]; i++) {
+          if (handler.handleEvent)
+            prevented |= handler.handleEvent.call(handler, event) === false;
+          else
+            prevented |= handler.call(this, event) === false;
+        }
+      }
+
+      return !prevented && event.returnValue;
+    }
+  };
+
+  // Export
+  return {
+    EventTarget: EventTarget
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui.js
new file mode 100644
index 0000000..ea286b2
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui.js
@@ -0,0 +1,161 @@
+// Copyright (c) 2010 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.
+
+cr.define('cr.ui', function() {
+
+  /**
+   * Decorates elements as an instance of a class.
+   * @param {string|!Element} source The way to find the element(s) to decorate.
+   *     If this is a string then {@code querySeletorAll} is used to find the
+   *     elements to decorate.
+   * @param {!Function} constr The constructor to decorate with. The constr
+   *     needs to have a {@code decorate} function.
+   */
+  function decorate(source, constr) {
+    var elements;
+    if (typeof source == 'string')
+      elements = cr.doc.querySelectorAll(source);
+    else
+      elements = [source];
+
+    for (var i = 0, el; el = elements[i]; i++) {
+      if (!(el instanceof constr))
+        constr.decorate(el);
+    }
+  }
+
+  /**
+   * Helper function for creating new element for define.
+   */
+  function createElementHelper(tagName, opt_bag) {
+    // Allow passing in ownerDocument to create in a different document.
+    var doc;
+    if (opt_bag && opt_bag.ownerDocument)
+      doc = opt_bag.ownerDocument;
+    else
+      doc = cr.doc;
+    return doc.createElement(tagName);
+  }
+
+  /**
+   * Creates the constructor for a UI element class.
+   *
+   * Usage:
+   * <pre>
+   * var List = cr.ui.define('list');
+   * List.prototype = {
+   *   __proto__: HTMLUListElement.prototype,
+   *   decorate: function() {
+   *     ...
+   *   },
+   *   ...
+   * };
+   * </pre>
+   *
+   * @param {string|Function} tagNameOrFunction The tagName or
+   *     function to use for newly created elements. If this is a function it
+   *     needs to return a new element when called.
+   * @return {function(Object=):Element} The constructor function which takes
+   *     an optional property bag. The function also has a static
+   *     {@code decorate} method added to it.
+   */
+  function define(tagNameOrFunction) {
+    var createFunction, tagName;
+    if (typeof tagNameOrFunction == 'function') {
+      createFunction = tagNameOrFunction;
+      tagName = '';
+    } else {
+      createFunction = createElementHelper;
+      tagName = tagNameOrFunction;
+    }
+
+    /**
+     * Creates a new UI element constructor.
+     * @param {Object=} opt_propertyBag Optional bag of properties to set on the
+     *     object after created. The property {@code ownerDocument} is special
+     *     cased and it allows you to create the element in a different
+     *     document than the default.
+     * @constructor
+     */
+    function f(opt_propertyBag) {
+      var el = createFunction(tagName, opt_propertyBag);
+      f.decorate(el);
+      for (var propertyName in opt_propertyBag) {
+        el[propertyName] = opt_propertyBag[propertyName];
+      }
+      return el;
+    }
+
+    /**
+     * Decorates an element as a UI element class.
+     * @param {!Element} el The element to decorate.
+     */
+    f.decorate = function(el) {
+      el.__proto__ = f.prototype;
+      el.decorate();
+    };
+
+    return f;
+  }
+
+  /**
+   * Input elements do not grow and shrink with their content. This is a simple
+   * (and not very efficient) way of handling shrinking to content with support
+   * for min width and limited by the width of the parent element.
+   * @param {HTMLElement} el The element to limit the width for.
+   * @param {number} parentEl The parent element that should limit the size.
+   * @param {number} min The minimum width.
+   */
+  function limitInputWidth(el, parentEl, min) {
+    // Needs a size larger than borders
+    el.style.width = '10px';
+    var doc = el.ownerDocument;
+    var win = doc.defaultView;
+    var computedStyle = win.getComputedStyle(el);
+    var parentComputedStyle = win.getComputedStyle(parentEl);
+    var rtl = computedStyle.direction == 'rtl';
+
+    // To get the max width we get the width of the treeItem minus the position
+    // of the input.
+    var inputRect = el.getBoundingClientRect();  // box-sizing
+    var parentRect = parentEl.getBoundingClientRect();
+    var startPos = rtl ? parentRect.right - inputRect.right :
+        inputRect.left - parentRect.left;
+
+    // Add up border and padding of the input.
+    var inner = parseInt(computedStyle.borderLeftWidth, 10) +
+        parseInt(computedStyle.paddingLeft, 10) +
+        parseInt(computedStyle.paddingRight, 10) +
+        parseInt(computedStyle.borderRightWidth, 10);
+
+    // We also need to subtract the padding of parent to prevent it to overflow.
+    var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) :
+        parseInt(parentComputedStyle.paddingRight, 10);
+
+    var max = parentEl.clientWidth - startPos - inner - parentPadding;
+
+    function limit() {
+      if (el.scrollWidth > max) {
+        el.style.width = max + 'px';
+      } else {
+        el.style.width = 0;
+        var sw = el.scrollWidth;
+        if (sw < min) {
+          el.style.width = min + 'px';
+        } else {
+          el.style.width = sw + 'px';
+        }
+      }
+    }
+
+    el.addEventListener('input', limit);
+    limit();
+  }
+
+  return {
+    decorate: decorate,
+    define: define,
+    limitInputWidth: limitInputWidth
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/array_data_model.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/array_data_model.js
new file mode 100644
index 0000000..f12bd1d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/array_data_model.js
@@ -0,0 +1,363 @@
+// 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 is a data model representin
+ */
+
+cr.define('cr.ui', function() {
+  const EventTarget = cr.EventTarget;
+  const Event = cr.Event;
+
+  /**
+   * A data model that wraps a simple array and supports sorting by storing
+   * initial indexes of elements for each position in sorted array.
+   * @param {!Array} array The underlying array.
+   * @constructor
+   * @extends {EventTarget}
+   */
+  function ArrayDataModel(array) {
+    this.array_ = array;
+    this.indexes_ = [];
+    this.compareFunctions_ = {};
+
+    for (var i = 0; i < array.length; i++) {
+      this.indexes_.push(i);
+    }
+  }
+
+  ArrayDataModel.prototype = {
+    __proto__: EventTarget.prototype,
+
+    /**
+     * The length of the data model.
+     * @type {number}
+     */
+    get length() {
+      return this.array_.length;
+    },
+
+    /**
+     * Returns the item at the given index.
+     * This implementation returns the item at the given index in the sorted
+     * array.
+     * @param {number} index The index of the element to get.
+     * @return {*} The element at the given index.
+     */
+    item: function(index) {
+      if (index >= 0 && index < this.length)
+        return this.array_[this.indexes_[index]];
+      return undefined;
+    },
+
+    /**
+     * Returns compare function set for given field.
+     * @param {string} field The field to get compare function for.
+     * @return {function(*, *): number} Compare function set for given field.
+     */
+    compareFunction: function(field) {
+      return this.compareFunctions_[field];
+    },
+
+    /**
+     * Sets compare function for given field.
+     * @param {string} field The field to set compare function.
+     * @param {function(*, *): number} Compare function to set for given field.
+     */
+    setCompareFunction: function(field, compareFunction) {
+      if (!this.compareFunctions_) {
+        this.compareFunctions_ = {};
+      }
+      this.compareFunctions_[field] = compareFunction;
+    },
+
+    /**
+     * Returns current sort status.
+     * @return {!Object} Current sort status.
+     */
+    get sortStatus() {
+      if (this.sortStatus_) {
+        return this.createSortStatus(
+            this.sortStatus_.field, this.sortStatus_.direction);
+      } else {
+        return this.createSortStatus(null, null);
+      }
+    },
+
+    /**
+     * Returns the first matching item.
+     * @param {*} item The item to find.
+     * @param {number=} opt_fromIndex If provided, then the searching start at
+     *     the {@code opt_fromIndex}.
+     * @return {number} The index of the first found element or -1 if not found.
+     */
+    indexOf: function(item, opt_fromIndex) {
+      return this.array_.indexOf(item, opt_fromIndex);
+    },
+
+    /**
+     * Returns an array of elements in a selected range.
+     * @param {number=} opt_from The starting index of the selected range.
+     * @param {number=} opt_to The ending index of selected range.
+     * @return {Array} An array of elements in the selected range.
+     */
+    slice: function(opt_from, opt_to) {
+      return this.array_.slice.apply(this.array_, arguments);
+    },
+
+    /**
+     * This removes and adds items to the model.
+     * This dispatches a splice event.
+     * This implementation runs sort after splice and creates permutation for
+     * the whole change.
+     * @param {number} index The index of the item to update.
+     * @param {number} deleteCount The number of items to remove.
+     * @param {...*} The items to add.
+     * @return {!Array} An array with the removed items.
+     */
+    splice: function(index, deleteCount, var_args) {
+      var addCount = arguments.length - 2;
+      var newIndexes = [];
+      var deletePermutation = [];
+      var deleted = 0;
+      for (var i = 0; i < this.indexes_.length; i++) {
+        var oldIndex = this.indexes_[i];
+        if (oldIndex < index) {
+          newIndexes.push(oldIndex);
+          deletePermutation.push(i - deleted);
+        } else if (oldIndex >= index + deleteCount) {
+          newIndexes.push(oldIndex - deleteCount + addCount);
+          deletePermutation.push(i - deleted);
+        } else {
+          deletePermutation.push(-1);
+          deleted++;
+        }
+      }
+      for (var i = 0; i < addCount; i++) {
+        newIndexes.push(index + i);
+      }
+      this.indexes_ = newIndexes;
+
+      var arr = this.array_;
+
+      // TODO(arv): Maybe unify splice and change events?
+      var spliceEvent = new Event('splice');
+      spliceEvent.index = index;
+      spliceEvent.removed = arr.slice(index, index + deleteCount);
+      spliceEvent.added = Array.prototype.slice.call(arguments, 2);
+
+      var rv = arr.splice.apply(arr, arguments);
+
+      var status = this.sortStatus;
+      // if sortStatus.field is null, this restores original order.
+      var sortPermutation = this.doSort_(this.sortStatus.field,
+                                         this.sortStatus.direction);
+      if (sortPermutation) {
+        var splicePermutation = deletePermutation.map(function(element) {
+          return element != -1 ? sortPermutation[element] : -1;
+        });
+        this.dispatchPermutedEvent_(splicePermutation);
+      } else {
+        this.dispatchPermutedEvent_(deletePermutation);
+      }
+
+      this.dispatchEvent(spliceEvent);
+
+      // If real sorting is needed, we should first call prepareSort (data may
+      // change), and then sort again.
+      // Still need to finish the sorting above (including events), so
+      // list will not go to inconsistent state.
+      if (status.field) {
+        setTimeout(this.sort.bind(this, status.field, status.direction), 0);
+      }
+      return rv;
+    },
+
+    /**
+     * Appends items to the end of the model.
+     *
+     * This dispatches a splice event.
+     *
+     * @param {...*} The items to append.
+     * @return {number} The new length of the model.
+     */
+    push: function(var_args) {
+      var args = Array.prototype.slice.call(arguments);
+      args.unshift(this.length, 0);
+      this.splice.apply(this, args);
+      return this.length;
+    },
+
+    /**
+     * Use this to update a given item in the array. This does not remove and
+     * reinsert a new item.
+     * This dispatches a change event.
+     * This runs sort after updating.
+     * @param {number} index The index of the item to update.
+     */
+    updateIndex: function(index) {
+      if (index < 0 || index >= this.length)
+        throw Error('Invalid index, ' + index);
+
+      // TODO(arv): Maybe unify splice and change events?
+      var e = new Event('change');
+      e.index = index;
+      this.dispatchEvent(e);
+
+      if (this.sortStatus.field) {
+        var status = this.sortStatus;
+        var sortPermutation = this.doSort_(this.sortStatus.field,
+                                           this.sortStatus.direction);
+        if (sortPermutation)
+          this.dispatchPermutedEvent_(sortPermutation);
+        // We should first call prepareSort (data may change), and then sort.
+        // Still need to finish the sorting above (including events), so
+        // list will not go to inconsistent state.
+        setTimeout(this.sort.bind(this, status.field, status.direction), 0);
+      }
+    },
+
+    /**
+     * Creates sort status with given field and direction.
+     * @param {string} field Sort field.
+     * @param {string} direction Sort direction.
+     * @return {!Object} Created sort status.
+     */
+    createSortStatus: function(field, direction) {
+      return {
+        field: field,
+        direction: direction
+      };
+    },
+
+    /**
+     * Called before a sort happens so that you may fetch additional data
+     * required for the sort.
+     *
+     * @param {string} field Sort field.
+     * @param {function()} callback The function to invoke when preparation
+     *     is complete.
+     */
+    prepareSort: function(field, callback) {
+      callback();
+    },
+
+    /**
+     * Sorts data model according to given field and direction and dispathes
+     * sorted event.
+     * @param {string} field Sort field.
+     * @param {string} direction Sort direction.
+     */
+    sort: function(field, direction) {
+      var self = this;
+
+      this.prepareSort(field, function() {
+        var sortPermutation = self.doSort_(field, direction);
+        if (sortPermutation)
+          self.dispatchPermutedEvent_(sortPermutation);
+        self.dispatchSortEvent_();
+      });
+    },
+
+    /**
+     * Sorts data model according to given field and direction.
+     * @param {string} field Sort field.
+     * @param {string} direction Sort direction.
+     */
+    doSort_: function(field, direction) {
+      var compareFunction = this.sortFunction_(field, direction);
+      var positions = [];
+      for (var i = 0; i < this.length; i++) {
+        positions[this.indexes_[i]] = i;
+      }
+      this.indexes_.sort(compareFunction);
+      this.sortStatus_ = this.createSortStatus(field, direction);
+      var sortPermutation = [];
+      var changed = false;
+      for (var i = 0; i < this.length; i++) {
+        if (positions[this.indexes_[i]] != i)
+          changed = true;
+        sortPermutation[positions[this.indexes_[i]]] = i;
+      }
+      if (changed)
+        return sortPermutation;
+      return null;
+    },
+
+    dispatchSortEvent_: function() {
+      var e = new Event('sorted');
+      this.dispatchEvent(e);
+    },
+
+    dispatchPermutedEvent_: function(permutation) {
+      var e = new Event('permuted');
+      e.permutation = permutation;
+      e.newLength = this.length;
+      this.dispatchEvent(e);
+    },
+
+    /**
+     * Creates compare function for the field.
+     * Returns the function set as sortFunction for given field
+     * or default compare function
+     * @param {string} field Sort field.
+     * @param {function(*, *): number} Compare function.
+     */
+    createCompareFunction_: function(field) {
+      var compareFunction =
+          this.compareFunctions_ ? this.compareFunctions_[field] : null;
+      var defaultValuesCompareFunction = this.defaultValuesCompareFunction;
+      if (compareFunction) {
+        return compareFunction;
+      } else {
+        return function(a, b) {
+          return defaultValuesCompareFunction.call(null, a[field], b[field]);
+        }
+      }
+      return compareFunction;
+    },
+
+    /**
+     * Creates compare function for given field and direction.
+     * @param {string} field Sort field.
+     * @param {string} direction Sort direction.
+     * @param {function(*, *): number} Compare function.
+     */
+    sortFunction_: function(field, direction) {
+      var compareFunction = null;
+      if (field !== null)
+        compareFunction = this.createCompareFunction_(field);
+      var dirMultiplier = direction == 'desc' ? -1 : 1;
+
+      return function(index1, index2) {
+        var item1 = this.array_[index1];
+        var item2 = this.array_[index2];
+
+        var compareResult = 0;
+        if (typeof(compareFunction) === 'function')
+          compareResult = compareFunction.call(null, item1, item2);
+        if (compareResult != 0)
+          return dirMultiplier * compareResult;
+        return dirMultiplier * this.defaultValuesCompareFunction(index1,
+                                                                 index2);
+      }.bind(this);
+    },
+
+    /**
+     * Default compare function.
+     */
+    defaultValuesCompareFunction: function(a, b) {
+      // We could insert i18n comparisons here.
+      if (a < b)
+        return -1;
+      if (a > b)
+        return 1;
+      return 0;
+    }
+  };
+
+  return {
+    ArrayDataModel: ArrayDataModel
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list.js
new file mode 100644
index 0000000..291d5ff
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list.js
@@ -0,0 +1,971 @@
+// 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.
+
+// require: array_data_model.js
+// require: list_selection_model.js
+// require: list_selection_controller.js
+// require: list_item.js
+
+/**
+ * @fileoverview This implements a list control.
+ */
+
+cr.define('cr.ui', function() {
+  const ListSelectionModel = cr.ui.ListSelectionModel;
+  const ListSelectionController = cr.ui.ListSelectionController;
+  const ArrayDataModel = cr.ui.ArrayDataModel;
+
+  /**
+   * Whether a mouse event is inside the element viewport. This will return
+   * false if the mouseevent was generated over a border or a scrollbar.
+   * @param {!HTMLElement} el The element to test the event with.
+   * @param {!Event} e The mouse event.
+   * @param {boolean} Whether the mouse event was inside the viewport.
+   */
+  function inViewport(el, e) {
+    var rect = el.getBoundingClientRect();
+    var x = e.clientX;
+    var y = e.clientY;
+    return x >= rect.left + el.clientLeft &&
+           x < rect.left + el.clientLeft + el.clientWidth &&
+           y >= rect.top + el.clientTop &&
+           y < rect.top + el.clientTop + el.clientHeight;
+  }
+
+  /**
+   * Creates an item (dataModel.item(0)) and measures its height.
+   * @param {!List} list The list to create the item for.
+   * @param {ListItem=} opt_item The list item to use to do the measuring. If
+   *     this is not provided an item will be created based on the first value
+   *     in the model.
+   * @return {{height: number, marginVertical: number, width: number,
+   *     marginHorizontal: number}} The height and width of the item, taking
+   *     margins into account, and the height and width of the margins
+   *     themselves.
+   */
+  function measureItem(list, opt_item) {
+    var dataModel = list.dataModel;
+    if (!dataModel || !dataModel.length)
+      return 0;
+    var item = opt_item || list.createItem(dataModel.item(0));
+    if (!opt_item)
+      list.appendChild(item);
+
+    var rect = item.getBoundingClientRect();
+    var cs = getComputedStyle(item);
+    var mt = parseFloat(cs.marginTop);
+    var mb = parseFloat(cs.marginBottom);
+    var ml = parseFloat(cs.marginLeft);
+    var mr = parseFloat(cs.marginRight);
+    var h = rect.height;
+    var w = rect.width;
+    var mh = 0;
+    var mv = 0;
+
+    // Handle margin collapsing.
+    if (mt < 0 && mb < 0) {
+      mv = Math.min(mt, mb);
+    } else if (mt >= 0 && mb >= 0) {
+      mv = Math.max(mt, mb);
+    } else {
+      mv = mt + mb;
+    }
+    h += mv;
+
+    if (ml < 0 && mr < 0) {
+      mh = Math.min(ml, mr);
+    } else if (ml >= 0 && mr >= 0) {
+      mh = Math.max(ml, mr);
+    } else {
+      mh = ml + mr;
+    }
+    w += mh;
+
+    if (!opt_item)
+      list.removeChild(item);
+    return {
+        height: Math.max(0, h), marginVertical: mv,
+        width: Math.max(0, w), marginHorizontal: mh};
+  }
+
+  function getComputedStyle(el) {
+    return el.ownerDocument.defaultView.getComputedStyle(el);
+  }
+
+  /**
+   * Creates a new list element.
+   * @param {Object=} opt_propertyBag Optional properties.
+   * @constructor
+   * @extends {HTMLUListElement}
+   */
+  var List = cr.ui.define('list');
+
+  List.prototype = {
+    __proto__: HTMLUListElement.prototype,
+
+    /**
+     * Measured size of list items. This is lazily calculated the first time it
+     * is needed. Note that lead item is allowed to have a different height, to
+     * accommodate lists where a single item at a time can be expanded to show
+     * more detail.
+     * @type {{height: number, marginVertical: number, width: number,
+     *     marginHorizontal: number}}
+     * @private
+     */
+    measured_: undefined,
+
+    /**
+     * The height of the lead item, which is allowed to have a different height
+     * than other list items to accommodate lists where a single item at a time
+     * can be expanded to show more detail. It is explicitly set by client code
+     * when the height of the lead item is changed with {@code set
+     * leadItemHeight}, and presumed equal to {@code itemHeight_} otherwise.
+     * @type {number}
+     * @private
+     */
+    leadItemHeight_: 0,
+
+    /**
+     * Whether or not the list is autoexpanding. If true, the list resizes
+     * its height to accomadate all children.
+     * @type {boolean}
+     * @private
+     */
+    autoExpands_: false,
+
+    /**
+     * Function used to create grid items.
+     * @type {function(): !ListItem}
+     * @private
+     */
+    itemConstructor_: cr.ui.ListItem,
+
+    /**
+     * Function used to create grid items.
+     * @type {function(): !ListItem}
+     */
+    get itemConstructor() {
+      return this.itemConstructor_;
+    },
+    set itemConstructor(func) {
+      if (func != this.itemConstructor_) {
+        this.itemConstructor_ = func;
+        this.cachedItems_ = {};
+        this.redraw();
+      }
+    },
+
+    dataModel_: null,
+
+    /**
+     * The data model driving the list.
+     * @type {ArrayDataModel}
+     */
+    set dataModel(dataModel) {
+      if (this.dataModel_ != dataModel) {
+        if (!this.boundHandleDataModelPermuted_) {
+          this.boundHandleDataModelPermuted_ =
+              this.handleDataModelPermuted_.bind(this);
+          this.boundHandleDataModelChange_ =
+              this.handleDataModelChange_.bind(this);
+        }
+
+        if (this.dataModel_) {
+          this.dataModel_.removeEventListener(
+              'permuted',
+              this.boundHandleDataModelPermuted_);
+          this.dataModel_.removeEventListener('change',
+                                              this.boundHandleDataModelChange_);
+        }
+
+        this.dataModel_ = dataModel;
+
+        this.cachedItems_ = {};
+        this.selectionModel.clear();
+        if (dataModel)
+          this.selectionModel.adjustLength(dataModel.length);
+
+        if (this.dataModel_) {
+          this.dataModel_.addEventListener(
+              'permuted',
+              this.boundHandleDataModelPermuted_);
+          this.dataModel_.addEventListener('change',
+                                           this.boundHandleDataModelChange_);
+        }
+
+        this.redraw();
+      }
+    },
+
+    get dataModel() {
+      return this.dataModel_;
+    },
+
+    /**
+     * The selection model to use.
+     * @type {cr.ui.ListSelectionModel}
+     */
+    get selectionModel() {
+      return this.selectionModel_;
+    },
+    set selectionModel(sm) {
+      var oldSm = this.selectionModel_;
+      if (oldSm == sm)
+        return;
+
+      if (!this.boundHandleOnChange_) {
+        this.boundHandleOnChange_ = this.handleOnChange_.bind(this);
+        this.boundHandleLeadChange_ = this.handleLeadChange_.bind(this);
+      }
+
+      if (oldSm) {
+        oldSm.removeEventListener('change', this.boundHandleOnChange_);
+        oldSm.removeEventListener('leadIndexChange',
+                                  this.boundHandleLeadChange_);
+      }
+
+      this.selectionModel_ = sm;
+      this.selectionController_ = this.createSelectionController(sm);
+
+      if (sm) {
+        sm.addEventListener('change', this.boundHandleOnChange_);
+        sm.addEventListener('leadIndexChange', this.boundHandleLeadChange_);
+      }
+    },
+
+    /**
+     * Whether or not the list auto-expands.
+     * @type {boolean}
+     */
+    get autoExpands() {
+      return this.autoExpands_;
+    },
+    set autoExpands(autoExpands) {
+      if (this.autoExpands_ == autoExpands)
+        return;
+      this.autoExpands_ = autoExpands;
+      this.redraw();
+    },
+
+    /**
+     * Convenience alias for selectionModel.selectedItem
+     * @type {cr.ui.ListItem}
+     */
+    get selectedItem() {
+      var dataModel = this.dataModel;
+      if (dataModel) {
+        var index = this.selectionModel.selectedIndex;
+        if (index != -1)
+          return dataModel.item(index);
+      }
+      return null;
+    },
+    set selectedItem(selectedItem) {
+      var dataModel = this.dataModel;
+      if (dataModel) {
+        var index = this.dataModel.indexOf(selectedItem);
+        this.selectionModel.selectedIndex = index;
+      }
+    },
+
+    /**
+     * The height of the lead item.
+     * If set to 0, resets to the same height as other items.
+     * @type {number}
+     */
+    get leadItemHeight() {
+      return this.leadItemHeight_ || this.getItemHeight_();
+    },
+    set leadItemHeight(height) {
+      if (height) {
+        var size = this.getItemSize_();
+        this.leadItemHeight_ = Math.max(0, height + size.marginVertical);
+      } else {
+        this.leadItemHeight_ = 0;
+      }
+    },
+
+    /**
+     * Convenience alias for selectionModel.selectedItems
+     * @type {!Array<cr.ui.ListItem>}
+     */
+    get selectedItems() {
+      var indexes = this.selectionModel.selectedIndexes;
+      var dataModel = this.dataModel;
+      if (dataModel) {
+        return indexes.map(function(i) {
+          return dataModel.item(i);
+        });
+      }
+      return [];
+    },
+
+    /**
+     * The HTML elements representing the items. This is just all the list item
+     * children but subclasses may override this to filter out certain elements.
+     * @type {HTMLCollection}
+     */
+    get items() {
+      return Array.prototype.filter.call(this.children, function(child) {
+        return !child.classList.contains('spacer');
+      });
+    },
+
+    batchCount_: 0,
+
+    /**
+     * When making a lot of updates to the list, the code could be wrapped in
+     * the startBatchUpdates and finishBatchUpdates to increase performance. Be
+     * sure that the code will not return without calling endBatchUpdates or the
+     * list will not be correctly updated.
+     */
+    startBatchUpdates: function() {
+      this.batchCount_++;
+    },
+
+    /**
+     * See startBatchUpdates.
+     */
+    endBatchUpdates: function() {
+      this.batchCount_--;
+      if (this.batchCount_ == 0)
+        this.redraw();
+    },
+
+    /**
+     * Initializes the element.
+     */
+    decorate: function() {
+      // Add fillers.
+      this.beforeFiller_ = this.ownerDocument.createElement('div');
+      this.afterFiller_ = this.ownerDocument.createElement('div');
+      this.beforeFiller_.className = 'spacer';
+      this.afterFiller_.className = 'spacer';
+      this.appendChild(this.beforeFiller_);
+      this.appendChild(this.afterFiller_);
+
+      var length = this.dataModel ? this.dataModel.length : 0;
+      this.selectionModel = new ListSelectionModel(length);
+
+      this.addEventListener('dblclick', this.handleDoubleClick_);
+      this.addEventListener('mousedown', this.handleMouseDownUp_);
+      this.addEventListener('mouseup', this.handleMouseDownUp_);
+      this.addEventListener('keydown', this.handleKeyDown);
+      this.addEventListener('focus', this.handleElementFocus_, true);
+      this.addEventListener('blur', this.handleElementBlur_, true);
+      this.addEventListener('scroll', this.redraw.bind(this));
+      this.setAttribute('role', 'listbox');
+
+      // Make list focusable
+      if (!this.hasAttribute('tabindex'))
+        this.tabIndex = 0;
+    },
+
+    /**
+     * @return {number} The height of an item, measuring it if necessary.
+     * @private
+     */
+    getItemHeight_: function() {
+      return this.getItemSize_().height;
+    },
+
+    /**
+     * @return {number} The width of an item, measuring it if necessary.
+     * @private
+     */
+    getItemWidth_: function() {
+      return this.getItemSize_().width;
+    },
+
+    /**
+     * @return {{height: number, width: number}} The height and width
+     *     of an item, measuring it if necessary.
+     * @private
+     */
+    getItemSize_: function() {
+      if (!this.measured_ || !this.measured_.height) {
+        this.measured_ = measureItem(this);
+      }
+      return this.measured_;
+    },
+
+    /**
+     * Callback for the double click event.
+     * @param {Event} e The mouse event object.
+     * @private
+     */
+    handleDoubleClick_: function(e) {
+      if (this.disabled)
+        return;
+
+      var target = this.getListItemAncestor(e.target);
+      if (target)
+        this.activateItemAtIndex(this.getIndexOfListItem(target));
+    },
+
+    /**
+     * Callback for mousedown and mouseup events.
+     * @param {Event} e The mouse event object.
+     * @private
+     */
+    handleMouseDownUp_: function(e) {
+      if (this.disabled)
+        return;
+
+      var target = e.target;
+
+      // If the target was this element we need to make sure that the user did
+      // not click on a border or a scrollbar.
+      if (target == this && !inViewport(target, e))
+        return;
+
+      target = this.getListItemAncestor(target);
+
+      var index = target ? this.getIndexOfListItem(target) : -1;
+      this.selectionController_.handleMouseDownUp(e, index);
+    },
+
+    /**
+     * Called when an element in the list is focused. Marks the list as having
+     * a focused element, and dispatches an event if it didn't have focus.
+     * @param {Event} e The focus event.
+     * @private
+     */
+    handleElementFocus_: function(e) {
+      if (!this.hasElementFocus) {
+        this.hasElementFocus = true;
+        // Force styles based on hasElementFocus to take effect.
+        this.forceRepaint_();
+      }
+    },
+
+    /**
+     * Called when an element in the list is blurred. If focus moves outside
+     * the list, marks the list as no longer having focus and dispatches an
+     * event.
+     * @param {Event} e The blur event.
+     * @private
+     */
+    handleElementBlur_: function(e) {
+      // When the blur event happens we do not know who is getting focus so we
+      // delay this a bit until we know if the new focus node is outside the
+      // list.
+      var list = this;
+      var doc = e.target.ownerDocument;
+      window.setTimeout(function() {
+        var activeElement = doc.activeElement;
+        if (!list.contains(activeElement)) {
+          list.hasElementFocus = false;
+          // Force styles based on hasElementFocus to take effect.
+          list.forceRepaint_();
+        }
+      });
+    },
+
+    /**
+     * Forces a repaint of the list. Changing custom attributes, even if there
+     * are style rules depending on them, doesn't cause a repaint
+     * (<https://bugs.webkit.org/show_bug.cgi?id=12519>), so this can be called
+     * to force the list to repaint.
+     * @private
+     */
+    forceRepaint_: function(e) {
+      var dummyElement = document.createElement('div');
+      this.appendChild(dummyElement);
+      this.removeChild(dummyElement);
+    },
+
+    /**
+     * Returns the list item element containing the given element, or null if
+     * it doesn't belong to any list item element.
+     * @param {HTMLElement} element The element.
+     * @return {ListItem} The list item containing |element|, or null.
+     */
+    getListItemAncestor: function(element) {
+      var container = element;
+      while (container && container.parentNode != this) {
+        container = container.parentNode;
+      }
+      return container;
+    },
+
+    /**
+     * Handle a keydown event.
+     * @param {Event} e The keydown event.
+     * @return {boolean} Whether the key event was handled.
+     */
+    handleKeyDown: function(e) {
+      if (this.disabled)
+        return;
+
+      return this.selectionController_.handleKeyDown(e);
+    },
+
+    /**
+     * Callback from the selection model. We dispatch {@code change} events
+     * when the selection changes.
+     * @param {!cr.Event} e Event with change info.
+     * @private
+     */
+    handleOnChange_: function(ce) {
+      ce.changes.forEach(function(change) {
+        var listItem = this.getListItemByIndex(change.index);
+        if (listItem)
+          listItem.selected = change.selected;
+      }, this);
+
+      cr.dispatchSimpleEvent(this, 'change');
+    },
+
+    /**
+     * Handles a change of the lead item from the selection model.
+     * @property {Event} pe The property change event.
+     * @private
+     */
+    handleLeadChange_: function(pe) {
+      var element;
+      if (pe.oldValue != -1) {
+        if ((element = this.getListItemByIndex(pe.oldValue)))
+          element.lead = false;
+      }
+
+      if (pe.newValue != -1) {
+        if ((element = this.getListItemByIndex(pe.newValue)))
+          element.lead = true;
+        this.scrollIndexIntoView(pe.newValue);
+        // If the lead item has a different height than other items, then we
+        // may run into a problem that requires a second attempt to scroll
+        // it into view. The first scroll attempt will trigger a redraw,
+        // which will clear out the list and repopulate it with new items.
+        // During the redraw, the list may shrink temporarily, which if the
+        // lead item is the last item, will move the scrollTop up since it
+        // cannot extend beyond the end of the list. (Sadly, being scrolled to
+        // the bottom of the list is not "sticky.") So, we set a timeout to
+        // rescroll the list after this all gets sorted out. This is perhaps
+        // not the most elegant solution, but no others seem obvious.
+        var self = this;
+        window.setTimeout(function() {
+          self.scrollIndexIntoView(pe.newValue);
+        });
+      }
+    },
+
+    /**
+     * This handles data model 'permuted' event.
+     * this event is dispatched as a part of sort or splice.
+     * We need to
+     *  - adjust the cache.
+     *  - adjust selection.
+     *  - redraw.
+     *  - scroll the list to show selection.
+     *  It is important that the cache adjustment happens before selection model
+     *  adjustments.
+     * @param {Event} e The 'permuted' event.
+     */
+    handleDataModelPermuted_: function(e) {
+      var newCachedItems = {};
+      for (var index in this.cachedItems_) {
+        if (e.permutation[index] != -1)
+          newCachedItems[e.permutation[index]] = this.cachedItems_[index];
+        else
+          delete this.cachedItems_[index];
+      }
+      this.cachedItems_ = newCachedItems;
+
+      this.startBatchUpdates();
+
+      var sm = this.selectionModel;
+      sm.adjustLength(e.newLength);
+      sm.adjustToReordering(e.permutation);
+
+      this.endBatchUpdates();
+
+      if (sm.leadIndex != -1)
+        this.scrollIndexIntoView(sm.leadIndex);
+    },
+
+    handleDataModelChange_: function(e) {
+      if (e.index >= this.firstIndex_ && e.index < this.lastIndex_) {
+        if (this.cachedItems_[e.index])
+          delete this.cachedItems_[e.index];
+        this.redraw();
+      }
+    },
+
+    /**
+     * @param {number} index The index of the item.
+     * @return {number} The top position of the item inside the list, not taking
+     *     into account lead item. May vary in the case of multiple columns.
+     */
+    getItemTop: function(index) {
+      return index * this.getItemHeight_();
+    },
+
+    /**
+     * @param {number} index The index of the item.
+     * @return {number} The row of the item. May vary in the case
+     *     of multiple columns.
+     */
+    getItemRow: function(index) {
+      return index;
+    },
+
+    /**
+     * @param {number} row The row.
+     * @return {number} The index of the first item in the row.
+     */
+    getFirstItemInRow: function(row) {
+      return row;
+    },
+
+    /**
+     * Ensures that a given index is inside the viewport.
+     * @param {number} index The index of the item to scroll into view.
+     * @return {boolean} Whether any scrolling was needed.
+     */
+    scrollIndexIntoView: function(index) {
+      var dataModel = this.dataModel;
+      if (!dataModel || index < 0 || index >= dataModel.length)
+        return false;
+
+      var itemHeight = this.getItemHeight_();
+      var scrollTop = this.scrollTop;
+      var top = this.getItemTop(index);
+      var leadIndex = this.selectionModel.leadIndex;
+
+      // Adjust for the lead item if it is above the given index.
+      if (leadIndex > -1 && leadIndex < index)
+        top += this.leadItemHeight - itemHeight;
+      else if (leadIndex == index)
+        itemHeight = this.leadItemHeight;
+
+      if (top < scrollTop) {
+        this.scrollTop = top;
+        return true;
+      } else {
+        var clientHeight = this.clientHeight;
+        var cs = getComputedStyle(this);
+        var paddingY = parseInt(cs.paddingTop, 10) +
+                       parseInt(cs.paddingBottom, 10);
+
+        if (top + itemHeight > scrollTop + clientHeight - paddingY) {
+          this.scrollTop = top + itemHeight - clientHeight + paddingY;
+          return true;
+        }
+      }
+
+      return false;
+    },
+
+    /**
+     * @return {!ClientRect} The rect to use for the context menu.
+     */
+    getRectForContextMenu: function() {
+      // TODO(arv): Add trait support so we can share more code between trees
+      // and lists.
+      var index = this.selectionModel.selectedIndex;
+      var el = this.getListItemByIndex(index);
+      if (el)
+        return el.getBoundingClientRect();
+      return this.getBoundingClientRect();
+    },
+
+    /**
+     * Takes a value from the data model and finds the associated list item.
+     * @param {*} value The value in the data model that we want to get the list
+     *     item for.
+     * @return {ListItem} The first found list item or null if not found.
+     */
+    getListItem: function(value) {
+      var dataModel = this.dataModel;
+      if (dataModel) {
+        var index = dataModel.indexOf(value);
+        return this.getListItemByIndex(index);
+      }
+      return null;
+    },
+
+    /**
+     * Find the list item element at the given index.
+     * @param {number} index The index of the list item to get.
+     * @return {ListItem} The found list item or null if not found.
+     */
+    getListItemByIndex: function(index) {
+      return this.cachedItems_[index] || null;
+    },
+
+    /**
+     * Find the index of the given list item element.
+     * @param {ListItem} item The list item to get the index of.
+     * @return {number} The index of the list item, or -1 if not found.
+     */
+    getIndexOfListItem: function(item) {
+      var index = item.listIndex;
+      if (this.cachedItems_[index] == item) {
+        return index;
+      }
+      return -1;
+    },
+
+    /**
+     * Creates a new list item.
+     * @param {*} value The value to use for the item.
+     * @return {!ListItem} The newly created list item.
+     */
+    createItem: function(value) {
+      var item = new this.itemConstructor_(value);
+      item.label = value;
+      if (typeof item.decorate == 'function')
+        item.decorate();
+      return item;
+    },
+
+    /**
+     * Creates the selection controller to use internally.
+     * @param {cr.ui.ListSelectionModel} sm The underlying selection model.
+     * @return {!cr.ui.ListSelectionController} The newly created selection
+     *     controller.
+     */
+    createSelectionController: function(sm) {
+      return new ListSelectionController(sm);
+    },
+
+    /**
+     * Return the heights (in pixels) of the top of the given item index within
+     * the list, and the height of the given item itself, accounting for the
+     * possibility that the lead item may be a different height.
+     * @param {number} index The index to find the top height of.
+     * @return {{top: number, height: number}} The heights for the given index.
+     * @private
+     */
+    getHeightsForIndex_: function(index) {
+      var itemHeight = this.getItemHeight_();
+      var top = this.getItemTop(index);
+      if (this.selectionModel.leadIndex > -1 &&
+          this.selectionModel.leadIndex < index) {
+        top += this.leadItemHeight - itemHeight;
+      } else if (this.selectionModel.leadIndex == index) {
+        itemHeight = this.leadItemHeight;
+      }
+      return {top: top, height: itemHeight};
+    },
+
+    /**
+     * Find the index of the list item containing the given y offset (measured
+     * in pixels from the top) within the list. In the case of multiple columns,
+     * returns the first index in the row.
+     * @param {number} offset The y offset in pixels to get the index of.
+     * @return {number} The index of the list item.
+     * @private
+     */
+    getIndexForListOffset_: function(offset) {
+      var itemHeight = this.getItemHeight_();
+      var leadIndex = this.selectionModel.leadIndex;
+      var leadItemHeight = this.leadItemHeight;
+      if (leadIndex < 0 || leadItemHeight == itemHeight) {
+        // Simple case: no lead item or lead item height is not different.
+        return this.getFirstItemInRow(Math.floor(offset / itemHeight));
+      }
+      var leadTop = this.getItemTop(leadIndex);
+      // If the given offset is above the lead item, it's also simple.
+      if (offset < leadTop)
+        return this.getFirstItemInRow(Math.floor(offset / itemHeight));
+      // If the lead item contains the given offset, we just return its index.
+      if (offset < leadTop + leadItemHeight)
+        return this.getFirstItemInRow(this.getItemRow(leadIndex));
+      // The given offset must be below the lead item. Adjust and recalculate.
+      offset -= leadItemHeight - itemHeight;
+      return this.getFirstItemInRow(Math.floor(offset / itemHeight));
+    },
+
+    /**
+     * Return the number of items that occupy the range of heights between the
+     * top of the start item and the end offset.
+     * @param {number} startIndex The index of the first visible item.
+     * @param {number} endOffset The y offset in pixels of the end of the list.
+     * @return {number} The number of list items visible.
+     * @private
+     */
+    countItemsInRange_: function(startIndex, endOffset) {
+      var endIndex = this.getIndexForListOffset_(endOffset);
+      return endIndex - startIndex + 1;
+    },
+
+    /**
+     * Calculates the number of items fitting in viewport given the index of
+     * first item and heights.
+     * @param {number} itemHeight The height of the item.
+     * @param {number} firstIndex Index of the first item in viewport.
+     * @param {number} scrollTop The scroll top position.
+     * @return {number} The number of items in view port.
+     */
+    getItemsInViewPort: function(itemHeight, firstIndex, scrollTop) {
+      // This is a bit tricky. We take the minimum of the available items to
+      // show and the number we want to show, so as not to go off the end of the
+      // list. For the number we want to show, we take the maximum of the number
+      // that would fit without a differently-sized lead item, and with one. We
+      // do this so that if the size of the lead item changes without a scroll
+      // event to trigger redrawing the list, we won't end up with empty space.
+      var clientHeight = this.clientHeight;
+      return this.autoExpands_ ? this.dataModel.length : Math.min(
+          this.dataModel.length - firstIndex,
+          Math.max(
+              Math.ceil(clientHeight / itemHeight) + 1,
+              this.countItemsInRange_(firstIndex, scrollTop + clientHeight)));
+    },
+
+    /**
+     * Adds items to the list and {@code newCachedItems}.
+     * @param {number} firstIndex The index of first item, inclusively.
+     * @param {number} lastIndex The index of last item, exclusively.
+     * @param {Object.<string, ListItem>} cachedItems Old items cache.
+     * @param {Object.<string, ListItem>} newCachedItems New items cache.
+     */
+    addItems: function(firstIndex, lastIndex, cachedItems, newCachedItems) {
+      var listItem;
+      var dataModel = this.dataModel;
+
+      window.l = this;
+      for (var y = firstIndex; y < lastIndex; y++) {
+        var dataItem = dataModel.item(y);
+        listItem = cachedItems[y] || this.createItem(dataItem);
+        listItem.listIndex = y;
+        this.appendChild(listItem);
+        newCachedItems[y] = listItem;
+      }
+    },
+
+    /**
+     * Returns the height of after filler in the list.
+     * @param {number} lastIndex The index of item past the last in viewport.
+     * @param {number} itemHeight The height of the item.
+     * @return {number} The height of after filler.
+     */
+    getAfterFillerHeight: function(lastIndex, itemHeight) {
+      return (this.dataModel.length - lastIndex) * itemHeight;
+    },
+
+    /**
+     * Redraws the viewport.
+     */
+    redraw: function() {
+      if (this.batchCount_ != 0)
+        return;
+
+      var dataModel = this.dataModel;
+      if (!dataModel) {
+        this.textContent = '';
+        return;
+      }
+
+      var scrollTop = this.scrollTop;
+      var clientHeight = this.clientHeight;
+
+      var itemHeight = this.getItemHeight_();
+
+      // We cache the list items since creating the DOM nodes is the most
+      // expensive part of redrawing.
+      var cachedItems = this.cachedItems_ || {};
+      var newCachedItems = {};
+
+      var desiredScrollHeight = this.getHeightsForIndex_(dataModel.length).top;
+
+      var autoExpands = this.autoExpands_;
+      var firstIndex = autoExpands ? 0 : this.getIndexForListOffset_(scrollTop);
+      var itemsInViewPort = this.getItemsInViewPort(itemHeight, firstIndex,
+          scrollTop);
+      var lastIndex = firstIndex + itemsInViewPort;
+
+      this.textContent = '';
+
+      this.beforeFiller_.style.height =
+          this.getHeightsForIndex_(firstIndex).top + 'px';
+      this.appendChild(this.beforeFiller_);
+
+      var sm = this.selectionModel;
+      var leadIndex = sm.leadIndex;
+
+      this.addItems(firstIndex, lastIndex, cachedItems, newCachedItems);
+
+      var afterFillerHeight = this.getAfterFillerHeight(lastIndex, itemHeight);
+      if (leadIndex >= lastIndex)
+        afterFillerHeight += this.leadItemHeight - itemHeight;
+      this.afterFiller_.style.height = afterFillerHeight + 'px';
+      this.appendChild(this.afterFiller_);
+
+      // We don't set the lead or selected properties until after adding all
+      // items, in case they force relayout in response to these events.
+      var listItem = null;
+      if (newCachedItems[leadIndex])
+        newCachedItems[leadIndex].lead = true;
+      for (var y = firstIndex; y < lastIndex; y++) {
+        if (sm.getIndexSelected(y))
+          newCachedItems[y].selected = true;
+        else if (y != leadIndex)
+          listItem = newCachedItems[y];
+      }
+
+      this.firstIndex_ = firstIndex;
+      this.lastIndex_ = lastIndex;
+
+      this.cachedItems_ = newCachedItems;
+
+      // Measure again in case the item height has changed due to a page zoom.
+      //
+      // The measure above is only done the first time but this measure is done
+      // after every redraw. It is done in a timeout so it will not trigger
+      // a reflow (which made the redraw speed 3 times slower on my system).
+      // By using a timeout the measuring will happen later when there is no
+      // need for a reflow.
+      if (listItem) {
+        var list = this;
+        window.setTimeout(function() {
+          if (listItem.parentNode == list) {
+            list.measured_ = measureItem(list, listItem);
+          }
+        });
+      }
+    },
+
+    /**
+     * Invalidates list by removing cached items.
+     */
+    invalidate: function() {
+      this.cachedItems_ = {};
+    },
+
+    /**
+     * Redraws a single item.
+     * @param {number} index The row index to redraw.
+     */
+    redrawItem: function(index) {
+      if (index >= this.firstIndex_ && index < this.lastIndex_) {
+        delete this.cachedItems_[index];
+        this.redraw();
+      }
+    },
+
+    /**
+     * Called when a list item is activated, currently only by a double click
+     * event.
+     * @param {number} index The index of the activated item.
+     */
+    activateItemAtIndex: function(index) {
+    },
+  };
+
+  cr.defineProperty(List, 'disabled', cr.PropertyKind.BOOL_ATTR);
+
+  /**
+   * Whether the list or one of its descendents has focus. This is necessary
+   * because list items can contain controls that can be focused, and for some
+   * purposes (e.g., styling), the list can still be conceptually focused at
+   * that point even though it doesn't actually have the page focus.
+   */
+  cr.defineProperty(List, 'hasElementFocus', cr.PropertyKind.BOOL_ATTR);
+
+  return {
+    List: List
+  }
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_item.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_item.js
new file mode 100644
index 0000000..68431bc
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_item.js
@@ -0,0 +1,75 @@
+// 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.
+
+cr.define('cr.ui', function() {
+
+  /**
+   * Creates a new list item element.
+   * @param {string} opt_label The text label for the item.
+   * @constructor
+   * @extends {HTMLLIElement}
+   */
+  var ListItem = cr.ui.define('li');
+
+  ListItem.prototype = {
+    __proto__: HTMLLIElement.prototype,
+
+    /**
+     * Plain text label.
+     * @type {string}
+     */
+    get label() {
+      return this.textContent;
+    },
+    set label(label) {
+      this.textContent = label;
+    },
+
+    /**
+     * This item's index in the containing list.
+     * @type {number}
+     */
+    listIndex_: -1,
+
+    /**
+     * Called when an element is decorated as a list item.
+     */
+    decorate: function() {
+      this.setAttribute('role', 'listitem');
+    },
+
+    /**
+     * Called when the selection state of this element changes.
+     */
+    selectionChanged: function() {
+    },
+  };
+
+  /**
+   * Whether the item is selected. Setting this does not update the underlying
+   * selection model. This is only used for display purpose.
+   * @type {boolean}
+   */
+  cr.defineProperty(ListItem, 'selected', cr.PropertyKind.BOOL_ATTR,
+                    function() {
+                      this.selectionChanged();
+                    });
+
+  /**
+   * Whether the item is the lead in a selection. Setting this does not update
+   * the underlying selection model. This is only used for display purpose.
+   * @type {boolean}
+   */
+  cr.defineProperty(ListItem, 'lead', cr.PropertyKind.BOOL_ATTR);
+
+  /**
+   * This item's index in the containing list.
+   * @type {number}
+   */
+  cr.defineProperty(ListItem, 'listIndex');
+
+  return {
+    ListItem: ListItem
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_controller.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_controller.js
new file mode 100644
index 0000000..35101d1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_controller.js
@@ -0,0 +1,289 @@
+// Copyright (c) 2010 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.
+
+cr.define('cr.ui', function() {
+  /**
+   * Creates a selection controller that is to be used with lists. This is
+   * implemented for vertical lists but changing the behavior for horizontal
+   * lists or icon views is a matter of overriding {@code getIndexBefore},
+   * {@code getIndexAfter}, {@code getIndexAbove} as well as
+   * {@code getIndexBelow}.
+   *
+   * @param {cr.ui.ListSelectionModel} selectionModel The selection model to
+   *     interact with.
+   *
+   * @constructor
+   * @extends {!cr.EventTarget}
+   */
+  function ListSelectionController(selectionModel) {
+    this.selectionModel_ = selectionModel;
+  }
+
+  ListSelectionController.prototype = {
+
+    /**
+     * The selection model we are interacting with.
+     * @type {cr.ui.ListSelectionModel}
+     */
+    get selectionModel() {
+      return this.selectionModel_;
+    },
+
+    /**
+     * Returns the index below (y axis) the given element.
+     * @param {number} index The index to get the index below.
+     * @return {number} The index below or -1 if not found.
+     */
+    getIndexBelow: function(index) {
+      if (index == this.getLastIndex())
+        return -1;
+      return index + 1;
+    },
+
+    /**
+     * Returns the index above (y axis) the given element.
+     * @param {number} index The index to get the index above.
+     * @return {number} The index below or -1 if not found.
+     */
+    getIndexAbove: function(index) {
+      return index - 1;
+    },
+
+    /**
+     * Returns the index before (x axis) the given element. This returns -1
+     * by default but override this for icon view and horizontal selection
+     * models.
+     *
+     * @param {number} index The index to get the index before.
+     * @return {number} The index before or -1 if not found.
+     */
+    getIndexBefore: function(index) {
+      return -1;
+    },
+
+    /**
+     * Returns the index after (x axis) the given element. This returns -1
+     * by default but override this for icon view and horizontal selection
+     * models.
+     *
+     * @param {number} index The index to get the index after.
+     * @return {number} The index after or -1 if not found.
+     */
+    getIndexAfter: function(index) {
+      return -1;
+    },
+
+    /**
+     * Returns the next list index. This is the next logical and should not
+     * depend on any kind of layout of the list.
+     * @param {number} index The index to get the next index for.
+     * @return {number} The next index or -1 if not found.
+     */
+    getNextIndex: function(index) {
+      if (index == this.getLastIndex())
+        return -1;
+      return index + 1;
+    },
+
+    /**
+     * Returns the prevous list index. This is the previous logical and should
+     * not depend on any kind of layout of the list.
+     * @param {number} index The index to get the previous index for.
+     * @return {number} The previous index or -1 if not found.
+     */
+    getPreviousIndex: function(index) {
+      return index - 1;
+    },
+
+    /**
+     * @return {number} The first index.
+     */
+    getFirstIndex: function() {
+      return 0;
+    },
+
+    /**
+     * @return {number} The last index.
+     */
+    getLastIndex: function() {
+      return this.selectionModel.length - 1;
+    },
+
+    /**
+     * Called by the view when the user does a mousedown or mouseup on the list.
+     * @param {!Event} e The browser mousedown event.
+     * @param {number} index The index that was under the mouse pointer, -1 if
+     *     none.
+     */
+    handleMouseDownUp: function(e, index) {
+      var sm = this.selectionModel;
+      var anchorIndex = sm.anchorIndex;
+      var isDown = e.type == 'mousedown';
+
+      sm.beginChange();
+
+      if (index == -1) {
+        // On Mac we always clear the selection if the user clicks a blank area.
+        // On Windows, we only clear the selection if neither Shift nor Ctrl are
+        // pressed.
+        if (cr.isMac) {
+          sm.leadIndex = sm.anchorIndex = -1;
+          if (sm.multiple)
+            sm.unselectAll();
+        } else if (!isDown && !e.shiftKey && !e.ctrlKey)
+          // Keep anchor and lead indexes. Note that this is intentionally
+          // different than on the Mac.
+          if (sm.multiple)
+            sm.unselectAll();
+      } else {
+        if (sm.multiple && (cr.isMac ? e.metaKey :
+                                       (e.ctrlKey && !e.shiftKey))) {
+          // Selection is handled at mouseUp on windows/linux, mouseDown on mac.
+          if (cr.isMac? isDown : !isDown) {
+            // Toggle the current one and make it anchor index.
+            sm.setIndexSelected(index, !sm.getIndexSelected(index));
+            sm.leadIndex = index;
+            sm.anchorIndex = index;
+          }
+        } else if (e.shiftKey && anchorIndex != -1 && anchorIndex != index) {
+          // Shift is done in mousedown.
+          if (isDown) {
+            sm.unselectAll();
+            sm.leadIndex = index;
+            if (sm.multiple)
+              sm.selectRange(anchorIndex, index);
+            else
+              sm.setIndexSelected(index, true);
+          }
+        } else {
+          // Right click for a context menu needs to not clear the selection.
+          var isRightClick = e.button == 2;
+
+          // If the index is selected this is handled in mouseup.
+          var indexSelected = sm.getIndexSelected(index);
+          if ((indexSelected && !isDown || !indexSelected && isDown) &&
+              !(indexSelected && isRightClick)) {
+            sm.unselectAll();
+            sm.setIndexSelected(index, true);
+            sm.leadIndex = index;
+            sm.anchorIndex = index;
+          }
+        }
+      }
+
+      sm.endChange();
+    },
+
+    /**
+     * Called by the view when it receives a keydown event.
+     * @param {Event} e The keydown event.
+     */
+    handleKeyDown: function(e) {
+      const SPACE_KEY_CODE = 32;
+      var tagName = e.target.tagName;
+      // If focus is in an input field of some kind, only handle navigation keys
+      // that aren't likely to conflict with input interaction (e.g., text
+      // editing, or changing the value of a checkbox or select).
+      if (tagName == 'INPUT') {
+        var inputType = e.target.type;
+        // Just protect space (for toggling) for checkbox and radio.
+        if (inputType == 'checkbox' || inputType == 'radio') {
+          if (e.keyCode == SPACE_KEY_CODE)
+            return;
+        // Protect all but the most basic navigation commands in anything else.
+        } else if (e.keyIdentifier != 'Up' && e.keyIdentifier != 'Down') {
+          return;
+        }
+      }
+      // Similarly, don't interfere with select element handling.
+      if (tagName == 'SELECT')
+        return;
+
+      var sm = this.selectionModel;
+      var newIndex = -1;
+      var leadIndex = sm.leadIndex;
+      var prevent = true;
+
+      // Ctrl/Meta+A
+      if (sm.multiple && e.keyCode == 65 &&
+          (cr.isMac && e.metaKey || !cr.isMac && e.ctrlKey)) {
+        sm.selectAll();
+        e.preventDefault();
+        return;
+      }
+
+      // Space
+      if (e.keyCode == SPACE_KEY_CODE) {
+        if (leadIndex != -1) {
+          var selected = sm.getIndexSelected(leadIndex);
+          if (e.ctrlKey || !selected) {
+            sm.setIndexSelected(leadIndex, !selected || !sm.multiple);
+            return;
+          }
+        }
+      }
+
+      switch (e.keyIdentifier) {
+        case 'Home':
+          newIndex = this.getFirstIndex();
+          break;
+        case 'End':
+          newIndex = this.getLastIndex();
+          break;
+        case 'Up':
+          newIndex = leadIndex == -1 ?
+              this.getLastIndex() : this.getIndexAbove(leadIndex);
+          break;
+        case 'Down':
+          newIndex = leadIndex == -1 ?
+              this.getFirstIndex() : this.getIndexBelow(leadIndex);
+          break;
+        case 'Left':
+          newIndex = leadIndex == -1 ?
+              this.getLastIndex() : this.getIndexBefore(leadIndex);
+          break;
+        case 'Right':
+          newIndex = leadIndex == -1 ?
+              this.getFirstIndex() : this.getIndexAfter(leadIndex);
+          break;
+        default:
+          prevent = false;
+      }
+
+      if (newIndex != -1) {
+        sm.beginChange();
+
+        sm.leadIndex = newIndex;
+        if (e.shiftKey) {
+          var anchorIndex = sm.anchorIndex;
+          if (sm.multiple)
+            sm.unselectAll();
+          if (anchorIndex == -1) {
+            sm.setIndexSelected(newIndex, true);
+            sm.anchorIndex = newIndex;
+          } else {
+            sm.selectRange(anchorIndex, newIndex);
+          }
+        } else if (e.ctrlKey && !cr.isMac) {
+          // Setting the lead index is done above.
+          // Mac does not allow you to change the lead.
+        } else {
+          if (sm.multiple)
+            sm.unselectAll();
+          sm.setIndexSelected(newIndex, true);
+          sm.anchorIndex = newIndex;
+        }
+
+        sm.endChange();
+
+        if (prevent)
+          e.preventDefault();
+      }
+    }
+  };
+
+  return {
+    ListSelectionController: ListSelectionController
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_model.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_model.js
new file mode 100644
index 0000000..8c16d57
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_selection_model.js
@@ -0,0 +1,275 @@
+// 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.
+
+cr.define('cr.ui', function() {
+  const Event = cr.Event;
+  const EventTarget = cr.EventTarget;
+
+  /**
+   * Creates a new selection model that is to be used with lists.
+   *
+   * @param {number=} opt_length The number items in the selection.
+   *
+   * @constructor
+   * @extends {!cr.EventTarget}
+   */
+  function ListSelectionModel(opt_length) {
+    this.length_ = opt_length || 0;
+    // Even though selectedIndexes_ is really a map we use an array here to get
+    // iteration in the order of the indexes.
+    this.selectedIndexes_ = [];
+  }
+
+  ListSelectionModel.prototype = {
+    __proto__: EventTarget.prototype,
+
+    /**
+     * The number of items in the model.
+     * @type {number}
+     */
+    get length() {
+      return this.length_;
+    },
+
+    /**
+     * @type {!Array} The selected indexes.
+     */
+    get selectedIndexes() {
+      return Object.keys(this.selectedIndexes_).map(Number);
+    },
+    set selectedIndexes(selectedIndexes) {
+      this.beginChange();
+      this.unselectAll();
+      for (var i = 0; i < selectedIndexes.length; i++) {
+        this.setIndexSelected(selectedIndexes[i], true);
+      }
+      if (selectedIndexes.length) {
+        this.leadIndex = this.anchorIndex = selectedIndexes[0];
+      } else {
+        this.leadIndex = this.anchorIndex = -1;
+      }
+      this.endChange();
+    },
+
+    /**
+     * Convenience getter which returns the first selected index.
+     * @type {number}
+     */
+    get selectedIndex() {
+      for (var i in this.selectedIndexes_) {
+        return Number(i);
+      }
+      return -1;
+    },
+    set selectedIndex(selectedIndex) {
+      this.beginChange();
+      this.unselectAll();
+      if (selectedIndex != -1) {
+        this.selectedIndexes = [selectedIndex];
+      } else {
+        this.leadIndex = this.anchorIndex = -1;
+      }
+      this.endChange();
+    },
+
+    /**
+     * Selects a range of indexes, starting with {@code start} and ends with
+     * {@code end}.
+     * @param {number} start The first index to select.
+     * @param {number} end The last index to select.
+     */
+    selectRange: function(start, end) {
+      // Swap if starts comes after end.
+      if (start > end) {
+        var tmp = start;
+        start = end;
+        end = tmp;
+      }
+
+      this.beginChange();
+
+      for (var index = start; index != end; index++) {
+        this.setIndexSelected(index, true);
+      }
+      this.setIndexSelected(end, true);
+
+      this.endChange();
+    },
+
+    /**
+     * Selects all indexes.
+     */
+    selectAll: function() {
+      this.selectRange(0, this.length - 1);
+    },
+
+    /**
+     * Clears the selection
+     */
+    clear: function() {
+      this.beginChange();
+      this.length_ = 0;
+      this.anchorIndex = this.leadIndex = -1;
+      this.unselectAll();
+      this.endChange();
+    },
+
+    /**
+     * Unselects all selected items.
+     */
+    unselectAll: function() {
+      this.beginChange();
+      for (var i in this.selectedIndexes_) {
+        this.setIndexSelected(i, false);
+      }
+      this.endChange();
+    },
+
+    /**
+     * Sets the selected state for an index.
+     * @param {number} index The index to set the selected state for.
+     * @param {boolean} b Whether to select the index or not.
+     */
+    setIndexSelected: function(index, b) {
+      var oldSelected = index in this.selectedIndexes_;
+      if (oldSelected == b)
+        return;
+
+      if (b)
+        this.selectedIndexes_[index] = true;
+      else
+        delete this.selectedIndexes_[index];
+
+      this.beginChange();
+
+      // Changing back?
+      if (index in this.changedIndexes_ && this.changedIndexes_[index] == !b) {
+        delete this.changedIndexes_[index];
+      } else {
+        this.changedIndexes_[index] = b;
+      }
+
+      // End change dispatches an event which in turn may update the view.
+      this.endChange();
+    },
+
+    /**
+     * Whether a given index is selected or not.
+     * @param {number} index The index to check.
+     * @return {boolean} Whether an index is selected.
+     */
+    getIndexSelected: function(index) {
+      return index in this.selectedIndexes_;
+    },
+
+    /**
+     * This is used to begin batching changes. Call {@code endChange} when you
+     * are done making changes.
+     */
+    beginChange: function() {
+      if (!this.changeCount_) {
+        this.changeCount_ = 0;
+        this.changedIndexes_ = {};
+      }
+      this.changeCount_++;
+    },
+
+    /**
+     * Call this after changes are done and it will dispatch a change event if
+     * any changes were actually done.
+     */
+    endChange: function() {
+      this.changeCount_--;
+      if (!this.changeCount_) {
+        var indexes = Object.keys(this.changedIndexes_);
+        if (indexes.length) {
+          var e = new Event('change');
+          e.changes = indexes.map(function(index) {
+            return {
+              index: index,
+              selected: this.changedIndexes_[index]
+            };
+          }, this);
+          this.dispatchEvent(e);
+        }
+        this.changedIndexes_ = {};
+      }
+    },
+
+    leadIndex_: -1,
+
+    /**
+     * The leadIndex is used with multiple selection and it is the index that
+     * the user is moving using the arrow keys.
+     * @type {number}
+     */
+    get leadIndex() {
+      return this.leadIndex_;
+    },
+    set leadIndex(leadIndex) {
+      var li = Math.max(-1, Math.min(this.length_ - 1, leadIndex));
+      if (li != this.leadIndex_) {
+        var oldLeadIndex = this.leadIndex_;
+        this.leadIndex_ = li;
+        cr.dispatchPropertyChange(this, 'leadIndex', li, oldLeadIndex);
+      }
+    },
+
+    anchorIndex_: -1,
+
+    /**
+     * The anchorIndex is used with multiple selection.
+     * @type {number}
+     */
+    get anchorIndex() {
+      return this.anchorIndex_;
+    },
+    set anchorIndex(anchorIndex) {
+      var ai = Math.max(-1, Math.min(this.length_ - 1, anchorIndex));
+      if (ai != this.anchorIndex_) {
+        var oldAnchorIndex = this.anchorIndex_;
+        this.anchorIndex_ = ai;
+        cr.dispatchPropertyChange(this, 'anchorIndex', ai, oldAnchorIndex);
+      }
+    },
+
+    /**
+     * Whether the selection model supports multiple selected items.
+     * @type {boolean}
+     */
+    get multiple() {
+      return true;
+    },
+
+    /**
+     * Adjusts the selection after reordering of items in the table.
+     * @param {!Array.<number>} permutation The reordering permutation.
+     */
+    adjustToReordering: function(permutation) {
+      var oldLeadIndex = this.leadIndex;
+
+      var oldSelectedIndexes = this.selectedIndexes;
+      this.selectedIndexes = oldSelectedIndexes.map(function(oldIndex) {
+        return permutation[oldIndex];
+      }).filter(function(index) {
+        return index != -1;
+      });
+
+      if (oldLeadIndex != -1)
+        this.leadIndex = permutation[oldLeadIndex];
+    },
+
+    /**
+     * Adjusts selection model length.
+     * @param {number} length New selection model length.
+     */
+    adjustLength: function(length) {
+      this.length_ = length;
+    }
+  };
+
+  return {
+    ListSelectionModel: ListSelectionModel
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_single_selection_model.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_single_selection_model.js
new file mode 100644
index 0000000..2cf43a9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/cr/ui/list_single_selection_model.js
@@ -0,0 +1,221 @@
+// 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.
+
+cr.define('cr.ui', function() {
+  const Event = cr.Event;
+  const EventTarget = cr.EventTarget;
+
+  /**
+   * Creates a new selection model that is to be used with lists. This only
+   * allows a single index to be selected.
+   *
+   * @param {number=} opt_length The number items in the selection.
+   *
+   * @constructor
+   * @extends {!cr.EventTarget}
+   */
+  function ListSingleSelectionModel(opt_length) {
+    this.length_ = opt_length || 0;
+    this.selectedIndex = -1;
+  }
+
+  ListSingleSelectionModel.prototype = {
+    __proto__: EventTarget.prototype,
+
+    /**
+     * The number of items in the model.
+     * @type {number}
+     */
+    get length() {
+      return this.length_;
+    },
+
+    /**
+     * @type {!Array} The selected indexes.
+     */
+    get selectedIndexes() {
+      var i = this.selectedIndex;
+      return i != -1 ? [this.selectedIndex] : [];
+    },
+
+    /**
+     * Convenience getter which returns the first selected index.
+     * @type {number}
+     */
+    get selectedIndex() {
+      return this.selectedIndex_;
+    },
+    set selectedIndex(selectedIndex) {
+      var oldSelectedIndex = this.selectedIndex;
+      var i = Math.max(-1, Math.min(this.length_ - 1, selectedIndex));
+
+      if (i != oldSelectedIndex) {
+        this.beginChange();
+        this.selectedIndex_ = i
+        this.endChange();
+      }
+    },
+
+    /**
+     * Selects a range of indexes, starting with {@code start} and ends with
+     * {@code end}.
+     * @param {number} start The first index to select.
+     * @param {number} end The last index to select.
+     */
+    selectRange: function(start, end) {
+      // Only select first index.
+      this.selectedIndex = Math.min(start, end);
+    },
+
+    /**
+     * Selects all indexes.
+     */
+    selectAll: function() {
+      // Select all is not allowed on a single selection model
+    },
+
+    /**
+     * Clears the selection
+     */
+    clear: function() {
+      this.beginChange();
+      this.length_ = 0;
+      this.selectedIndex = this.anchorIndex = this.leadIndex = -1;
+      this.endChange();
+    },
+
+    /**
+     * Unselects all selected items.
+     */
+    unselectAll: function() {
+      this.selectedIndex = -1;
+    },
+
+    /**
+     * Sets the selected state for an index.
+     * @param {number} index The index to set the selected state for.
+     * @param {boolean} b Whether to select the index or not.
+     */
+    setIndexSelected: function(index, b) {
+      // Only allow selection
+      var oldSelected = index == this.selectedIndex_;
+      if (oldSelected == b)
+        return;
+
+      if (b)
+        this.selectedIndex = index;
+      else if (index == this.selectedIndex_)
+        this.selectedIndex = -1;
+    },
+
+    /**
+     * Whether a given index is selected or not.
+     * @param {number} index The index to check.
+     * @return {boolean} Whether an index is selected.
+     */
+    getIndexSelected: function(index) {
+      return index == this.selectedIndex_;
+    },
+
+    /**
+     * This is used to begin batching changes. Call {@code endChange} when you
+     * are done making changes.
+     */
+    beginChange: function() {
+      if (!this.changeCount_) {
+        this.changeCount_ = 0;
+        this.selectedIndexBefore_ = this.selectedIndex_;
+      }
+      this.changeCount_++;
+    },
+
+    /**
+     * Call this after changes are done and it will dispatch a change event if
+     * any changes were actually done.
+     */
+    endChange: function() {
+      this.changeCount_--;
+      if (!this.changeCount_) {
+        if (this.selectedIndexBefore_ != this.selectedIndex_) {
+          var e = new Event('change');
+          var indexes = [this.selectedIndexBefore_, this.selectedIndex_];
+          e.changes = indexes.filter(function(index) {
+            return index != -1;
+          }).map(function(index) {
+            return {
+              index: index,
+              selected: index == this.selectedIndex_
+            };
+          }, this);
+          this.dispatchEvent(e);
+        }
+      }
+    },
+
+    leadIndex_: -1,
+
+    /**
+     * The leadIndex is used with multiple selection and it is the index that
+     * the user is moving using the arrow keys.
+     * @type {number}
+     */
+    get leadIndex() {
+      return this.leadIndex_;
+    },
+    set leadIndex(leadIndex) {
+      var li = Math.max(-1, Math.min(this.length_ - 1, leadIndex));
+      if (li != this.leadIndex_) {
+        var oldLeadIndex = this.leadIndex_;
+        this.leadIndex_ = li;
+        cr.dispatchPropertyChange(this, 'leadIndex', li, oldLeadIndex);
+        cr.dispatchPropertyChange(this, 'anchorIndex', li, oldLeadIndex);
+      }
+    },
+
+    /**
+     * The anchorIndex is used with multiple selection.
+     * @type {number}
+     */
+    get anchorIndex() {
+      return this.leadIndex;
+    },
+    set anchorIndex(anchorIndex) {
+      this.leadIndex = anchorIndex;
+    },
+
+    /**
+     * Whether the selection model supports multiple selected items.
+     * @type {boolean}
+     */
+    get multiple() {
+      return false;
+    },
+
+    /**
+     * Adjusts the selection after reordering of items in the table.
+     * @param {!Array.<number>} permutation The reordering permutation.
+     */
+    adjustToReordering: function(permutation) {
+      if (this.leadIndex != -1)
+        this.leadIndex = permutation[this.leadIndex];
+
+      var oldSelectedIndex = this.selectedIndex;
+      if (oldSelectedIndex != -1) {
+        this.selectedIndex = permutation[oldSelectedIndex];
+      }
+    },
+
+    /**
+     * Adjusts selection model length.
+     * @param {number} length New selection model length.
+     */
+    adjustLength: function(length) {
+      this.length_ = length;
+    }
+  };
+
+  return {
+    ListSingleSelectionModel: ListSingleSelectionModel
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/util.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/util.js
new file mode 100644
index 0000000..1efbf19
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/domui/js/util.js
@@ -0,0 +1,151 @@
+// 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.
+
+/**
+ * The global object.
+ * @type {!Object}
+ */
+const global = this;
+
+/**
+ * Alias for document.getElementById.
+ * @param {string} id The ID of the element to find.
+ * @return {HTMLElement} The found element or null if not found.
+ */
+function $(id) {
+  return document.getElementById(id);
+}
+
+/**
+ * Calls chrome.send with a callback and restores the original afterwards.
+ * @param {string} name The name of the message to send.
+ * @param {!Array} params The parameters to send.
+ * @param {string} callbackName The name of the function that the backend calls.
+ * @param {!Function} The function to call.
+ */
+function chromeSend(name, params, callbackName, callback) {
+  var old = global[callbackName];
+  global[callbackName] = function() {
+    // restore
+    global[callbackName] = old;
+
+    var args = Array.prototype.slice.call(arguments);
+    return callback.apply(global, args);
+  };
+  chrome.send(name, params);
+}
+
+/**
+ * Generates a CSS url string.
+ * @param {string} s The URL to generate the CSS url for.
+ * @return {string} The CSS url string.
+ */
+function url(s) {
+  // http://www.w3.org/TR/css3-values/#uris
+  // Parentheses, commas, whitespace characters, single quotes (') and double
+  // quotes (") appearing in a URI must be escaped with a backslash
+  var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1');
+  // WebKit has a bug when it comes to URLs that end with \
+  // https://bugs.webkit.org/show_bug.cgi?id=28885
+  if (/\\\\$/.test(s2)) {
+    // Add a space to work around the WebKit bug.
+    s2 += ' ';
+  }
+  return 'url("' + s2 + '")';
+}
+
+/**
+ * Parses query parameters from Location.
+ * @param {string} s The URL to generate the CSS url for.
+ * @return {object} Dictionary containing name value pairs for URL
+ */
+function parseQueryParams(location) {
+  var params = {};
+  var query = unescape(location.search.substring(1));
+  var vars = query.split("&");
+  for (var i=0; i < vars.length; i++) {
+    var pair = vars[i].split("=");
+    params[pair[0]] = pair[1];
+  }
+  return params;
+}
+
+function findAncestorByClass(el, className) {
+  return findAncestor(el, function(el) {
+    if (el.classList)
+      return el.classList.contains(className);
+    return null;
+  });
+}
+
+/**
+ * Return the first ancestor for which the {@code predicate} returns true.
+ * @param {Node} node The node to check.
+ * @param {function(Node) : boolean} predicate The function that tests the
+ *     nodes.
+ * @return {Node} The found ancestor or null if not found.
+ */
+function findAncestor(node, predicate) {
+  var last = false;
+  while (node != null && !(last = predicate(node))) {
+    node = node.parentNode;
+  }
+  return last ? node : null;
+}
+
+function swapDomNodes(a, b) {
+  var afterA = a.nextSibling;
+  if (afterA == b) {
+    swapDomNodes(b, a);
+    return;
+  }
+  var aParent = a.parentNode;
+  b.parentNode.replaceChild(a, b);
+  aParent.insertBefore(b, afterA);
+}
+
+/**
+ * Disables text selection and dragging.
+ */
+function disableTextSelectAndDrag() {
+  // Disable text selection.
+  document.onselectstart = function(e) {
+    e.preventDefault();
+  }
+
+  // Disable dragging.
+  document.ondragstart = function(e) {
+    e.preventDefault();
+  }
+}
+
+// Handle click on a link. If the link points to a chrome: or file: url, then
+// call into the browser to do the navigation.
+document.addEventListener('click', function(e) {
+  // Allow preventDefault to work.
+  if (!e.returnValue)
+    return;
+
+  var el = e.target;
+  if (el.nodeType == Node.ELEMENT_NODE &&
+      el.webkitMatchesSelector('A, A *')) {
+    while (el.tagName != 'A') {
+      el = el.parentElement;
+    }
+
+    if ((el.protocol == 'file:' || el.protocol == 'about:') &&
+        (e.button == 0 || e.button == 1)) {
+      chrome.send('navigateToUrl', [
+        el.href,
+        el.target,
+        e.button,
+        e.altKey,
+        e.ctrlKey,
+        e.metaKey,
+        e.shiftKey
+      ]);
+      e.preventDefault();
+    }
+  }
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/chrome_stubs.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/chrome_stubs.js
new file mode 100644
index 0000000..38b7514
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/chrome_stubs.js
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 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 Stubs for Chrome extension APIs that aren't available to
+ * regular web pages, to allow tests to run.
+ */
+
+chrome = chrome || {};
+chrome.extension = chrome.extension || {};
+chrome.contentSettings = chrome.contentSettings || {};
+
+var _rules = {};
+chrome.contentSettings.plugins = {
+  'set': function(details, callback) {
+    assertObjectEquals({'id': 'myplugin'}, details.resourceIdentifier);
+    var pattern = details.primaryPattern;
+    var setting = details.setting;
+    if (pattern == '__invalid_pattern') {
+      chrome.extension.lastError = {'message': 'Invalid pattern'};
+    } else if (setting == '__invalid_setting') {
+      throw Error('Invalid setting');
+    } else {
+      chrome.extension.lastError = undefined;
+      _rules[pattern] = setting;
+    }
+    callback();
+  },
+
+  'clear': function(details, callback) {
+    assertObjectEquals({}, details);
+    _rules = {};
+    callback();
+  }
+};
+
+chrome.i18n = chrome.i18n || {};
+chrome.i18n.getMessage = function(id) {
+  var messages = {
+    'patternColumnHeader': 'Hostname Pattern',
+    'settingColumnHeader': 'Behavior',
+    'allowRule': 'Allow',
+    'blockRule': 'Block',
+    'addNewPattern': 'Add a new hostname pattern',
+  };
+  return messages[id];
+}
+
+/**
+ * Creates a new Settings object with a set of rules for a dummy plug-in.
+ * Because we provide stub implementations for the Chrome contentSettings
+ * extension API, we know that the methods will execute immediately instead of
+ * asynchronously.
+ * @param {!Object} rules A map from content settings pattern to setting.
+ * @return {!pluginSettings.Settings} A newly created Settings object with the
+ *     passed in set of rules.
+ */
+function createSettings(rules) {
+  var settings = new pluginSettings.Settings('myplugin');
+  if (rules) {
+    for (var pattern in rules) {
+      settings.set(pattern, rules[pattern], function() {});
+    }
+  }
+  return settings;
+}
+
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/main.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/main.js
new file mode 100644
index 0000000..89aeca6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/main.js
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 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 Main entry point that creates a new plug-in list on document
+ * load.
+ */
+
+document.addEventListener('DOMContentLoaded', function() {
+  chrome.contentSettings.plugins.getResourceIdentifiers(function(r) {
+    if (chrome.extension.lastError) {
+      $('error').textContent =
+          'Error: ' + chrome.extension.lastError.message;
+      return;
+    }
+    var pluginList = $('plugin-list');
+    pluginSettings.ui.PluginList.decorate(pluginList);
+    pluginList.dataModel = new cr.ui.ArrayDataModel(r);
+  });
+});
+
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_list.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_list.js
new file mode 100644
index 0000000..f1d3032
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_list.js
@@ -0,0 +1,297 @@
+// Copyright (c) 2012 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 Defines a list of plug-ins that shows for each plug-in a list
+ * of content setting rules.
+ */
+
+cr.define('pluginSettings.ui', function() {
+  const List = cr.ui.List;
+  const ListItem = cr.ui.ListItem;
+  const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
+
+  /**
+   * CSS classes used by this class.
+   * @enum {string}
+   */
+  const CSSClass = {
+
+    /**
+     * Hides an element.
+     */
+    HIDDEN: 'hidden',
+
+    /**
+     * A plug-in list.
+     */
+    PLUGIN_LIST: 'plugin-list',
+
+    /**
+     * Set on a plug-in list entry to show details about the plug-in.
+     */
+    PLUGIN_SHOW_DETAILS: 'plugin-show-details',
+
+    /**
+     * The plug-in name.
+     */
+    PLUGIN_NAME: 'plugin-name',
+
+    /**
+     * The number of rules set for a plug-in.
+     */
+    NUM_RULES: 'num-rules',
+
+    /**
+     * The element containing details about a plug-in.
+     */
+    PLUGIN_DETAILS: 'plugin-details',
+
+    /**
+     * The element containing the column headers for the list of rules.
+     */
+    COLUMN_HEADERS: 'column-headers',
+
+    /**
+     * The header for the pattern column.
+     */
+    PATTERN_COLUMN_HEADER: 'pattern-column-header',
+
+    /**
+     * The header for the setting column.
+     */
+    SETTING_COLUMN_HEADER: 'setting-column-header',
+  };
+
+  /**
+   * Returns the item's height, like offsetHeight but such that it works better
+   * when the page is zoomed. See the similar calculation in @{code cr.ui.List}.
+   * This version also accounts for the animation done in this file.
+   * @param {!Element} item The item to get the height of.
+   * @return {number} The height of the item, calculated with zooming in mind.
+   */
+  function getItemHeight(item) {
+    var height = item.style.height;
+    // Use the fixed animation target height if set, in case the element is
+    // currently being animated and we'd get an intermediate height below.
+    if (height && height.substr(-2) == 'px') {
+      return parseInt(height.substr(0, height.length - 2));
+    }
+    return item.getBoundingClientRect().height;
+  }
+
+  /**
+   * Creates a new plug-in list item element.
+   * @param {!PluginList} list The plug-in list containing this item.
+   * @param {!Object} info Information about the plug-in.
+   * @constructor
+   * @extends {cr.ui.ListItem}
+   */
+  function PluginListItem(list, info) {
+    var el = cr.doc.createElement('li');
+
+    /**
+     * The plug-in list containing this item.
+     * @type {!PluginList}
+     * @private
+     */
+    el.list_ = list;
+
+    /**
+     * Information about the plug-in.
+     * @type {!Object}
+     * @private
+     */
+    el.info_ = info;
+
+    el.__proto__ = PluginListItem.prototype;
+    el.decorate();
+    return el;
+  }
+
+  PluginListItem.prototype = {
+    __proto__: ListItem.prototype,
+
+    /**
+     * The element containing details about the plug-in. This is only null in
+     * the prototype.
+     * @type {?HTMLDivElement}
+     * @private
+     */
+    detailsElement_: null,
+
+    /**
+     * Initializes the element.
+     */
+    decorate: function() {
+      ListItem.prototype.decorate.call(this);
+
+      var info = this.info_;
+
+      var contentElement = this.ownerDocument.createElement('div');
+
+      var titleEl = this.ownerDocument.createElement('div');
+      var nameEl = this.ownerDocument.createElement('span');
+      nameEl.className = CSSClass.PLUGIN_NAME;
+      nameEl.textContent = info.description;
+      nameEl.title = info.description;
+      titleEl.appendChild(nameEl);
+      this.numRulesEl_ = this.ownerDocument.createElement('span');
+      this.numRulesEl_.className = CSSClass.NUM_RULES;
+      titleEl.appendChild(this.numRulesEl_);
+      contentElement.appendChild(titleEl);
+
+      this.detailsElement_ = this.ownerDocument.createElement('div');
+      this.detailsElement_.classList.add(CSSClass.PLUGIN_DETAILS);
+      this.detailsElement_.classList.add(CSSClass.HIDDEN);
+
+      var columnHeadersEl = this.ownerDocument.createElement('div');
+      columnHeadersEl.className = CSSClass.COLUMN_HEADERS;
+      var patternColumnEl = this.ownerDocument.createElement('div');
+      patternColumnEl.textContent =
+          chrome.i18n.getMessage('patternColumnHeader');
+      patternColumnEl.className = CSSClass.PATTERN_COLUMN_HEADER;
+      var settingColumnEl = this.ownerDocument.createElement('div');
+      settingColumnEl.textContent =
+          chrome.i18n.getMessage('settingColumnHeader');
+      settingColumnEl.className = CSSClass.SETTING_COLUMN_HEADER;
+      columnHeadersEl.appendChild(patternColumnEl);
+      columnHeadersEl.appendChild(settingColumnEl);
+      this.detailsElement_.appendChild(columnHeadersEl);
+      contentElement.appendChild(this.detailsElement_);
+
+      this.appendChild(contentElement);
+
+      var settings = new pluginSettings.Settings(this.info_.id);
+      this.updateRulesCount_(settings);
+      settings.addEventListener(
+          'change',
+          this.updateRulesCount_.bind(this, settings));
+
+      // Create the rule list asynchronously, to make sure that it is already
+      // fully integrated in the DOM tree.
+      window.setTimeout(this.loadRules_.bind(this, settings), 0);
+    },
+
+    /**
+     * Create the list of content setting rules applying to this plug-in.
+     * @param {!pluginSettings.Settings} The settings object storing the content
+     *     setting rules.
+     * @private
+     */
+    loadRules_: function(settings) {
+      var rulesEl = this.ownerDocument.createElement('list');
+      this.detailsElement_.appendChild(rulesEl);
+
+      pluginSettings.ui.RuleList.decorate(rulesEl);
+      rulesEl.setPluginSettings(settings);
+    },
+
+    /**
+     * Called when the list of rules changes to update the rule count shown when
+     * the list is not expanded.
+     * @param {!pluginSettings.Settings} The settings object storing the content
+     *     setting rules.
+     * @private
+     */
+    updateRulesCount_: function(settings) {
+      this.numRulesEl_.textContent = '(' + settings.getAll().length + ' rules)';
+    },
+
+    /**
+     * Whether this item is expanded or not.
+     * @type {boolean}
+     */
+    expanded_: false,
+    /**
+     * Whether this item is expanded or not.
+     * @type {boolean}
+     */
+    get expanded() {
+      return this.expanded_;
+    },
+    set expanded(expanded) {
+      if (this.expanded_ == expanded) {
+        return;
+      }
+      this.expanded_ = expanded;
+      if (expanded) {
+        var oldExpanded = this.list_.expandItem;
+        this.list_.expandItem = this;
+        this.detailsElement_.classList.remove(CSSClass.HIDDEN);
+        if (oldExpanded) {
+          oldExpanded.expanded = false;
+        }
+        this.classList.add(CSSClass.PLUGIN_SHOW_DETAILS);
+      } else {
+        if (this.list_.expandItem == this) {
+          this.list_.leadItemHeight = 0;
+          this.list_.expandItem = null;
+        }
+        this.style.height = '';
+        this.detailsElement_.classList.add(CSSClass.HIDDEN);
+        this.classList.remove(CSSClass.PLUGIN_SHOW_DETAILS);
+      }
+    },
+  };
+
+  /**
+   * Creates a new plug-in list.
+   * @constructor
+   * @extends {cr.ui.List}
+   */
+  var PluginList = cr.ui.define('list');
+
+  PluginList.prototype = {
+    __proto__: List.prototype,
+
+    /**
+     * Initializes the element.
+     */
+    decorate: function() {
+      List.prototype.decorate.call(this);
+      this.classList.add(CSSClass.PLUGIN_LIST);
+      var sm = new ListSingleSelectionModel();
+      sm.addEventListener('change', this.handleSelectionChange_.bind(this));
+      this.selectionModel = sm;
+      this.autoExpands = true;
+    },
+
+    /**
+     * Creates a new plug-in list item.
+     * @param {!Object} info Information about the plug-in.
+     */
+    createItem: function(info) {
+      return new PluginListItem(this, info);
+    },
+
+    /**
+     * Called when the selection changes.
+     * @param {!cr.Event} ce The change event.
+     * @private
+     */
+    handleSelectionChange_: function(ce) {
+      ce.changes.forEach(function(change) {
+        var listItem = this.getListItemByIndex(change.index);
+        if (listItem) {
+          if (!change.selected) {
+            // TODO(bsmith) explain window timeout (from cookies_list.js)
+            window.setTimeout(function() {
+              if (!listItem.selected || !listItem.lead) {
+                listItem.expanded = false;
+              }
+            }, 0);
+          } else if (listItem.lead) {
+            listItem.expanded = true;
+          }
+        }
+      }, this);
+    },
+  };
+
+  return {
+    PluginList: PluginList,
+    PluginListItem: PluginListItem,
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_list_test.html b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_list_test.html
new file mode 100644
index 0000000..9fd852a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_list_test.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+#error {
+  display: none;
+}
+</style>
+<link rel="stylesheet" href="../domui/css/button.css">
+<link rel="stylesheet" href="../domui/css/chrome_shared.css">
+<link rel="stylesheet" href="../domui/css/list.css">
+<link rel="stylesheet" href="../domui/css/select.css">
+
+<link rel="stylesheet" href="../options/css/list.css">
+
+<link rel="stylesheet" href="../css/plugin_list.css">
+<link rel="stylesheet" href="../css/rule_list.css">
+
+<script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script>
+<script src="../domui/js/cr.js"></script>
+<script src="../domui/js/cr/event_target.js"></script>
+<script src="../domui/js/cr/ui.js"></script>
+<script src="../domui/js/cr/ui/array_data_model.js"></script>
+<script src="../domui/js/cr/ui/list_item.js"></script>
+<script src="../domui/js/cr/ui/list_selection_controller.js"></script>
+<script src="../domui/js/cr/ui/list_selection_model.js"></script>
+<script src="../domui/js/cr/ui/list_single_selection_model.js"></script>
+<script src="../domui/js/cr/ui/list.js"></script>
+<script src="../domui/js/util.js"></script>
+
+<script src="../options/js/deletable_item_list.js"></script>
+<script src="../options/js/inline_editable_list.js"></script>
+
+<script src="plugin_list.js" type="text/javascript"></script>
+<script src="plugin_settings.js" type="text/javascript"></script>
+<script src="rule_list.js" type="text/javascript"></script>
+
+<script>
+goog.require('goog.testing.jsunit');
+</script>
+<script src="chrome_stubs.js" type="text/javascript"></script>
+</head>
+<body>
+<div id="error"></div>
+<script>
+function testConstruction() {
+  var pluginList = document.createElement('list');
+  document.body.appendChild(pluginList);
+  pluginSettings.ui.PluginList.decorate(pluginList);
+  var plugins = [
+    {
+      'id': 'myplugin',
+      'description': 'My Plugin'
+    }
+  ];
+  var rules = {
+    'http://example.com/*': 'block',
+    'http://moose.org/*': 'allow',
+  };
+  createSettings(rules);
+  pluginList.dataModel = new cr.ui.ArrayDataModel(plugins);
+  assertEquals('My Plugin',
+               pluginList.querySelector('.plugin-name').textContent);
+  assertEquals('(2 rules)', pluginList.querySelector('.num-rules').textContent);
+}
+</script>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_settings.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_settings.js
new file mode 100644
index 0000000..253240d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_settings.js
@@ -0,0 +1,220 @@
+// Copyright (c) 2012 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 Defines a class that provides some convenient wrapper methods
+ * around the Chrome contentSettings extension API.
+ */
+
+cr.define('pluginSettings', function() {
+  const EventTarget = cr.EventTarget;
+  const Event = cr.Event;
+
+  /**
+   * Creates a new content settings model.
+   * @param {string} plugin Identifies the plug-in for which this object stores
+   *     settings.
+   * @constructor
+   * @extends {cr.EventTarget}
+   */
+  function Settings(plugin) {
+    /**
+     * Identifies the plug-in for which this object stores settings.
+     * @type {string}
+     * @private
+     */
+    this.plugin_ = plugin;
+  }
+
+  Settings.prototype = {
+    __proto__: cr.EventTarget.prototype,
+
+    /**
+     * Clears all content settings, and recreates them from local storage. If a
+     * content setting can't be set (which shouldn't really happen, as it has
+     * been successfully set previously), it is removed from local storage as
+     * well.
+     * @param {function()} callback Called when the content settings have been
+     *     recreated, or on error.
+     * @private
+     */
+    recreateRules_: function(callback) {
+      chrome.contentSettings.plugins.clear(
+          {}, this.didClearRules_.bind(this, callback));
+    },
+
+    /**
+     * Recreates all content settings from local storage.
+     * @param {function()} callback Called when the content settings have been
+     *     recreated, or on error.
+     * @private
+     */
+    didClearRules_: function(callback) {
+      if (chrome.extension.lastError) {
+        console.error('Error clearing rules');
+        callback();
+        return;
+      }
+      var length = window.localStorage.length;
+      if (length == 0) {
+        cr.dispatchSimpleEvent(settings, 'change');
+        callback();
+        return;
+      }
+      var counter = {
+        'value': length
+      };
+      for (var i = 0; i < length; i++) {
+        var key = window.localStorage.key(i);
+        var keyArray = JSON.parse(key);
+        var plugin = keyArray[0];
+        var pattern = keyArray[1];
+        var setting = window.localStorage.getItem(key)
+        chrome.contentSettings.plugins.set(
+            {
+              'primaryPattern': pattern,
+              'resourceIdentifier': {'id': plugin},
+              'setting': setting,
+            },
+            this.didSetContentSetting_.bind(
+                this, key, setting, counter, callback));
+      }
+    },
+
+    /**
+     * Checks if we're finished with recreating content settings and calls the
+     * passed in callback if so.
+     * @param {string} key The local storage key under which the content
+     *     setting was stored.
+     * @param {string} value The content setting value in local storage.
+     * @param {{value:number}} counter Contains the number of callbacks still
+     *     outstanding.
+     * @param {function()} callback Called when the content settings have been
+     *     recreated, or on error.
+     * @private
+     */
+    didSetContentSetting_: function(plugin, pattern, key, counter, callback) {
+      if (chrome.extension.lastError) {
+        console.error(
+            'Error restoring [' + key + ': ' + value + ']: ' +
+                chrome.extension.lastError.message);
+        window.localStorage.removeItem(key);
+      }
+      counter.value--;
+      if (counter.value == 0) {
+        cr.dispatchSimpleEvent(this, 'change');
+        callback();
+      }
+    },
+
+    /**
+     * Creates a content setting rule and calls the passed in callback with the
+     * result.
+     * @param {string} pattern The content setting pattern for the rule.
+     * @param {string} setting The setting for the rule.
+     * @param {function(?string)} callback Called when the content settings
+     *     have been updated, or on error.
+     */
+    set: function(pattern, setting, callback) {
+      var plugin = this.plugin_;
+      var settings = this;
+      chrome.contentSettings.plugins.set({
+        'primaryPattern': pattern,
+        'resourceIdentifier': { 'id': plugin },
+        'setting': setting,
+      }, function() {
+        if (chrome.extension.lastError) {
+          callback(chrome.extension.lastError.message);
+        } else {
+          window.localStorage.setItem(JSON.stringify([plugin, pattern]),
+                                      setting);
+          cr.dispatchSimpleEvent(settings, 'change');
+          callback();
+        }
+      });
+    },
+
+    /**
+     * Removes the content setting rule with a given pattern, and calls the
+     * passed in callback afterwards.
+     * @param {string} pattern The content setting pattern for the rule.
+     * @param {function()} callback Called when the content settings have
+     * been updated.
+     */
+    clear: function(pattern, callback) {
+      window.localStorage.removeItem(
+          JSON.stringify([this.plugin_, pattern]));
+      this.recreateRules_(callback);
+    },
+
+    /**
+     * Updates the content setting rule with a given pattern to a new pattern
+     * and setting and calls the passed in callback with the result.
+     * @param {string} oldPattern The old content setting pattern for the rule.
+     * @param {string} newPattern The new content setting pattern for the rule.
+     * @param {string} setting The setting for the rule.
+     * @param {function(?string)} callback Called when the content settings
+     *     have been updated, or on error.
+     */
+    update: function(oldPattern, newPattern, setting, callback) {
+      if (oldPattern == newPattern) {
+        // Avoid recreating all rules if only the setting changed.
+        this.set(newPattern, setting, callback);
+        return;
+      }
+      var oldSetting = this.get(oldPattern);
+      var settings = this;
+      // Remove the old rule.
+      this.clear(oldPattern, function() {
+        // Try to set the new rule.
+        settings.set(newPattern, setting, function(error) {
+          if (error) {
+            // If setting the new rule failed, restore the old rule.
+            settings.set(oldPattern, oldSetting, function(restoreError) {
+              if (restoreError) {
+                console.error('Error restoring [' + settings.plugin_ + ', ' +
+                              oldPattern + oldSetting + ']: ' + restoreError);
+              }
+              callback(error);
+            });
+          } else {
+            callback();
+          }
+        });
+      });
+    },
+
+    /**
+     * Returns the content setting for a given pattern.
+     * @param {string} pattern The content setting pattern for the rule.
+     * @return {string} The setting for the rule.
+     */
+    get: function(primaryPattern) {
+      return window.localStorage.getItem(
+          JSON.stringify([this.plugin_, primaryPattern]));
+    },
+
+    /**
+     * @return {!Array} A list of all content setting rules for this plug-in.
+     */
+    getAll: function() {
+      var rules = [];
+      for (var i = 0; i < window.localStorage.length; i++) {
+        var key = window.localStorage.key(i);
+        var keyArray = JSON.parse(key);
+        if (keyArray[0] == this.plugin_) {
+          rules.push({
+            'primaryPattern': keyArray[1],
+            'setting': window.localStorage.getItem(key),
+          });
+        }
+      }
+      return rules;
+    }
+  };
+
+  return {
+    Settings: Settings,
+  }
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_settings_test.html b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_settings_test.html
new file mode 100644
index 0000000..7bcad36
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/plugin_settings_test.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script>
+<script src="../domui/js/cr.js"></script>
+<script src="../domui/js/cr/event_target.js"></script>
+<script src="plugin_settings.js" type="text/javascript"></script>
+<script>
+goog.require('goog.testing.jsunit');
+</script>
+<script src="chrome_stubs.js" type="text/javascript"></script>
+</head>
+<body>
+<script>
+function testConstruction() {
+  var settings = createSettings();
+}
+
+function testSet() {
+  var settings = createSettings();
+  var rules = {
+    'http://example.com/*': 'block',
+    'http://google.com/*': 'allow',
+    'http://moose.org/*': 'allow',
+  };
+  var numCallbacks = 0;
+  for (var pattern in rules) {
+    settings.set(pattern, rules[pattern], function(error) {
+      numCallbacks++;
+      assertUndefined(error);
+    });
+  }
+  assertEquals(Object.keys(rules).length, numCallbacks);
+  assertObjectEquals(rules, _rules);
+}
+
+function testSetInvalid() {
+  var settings = createSettings();
+  // Attempting to set an invalid pattern should return an error in the
+  // callback.
+  var callbackCalled = false;
+  settings.set('__invalid_pattern', 'block', function(error) {
+    callbackCalled = true;
+    assertEquals('Invalid pattern', error);
+  });
+  assertTrue(callbackCalled);
+  assertObjectEquals({}, _rules);
+
+  // Attempting to set an invalid setting should immediately throw an exception.
+  callbackCalled = false;
+  assertThrows(function() {
+    settings.set('http://example.com/*', '__invalid_setting', function() {
+      callbackCalled = true;
+    });
+  });
+  assertFalse(callbackCalled);
+  assertObjectEquals({}, _rules);
+}
+
+function testGet() {
+  var rules = {
+    'http://example.com/*': 'block',
+    'http://google.com/*': 'allow',
+    'http://moose.org/*': 'allow',
+  };
+  var settings = createSettings(rules);
+  for (var pattern in rules)
+    assertEquals(rules[pattern], settings.get(pattern));
+}
+
+function testGetAll() {
+  var settings = createSettings({
+    'http://example.com/*': 'block',
+    'http://google.com/*': 'allow',
+    'http://moose.org/*': 'allow',
+  });
+  var rules = settings.getAll();
+  // Sort the rules lexicographically by their pattern.
+  rules.sort(function(a, b) {
+    if (a.primaryPattern == b.primaryPattern) {
+      return 0;
+    }
+    if (a.primaryPattern > b.primaryPattern) {
+      return 1;
+    }
+    return -1;
+  });
+  assertEquals(3, rules.length);
+  assertObjectEquals({'primaryPattern': 'http://example.com/*',
+                      'setting': 'block'},
+                     rules[0]);
+  assertObjectEquals({'primaryPattern': 'http://google.com/*',
+                      'setting': 'allow'},
+                     rules[1]);
+  assertObjectEquals({'primaryPattern': 'http://moose.org/*',
+                      'setting': 'allow'},
+                     rules[2]);
+}
+
+function testUpdate() {
+  var settings = createSettings({
+    'http://example.com/*': 'block',
+    'http://google.com/*': 'allow',
+    'http://moose.org/*': 'allow',
+  });
+  var numCallbacks = 0;
+  settings.update('http://google.com/*', 'http://google.com/*', 'ask',
+                  function(error) {
+    numCallbacks++;
+    assertUndefined(error);
+  });
+  assertEquals('ask', _rules['http://google.com/*']);
+
+  settings.update('http://google.com/*', 'http://blurp.net/*', 'ask',
+                  function(error) {
+    numCallbacks++;
+    assertUndefined(error);
+  });
+  assertUndefined(_rules['http://google.com/*']);
+  assertEquals('ask', _rules['http://blurp.net/*']);
+
+  // Attempting to update a rule to an invalid pattern should return an error
+  // and leave the rules unchanged.
+  settings.update('http://example.com/*', '__invalid_pattern', 'ask',
+                  function(error) {
+    numCallbacks++;
+    assertEquals('Invalid pattern', error);
+  });
+  assertUndefined(_rules['__invalid_pattern']);
+  assertEquals('block', _rules['http://example.com/*']);
+
+  assertEquals(3, numCallbacks);
+}
+
+function tearDown() {
+  // Clear local storage and |rules_| to make sure no state leaks into the next
+  // test.
+  window.localStorage.clear();
+  _rules = {};
+}
+</script>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/rule_list.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/rule_list.js
new file mode 100644
index 0000000..9919958
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/rule_list.js
@@ -0,0 +1,425 @@
+// Copyright (c) 2012 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 Defines a list of content setting rules.
+ */
+
+cr.define('pluginSettings.ui', function() {
+  const InlineEditableItemList = options.InlineEditableItemList;
+  const InlineEditableItem = options.InlineEditableItem;
+  const ArrayDataModel = cr.ui.ArrayDataModel;
+
+  /**
+   * CSS classes used by this class.
+   * @enum {string}
+   */
+  const CSSClass = {
+    /**
+     * A list of content setting rules.
+     */
+    RULE_LIST: 'rule-list',
+
+    /**
+     * The element containing the content setting pattern for a rule.
+     */
+    RULE_PATTERN: 'rule-pattern',
+
+    /**
+     * The element containing the behavior (allow or block) for a rule.
+     */
+    RULE_BEHAVIOR: 'rule-behavior',
+
+    /**
+     * Static text (as opposed to an editable text field).
+     */
+    STATIC_TEXT: 'static-text',
+  };
+  /**
+   * A single item in a list of rules.
+   * @param {!RuleList} list The rule list containing this item.
+   * @param {!Object} rule The content setting rule.
+   * @constructor
+   * @extends {options.InlineEditableItem}
+   */
+  function RuleListItem(list, rule) {
+    var el = cr.doc.createElement('li');
+
+    /**
+     * The content setting rule.
+     * @type {!Object}
+     * @private
+     */
+    el.dataItem_ = rule;
+
+    /**
+     * The rule list containing this item.
+     * @type {!RuleList}
+     * @private
+     */
+    el.list_ = list;
+    el.__proto__ = RuleListItem.prototype;
+    el.decorate();
+
+    return el;
+  }
+
+  RuleListItem.prototype = {
+    __proto__: InlineEditableItem.prototype,
+
+    /**
+     * The text input element for the pattern. This is only null in the
+     * prototype.
+     * @type {?HTMLInputElement}
+     * @private
+     */
+    input_: null,
+
+    /**
+     * The popup button for the setting. This is only null in the prototype.
+     * @type {?HTMLSelectElement}
+     * @private
+     */
+    select_: null,
+
+    /**
+     * The static text field containing the pattern.
+     * @type {?HTMLDivElement}
+     * @private
+     */
+    patternLabel_: null,
+
+    /**
+     * The static text field containing the setting.
+     * @type {?HTMLDivElement}
+     * @private
+     */
+    settingLabel_: null,
+
+    /**
+     * Decorates an elements as a list item.
+     */
+    decorate: function() {
+      InlineEditableItem.prototype.decorate.call(this);
+
+      this.isPlaceholder = !this.pattern;
+      var patternCell = this.createEditableTextCell(this.pattern);
+      patternCell.className = CSSClass.RULE_PATTERN;
+      this.contentElement.appendChild(patternCell);
+      var input = patternCell.querySelector('input');
+      if (this.pattern) {
+        this.patternLabel_ =
+            patternCell.querySelector('.' + CSSClass.STATIC_TEXT);
+      } else {
+        input.placeholder = chrome.i18n.getMessage('addNewPattern');
+      }
+
+      // TODO(stuartmorgan): Create an createEditableSelectCell abstracting
+      // this code.
+      // Setting label for display mode. |pattern| will be null for the 'add new
+      // exception' row.
+      if (this.pattern) {
+        var settingLabel = cr.doc.createElement('span');
+        settingLabel.textContent = this.settingForDisplay();
+        settingLabel.className = CSSClass.RULE_BEHAVIOR;
+        settingLabel.setAttribute('displaymode', 'static');
+        this.contentElement.appendChild(settingLabel);
+        this.settingLabel_ = settingLabel;
+      }
+
+      // Setting select element for edit mode.
+      var select = cr.doc.createElement('select');
+      var optionAllow = cr.doc.createElement('option');
+      optionAllow.textContent = chrome.i18n.getMessage('allowRule');
+      optionAllow.value = 'allow';
+      select.appendChild(optionAllow);
+
+      var optionBlock = cr.doc.createElement('option');
+      optionBlock.textContent = chrome.i18n.getMessage('blockRule');
+      optionBlock.value = 'block';
+      select.appendChild(optionBlock);
+
+      this.contentElement.appendChild(select);
+      select.className = CSSClass.RULE_BEHAVIOR;
+      if (this.pattern) {
+        select.setAttribute('displaymode', 'edit');
+      }
+
+      this.input_ = input;
+      this.select_ = select;
+
+      this.updateEditables();
+
+      // Listen for edit events.
+      this.addEventListener('canceledit', this.onEditCancelled_);
+      this.addEventListener('commitedit', this.onEditCommitted_);
+    },
+
+    /**
+     * The pattern (e.g., a URL) for the rule.
+     * @type {string}
+     */
+    get pattern() {
+      return this.dataItem_['primaryPattern'];
+    },
+    set pattern(pattern) {
+      this.dataItem_['primaryPattern'] = pattern;
+    },
+
+    /**
+     * The setting (allow/block) for the rule.
+     * @type {string}
+     */
+    get setting() {
+      return this.dataItem_['setting'];
+    },
+    set setting(setting) {
+      this.dataItem_['setting'] = setting;
+    },
+
+    /**
+     * Gets a human-readable setting string.
+     * @type {string}
+     */
+    settingForDisplay: function() {
+      var setting = this.setting;
+      if (setting == 'allow') {
+        return chrome.i18n.getMessage('allowRule');
+      }
+      if (setting == 'block') {
+        return chrome.i18n.getMessage('blockRule');
+      }
+    },
+
+    /**
+     * Set the <input> to its original contents. Used when the user quits
+     * editing.
+     */
+    resetInput: function() {
+      this.input_.value = this.pattern;
+    },
+
+    /**
+     * Copy the data model values to the editable nodes.
+     */
+    updateEditables: function() {
+      this.resetInput();
+
+      var settingOption =
+          this.select_.querySelector('[value=\'' + this.setting + '\']');
+      if (settingOption) {
+        settingOption.selected = true;
+      }
+    },
+
+    /** @inheritDoc */
+    get hasBeenEdited() {
+      var livePattern = this.input_.value;
+      var liveSetting = this.select_.value;
+      return livePattern != this.pattern || liveSetting != this.setting;
+    },
+
+    /**
+     * Called when committing an edit.
+     * @param {!Event} e The end event.
+     * @private
+     */
+    onEditCommitted_: function(e) {
+      var newPattern = this.input_.value;
+      var newSetting = this.select_.value;
+
+      this.finishEdit(newPattern, newSetting);
+    },
+
+    /**
+     * Called when cancelling an edit; resets the control states.
+     * @param {!Event} e The cancel event.
+     * @private
+     */
+    onEditCancelled_: function() {
+      this.updateEditables();
+    },
+
+    /**
+     * Editing is complete; update the model.
+     * @param {string} newPattern The pattern that the user entered.
+     * @param {string} newSetting The setting the user chose.
+     */
+    finishEdit: function(newPattern, newSetting) {
+      this.patternLabel_.textContent = newPattern;
+      this.settingLabel_.textContent = this.settingForDisplay();
+      var oldPattern = this.pattern;
+      this.pattern = newPattern;
+      this.setting = newSetting;
+
+      this.list_.settings.update(oldPattern, newPattern, newSetting,
+                                 this.list_.settingsChangedCallback());
+    }
+  };
+
+  /**
+   * Create a new list item to add a rule.
+   * @param {!RuleList} list The rule list containing this item.
+   * @constructor
+   * @extends {AddRuleListItem}
+   */
+  function AddRuleListItem(list) {
+    var el = cr.doc.createElement('div');
+    el.dataItem_ = {};
+    el.list_ = list;
+    el.__proto__ = AddRuleListItem.prototype;
+    el.decorate();
+
+    return el;
+  }
+
+  AddRuleListItem.prototype = {
+    __proto__: RuleListItem.prototype,
+
+    /**
+     * Initializes the element.
+     */
+    decorate: function() {
+      RuleListItem.prototype.decorate.call(this);
+
+      this.setting = 'allow';
+    },
+
+    /**
+     * Clear the <input> and let the placeholder text show again.
+     */
+    resetInput: function() {
+      this.input_.value = '';
+    },
+
+    /** @inheritDoc */
+    get hasBeenEdited() {
+      return this.input_.value != '';
+    },
+
+    /**
+     * Editing is complete; update the model. As long as the pattern isn't
+     * empty, we'll just add it.
+     * @param {string} newPattern The pattern that the user entered.
+     * @param {string} newSetting The setting the user chose.
+     */
+    finishEdit: function(newPattern, newSetting) {
+      this.resetInput();
+      this.list_.settings.set(newPattern, newSetting,
+                              this.list_.settingsChangedCallback());
+    },
+  };
+
+  /**
+   * A list of content setting rules.
+   * @constructor
+   * @extends {cr.ui.List}
+   */
+  var RuleList = cr.ui.define('list');
+
+  RuleList.prototype = {
+    __proto__: InlineEditableItemList.prototype,
+
+    /**
+     * The content settings model for this list.
+     * @type {?Settings}
+     */
+    settings: null,
+
+    /**
+     * Called when an element is decorated as a list.
+     */
+    decorate: function() {
+      InlineEditableItemList.prototype.decorate.call(this);
+
+      this.classList.add(CSSClass.RULE_LIST);
+
+      this.autoExpands = true;
+      this.reset();
+    },
+
+    /**
+     * Creates an item to go in the list.
+     * @param {?Object} entry The element from the data model for this row.
+     */
+    createItem: function(entry) {
+      if (entry) {
+        return new RuleListItem(this, entry);
+      } else {
+        var addRuleItem = new AddRuleListItem(this);
+        addRuleItem.deletable = false;
+        return addRuleItem;
+      }
+    },
+
+    /**
+     * Sets the rules in the js model.
+     * @param {!Array} entries A list of dictionaries of values, each dictionary
+     *     represents a rule.
+     */
+    setRules_: function(entries) {
+      var deleteCount = this.dataModel.length - 1;
+
+      var args = [0, deleteCount];
+      args.push.apply(args, entries);
+      this.dataModel.splice.apply(this.dataModel, args);
+    },
+
+    /**
+     * Called when the list of content setting rules has been changed.
+     * @param {?string} error The error message, if an error occurred.
+     *     Otherwise, this is null.
+     * @private
+     */
+    settingsChanged_: function(error) {
+      if (error) {
+        $('error').textContent = 'Error: ' + error;
+      } else {
+        $('error').textContent = '';
+      }
+      this.setRules_(this.settings.getAll());
+    },
+
+    /**
+     * @return {function()} A bound callback to update the UI after the
+     *     settings have been changed.
+     */
+    settingsChangedCallback: function() {
+      return this.settingsChanged_.bind(this);
+    },
+
+    /**
+     * Binds this list to the content settings model.
+     * @param {!Settings} settings The content settings model.
+     */
+    setPluginSettings: function(settings) {
+      this.settings = settings;
+      this.settingsChanged_();
+    },
+
+    /**
+     * Removes all rules from the js model.
+     */
+    reset: function() {
+      // The null creates the Add New Rule row.
+      this.dataModel = new ArrayDataModel([null]);
+    },
+
+    /** @inheritDoc */
+    deleteItemAtIndex: function(index) {
+      var listItem = this.getListItemByIndex(index);
+      if (listItem.undeletable) {
+        return;
+      }
+
+      this.settings.clear(listItem.pattern, this.settingsChangedCallback());
+    },
+  };
+
+  return {
+    RuleListItem: RuleListItem,
+    AddRuleListItem: AddRuleListItem,
+    RuleList: RuleList,
+  }
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/rule_list_test.html b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/rule_list_test.html
new file mode 100644
index 0000000..fee89f5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/js/rule_list_test.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="stylesheet" href="../domui/css/button.css">
+<link rel="stylesheet" href="../domui/css/chrome_shared.css">
+<link rel="stylesheet" href="../domui/css/list.css">
+<link rel="stylesheet" href="../domui/css/select.css">
+
+<link rel="stylesheet" href="../options/css/list.css">
+
+<link rel="stylesheet" href="../css/rule_list.css">
+
+<script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script>
+<script src="../domui/js/cr.js"></script>
+<script src="../domui/js/cr/event_target.js"></script>
+<script src="../domui/js/cr/ui.js"></script>
+<script src="../domui/js/cr/ui/array_data_model.js"></script>
+<script src="../domui/js/cr/ui/list_item.js"></script>
+<script src="../domui/js/cr/ui/list_selection_controller.js"></script>
+<script src="../domui/js/cr/ui/list_selection_model.js"></script>
+<script src="../domui/js/cr/ui/list_single_selection_model.js"></script>
+<script src="../domui/js/cr/ui/list.js"></script>
+<script src="../domui/js/util.js"></script>
+
+<script src="../options/js/deletable_item_list.js"></script>
+<script src="../options/js/inline_editable_list.js"></script>
+
+<script src="plugin_settings.js" type="text/javascript"></script>
+<script src="rule_list.js" type="text/javascript"></script>
+
+<script>
+goog.require('goog.testing.jsunit');
+</script>
+<script src="chrome_stubs.js" type="text/javascript"></script>
+</head>
+<body>
+<list id="rule-list"></list>
+<div id="error"></div>
+<script>
+function testConstruction() {
+  var rulesEl = document.createElement('list');
+  document.body.appendChild(rulesEl);
+  pluginSettings.ui.RuleList.decorate(rulesEl);
+  var rules = {
+    'http://example.com/*': 'block',
+    'http://moose.org/*': 'allow',
+  };
+  rulesEl.setPluginSettings(createSettings(rules));
+  var ruleElements = rulesEl.querySelectorAll('[role=listitem]');
+  assertEquals(3, ruleElements.length);
+  assertEquals('http://example.com/*',
+               ruleElements[0].querySelector('.rule-pattern').textContent);
+  assertEquals('http://moose.org/*',
+               ruleElements[1].querySelector('.rule-pattern').textContent);
+  assertEquals('', ruleElements[2].querySelector('.rule-pattern').textContent);
+  assertEquals('Block',
+               ruleElements[0].querySelector('.rule-behavior').textContent);
+  assertEquals('Allow',
+               ruleElements[1].querySelector('.rule-behavior').textContent);
+  assertEquals('allow', ruleElements[2].querySelector('.rule-behavior').value);
+}
+</script>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/manifest.json b/chrome/common/extensions/docs/examples/extensions/plugin_settings/manifest.json
new file mode 100644
index 0000000..e5914f1
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/manifest.json
@@ -0,0 +1,16 @@
+{

+  "name" : "__MSG_extName__",

+  "version" : "0.6",

+  "description" : "__MSG_extDescription__",

+  "options_page": "options.html",

+  "permissions": [

+    "contentSettings"

+  ],

+  "icons": {

+    "128": "bunny128.png",

+    "48": "bunny48.png"

+  },

+  "minimum_chrome_version": "16.0.912",

+  "default_locale": "en",

+  "manifest_version": 2

+}

diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/options.html b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options.html
new file mode 100644
index 0000000..abfdd7e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<link rel="stylesheet" href="domui/css/button.css">
+<link rel="stylesheet" href="domui/css/chrome_shared.css">
+<link rel="stylesheet" href="domui/css/list.css">
+<link rel="stylesheet" href="domui/css/select.css">
+
+<link rel="stylesheet" href="options/css/list.css">
+
+<link rel="stylesheet" href="css/plugin_list.css">
+<link rel="stylesheet" href="css/rule_list.css">
+
+<script src="domui/js/cr.js"></script>
+<script src="domui/js/cr/event_target.js"></script>
+<script src="domui/js/cr/ui.js"></script>
+<script src="domui/js/cr/ui/array_data_model.js"></script>
+<script src="domui/js/cr/ui/list_item.js"></script>
+<script src="domui/js/cr/ui/list_selection_controller.js"></script>
+<script src="domui/js/cr/ui/list_selection_model.js"></script>
+<script src="domui/js/cr/ui/list_single_selection_model.js"></script>
+<script src="domui/js/cr/ui/list.js"></script>
+<script src="domui/js/util.js"></script>
+
+<script src="options/js/deletable_item_list.js"></script>
+<script src="options/js/inline_editable_list.js"></script>
+
+<script src="js/plugin_list.js" type="text/javascript"></script>
+<script src="js/plugin_settings.js" type="text/javascript"></script>
+<script src="js/rule_list.js" type="text/javascript"></script>
+
+<script src="js/main.js" type="text/javascript"></script>
+</head>
+<body>
+<list id="plugin-list"></list>
+<div id="error"></div>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/css/list.css b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/css/list.css
new file mode 100644
index 0000000..9cc716d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/css/list.css
@@ -0,0 +1,124 @@
+/*
+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.
+*/
+
+.raw-button,
+.raw-button:hover,
+.raw-button:active {
+  -webkit-box-shadow: none;
+  background-color: transparent;
+  background-repeat: no-repeat;
+  border: none;
+  min-width: 0;
+  padding: 1px 6px;
+}
+
+list > * {
+  -webkit-box-align: center;
+  -webkit-transition: .15s background-color;
+  box-sizing: border-box;
+  border-radius: 0;
+  display: -webkit-box;
+  height: 32px;
+  border: none;
+  margin: 0;
+}
+
+list:not([disabled]) > :hover {
+  background-color: #e4ecf7;
+}
+
+/* TODO(stuartmorgan): Once this becomes the list style for other WebUI pages
+ * these rules can be simplified (since they wont need to override other rules).
+ */
+
+list:not([hasElementFocus]) > [selected],
+list:not([hasElementFocus]) > [lead][selected] {
+  background-color: #d0d0d0;
+  background-image: none;
+}
+
+list[hasElementFocus] > [selected],
+list[hasElementFocus] > [lead][selected],
+list:not([hasElementFocus]) > [selected]:hover,
+list:not([hasElementFocus]) > [selected][lead]:hover {
+  background-color: #bbcee9;
+  background-image: none;
+}
+
+list[disabled] {
+  opacity: 0.6;
+}
+
+list > .heading {
+  color: #666666;
+}
+
+list > .heading:hover {
+  background-color: transparent;
+  border-color: transparent;
+}
+
+list .deletable-item {
+  -webkit-box-align: center;
+}
+
+list .deletable-item > :first-child {
+  -webkit-box-align: center;
+  -webkit-box-flex: 1;
+  -webkit-padding-end: 5px;
+  display: -webkit-box;
+}
+
+list .close-button {
+  -webkit-transition: .15s opacity;
+  background-color: transparent;
+  /* TODO(stuartmorgan): Replace with real images once they are available. */
+  background-image: url("../images/close_bar.png");
+  border: none;
+  display: block;
+  height: 16px;
+  opacity: 1;
+  width: 16px;
+}
+
+list > *:not(:hover):not([lead]) .close-button,
+list > *:not(:hover):not([selected]) .close-button,
+list:not([hasElementFocus]) > *:not(:hover) .close-button,
+list[disabled] .close-button,
+list .close-button[disabled] {
+  opacity: 0;
+  pointer-events: none;
+}
+
+list .close-button:hover {
+  background-image: url("../images/close_bar_h.png");
+}
+
+list .close-button:active {
+  background-image: url("../images/close_bar_p.png");
+}
+
+list .static-text {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+list[inlineeditable] input {
+  box-sizing: border-box;
+  margin: 0;
+  width: 100%;
+}
+
+list[inlineeditable] > :not([editing]) [displaymode="edit"],
+list[inlineeditable] > [editing] [displaymode="static"] {
+  display: none;
+}
+
+list > [editing] input:invalid {
+  /* TODO(stuartmorgan): Replace with validity badge */
+  background-color: pink;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar.png b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar.png
new file mode 100644
index 0000000..912df05
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar_h.png b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar_h.png
new file mode 100644
index 0000000..c5e1481
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar_h.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar_p.png b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar_p.png
new file mode 100644
index 0000000..cc5bbbe
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/images/close_bar_p.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/deletable_item_list.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/deletable_item_list.js
new file mode 100644
index 0000000..4d2e68e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/deletable_item_list.js
@@ -0,0 +1,185 @@
+// 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.
+
+cr.define('options', function() {
+  const List = cr.ui.List;
+  const ListItem = cr.ui.ListItem;
+
+  /**
+   * Creates a deletable list item, which has a button that will trigger a call
+   * to deleteItemAtIndex(index) in the list.
+   */
+  var DeletableItem = cr.ui.define('li');
+
+  DeletableItem.prototype = {
+    __proto__: ListItem.prototype,
+
+    /**
+     * The element subclasses should populate with content.
+     * @type {HTMLElement}
+     * @private
+     */
+    contentElement_: null,
+
+    /**
+     * The close button element.
+     * @type {HTMLElement}
+     * @private
+     */
+    closeButtonElement_: null,
+
+    /**
+     * Whether or not this item can be deleted.
+     * @type {boolean}
+     * @private
+     */
+    deletable_: true,
+
+    /** @inheritDoc */
+    decorate: function() {
+      ListItem.prototype.decorate.call(this);
+
+      this.classList.add('deletable-item');
+
+      this.contentElement_ = this.ownerDocument.createElement('div');
+      this.appendChild(this.contentElement_);
+
+      this.closeButtonElement_ = this.ownerDocument.createElement('button');
+      this.closeButtonElement_.classList.add('raw-button');
+      this.closeButtonElement_.classList.add('close-button');
+      this.closeButtonElement_.addEventListener('mousedown',
+                                                this.handleMouseDownUpOnClose_);
+      this.closeButtonElement_.addEventListener('mouseup',
+                                                this.handleMouseDownUpOnClose_);
+      this.closeButtonElement_.addEventListener('focus',
+                                                this.handleFocus_.bind(this));
+      this.appendChild(this.closeButtonElement_);
+    },
+
+    /**
+     * Returns the element subclasses should add content to.
+     * @return {HTMLElement} The element subclasses should popuplate.
+     */
+    get contentElement() {
+      return this.contentElement_;
+    },
+
+    /* Gets/sets the deletable property. An item that is not deletable doesn't
+     * show the delete button (although space is still reserved for it).
+     */
+    get deletable() {
+      return this.deletable_;
+    },
+    set deletable(value) {
+      this.deletable_ = value;
+      this.closeButtonElement_.disabled = !value;
+    },
+
+    /**
+     * Called when a focusable child element receives focus. Selects this item
+     * in the list selection model.
+     * @private
+     */
+    handleFocus_: function() {
+      var list = this.parentNode;
+      var index = list.getIndexOfListItem(this);
+      list.selectionModel.selectedIndex = index;
+      list.selectionModel.anchorIndex = index;
+    },
+
+    /**
+     * Don't let the list have a crack at the event. We don't want clicking the
+     * close button to change the selection of the list.
+     * @param {Event} e The mouse down/up event object.
+     * @private
+     */
+    handleMouseDownUpOnClose_: function(e) {
+      if (!e.target.disabled)
+        e.stopPropagation();
+    },
+  };
+
+  var DeletableItemList = cr.ui.define('list');
+
+  DeletableItemList.prototype = {
+    __proto__: List.prototype,
+
+    /** @inheritDoc */
+    decorate: function() {
+      List.prototype.decorate.call(this);
+      this.addEventListener('click', this.handleClick_);
+      this.addEventListener('keydown', this.handleKeyDown_);
+    },
+
+    /**
+     * Callback for onclick events.
+     * @param {Event} e The click event object.
+     * @private
+     */
+    handleClick_: function(e) {
+      if (this.disabled)
+        return;
+
+      var target = e.target;
+      if (target.classList.contains('close-button')) {
+        var listItem = this.getListItemAncestor(target);
+        var selected = this.selectionModel.selectedIndexes;
+
+        // Check if the list item that contains the close button being clicked
+        // is not in the list of selected items. Only delete this item in that
+        // case.
+        var idx = this.getIndexOfListItem(listItem);
+        if (selected.indexOf(idx) == -1) {
+          this.deleteItemAtIndex(idx);
+        } else {
+          this.deleteSelectedItems_();
+        }
+      }
+    },
+
+    /**
+     * Callback for keydown events.
+     * @param {Event} e The keydown event object.
+     * @private
+     */
+    handleKeyDown_: function(e) {
+      // Map delete (and backspace on Mac) to item deletion (unless focus is
+      // in an input field, where it's intended for text editing).
+      if ((e.keyCode == 46 || (e.keyCode == 8 && cr.isMac)) &&
+          e.target.tagName != 'INPUT') {
+        this.deleteSelectedItems_();
+        // Prevent the browser from going back.
+        e.preventDefault();
+      }
+    },
+
+    /**
+     * Deletes all the currently selected items that are deletable.
+     * @private
+     */
+    deleteSelectedItems_: function() {
+      var selected = this.selectionModel.selectedIndexes;
+      // Reverse through the list of selected indexes to maintain the
+      // correct index values after deletion.
+      for (var j = selected.length - 1; j >= 0; j--) {
+        var index = selected[j];
+        if (this.getListItemByIndex(index).deletable)
+          this.deleteItemAtIndex(index);
+      }
+    },
+
+    /**
+     * Called when an item should be deleted; subclasses are responsible for
+     * implementing.
+     * @param {number} index The index of the item that is being deleted.
+     */
+    deleteItemAtIndex: function(index) {
+    },
+  };
+
+  return {
+    DeletableItemList: DeletableItemList,
+    DeletableItem: DeletableItem,
+  };
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/inline_editable_list.js b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/inline_editable_list.js
new file mode 100644
index 0000000..8aed93b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/plugin_settings/options/js/inline_editable_list.js
@@ -0,0 +1,414 @@
+// 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.
+
+cr.define('options', function() {
+  const DeletableItem = options.DeletableItem;
+  const DeletableItemList = options.DeletableItemList;
+
+  /**
+   * Creates a new list item with support for inline editing.
+   * @constructor
+   * @extends {options.DeletableListItem}
+   */
+  function InlineEditableItem() {
+    var el = cr.doc.createElement('div');
+    InlineEditableItem.decorate(el);
+    return el;
+  }
+
+  /**
+   * Decorates an element as a inline-editable list item. Note that this is
+   * a subclass of DeletableItem.
+   * @param {!HTMLElement} el The element to decorate.
+   */
+  InlineEditableItem.decorate = function(el) {
+    el.__proto__ = InlineEditableItem.prototype;
+    el.decorate();
+  };
+
+  InlineEditableItem.prototype = {
+    __proto__: DeletableItem.prototype,
+
+    /**
+     * Whether or not this item can be edited.
+     * @type {boolean}
+     * @private
+     */
+    editable_: true,
+
+    /**
+     * Whether or not this is a placeholder for adding a new item.
+     * @type {boolean}
+     * @private
+     */
+    isPlaceholder_: false,
+
+    /**
+     * Fields associated with edit mode.
+     * @type {array}
+     * @private
+     */
+    editFields_: null,
+
+    /**
+     * Whether or not the current edit should be considered cancelled, rather
+     * than committed, when editing ends.
+     * @type {boolean}
+     * @private
+     */
+    editCancelled_: true,
+
+    /**
+     * The editable item corresponding to the last click, if any. Used to decide
+     * initial focus when entering edit mode.
+     * @type {HTMLElement}
+     * @private
+     */
+    editClickTarget_: null,
+
+    /** @inheritDoc */
+    decorate: function() {
+      DeletableItem.prototype.decorate.call(this);
+
+      this.editFields_ = [];
+      this.addEventListener('mousedown', this.handleMouseDown_);
+      this.addEventListener('keydown', this.handleKeyDown_);
+      this.addEventListener('leadChange', this.handleLeadChange_);
+    },
+
+    /** @inheritDoc */
+    selectionChanged: function() {
+      this.updateEditState();
+    },
+
+    /**
+     * Called when this element gains or loses 'lead' status. Updates editing
+     * mode accordingly.
+     * @private
+     */
+    handleLeadChange_: function() {
+      this.updateEditState();
+    },
+
+    /**
+     * Updates the edit state based on the current selected and lead states.
+     */
+    updateEditState: function() {
+      if (this.editable)
+        this.editing = this.selected && this.lead;
+    },
+
+    /**
+     * Whether the user is currently editing the list item.
+     * @type {boolean}
+     */
+    get editing() {
+      return this.hasAttribute('editing');
+    },
+    set editing(editing) {
+      if (this.editing == editing)
+        return;
+
+      if (editing)
+        this.setAttribute('editing', '');
+      else
+        this.removeAttribute('editing');
+
+      if (editing) {
+        this.editCancelled_ = false;
+
+        cr.dispatchSimpleEvent(this, 'edit', true);
+
+        var focusElement = this.editClickTarget_ || this.initialFocusElement;
+        this.editClickTarget_ = null;
+
+        // When this is called in response to the selectedChange event,
+        // the list grabs focus immediately afterwards. Thus we must delay
+        // our focus grab.
+        var self = this;
+        if (focusElement) {
+          window.setTimeout(function() {
+            // Make sure we are still in edit mode by the time we execute.
+            if (self.editing) {
+              focusElement.focus();
+              focusElement.select();
+            }
+          }, 50);
+        }
+      } else {
+        if (!this.editCancelled_ && this.hasBeenEdited &&
+            this.currentInputIsValid) {
+          if (this.isPlaceholder)
+            this.parentNode.focusPlaceholder = true;
+
+          this.updateStaticValues_();
+          cr.dispatchSimpleEvent(this, 'commitedit', true);
+        } else {
+          this.resetEditableValues_();
+          cr.dispatchSimpleEvent(this, 'canceledit', true);
+        }
+      }
+    },
+
+    /**
+     * Whether the item is editable.
+     * @type {boolean}
+     */
+    get editable() {
+      return this.editable_;
+    },
+    set editable(editable) {
+      this.editable_ = editable;
+      if (!editable)
+        this.editing = false;
+    },
+
+    /**
+     * Whether the item is a new item placeholder.
+     * @type {boolean}
+     */
+    get isPlaceholder() {
+      return this.isPlaceholder_;
+    },
+    set isPlaceholder(isPlaceholder) {
+      this.isPlaceholder_ = isPlaceholder;
+      if (isPlaceholder)
+        this.deletable = false;
+    },
+
+    /**
+     * The HTML element that should have focus initially when editing starts,
+     * if a specific element wasn't clicked.
+     * Defaults to the first <input> element; can be overriden by subclasses if
+     * a different element should be focused.
+     * @type {HTMLElement}
+     */
+    get initialFocusElement() {
+      return this.contentElement.querySelector('input');
+    },
+
+    /**
+     * Whether the input in currently valid to submit. If this returns false
+     * when editing would be submitted, either editing will not be ended,
+     * or it will be cancelled, depending on the context.
+     * Can be overrided by subclasses to perform input validation.
+     * @type {boolean}
+     */
+    get currentInputIsValid() {
+      return true;
+    },
+
+    /**
+     * Returns true if the item has been changed by an edit.
+     * Can be overrided by subclasses to return false when nothing has changed
+     * to avoid unnecessary commits.
+     * @type {boolean}
+     */
+    get hasBeenEdited() {
+      return true;
+    },
+
+    /**
+     * Returns a div containing an <input>, as well as static text if
+     * isPlaceholder is not true.
+     * @param {string} text The text of the cell.
+     * @return {HTMLElement} The HTML element for the cell.
+     * @private
+     */
+    createEditableTextCell: function(text) {
+      var container = this.ownerDocument.createElement('div');
+
+      if (!this.isPlaceholder) {
+        var textEl = this.ownerDocument.createElement('div');
+        textEl.className = 'static-text';
+        textEl.textContent = text;
+        textEl.setAttribute('displaymode', 'static');
+        container.appendChild(textEl);
+      }
+
+      var inputEl = this.ownerDocument.createElement('input');
+      inputEl.type = 'text';
+      inputEl.value = text;
+      if (!this.isPlaceholder) {
+        inputEl.setAttribute('displaymode', 'edit');
+        inputEl.staticVersion = textEl;
+      } else {
+        // At this point |this| is not attached to the parent list yet, so give
+        // a short timeout in order for the attachment to occur.
+        var self = this;
+        window.setTimeout(function() {
+          var list = self.parentNode;
+          if (list && list.focusPlaceholder) {
+            list.focusPlaceholder = false;
+            if (list.shouldFocusPlaceholder())
+              inputEl.focus();
+          }
+        }, 50);
+      }
+
+      inputEl.addEventListener('focus', this.handleFocus_.bind(this));
+      container.appendChild(inputEl);
+      this.editFields_.push(inputEl);
+
+      return container;
+    },
+
+    /**
+     * Resets the editable version of any controls created by createEditable*
+     * to match the static text.
+     * @private
+     */
+    resetEditableValues_: function() {
+      var editFields = this.editFields_;
+      for (var i = 0; i < editFields.length; i++) {
+        var staticLabel = editFields[i].staticVersion;
+        if (!staticLabel && !this.isPlaceholder)
+          continue;
+
+        if (editFields[i].tagName == 'INPUT') {
+          editFields[i].value =
+            this.isPlaceholder ? '' : staticLabel.textContent;
+        }
+        // Add more tag types here as new createEditable* methods are added.
+
+        editFields[i].setCustomValidity('');
+      }
+    },
+
+    /**
+     * Sets the static version of any controls created by createEditable*
+     * to match the current value of the editable version. Called on commit so
+     * that there's no flicker of the old value before the model updates.
+     * @private
+     */
+    updateStaticValues_: function() {
+      var editFields = this.editFields_;
+      for (var i = 0; i < editFields.length; i++) {
+        var staticLabel = editFields[i].staticVersion;
+        if (!staticLabel)
+          continue;
+
+        if (editFields[i].tagName == 'INPUT')
+          staticLabel.textContent = editFields[i].value;
+        // Add more tag types here as new createEditable* methods are added.
+      }
+    },
+
+    /**
+     * Called a key is pressed. Handles committing and cancelling edits.
+     * @param {Event} e The key down event.
+     * @private
+     */
+    handleKeyDown_: function(e) {
+      if (!this.editing)
+        return;
+
+      var endEdit = false;
+      switch (e.keyIdentifier) {
+        case 'U+001B':  // Esc
+          this.editCancelled_ = true;
+          endEdit = true;
+          break;
+        case 'Enter':
+          if (this.currentInputIsValid)
+            endEdit = true;
+          break;
+      }
+
+      if (endEdit) {
+        // Blurring will trigger the edit to end; see InlineEditableItemList.
+        this.ownerDocument.activeElement.blur();
+        // Make sure that handled keys aren't passed on and double-handled.
+        // (e.g., esc shouldn't both cancel an edit and close a subpage)
+        e.stopPropagation();
+      }
+    },
+
+    /**
+     * Called when the list item is clicked. If the click target corresponds to
+     * an editable item, stores that item to focus when edit mode is started.
+     * @param {Event} e The mouse down event.
+     * @private
+     */
+    handleMouseDown_: function(e) {
+      if (!this.editable || this.editing)
+        return;
+
+      var clickTarget = e.target;
+      var editFields = this.editFields_;
+      for (var i = 0; i < editFields.length; i++) {
+        if (editFields[i] == clickTarget ||
+            editFields[i].staticVersion == clickTarget) {
+          this.editClickTarget_ = editFields[i];
+          return;
+        }
+      }
+    },
+  };
+
+  /**
+   * Takes care of committing changes to inline editable list items when the
+   * window loses focus.
+   */
+  function handleWindowBlurs() {
+    window.addEventListener('blur', function(e) {
+      var itemAncestor = findAncestor(document.activeElement, function(node) {
+        return node instanceof InlineEditableItem;
+      });
+      if (itemAncestor);
+        document.activeElement.blur();
+    });
+  }
+  handleWindowBlurs();
+
+  var InlineEditableItemList = cr.ui.define('list');
+
+  InlineEditableItemList.prototype = {
+    __proto__: DeletableItemList.prototype,
+
+    /**
+     * Focuses the input element of the placeholder if true.
+     * @type {boolean}
+     */
+    focusPlaceholder: false,
+
+    /** @inheritDoc */
+    decorate: function() {
+      DeletableItemList.prototype.decorate.call(this);
+      this.setAttribute('inlineeditable', '');
+      this.addEventListener('hasElementFocusChange',
+                            this.handleListFocusChange_);
+    },
+
+    /**
+     * Called when the list hierarchy as a whole loses or gains focus; starts
+     * or ends editing for the lead item if necessary.
+     * @param {Event} e The change event.
+     * @private
+     */
+    handleListFocusChange_: function(e) {
+      var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex);
+      if (leadItem) {
+        if (e.newValue)
+          leadItem.updateEditState();
+        else
+          leadItem.editing = false;
+      }
+    },
+
+    /**
+     * May be overridden by subclasses to disable focusing the placeholder.
+     * @return true if the placeholder element should be focused on edit commit.
+     */
+    shouldFocusPlaceholder: function() {
+      return true;
+    },
+  };
+
+  // Export
+  return {
+    InlineEditableItem: InlineEditableItem,
+    InlineEditableItemList: InlineEditableItemList,
+  };
+});
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..74bc6f3
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/_locales/en/messages.json
@@ -0,0 +1,54 @@
+{
+  "extName":  {
+    "message": "Proxy Extension API Sample",
+    "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 'not_controllable'."
+  },
+  "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 'controlled_by_other_extensions'."
+  },
+  "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."
+  },
+  "errorProxyDetailedError": {
+    "message": "ProxyError: $1. $2.",
+    "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.js b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/background.js
new file mode 100644
index 0000000..8640d41
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/background.js
@@ -0,0 +1,23 @@
+// 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 initializes the background page by loading a
+ * ProxyErrorHandler, and resetting proxy settings if required.
+ *
+ * @author Mike West <mkwst@google.com>
+ */
+
+document.addEventListener("DOMContentLoaded", function () {
+  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.proxy.settings.set(
+        {'value': persistedSettings.regular});
+  }
+});
diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/icon128.png b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/icon128.png
new file mode 100644
index 0000000..0e4f441
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/icon128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/icon16.png b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/icon16.png
new file mode 100644
index 0000000..ddab866
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/icon16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/icon32.png b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/icon32.png
new file mode 100644
index 0000000..29504c0
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/icon32.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/icon48.png b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/icon48.png
new file mode 100644
index 0000000..b85ae2e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/icon48.png
Binary files differ
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..41e4e86
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/manifest.json
@@ -0,0 +1,27 @@
+{
+  "name": "__MSG_extName__",
+  "version": "0.4",
+  "description": "__MSG_extDescription__",
+  "default_locale": "en",
+  "browser_action": {
+    "default_icon": "icon16.png",
+    "default_popup": "popup.html"
+  },
+  "icons": {
+    "16": "icon16.png",
+    "32": "icon32.png",
+    "48": "icon48.png",
+    "128": "icon128.png"
+  },
+  "background": {
+    "scripts": [
+      "proxy_form_controller.js",
+      "proxy_error_handler.js",
+      "background.js"
+    ]
+  },
+  "permissions": [
+    "proxy"
+  ],
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/popup.css b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/popup.css
new file mode 100644
index 0000000..5dc17d4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/popup.css
@@ -0,0 +1,198 @@
+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.port {
+  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: 500px;
+  padding: 20px;
+  margin: -80px 0 0 -270px;
+  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;
+  -webkit-transform: scale(0);
+}
+
+.overlay a {
+  color:  #FFF;
+}
+
+.overlay.visible {
+  opacity: 1;
+  -webkit-transform: scale(1);
+}
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..595283c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/popup.html
@@ -0,0 +1,109 @@
+<!doctype html>
+<html>
+<head>
+  <title>Popup for Proxy API Test</title>
+  <link href="./popup.css" type="text/css" rel="stylesheet">
+</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="text" name="proxyPortHttp" id="proxyPortHttp" class="port">
+
+            <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="text" name="proxyPortHttps" id="proxyPortHttps" class="port">
+          </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="text" name="proxyPortFtp" id="proxyPortFtp" class="port">
+          </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="text" name="proxyPortFallback" id="proxyPortFallback" class="port">
+          </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 src="./popup.js"></script>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/proxy_configuration/popup.js b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/popup.js
new file mode 100644
index 0000000..1c1f36d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/popup.js
@@ -0,0 +1,14 @@
+// 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 initializes the extension's popup by creating a
+ * ProxyFormController object.
+ *
+ * @author Mike West <mkwst@google.com>
+ */
+
+document.addEventListener('DOMContentLoaded', function () {
+  var c = new ProxyFormController( 'proxyForm' );
+});
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..7d2cd22
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/proxy_error_handler.js
@@ -0,0 +1,104 @@
+// 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.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() {
+    chrome.browserAction.setBadgeText({text: ''});
+    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..80f909b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/proxy_form_controller.js
@@ -0,0 +1,793 @@
+// 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: 'not_controllable',
+  OTHER_EXTENSION: 'controlled_by_other_extension',
+  AVAILABLE: 'controllable_by_this_extension',
+  CONTROLLING: 'controlled_by_this_extension'
+};
+
+/**
+ * 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 = null;
+  if (window.localStorage['proxyConfig'] !== undefined)
+    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},
+
+  /**
+   * Do we have access to incognito mode?
+   * @type {boolean}
+   * @private
+   */
+  isAllowedIncognitoAccess_: false,
+
+  /**
+   * @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/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/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/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.extension.isAllowedIncognitoAccess(
+        this.handleIncognitoAccessResponse_.bind(this));
+  },
+
+  /**
+   * Handles the respnse from `chrome.extension.isAllowedIncognitoAccess`
+   * We can't render the form until we know what our access level is, so
+   * we wait until we have confirmed incognito access levels before
+   * asking for the proxy state.
+   *
+   * @param {boolean} state The state of incognito access.
+   * @private
+   */
+  handleIncognitoAccessResponse_: function(state) {
+    this.isAllowedIncognitoAccess_ = state;
+    chrome.proxy.settings.get({incognito: false},
+        this.handleRegularState_.bind(this));
+    if (this.isAllowedIncognitoAccess_) {
+      chrome.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.
+   *
+   * Proxy errors (and the browser action's badge) are cleared upon setting new
+   * values.
+   *
+   * @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.proxy.settings.set(
+        {value: this.config_.regular, scope: 'regular'},
+        this.callbackForRegularSettings_.bind(this));
+    chrome.extension.sendRequest({type: 'clearError'});
+  },
+
+  /**
+   * 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.proxy.settings.set(
+          {value: this.config_.incognito, scope: 'incognito_persistent'},
+          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('div');
+    success.classList.add('overlay');
+    success.setAttribute('role', 'alert');
+    success.textContent = msg;
+    document.body.appendChild(success);
+
+    setTimeout(function() { success.classList.add('visible'); }, 10);
+    setTimeout(function() {
+      if (close === false)
+        success.classList.remove('visible');
+      else
+        window.close();
+    }, 4000);
+  },
+
+
+  /**
+   * Parses the proxy configuration form, and generates a ProxyConfig object
+   * that can be passed to `useCustomProxyConfig`.
+   *
+   * @see http://code.google.com/chrome/extensions/trunk/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, mandatory: true}};
+        else if (pacManual)
+          return {mode: 'pac_script',
+                  pacScript: {data: pacManual, mandatory: true}};
+        else
+          return {mode: 'auto_detect'};
+      case ProxyFormController.ProxyTypes.FIXED:
+        var config = {mode: 'fixed_servers'};
+        if (this.singleProxy) {
+          config.rules = {
+            singleProxy: this.singleProxy,
+            bypassList: this.bypassList
+          };
+        } 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];
+
+    // Cancel the button click.
+    e.preventDefault();
+    e.stopPropagation();
+
+    // If we can't access Incognito settings, throw a message and return.
+    if (!this.isAllowedIncognitoAccess_) {
+      var msg = "I'm sorry, Dave, I'm afraid I can't do that. Give me access " +
+                "to Incognito settings by checking the checkbox labeled " +
+                "'Allow in Incognito mode', which is visible at " +
+                "chrome://extensions.";
+      this.generateAlert_(msg, false);
+      return;
+    }
+
+    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 regular window settings.';
+    }
+  },
+
+
+  /**
+   * 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 = JSON.parse(response.result);
+      console.error(error);
+      // TODO(mkwst): Do something more interesting
+      this.generateAlert_(
+          chrome.i18n.getMessage(
+              error.details ? 'errorProxyDetailedError' : 'errorProxyError',
+              [error.error, error.details]),
+          false);
+    }
+  }
+};
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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+  },
+  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..f778ec9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/proxy_configuration/test/proxy_form_controller_test.js
@@ -0,0 +1,513 @@
+// 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.proxy`, `chrome.i18n`, and `chrome.extension` APIs
+chrome = chrome || {
+  proxy: {
+    settings: {
+      get: function() {},
+      clear: function() {},
+      set: function() {}
+    }
+  },
+  i18n: {
+    getMessage: function(x) { return x; }
+  },
+  extension: {
+    sendRequest: function() {},
+    isAllowedIncognitoAccess: function(funk) {
+      funk(true);
+    }
+  }
+};
+
+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 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.proxy = {
+      settings: {
+        get: mockFunctionFactory({
+               value: {mode: 'system' },
+               levelOfControl: 'controllable_by_this_extension' }),
+        clear: mockFunctionFactory({
+                 value: {mode: 'system' },
+                 levelOfControl: 'controllable_by_this_extension' }),
+        set: mockFunctionFactory({
+               value: {mode: 'system' },
+               levelOfControl: 'controllable_by_this_extension' })
+      }
+    };
+  },
+
+  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.proxy.settings.get`, and react accordingly.
+  // Let's see if that happens with the next four sets of assertions.
+  testSetupFormSystem: function() {
+    chrome.proxy.settings.get = mockFunctionFactory({
+      value: {mode: 'system'},
+      levelOfControl: 'controllable_by_this_extension'
+    });
+
+    fixture.innerHTML = baselineHTML;
+    this.controller_ = new ProxyFormController('proxyForm');
+    // Wait for async calls to fire
+    this.wait(100, function() {
+      this.assertEqual(
+          6,
+          chrome.proxy.settings.get.getCallList().length);
+      this.assert(
+          document.getElementById(ProxyFormController.ProxyTypes.SYSTEM)
+              .classList.contains('active'));
+    });
+  },
+
+  testSetupFormDirect: function() {
+    chrome.proxy.settings.get =
+        mockFunctionFactory({value: {mode: 'direct'},
+             levelOfControl: 'controllable_by_this_extension'}, true);
+
+    fixture.innerHTML = baselineHTML;
+    this.controller_ = new ProxyFormController('proxyForm');
+    // Wait for async calls to fire
+    this.wait(100, function() {
+      this.assertEqual(
+          2,
+          chrome.proxy.settings.get.getCallList().length);
+      this.assert(
+          document.getElementById(ProxyFormController.ProxyTypes.DIRECT)
+              .classList.contains('active'));
+    });
+  },
+
+  testSetupFormPac: function() {
+    chrome.proxy.settings.get =
+        mockFunctionFactory({value: {mode: 'pac_script' },
+             levelOfControl: 'controllable_by_this_extension'});
+
+    fixture.innerHTML = baselineHTML;
+    this.controller_ = new ProxyFormController('proxyForm');
+    // Wait for async calls to fire
+    this.wait(100, function() {
+      this.assertEqual(
+          2,
+          chrome.proxy.settings.get.getCallList().length);
+      this.assert(
+          document.getElementById(ProxyFormController.ProxyTypes.PAC)
+              .classList.contains('active'));
+    });
+  },
+
+  testSetupFormFixed: function() {
+    chrome.proxy.settings.get =
+        mockFunctionFactory({value: {mode: 'fixed_servers' },
+             levelOfControl: 'controllable_by_this_extension'});
+
+    fixture.innerHTML = baselineHTML;
+    this.controller_ = new ProxyFormController('proxyForm');
+    // Wait for async calls to fire
+    this.wait(100, function() {
+      this.assertEqual(
+          2,
+          chrome.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/examples/extensions/speak_selection/SpeakSel128.png b/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel128.png
new file mode 100644
index 0000000..800ef3d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel16.png b/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel16.png
new file mode 100644
index 0000000..5c9c6b6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel19-active.png b/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel19-active.png
new file mode 100644
index 0000000..e0e4112
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel19-active.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel19.png b/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel19.png
new file mode 100644
index 0000000..03f4eba
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel19.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel256.png b/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel256.png
new file mode 100644
index 0000000..d91b29f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel256.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel48.png b/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel48.png
new file mode 100644
index 0000000..cece266
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/speak_selection/SpeakSel48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/speak_selection/background.js b/chrome/common/extensions/docs/examples/extensions/speak_selection/background.js
new file mode 100644
index 0000000..a67629a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/speak_selection/background.js
@@ -0,0 +1,83 @@
+/**
+ * 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.
+ */
+
+var lastUtterance = '';
+var speaking = false;
+var globalUtteranceIndex = 0;
+
+if (localStorage['lastVersionUsed'] != '1') {
+  localStorage['lastVersionUsed'] = '1';
+  chrome.tabs.create({
+    url: chrome.extension.getURL('options.html')
+  });
+}
+
+function speak(utterance) {
+  if (speaking && utterance == lastUtterance) {
+    chrome.tts.stop();
+    return;
+  }
+
+  speaking = true;
+  lastUtterance = utterance;
+  globalUtteranceIndex++;
+  var utteranceIndex = globalUtteranceIndex;
+
+  chrome.browserAction.setIcon({path: 'SpeakSel19-active.png'});
+
+  var rate = localStorage['rate'] || 1.0;
+  var pitch = localStorage['pitch'] || 1.0;
+  var volume = localStorage['volume'] || 1.0;
+  var voice = localStorage['voice'];
+  chrome.tts.speak(
+      utterance,
+      {voiceName: voice,
+       rate: parseFloat(rate),
+       pitch: parseFloat(pitch),
+       volume: parseFloat(volume),
+       onEvent: function(evt) {
+         if (evt.type == 'end' ||
+             evt.type == 'interrupted' ||
+             evt.type == 'cancelled' ||
+             evt.type == 'error') {
+           if (utteranceIndex == globalUtteranceIndex) {
+             speaking = false;
+             chrome.browserAction.setIcon({path: 'SpeakSel19.png'});
+           }
+         }
+       }
+      });
+}
+
+function initBackground() {
+  loadContentScriptInAllTabs();
+
+  var defaultKeyString = getDefaultKeyString();
+  var keyString = localStorage['speakKey'];
+  if (keyString == undefined) {
+    keyString = defaultKeyString;
+    localStorage['speakKey'] = keyString;
+  }
+  sendKeyToAllTabs(keyString);
+
+  chrome.extension.onRequest.addListener(
+      function(request, sender, sendResponse) {
+        if (request['init']) {
+          sendResponse({'key': localStorage['speakKey']});
+        } else if (request['speak']) {
+          speak(request['speak']);
+        }
+      });
+
+  chrome.browserAction.onClicked.addListener(
+      function(tab) {
+        chrome.tabs.sendRequest(
+            tab.id,
+            {'speakSelection': true});
+      });
+}
+
+initBackground();
diff --git a/chrome/common/extensions/docs/examples/extensions/speak_selection/content_script.js b/chrome/common/extensions/docs/examples/extensions/speak_selection/content_script.js
new file mode 100644
index 0000000..69f8879
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/speak_selection/content_script.js
@@ -0,0 +1,56 @@
+/**
+ * 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.
+ */
+
+var speakKeyStr;
+
+function speakSelection() {
+  var focused = document.activeElement;
+  var selectedText;
+  if (focused) {
+    try {
+      selectedText = focused.value.substring(
+          focused.selectionStart, focused.selectionEnd);
+    } catch (err) {
+    }
+  }
+  if (selectedText == undefined) {
+    var sel = window.getSelection();
+    var selectedText = sel.toString();
+  }
+  chrome.extension.sendRequest({'speak': selectedText});
+}
+
+function onExtensionMessage(request) {
+  if (request['speakSelection'] != undefined) {
+    if (!document.hasFocus()) {
+      return;
+    }
+    speakSelection();
+  } else if (request['key'] != undefined) {
+    speakKeyStr = request['key'];
+  }
+}
+
+function initContentScript() {
+  chrome.extension.onRequest.addListener(onExtensionMessage);
+  chrome.extension.sendRequest({'init': true}, onExtensionMessage);
+
+  document.addEventListener('keydown', function(evt) {
+    if (!document.hasFocus()) {
+      return true;
+    }
+    var keyStr = keyEventToString(evt);
+    if (keyStr == speakKeyStr && speakKeyStr.length > 0) {
+      speakSelection();
+      evt.stopPropagation();
+      evt.preventDefault();
+      return false;
+    }
+    return true;
+  }, false);
+}
+
+initContentScript();
diff --git a/chrome/common/extensions/docs/examples/extensions/speak_selection/keycodes.js b/chrome/common/extensions/docs/examples/extensions/speak_selection/keycodes.js
new file mode 100644
index 0000000..edea93e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/speak_selection/keycodes.js
@@ -0,0 +1,94 @@
+/**
+ * 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.
+ */
+
+var KEY_MAP = {
+  12: 'Clear',
+  14: 'Enter',
+  33: 'PgUp',
+  34: 'PgDown',
+  35: 'End',
+  36: 'Home',
+  37: 'Left',
+  38: 'Up',
+  39: 'Right',
+  40: 'Down',
+  45: 'Insert',
+  46: 'Delete',
+  96: 'Numpad0',
+  97: 'Numpad1',
+  98: 'Numpad2',
+  99: 'Numpad3',
+  100: 'Numpad4',
+  101: 'Numpad5',
+  102: 'Numpad6',
+  103: 'Numpad7',
+  104: 'Numpad8',
+  105: 'Numpad9',
+  106: '*',
+  107: 'Plus',
+  108: '_',
+  109: '-',
+  111: '/',
+  112: 'F1',
+  113: 'F2',
+  114: 'F3',
+  115: 'F4',
+  116: 'F5',
+  117: 'F6',
+  118: 'F7',
+  119: 'F8',
+  120: 'F9',
+  121: 'F10',
+  122: 'F11',
+  123: 'F12',
+  124: 'F13',
+  125: 'F14',
+  126: 'F15',
+  186: ';',
+  187: '=',
+  188: ',',
+  189: '-',
+  190: '.',
+  191: '/',
+  192: '`',
+  219: '[',
+  221: ']'
+};
+
+var isMac = (navigator.appVersion.indexOf("Mac") != -1);
+
+function keyEventToString(evt) {
+  var tokens = [];
+  if (evt.ctrlKey) {
+    tokens.push('Control');
+  }
+  if (evt.altKey) {
+    tokens.push(isMac ? 'Option' : 'Alt');
+  }
+  if (evt.metaKey) {
+    tokens.push(isMac ? 'Command' : 'Meta');
+  }
+  if (evt.shiftKey) {
+    tokens.push('Shift');
+  }
+  if (evt.keyCode >= 48 && evt.keyCode <= 90) {
+    tokens.push(String.fromCharCode(evt.keyCode));
+  } else if (KEY_MAP[evt.keyCode]) {
+    tokens.push(KEY_MAP[evt.keyCode]);
+        } else {
+    return '';
+        }
+  return tokens.join('+');
+}
+
+function getDefaultKeyString() {
+  return keyEventToString({
+    keyCode: 83,  // 's'
+    shiftKey: true,
+    altKey: true,
+    ctrlKey: true,
+    metaKey: false});
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/speak_selection/manifest.json b/chrome/common/extensions/docs/examples/extensions/speak_selection/manifest.json
new file mode 100644
index 0000000..fed3608
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/speak_selection/manifest.json
@@ -0,0 +1,49 @@
+{
+  "name": "Speak Selection",
+  "version": "1.1",
+  "description": "Speaks the current selection out loud.",
+  "permissions": [
+    "<all_urls>",
+    "tts",
+    "tabs"
+  ],
+
+  "background": {
+    "scripts": [
+      "keycodes.js",
+      "tabs.js",
+      "background.js"
+    ]
+  },
+
+  "browser_action": {
+    "default_icon": "SpeakSel19.png",
+    "default_title": "Speak Selection"
+  },
+
+  "options_page": "options.html",
+
+  "minimum_chrome_version": "14",
+
+  "content_scripts": [
+    {
+      "matches": [
+        "<all_urls>"
+      ],
+      "all_frames": true,
+      "js": [
+        "keycodes.js",
+        "content_script.js"
+      ]
+    }
+  ],
+
+  "icons": {
+    "16": "SpeakSel16.png",
+    "48": "SpeakSel48.png",
+    "128": "SpeakSel128.png",
+    "256": "SpeakSel256.png"
+  },
+
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/speak_selection/options.html b/chrome/common/extensions/docs/examples/extensions/speak_selection/options.html
new file mode 100644
index 0000000..f52ce7e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/speak_selection/options.html
@@ -0,0 +1,152 @@
+<html>
+<head>
+  <title>Speak Selection Options</title>
+  <style>
+    body {
+      font-family: arial, helvetica, sans-serif;
+    }
+    .banner {
+      width: 100%;
+      float: left;
+    }
+    .banner_left {
+      padding: 8px;
+      float: left;
+    }
+    .banner_right {
+      padding: 8px;
+    }
+    .body_wrapper {
+      width: 100%;
+      float: left;
+    }
+    .body_left {
+      border: 0;
+      padding: 0;
+      margin: 0;
+      width: 50%;
+      float: left;
+    }
+    .body_right {
+      border: 0;
+      padding: 0;
+      margin: 0;
+      width: 46%;
+      float: left;
+    }
+    .body_inner {
+      padding: 0 32px;
+    }
+    .browser_action {
+      vertical-align: middle;
+      margin: 0 2px 3px 2px;
+    }
+    .ctrl_label {
+      width: 100px;
+      float: left;
+    }
+    .ctrl_wrap {
+      margin: 18px 8px;
+    }
+    .ctrl {
+      width: 200px;
+    }
+    #hotkey {
+      font-size: 16px;
+      width: 15em;
+      margin-left: 12px;
+    }
+    #test {
+    }
+    #defaults {
+      margin-left: 24px;
+    }
+  </style>
+  <script src="keycodes.js"></script>
+  <script src="tabs.js"></script>
+  <script src="options.js"></script>
+  <script src="content_script.js"></script>
+</head>
+<body>
+
+<div class="banner">
+  <div class="banner_left">
+    <img src="SpeakSel128.png" class="logo" alt="">
+  </div>
+  <div class="banner_right">
+    <h1>Speak Selection</h1>
+    <p>
+      This extension lets you use Chrome's text-to-speech (TTS) capabilities
+      to speak any text you find on the web.
+    </p>
+  </div>
+</div>
+
+<div class="body_wrapper">
+  <div class="body_left">
+    <div class="body_inner">
+      <h2>Speech Settings</h2>
+
+      <div class="ctrl_label">
+        <label for="voice">Voice:</label>
+      </div>
+      <div class="ctrl_wrap">
+        <select id="voice" class="ctrl"></select>
+      </div>
+
+      <div class="ctrl_label">
+        <label for="rate">Rate:</label>
+      </div>
+      <div class="ctrl_wrap">
+        <input id="rate" type="range" class="ctrl"
+               min=0.5 max=2.0 step=0.1 value=1.0></input>
+      </div>
+
+      <div class="ctrl_label">
+        <label for="pitch">Pitch:</label>
+      </div>
+      <div class="ctrl_wrap">
+        <input id="pitch" type="range" class="ctrl"
+               min=0.5 max=1.5 step=0.1 value=1.0></input>
+      </div>
+
+      <div class="ctrl_label">
+        <label for="volume">Volume:</label>
+      </div>
+      <div class="ctrl_wrap">
+        <input id="volume" type="range" class="ctrl"
+               min=0.0 max=1.0 step=0.1 value=1.0></input>
+      </div>
+
+      <div class="ctrl_label">
+         &nbsp;
+      </div>
+      <div class="ctrl_wrap">
+        <button id="test">Test Speech</button>
+        <button id="defaults">Defaults</button>
+      </div>
+
+    </div>
+  </div>
+  <div class="body_right">
+    <div class="body_inner">
+      <h2>When to speak</h2>
+      <p>
+        <span id="selected">Select some text</span>
+        anywhere on a webpage, then either:
+      </p>
+
+      <p>1. Click on the
+        <img class="browser_action" src="SpeakSel19.png" alt="Speak Selection">
+        button in the toolbar &#x2197;, or
+      </p>
+
+      <p>2. Use this hot key: <input type="text" id="hotkey"></input>
+      </p>
+      <p>Click the button or press the key again to stop speech.</p>
+    </div>
+  </div>
+</div>
+
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/speak_selection/options.js b/chrome/common/extensions/docs/examples/extensions/speak_selection/options.js
new file mode 100644
index 0000000..f77e739
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/speak_selection/options.js
@@ -0,0 +1,130 @@
+// Copyright (c) 2012 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.
+
+function load() {
+  var selectedElement = document.getElementById('selected');
+  var sel = window.getSelection();
+  sel.removeAllRanges();
+  var range = document.createRange();
+  range.selectNode(selectedElement);
+  sel.addRange(range);
+
+  var rateElement = document.getElementById('rate');
+  var pitchElement = document.getElementById('pitch');
+  var volumeElement = document.getElementById('volume');
+  var rate = localStorage['rate'] || 1.0;
+  var pitch = localStorage['pitch'] || 1.0;
+  var volume = localStorage['volume'] || 1.0;
+  rateElement.value = rate;
+  pitchElement.value = pitch;
+  volumeElement.value = volume;
+  function listener(evt) {
+    rate = rateElement.value;
+    localStorage['rate'] = rate;
+    pitch = pitchElement.value;
+    localStorage['pitch'] = pitch;
+    volume = volumeElement.value;
+    localStorage['volume'] = volume;
+  }
+  rateElement.addEventListener('keyup', listener, false);
+  pitchElement.addEventListener('keyup', listener, false);
+  volumeElement.addEventListener('keyup', listener, false);
+  rateElement.addEventListener('mouseup', listener, false);
+  pitchElement.addEventListener('mouseup', listener, false);
+  volumeElement.addEventListener('mouseup', listener, false);
+
+  var defaultsButton = document.getElementById('defaults');
+  defaultsButton.addEventListener('click', function(evt) {
+    rate = 1.0;
+    pitch = 1.0;
+    volume = 1.0;
+    localStorage['rate'] = rate;
+    localStorage['pitch'] = pitch;
+    localStorage['volume'] = volume;
+    rateElement.value = rate;
+    pitchElement.value = pitch;
+    volumeElement.value = volume;
+  }, false);
+
+  var voice = document.getElementById('voice');
+  var voiceArray = [];
+  chrome.tts.getVoices(function(va) {
+    voiceArray = va;
+    for (var i = 0; i < voiceArray.length; i++) {
+      var opt = document.createElement('option');
+      var name = voiceArray[i].voiceName;
+      if (name == localStorage['voice']) {
+        opt.setAttribute('selected', '');
+      }
+      opt.setAttribute('value', name);
+      opt.innerText = voiceArray[i].voiceName;
+      voice.appendChild(opt);
+    }
+  });
+  voice.addEventListener('change', function() {
+    var i = voice.selectedIndex;
+    localStorage['voice'] = voiceArray[i].voiceName;
+  }, false);
+
+  var defaultKeyString = getDefaultKeyString();
+
+  var keyString = localStorage['speakKey'];
+  if (keyString == undefined) {
+    keyString = defaultKeyString;
+  }
+
+  var testButton = document.getElementById('test');
+  testButton.addEventListener('click', function(evt) {
+    chrome.tts.speak(
+        'Testing speech synthesis',
+        {voiceName: localStorage['voice'],
+         rate: parseFloat(rate),
+         pitch: parseFloat(pitch),
+         volume: parseFloat(volume)});
+  });
+
+  var hotKeyElement = document.getElementById('hotkey');
+  hotKeyElement.value = keyString;
+  hotKeyElement.addEventListener('keydown', function(evt) {
+    switch (evt.keyCode) {
+      case 27:  // Escape
+        evt.stopPropagation();
+        evt.preventDefault();
+        hotKeyElement.blur();
+        return false;
+      case 8:   // Backspace
+      case 46:  // Delete
+        evt.stopPropagation();
+        evt.preventDefault();
+        hotKeyElement.value = '';
+        localStorage['speakKey'] = '';
+        sendKeyToAllTabs('');
+        window.speakKeyStr = '';
+        return false;
+      case 9:  // Tab
+        return false;
+      case 16:  // Shift
+      case 17:  // Control
+      case 18:  // Alt/Option
+      case 91:  // Meta/Command
+        evt.stopPropagation();
+        evt.preventDefault();
+        return false;
+    }
+    var keyStr = keyEventToString(evt);
+    if (keyStr) {
+      hotKeyElement.value = keyStr;
+      localStorage['speakKey'] = keyStr;
+      sendKeyToAllTabs(keyStr);
+
+      // Set the key used by the content script running in the options page.
+      window.speakKeyStr = keyStr;
+    }
+    evt.stopPropagation();
+    evt.preventDefault();
+    return false;
+  }, true);
+}
+
+document.addEventListener('DOMContentLoaded', load);
diff --git a/chrome/common/extensions/docs/examples/extensions/speak_selection/tabs.js b/chrome/common/extensions/docs/examples/extensions/speak_selection/tabs.js
new file mode 100644
index 0000000..4edac9c
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/speak_selection/tabs.js
@@ -0,0 +1,34 @@
+/**
+ * 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.
+ */
+
+function sendKeyToAllTabs(keyStr) {
+  chrome.windows.getAll({'populate': true}, function(windows) {
+    for (var i = 0; i < windows.length; i++) {
+      var tabs = windows[i].tabs;
+      for (var j = 0; j < tabs.length; j++) {
+        chrome.tabs.sendRequest(
+            tabs[j].id,
+            {'key': keyStr});
+      }
+    }
+  });
+}
+
+function loadContentScriptInAllTabs() {
+  chrome.windows.getAll({'populate': true}, function(windows) {
+    for (var i = 0; i < windows.length; i++) {
+      var tabs = windows[i].tabs;
+      for (var j = 0; j < tabs.length; j++) {
+        chrome.tabs.executeScript(
+            tabs[j].id,
+            {file: 'keycodes.js', allFrames: true});
+        chrome.tabs.executeScript(
+            tabs[j].id,
+            {file: 'content_script.js', allFrames: true});
+      }
+    }
+  });
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/cuckoo.ogg b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/cuckoo.ogg
new file mode 100644
index 0000000..7a5cd56
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/cuckoo.ogg
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/digital.ogg b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/digital.ogg
new file mode 100644
index 0000000..0c9cf42
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/digital.ogg
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/forest.ogg b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/forest.ogg
new file mode 100644
index 0000000..903b0a4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/forest.ogg
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/grandfather.ogg b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/grandfather.ogg
new file mode 100644
index 0000000..5eb7b64
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/grandfather.ogg
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/groove.ogg b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/groove.ogg
new file mode 100644
index 0000000..c106544
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/groove.ogg
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/metal.ogg b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/metal.ogg
new file mode 100644
index 0000000..22fbceb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/metal.ogg
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/ringing.ogg b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/ringing.ogg
new file mode 100644
index 0000000..04ee778
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/ringing.ogg
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/rooster.ogg b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/rooster.ogg
new file mode 100644
index 0000000..5ef6a0b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/audio/rooster.ogg
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/background.js b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/background.js
new file mode 100644
index 0000000..ae94ff8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/background.js
@@ -0,0 +1,126 @@
+/**
+ * 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.
+ */
+
+var a1Timer = null;
+var a2Timer = null;
+var port = null;
+var iconFlashTimer = null;
+
+var HOUR_MS = 1000 * 60 * 60;
+
+// Override from common.js
+window.stopFlashingIcon = function() {
+  window.clearTimeout(iconFlashTimer);
+  chrome.browserAction.setIcon({'path': 'clock-19.png'});
+};
+
+// Override from common.js
+window.flashIcon = function() {
+  var flashes = 10;
+  function flash() {
+    if (flashes == 0) {
+      stopFlashingIcon();
+      return;
+    }
+
+    if (flashes % 2 == 0) {
+      chrome.browserAction.setIcon({'path': 'clock-highlighted-19.png'});
+    } else {
+      chrome.browserAction.setIcon({'path': 'clock-19.png'});
+    }
+    flashes--;
+    iconFlashTimer = window.setTimeout(flash, 500);
+  }
+  flash();
+};
+
+function setTimer(alarmHours, alarmMinutes) {
+  var alarmTime = (alarmHours * 60 + alarmMinutes) * 60 * 1000;
+  var d = new Date();
+  var now = d.getHours() * HOUR_MS +
+            d.getMinutes() * 60 * 1000 +
+            d.getSeconds() * 1000;
+  var delta = (alarmTime - now);
+
+  if (delta >= -5000 && delta < 1000) {
+    ringAlarm(alarmHours, alarmMinutes);
+    if (port) {
+      port.postMessage({'cmd': 'anim'});
+    }
+    return null;
+  }
+
+  if (delta < 0) {
+    delta += HOUR_MS * 24;
+  }
+  if (delta >= 1000) {
+    if (delta > HOUR_MS) {
+      delta = HOUR_MS;
+    }
+    console.log('Timer set for ' + delta + ' ms');
+    return window.setTimeout(resetTimers, delta);
+  }
+
+  return null;
+};
+
+function resetTimers() {
+  if (a1Timer) {
+    window.clearTimeout(a1Timer);
+  }
+
+  try {
+    var a1_on = (localStorage['a1_on'] == 'true');
+    var a1_tt = localStorage['a1_tt'] || DEFAULT_A1_TT;
+    var a1_ampm = localStorage['a1_ampm'] || DEFAULT_A1_AMPM;
+    if (a1_on) {
+      var alarmHoursMinutes = parseTime(a1_tt, a1_ampm);
+      var alarmHours = alarmHoursMinutes[0];
+      var alarmMinutes = alarmHoursMinutes[1];
+      a1Timer = setTimer(alarmHours, alarmMinutes);
+    }
+  } catch (e) {
+    console.log(e);
+  }
+
+  try {
+    var a2_on = (localStorage['a2_on'] == 'true');
+    var a2_tt = localStorage['a2_tt'] || DEFAULT_A2_TT;
+    var a2_ampm = localStorage['a2_ampm'] || DEFAULT_A2_AMPM;
+    if (a2_on) {
+      var alarmHoursMinutes = parseTime(a2_tt, a2_ampm);
+      var alarmHours = alarmHoursMinutes[0];
+      var alarmMinutes = alarmHoursMinutes[1];
+      a2Timer = setTimer(alarmHours, alarmMinutes);
+    }
+  } catch (e) {
+    console.log(e);
+  }
+
+  if (a1_on || a2_on) {
+    chrome.browserAction.setIcon({'path': 'clock-19.png'});
+  } else {
+    chrome.browserAction.setIcon({'path': 'clock-disabled-19.png'});
+  }
+}
+
+function onLocalStorageChange() {
+  resetTimers();
+}
+
+function initBackground() {
+  window.addEventListener('storage', onLocalStorageChange, false);
+
+  chrome.extension.onConnect.addListener(function(popupPort) {
+    port = popupPort;
+    port.onDisconnect.addListener(function() {
+      port = null;
+    });
+  });
+}
+
+initBackground();
+resetTimers();
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/blank-clock-150.png b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/blank-clock-150.png
new file mode 100644
index 0000000..8d30079
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/blank-clock-150.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/blank-clock-ring1-150.png b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/blank-clock-ring1-150.png
new file mode 100644
index 0000000..63d8018
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/blank-clock-ring1-150.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/blank-clock-ring2-150.png b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/blank-clock-ring2-150.png
new file mode 100644
index 0000000..cbdced9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/blank-clock-ring2-150.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-128.png b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-128.png
new file mode 100644
index 0000000..e72415b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-16.png b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-16.png
new file mode 100644
index 0000000..2c064fe
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-19.png b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-19.png
new file mode 100644
index 0000000..e3d68d4
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-19.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-256.png b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-256.png
new file mode 100644
index 0000000..ce65df0
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-256.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-48.png b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-48.png
new file mode 100644
index 0000000..2d34d43
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-disabled-19.png b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-disabled-19.png
new file mode 100644
index 0000000..553e11a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-disabled-19.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-highlighted-19.png b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-highlighted-19.png
new file mode 100644
index 0000000..99b669e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/clock-highlighted-19.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/common.js b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/common.js
new file mode 100644
index 0000000..037aa39
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/common.js
@@ -0,0 +1,179 @@
+/**
+ * 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.
+ */
+
+var DEFAULT_A1_TT = '09:30';
+var DEFAULT_A1_AMPM = 0;
+var DEFAULT_A2_TT = '03:30';
+var DEFAULT_A2_AMPM = 1;
+var DEFAULT_RATE = 1.0;
+var DEFAULT_VOLUME = 1.0;
+var DEFAULT_PHRASE = 'It\'s $TIME, so get up!';
+var DEFAULT_SOUND = 'ringing';
+
+var audio = null;
+
+var isPlaying = false;
+var isSpeaking = false;
+var isAnimating = false;
+
+// Overridden in popup.js but not in background.js.
+window.displayAlarmAnimation = function() {
+};
+
+// Overridden in popup.js but not in background.js.
+window.stopAlarmAnimation = function() {
+};
+
+// Overridden in background.js but not in popup.js.
+window.flashIcon = function() {
+};
+
+// Overridden in background.js but not in popup.js.
+window.stopFlashingIcon = function() {
+};
+
+function $(id) {
+  return document.getElementById(id);
+}
+
+function parseTime(timeString, ampm) {
+  var time = timeString.match(/^(\d\d):(\d\d)$/);
+  if (!time) {
+    throw 'Cannot parse: ' + timeString;
+  }
+
+  var hours = parseInt(time[1], 10);
+  if (hours == 12 && ampm == 0) {
+    hours = 0;
+  } else {
+    hours += (hours < 12 && ampm == 1)? 12 : 0;
+  }
+  var minutes = parseInt(time[2], 10) || 0;
+
+  return [hours, minutes];
+}
+
+function stopAll() {
+  if (audio) {
+    audio.pause();
+    isPlaying = false;
+  }
+  try {
+    chrome.tts.stop();
+    isSpeaking = false;
+  } catch (e) {
+  }
+  window.stopAlarmAnimation();
+  window.stopFlashingIcon();
+}
+
+function playSound(duckAudio) {
+  if (audio) {
+    audio.pause();
+    document.body.removeChild(audio);
+    audio = null;
+  }
+
+  var currentSound = localStorage['sound'] || DEFAULT_SOUND;
+  if (currentSound == 'none') {
+    return;
+  }
+
+  audio = document.createElement('audio');
+  audio.addEventListener('ended', function(evt) {
+    isPlaying = false;
+  });
+  document.body.appendChild(audio);
+  audio.autoplay = true;
+
+  var src = 'audio/' + currentSound + '.ogg';
+  var volume = parseFloat(localStorage['volume']) || DEFAULT_VOLUME;
+  audio.volume = volume;
+  audio.src = src;
+  isPlaying = true;
+
+  if (duckAudio) {
+    for (var i = 0; i < 10; i++) {
+      (function(i) {
+         window.setTimeout(function() {
+           var duckedVolume = volume * (1.0 - 0.07 * (i + 1));
+           audio.volume = duckedVolume;
+         }, 1800 + 50 * i);
+      })(i);
+    }
+  }
+}
+
+function getTimeString(hh, mm) {
+  var ampm = hh >= 12 ? 'P M' : 'A M';
+  hh = (hh % 12);
+  if (hh == 0)
+    hh = 12;
+  if (mm == 0)
+    mm = 'o\'clock';
+  else if (mm < 10)
+    mm = 'O ' + mm;
+
+  return hh + ' ' + mm + ' ' + ampm;
+}
+
+function speak(text) {
+  var rate = parseFloat(localStorage['rate']) || DEFAULT_RATE;
+  var pitch = 1.0;
+  var volume = parseFloat(localStorage['volume']) || DEFAULT_VOLUME;
+  var voice = localStorage['voice'];
+  chrome.tts.speak(
+      text,
+      {voiceName: voice,
+       rate: rate,
+       pitch: pitch,
+       volume: volume,
+       onEvent: function(evt) {
+         if (evt.type == 'end') {
+           isSpeaking = false;
+         }
+       }
+      });
+}
+
+function speakPhraseWithTimeString(timeString) {
+  var phraseTemplate = localStorage['phrase'] || DEFAULT_PHRASE;
+  var utterance = phraseTemplate.replace(/\$TIME/g, timeString);
+  speak(utterance);
+}
+
+function speakPhraseWithCurrentTime() {
+  var d = new Date();
+  speakPhraseWithTimeString(getTimeString(d.getHours(), d.getMinutes()));
+}
+
+function ringAlarm(alarmHours, alarmMinutes) {
+  window.displayAlarmAnimation();
+  window.flashIcon();
+
+  var phraseTemplate = localStorage['phrase'] || DEFAULT_PHRASE;
+  var currentSound = localStorage['sound'] || DEFAULT_SOUND;
+
+  if (phraseTemplate == '') {
+    playSound(false);
+  } else if (currentSound == 'none') {
+    speakPhraseWithTimeString(getTimeString(alarmHours, alarmMinutes));
+  } else {
+    chrome.tts.stop();
+    playSound(true);
+    isSpeaking = true;
+    window.setTimeout(function() {
+      if (isSpeaking) {
+        speakPhraseWithTimeString(getTimeString(alarmHours, alarmMinutes));
+      }
+    }, 2000);
+  }
+}
+
+function ringAlarmWithCurrentTime() {
+  var d = new Date();
+  ringAlarm(d.getHours(), d.getMinutes());
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/credits.html b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/credits.html
new file mode 100644
index 0000000..a96f048
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/credits.html
@@ -0,0 +1,40 @@
+<html>
+<head>
+  <title>Talking Alarm Clock</title>
+  <style>
+    body {
+      font-family: arial, helvetica, sans-serif;
+      font-size: 13;
+    }
+  </style>
+</head>
+<body>
+
+<img src="clock-128.png" style="float: left; margin: 0 20px 200px 0;">
+
+<p>
+<b>Talking Alarm Clock for Google Chrome</b>
+</p>
+
+<p>
+by Dominic Mazzoni
+</p>
+
+<p>
+Talking Alarm Clock uses the following sound files from Freesound
+(<a href="http://www.freesound.org">http://www.freesound.org</a>):
+</p>
+
+<ul style="margin-left: 150px;">
+<li>alarmclockbeeps from tedthetrumpet
+<li>alarm_clock_ringing from joedeshon
+<li>CuckooClock6DP from acclivity
+<li>ClockStrikes12 from acclivity
+<li>Maxines from TexasMusicForge
+<li>fat_beat_1 from -zin-
+<li>morning_in_the_forest_2007_04_15 from reinsamba
+<li>20070812.rooster from dobroide
+</ul>
+
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/manifest.json b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/manifest.json
new file mode 100644
index 0000000..79f5b50
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/manifest.json
@@ -0,0 +1,23 @@
+{
+  "name": "Talking Alarm Clock",
+  "version": "1.3",
+  "description": "A clock with two configurable alarms that will play a sound and speak a phrase of your choice.",
+  "permissions": [ "background", "tts" ],
+
+  "background": { "scripts": ["common.js", "background.js"] },
+
+  "browser_action": {
+    "default_icon": "clock-19.png",
+    "default_title": "Talking Alarm Clock",
+    "default_popup": "popup.html"
+  },
+
+  "icons": {
+    "16": "clock-16.png",
+    "48": "clock-48.png",
+    "128": "clock-128.png",
+    "256": "clock-256.png"
+  },
+
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/play.png b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/play.png
new file mode 100644
index 0000000..4a805a7
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/play.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/popup.html b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/popup.html
new file mode 100644
index 0000000..1619949
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/popup.html
@@ -0,0 +1,208 @@
+<html>
+<head>
+  <title>Talking Alarm Clock</title>
+  <link href='http://fonts.googleapis.com/css?family=Nova%20Mono' rel='stylesheet' type='text/css'>
+  <style>
+    body {
+      overflow: hidden;
+      margin-top: 0px;
+    }
+    #main {
+      width: 200px;
+      font-family: arial, helvetica, sans-serif;
+      font-size: 13;
+    }
+    #clock {
+      cursor: pointer;
+    }
+    .alarm_wrap {
+      margin-top: 6px;
+      padding: 6px;
+      background-color: #dfd;
+      border: 1px solid #aaa;
+      border-radius: 4px;
+    }
+    .alarm_wrap.disabled {
+      background-color: #eee;
+    }
+    .alarm_wrap[aria-invalid] {
+      background-color: #f99;
+    }
+    .alarm_wrap[aria-invalid] input {
+      color: #900;
+    }
+    .alarm_wrap input {
+      font-size: 20;
+    }
+    .alarm_wrap input[type=time] {
+      font-size: 20;
+      width: 85px;
+    }
+    .alarm_wrap.disabled input {
+      color: #999;
+    }
+    .ctrl_label { 
+      float: left;
+      width: 100%;
+      margin-top: 6px;
+    }
+    .ctrl_wrap {
+      width: 200px;
+      float: left;
+    }
+    .ctrl_wrap input[type=range] {
+      width: 180px;
+      height: 16px;
+    }
+    .buttons {
+      margin-top: 10px;
+    }
+    .voice_options {
+    }
+    #sound {
+      float: left;
+      width: 160px;
+      margin: 8px 0px;
+    }
+    #playsound {
+      padding: 5px;
+      margin: 5px;
+    }
+    #phrase {
+      float: left;
+      width: 160px;
+    }
+    #playspeech {
+      padding: 5px;
+      margin: 5px;
+    }
+    #a1_tt {
+      font-family: Nova Mono;
+      height: 34px;
+    }
+    #a2_tt {
+      font-family: Nova Mono;
+      height: 34px;
+    }
+    #a1_ampm {
+      font-family: Nova Mono;
+    }
+    #a2_ampm {
+      font-family: Nova Mono;
+    }
+    #current_time {
+      font-family: Nova Mono;
+      font-size: 24;
+      height: 32px;
+    }
+    body.nooutline * {
+      outline: none;
+    }
+    .footer {
+      clear:left;
+    }
+  </style>
+  <script src="common.js"></script>
+  <script src="popup.js"></script>
+</head>
+<body>
+
+<div id="main">
+
+  <center>
+    <canvas id="clock" width="150" height="150"
+            role="button" tabindex="0"></canvas>
+    <div id="current_time">00:00:00</div>
+  </center>
+
+  <div id="a1_wrap" class="alarm_wrap">
+    <label for="a1_on">Alarm 1</label>
+    <br>
+    <input id="a1_on" type="checkbox">
+    <input id="a1_tt" type="time" step="300" size="5">
+    <select id="a1_ampm">
+      <option>AM</option>
+      <option selected>PM</option>
+    </select>
+  </div>
+  <div id="a2_wrap" class="alarm_wrap">
+    <label for="a2_on">Alarm 2</label>
+    <br>
+    <input id="a2_on" type="checkbox">
+    <input id="a2_tt" type="time" step="300" size="5">
+    <select id="a2_ampm">
+      <option>AM</option>
+      <option selected>PM</option>
+    </select>
+  </div>
+
+  <div class="voice_options">
+    <div>
+      <div class="ctrl_label">
+        <label for="sound">Sound:</label>
+      </div>
+      <div class="ctrl_wrap">
+        <select id="sound" class="ctrl">
+          <option value="none">None</option>
+          <option selected value="cuckoo">Cuckoo Clock</option>
+          <option value="digital">Digital Alarm Clock</option>
+          <option value="grandfather">Grandfather Clock</option>
+          <option value="groove">Groove</option>
+          <option value="metal">Metal</option>
+          <option value="forest">Morning in the Forest</option>
+          <option value="ringing">Ringing Alarm Clock</option>
+          <option value="rooster">Rooster</option>
+        </select>
+
+        <button id="playsound" title="Play Sound">
+          <img src="play.png">
+        </button>
+      </div>
+
+      <div class="ctrl_label">
+        <label for="sound">Phrase (use $TIME to say time):</label>
+      </div>
+      <div class="ctrl_wrap">
+        <textarea id="phrase"></textarea>
+
+        <button id="playspeech" title="Play Speech">
+          <img src="play.png">
+        </button>
+      </div>
+
+      <div class="ctrl_label">
+        <label for="voice">Voice:</label>
+      </div>
+      <div class="ctrl_wrap">
+        <select id="voice" class="ctrl"></select>
+      </div>
+
+      <div class="ctrl_label">
+        <label for="rate">Speech rate:</label>
+      </div>
+      <div class="ctrl_wrap">
+        <input id="rate" type="range" class="ctrl"
+               min=0.5 max=1.5 step=0.1 value=1.0></input>
+      </div>
+
+      <div class="ctrl_label">
+        <label for="volume">Speech and sound volume:</label>
+      </div>
+      <div class="ctrl_wrap">
+        <input id="volume" type="range" class="ctrl"
+               min=0.0 max=1.0 step=0.1 value=1.0></input>
+      </div>
+    </div>
+  </div>
+
+  <div class="footer">
+    <a href="credits.html" target="_blank">Credits</a>
+    &nbsp;|&nbsp;
+    <a href="https://chrome.google.com/webstore/search?q=tts"
+       target="_blank">Get more voices</a>
+  </div>
+
+</div>
+
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/popup.js b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/popup.js
new file mode 100644
index 0000000..1f0bf06
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/talking_alarm_clock/popup.js
@@ -0,0 +1,403 @@
+// Copyright (c) 2012 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.
+
+var blankClockImage;
+var blankClockAnim1Image;
+var blankClockAnim2Image;
+var animationTimer;
+var currentClockImage;
+var port;
+
+function updateEnabledStatus(alarm) {
+  var enabled = $('a' + alarm + '_on').checked;
+  $('a' + alarm + '_tt').disabled = !enabled;
+  $('a' + alarm + '_ampm').disabled = !enabled;
+  var valid = true;
+  try {
+    var tt = $('a' + alarm + '_tt').value;
+    var ampm = $('a' + alarm + '_ampm').selectedIndex;
+    parseTime(tt, ampm);
+  } catch (x) {
+    valid = false;
+  }
+  if (valid) {
+    $('a' + alarm + '_wrap').removeAttribute('aria-invalid');
+  } else {
+    $('a' + alarm + '_wrap').setAttribute('aria-invalid', 'true');
+  }
+  if (enabled) {
+    $('a' + alarm + '_wrap').classList.remove('disabled');
+  } else {
+    $('a' + alarm + '_wrap').classList.add('disabled');
+  }
+}
+
+function loadAllImages() {
+  var loadCount = 0;
+  var img = new Image();
+  img.onload = function() {
+    blankClockImage = img;
+    currentClockImage = blankClockImage;
+    drawClock();
+  };
+  img.src = 'blank-clock-150.png';
+
+  // These will finish loading before they're needed, no need
+  // for an onload handler.
+  blankClockAnim1Image = new Image();
+  blankClockAnim1Image.src = 'blank-clock-ring1-150.png';
+  blankClockAnim2Image = new Image();
+  blankClockAnim2Image.src = 'blank-clock-ring2-150.png';
+}
+
+function drawClock(hh, mm, ss) {
+  if (hh == undefined || mm == undefined) {
+    var d = new Date();
+    hh = d.getHours();
+    mm = d.getMinutes();
+    ss = d.getSeconds() + 0.001 * d.getMilliseconds();
+  }
+
+  if (!currentClockImage) {
+    loadAllImages();
+    return;
+  }
+
+  var ctx = $('clock').getContext('2d');
+  ctx.drawImage(currentClockImage, 0, 0);
+
+  // Move the hour by the fraction of the minute
+  hh = (hh % 12) + (mm / 60);
+
+  // Move the minute by the fraction of the second
+  mm += (ss / 60);
+
+  var hourAngle = Math.PI * hh / 6;
+  var hourX = Math.sin(hourAngle);
+  var hourY = -Math.cos(hourAngle);
+  var minAngle = Math.PI * mm / 30;
+  var minX = Math.sin(minAngle);
+  var minY = -Math.cos(minAngle);
+  var secAngle = Math.PI * ss / 30;
+  var secX = Math.sin(secAngle);
+  var secY = -Math.cos(secAngle);
+
+  var cx = 75;
+  var cy = 77;
+
+  ctx.lineWidth = 5;
+  ctx.strokeStyle = '#ffffff';
+  ctx.globalAlpha = 0.5;
+  ctx.beginPath();
+  ctx.moveTo(cx - 4 * hourX, cy - 4 * hourY);
+  ctx.lineTo(cx + 20 * hourX, cy + 20 * hourY);
+  ctx.stroke();
+  ctx.beginPath();
+  ctx.moveTo(cx - 8 * minX, cy - 8 * minY);
+  ctx.lineTo(cx + 35 * minX, cy + 33 * minY);
+  ctx.stroke();
+
+  ctx.lineWidth = 3;
+  ctx.strokeStyle = '#696969';
+  ctx.globalAlpha = 1.0;
+  ctx.beginPath();
+  ctx.moveTo(cx - 4 * hourX, cy - 4 * hourY);
+  ctx.lineTo(cx + 20 * hourX, cy + 20 * hourY);
+  ctx.stroke();
+  ctx.beginPath();
+  ctx.moveTo(cx - 8 * minX, cy - 8 * minY);
+  ctx.lineTo(cx + 35 * minX, cy + 33 * minY);
+  ctx.stroke();
+
+  ctx.lineWidth = 1;
+  ctx.strokeStyle = '#990000';
+  ctx.globalAlpha = 1.0;
+  ctx.beginPath();
+  ctx.moveTo(cx - 4 * secX, cy - 4 * secY);
+  ctx.lineTo(cx + 40 * secX, cy + 40 * secY);
+  ctx.stroke();
+}
+
+function updateCurrentTime() {
+  var now = new Date();
+  var hh = now.getHours();
+  var mm = now.getMinutes();
+  var ss = now.getSeconds();
+  var str = '';
+  if (hh % 12 == 0) {
+    str += '12';
+  } else {
+    str += (hh % 12);
+  }
+  str += ':';
+  if (mm >= 10) {
+    str += mm;
+  } else {
+    str += '0' + mm;
+  }
+  str += ':';
+  if (ss >= 10) {
+    str += ss;
+  } else {
+    str += '0' + ss;
+  }
+  if (hh >= 12) {
+    str += " PM";
+  } else {
+    str += " AM";
+  }
+  $('current_time').innerText = str;
+}
+
+// Override from common.js
+window.stopAlarmAnimation = function() {
+  window.clearTimeout(animationTimer);
+  currentClockImage = blankClockImage;
+  drawClock();
+  isAnimating = false;
+};
+
+// Override from common.js
+window.displayAlarmAnimation = function() {
+  isAnimating = true;
+  var rings = 100;
+  function ring() {
+    if (rings == 0) {
+      stopAlarmAnimation();
+      return;
+    }
+    currentClockImage = (rings % 2 == 0)?
+                        blankClockAnim1Image:
+                        blankClockAnim2Image;
+    drawClock();
+    rings--;
+    animationTimer = window.setTimeout(ring, 50);
+  }
+  ring();
+};
+
+function addOutlineStyleListeners() {
+  document.addEventListener('click', function(evt) {
+    document.body.classList.add('nooutline');
+    return true;
+  }, true);
+  document.addEventListener('keydown', function(evt) {
+    document.body.classList.remove('nooutline');
+    return true;
+  }, true);
+}
+
+function load() {
+  try {
+    port = chrome.extension.connect();
+    port.onMessage.addListener(function(msg) {
+      if (msg.cmd == 'anim') {
+        displayAlarmAnimation();
+      }
+    });
+  } catch (e) {
+  }
+
+  addOutlineStyleListeners();
+
+  stopAll();
+  drawClock();
+  setInterval(drawClock, 100);
+
+  updateCurrentTime();
+  setInterval(updateCurrentTime, 250);
+
+  function updateTime(timeElement) {
+    if (!parseTime(timeElement.value)) {
+      return false;
+    }
+
+    timeElement.valueAsNumber =
+        timeElement.valueAsNumber % (12 * 60 * 60 * 1000);
+    if (timeElement.valueAsNumber < (1 * 60 * 60 * 1000))
+      timeElement.valueAsNumber += (12 * 60 * 60 * 1000);
+    return true;
+  }
+
+  $('clock').addEventListener('click', function(evt) {
+    if (isPlaying || isSpeaking || isAnimating) {
+      stopAll();
+    } else {
+      ringAlarmWithCurrentTime();
+    }
+  }, false);
+  $('clock').addEventListener('keydown', function(evt) {
+    if (evt.keyCode == 13 || evt.keyCode == 32) {
+      if (isPlaying || isSpeaking || isAnimating) {
+        stopAll();
+      } else {
+        ringAlarmWithCurrentTime();
+      }
+    }
+  }, false);
+
+  // Alarm 1
+
+  var a1_tt = localStorage['a1_tt'] || DEFAULT_A1_TT;
+  $('a1_tt').value = a1_tt;
+  $('a1_tt').addEventListener('input', function(evt) {
+    updateEnabledStatus(1);
+    if (!updateTime($('a1_tt'))) {
+      evt.stopPropagation();
+      return false;
+    }
+    localStorage['a1_tt'] = $('a1_tt').value;
+    updateEnabledStatus(1);
+    return true;
+  }, false);
+  $('a1_tt').addEventListener('change', function(evt) {
+    if ($('a1_tt').value.length == 4 &&
+        parseTime('0' + $('a1_tt').value)) {
+      $('a1_tt').value = '0' + $('a1_tt').value;
+    }
+    if (!updateTime($('a1_tt'))) {
+      evt.stopPropagation();
+      return false;
+    }
+    localStorage['a1_tt'] = $('a1_tt').value;
+    updateEnabledStatus(1);
+    return true;
+  }, false);
+
+  var a1_on = (localStorage['a1_on'] == 'true');
+  $('a1_on').checked = a1_on;
+  $('a1_on').addEventListener('change', function(evt) {
+    window.setTimeout(function() {
+      localStorage['a1_on'] = $('a1_on').checked;
+      updateEnabledStatus(1);
+    }, 0);
+  }, false);
+
+  var a1_ampm = localStorage['a1_ampm'] || DEFAULT_A1_AMPM;
+  $('a1_ampm').selectedIndex = a1_ampm;
+  $('a1_ampm').addEventListener('change', function(evt) {
+    localStorage['a1_ampm'] = $('a1_ampm').selectedIndex;
+  }, false);
+
+  updateEnabledStatus(1);
+
+  // Alarm 2
+
+  var a2_tt = localStorage['a2_tt'] || DEFAULT_A2_TT;
+  $('a2_tt').value = a2_tt;
+  $('a2_tt').addEventListener('input', function(evt) {
+    updateEnabledStatus(2);
+    if (!updateTime($('a2_tt'))) {
+      evt.stopPropagation();
+      return false;
+    }
+    localStorage['a2_tt'] = $('a2_tt').value;
+    updateEnabledStatus(2);
+    return true;
+  }, false);
+  $('a2_tt').addEventListener('change', function(evt) {
+    if ($('a2_tt').value.length == 4 &&
+        parseTime('0' + $('a2_tt').value)) {
+      $('a2_tt').value = '0' + $('a2_tt').value;
+    }
+    if (!updateTime($('a2_tt'))) {
+      evt.stopPropagation();
+      return false;
+    }
+    localStorage['a2_tt'] = $('a2_tt').value;
+    updateEnabledStatus(2);
+    return true;
+  }, false);
+
+  var a2_on = (localStorage['a2_on'] == 'true');
+  $('a2_on').checked = a2_on;
+  $('a2_on').addEventListener('change', function(evt) {
+    window.setTimeout(function() {
+      localStorage['a2_on'] = $('a2_on').checked;
+      updateEnabledStatus(2);
+    }, 0);
+  }, false);
+
+  var a2_ampm = localStorage['a2_ampm'] || DEFAULT_A2_AMPM;
+  $('a2_ampm').selectedIndex = a2_ampm;
+  $('a2_ampm').addEventListener('change', function(evt) {
+    localStorage['a2_ampm'] = $('a2_ampm').selectedIndex;
+  }, false);
+
+  updateEnabledStatus(2);
+
+  // Phrase
+
+  var phrase = localStorage['phrase'] || DEFAULT_PHRASE;
+  $('phrase').value = phrase;
+  $('phrase').addEventListener('change', function(evt) {
+    localStorage['phrase'] = $('phrase').value;
+  }, false);
+
+  // Speech parameters
+
+  var rateElement = $('rate');
+  var volumeElement = $('volume');
+  var rate = localStorage['rate'] || DEFAULT_RATE;
+  var volume = localStorage['volume'] || DEFAULT_VOLUME;
+  rateElement.value = rate;
+  volumeElement.value = volume;
+  function listener(evt) {
+    rate = rateElement.value;
+    localStorage['rate'] = rate;
+    volume = volumeElement.value;
+    localStorage['volume'] = volume;
+  }
+  rateElement.addEventListener('keyup', listener, false);
+  volumeElement.addEventListener('keyup', listener, false);
+  rateElement.addEventListener('mouseup', listener, false);
+  volumeElement.addEventListener('mouseup', listener, false);
+
+  var sound = $('sound');
+  var currentSound = localStorage['sound'] || DEFAULT_SOUND;
+  for (var i = 0; i < sound.options.length; i++) {
+    if (sound.options[i].value == currentSound) {
+      sound.selectedIndex = i;
+      break;
+    }
+  }
+  localStorage['sound'] = sound.options[sound.selectedIndex].value;
+  sound.addEventListener('change', function() {
+    localStorage['sound'] = sound.options[sound.selectedIndex].value;
+  }, false);
+
+  var playSoundButton = $('playsound');
+  playSoundButton.addEventListener('click', function(evt) {
+    playSound(false);
+  });
+
+  var playSpeechButton = $('playspeech');
+  playSpeechButton.addEventListener('click', function(evt) {
+    speakPhraseWithCurrentTime();
+  });
+
+  var voice = $('voice');
+  var voiceArray = [];
+  if (chrome && chrome.tts) {
+    chrome.tts.getVoices(function(va) {
+      voiceArray = va;
+      for (var i = 0; i < voiceArray.length; i++) {
+        var opt = document.createElement('option');
+        var name = voiceArray[i].voiceName;
+        if (name == localStorage['voice']) {
+          opt.setAttribute('selected', '');
+        }
+        opt.setAttribute('value', name);
+        opt.innerText = voiceArray[i].voiceName;
+        voice.appendChild(opt);
+      }
+    });
+  }
+  voice.addEventListener('change', function() {
+    var i = voice.selectedIndex;
+    localStorage['voice'] = voiceArray[i].voiceName;
+  }, false);
+}
+
+document.addEventListener('DOMContentLoaded', load);
diff --git a/chrome/common/extensions/docs/examples/extensions/ttsdebug/128.png b/chrome/common/extensions/docs/examples/extensions/ttsdebug/128.png
new file mode 100644
index 0000000..98e75ee
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/ttsdebug/128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/ttsdebug/16.png b/chrome/common/extensions/docs/examples/extensions/ttsdebug/16.png
new file mode 100644
index 0000000..76e2766
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/ttsdebug/16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/ttsdebug/256.png b/chrome/common/extensions/docs/examples/extensions/ttsdebug/256.png
new file mode 100644
index 0000000..a16e1cb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/ttsdebug/256.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/ttsdebug/manifest.json b/chrome/common/extensions/docs/examples/extensions/ttsdebug/manifest.json
new file mode 100644
index 0000000..0a856f9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/ttsdebug/manifest.json
@@ -0,0 +1,18 @@
+{
+  "app": {
+    "launch": {
+       "local_path": "ttsdebug.html"
+    }
+  },
+  "description": "Tool for developers of Chrome TTS engine extensions to help them test their engines are implementing the API correctly.",
+  "icons": {
+    "16": "16.png",
+    "128": "128.png",
+    "256": "256.png"
+  },
+  "minimum_chrome_version": "14",
+  "name": "TTS Debug",
+  "permissions": [ "tts" ],
+  "version": "1.0",
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/ttsdebug/pacman.gif b/chrome/common/extensions/docs/examples/extensions/ttsdebug/pacman.gif
new file mode 100644
index 0000000..d43fcb5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/ttsdebug/pacman.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/ttsdebug/ttsdebug.css b/chrome/common/extensions/docs/examples/extensions/ttsdebug/ttsdebug.css
new file mode 100644
index 0000000..86f603e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/ttsdebug/ttsdebug.css
@@ -0,0 +1,64 @@
+/**
+ * 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.
+ */
+
+#main {
+  font-family: arial,helvetica;
+  font-size: 10pt;
+  text-align: center;
+  margin-left: auto;
+  margin-right: auto;
+  padding: 10px 30px 20px 30px;
+  width: 800px;
+  border-left: solid 1px #ccc;
+  border-right: solid 1px #ccc;
+}
+#stop {
+  margin-left: 100px;
+}
+#container {
+  text-align: left;
+}
+#instructions {
+  text-align: left;
+}
+.outer {
+  margin: 12px 6px 6px 6px;
+  padding: 6px;
+  border: 1px solid #000;
+}
+.outer.disabled {
+  border: 1px solid #696969;
+}
+.runTestButton {
+  margin: 12px;
+}
+.description {
+  margin: 6px 12px;
+}
+.results {
+  margin: 12px;
+}
+.messages {
+  margin: 12px;
+}
+.error {
+  color: #900;
+}
+.result {
+  margin: 6px;
+  padding: 6px;
+}
+.success {
+  font-weight: bold;
+  color: #090;
+}
+.failure {
+  font-weight: bold;
+  color: #900;
+}
+.disabled {
+  color: #696969 !important;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/ttsdebug/ttsdebug.html b/chrome/common/extensions/docs/examples/extensions/ttsdebug/ttsdebug.html
new file mode 100644
index 0000000..745fb2f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/ttsdebug/ttsdebug.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Chrome TTS Debug</title>
+    <link href="ttsdebug.css" rel="stylesheet" type="text/css">
+    <script src="ttsdebug.js"></script>
+  </head>
+  <body>
+    <div id="main">
+      <h2>Chrome Text-to-Speech Debug</h2>
+
+      <div id="instructions">
+        <p>
+        This app is for developers of Chrome TTS engines, to help validate that
+        an engine is properly implementing the API. Click on a button to run a
+        test. A lot of errors can be detected automatically, but some tests
+        require manually listening to the speech, and it's important to listen to
+        all tests running to identify other potential glitches.</p>
+        <p>For more diagnostic information, open the JavaScript console.</p>
+      </div>
+
+      <div>
+        <label for="voices">Voice:</label>
+        &nbsp;
+        <select id="voices">
+          <option value="">Unspecified</option>
+        </select>
+
+        <button id="stop">Emergency Stop!</button>
+      </div>
+
+      <div id="container"></div>
+    </div>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/ttsdebug/ttsdebug.js b/chrome/common/extensions/docs/examples/extensions/ttsdebug/ttsdebug.js
new file mode 100644
index 0000000..61b71c9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/ttsdebug/ttsdebug.js
@@ -0,0 +1,694 @@
+/**
+ * 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.
+ */
+
+var voiceArray;
+var trials = 3;
+var resultMap = {};
+var updateDependencyFunctions = [];
+var testRunIndex = 0;
+var emergencyStop = false;
+
+function $(id) {
+  return document.getElementById(id);
+}
+
+function isErrorEvent(evt) {
+  return (evt.type == 'error' ||
+          evt.type == 'interrupted' ||
+          evt.type == 'cancelled');
+}
+
+function logEvent(callTime, testRunName, evt) {
+  var elapsed = ((new Date() - callTime) / 1000).toFixed(3);
+  while (elapsed.length < 7) {
+    elapsed = ' ' + elapsed;
+  }
+  console.log(elapsed + ' ' + testRunName + ': ' + JSON.stringify(evt));
+}
+
+function logSpeakCall(utterance, options, callback) {
+  var optionsCopy = {};
+  for (var key in options) {
+    if (key != 'onEvent') {
+      optionsCopy[key] = options[key];
+    }
+  }
+  console.log('Calling chrome.tts.speak(\'' +
+              utterance + '\', ' +
+              JSON.stringify(optionsCopy) + ')');
+  if (callback)
+    chrome.tts.speak(utterance, options, callback);
+  else
+    chrome.tts.speak(utterance, options);
+}
+
+var tests = [
+  {
+    name: 'Baseline',
+    description: 'Ensures that the speech engine sends both start and ' +
+                 'end events, and establishes a baseline time to speak a ' +
+                 'key phrase, to compare other tests against.',
+    dependencies: [],
+    trials: 3,
+    run: function(testRunName, voiceName, callback) {
+      var callTime = new Date();
+      var startTime;
+      var warnings = [];
+      var errors = [];
+      logSpeakCall('Alpha Bravo Charlie Delta Echo', {
+        voiceName: voiceName,
+        onEvent: function(evt) {
+          logEvent(callTime, testRunName, evt);
+          if (isErrorEvent(evt)) {
+            callback(false, null, []);
+          } else if (evt.type == 'start') {
+            startTime = new Date();
+            if (evt.charIndex != 0) {
+              errors.push('Error: start event should have a charIndex of 0.');
+            }
+          } else if (evt.type == 'end') {
+            if (startTime == undefined) {
+              errors.push('Error: no "start" event received!');
+              startTime = callTime;
+            }
+            if (evt.charIndex != 30) {
+              errors.push('Error: end event should have a charIndex of 30.');
+            }
+            var endTime = new Date();
+            if (startTime - callTime > 1000) {
+              var delta = ((startTime - callTime) / 1000).toFixed(3);
+              warnings.push('Note: Delay of ' + delta +
+                            ' before speech started. ' +
+                            'Less than 1.0 s latency is recommended.');
+            }
+            var delta = (endTime - startTime) / 1000;
+            if (delta < 1.0) {
+              warnings.push('Warning: Default speech rate seems too fast.');
+            } else if (delta > 3.0) {
+              warnings.push('Warning: Default speech rate seems too slow.');
+            }
+            callback(errors.length == 0, delta, warnings.concat(errors));
+          }
+        }
+      });
+    }
+  },
+  {
+    name: 'Fast',
+    description: 'Speaks twice as fast and compares the time to the baseline.',
+    dependencies: ['Baseline'],
+    trials: 3,
+    run: function(testRunName, voiceName, callback) {
+      var callTime = new Date();
+      var startTime;
+      var errors = [];
+      logSpeakCall('Alpha Bravo Charlie Delta Echo', {
+        voiceName: voiceName,
+        rate: 2.0,
+        onEvent: function(evt) {
+          logEvent(callTime, testRunName, evt);
+          if (isErrorEvent(evt)) {
+            callback(false, null, []);
+          } else if (evt.type == 'start') {
+            startTime = new Date();
+          } else if (evt.type == 'end') {
+            if (startTime == undefined)
+              startTime = callTime;
+            var endTime = new Date();
+            var delta = (endTime - startTime) / 1000;
+            var relative = delta / resultMap['Baseline'];
+            if (relative < 0.35) {
+              errors.push('2x speech rate seems too fast.');
+            } else if (relative > 0.65) {
+              errors.push('2x speech rate seems too slow.');
+            }
+            callback(errors.length == 0, delta, errors);
+          }
+        }
+      });
+    }
+  },
+  {
+    name: 'Slow',
+    description: 'Speaks twice as slow and compares the time to the baseline.',
+    dependencies: ['Baseline'],
+    trials: 3,
+    run: function(testRunName, voiceName, callback) {
+      var callTime = new Date();
+      var startTime;
+      var errors = [];
+      logSpeakCall('Alpha Bravo Charlie Delta Echo', {
+        voiceName: voiceName,
+        rate: 0.5,
+        onEvent: function(evt) {
+          logEvent(callTime, testRunName, evt);
+          if (isErrorEvent(evt)) {
+            callback(false, null, []);
+          } else if (evt.type == 'start') {
+            startTime = new Date();
+          } else if (evt.type == 'end') {
+            if (startTime == undefined)
+              startTime = callTime;
+            var endTime = new Date();
+            var delta = (endTime - startTime) / 1000;
+            var relative = delta / resultMap['Baseline'];
+            if (relative < 1.6) {
+              errors.push('Half-speed speech rate seems too fast.');
+            } else if (relative > 2.4) {
+              errors.push('Half-speed speech rate seems too slow.');
+            }
+            callback(errors.length == 0, delta, errors);
+          }
+        }
+      });
+    }
+  },
+  {
+    name: 'Interrupt and restart',
+    description: 'Interrupts partway through a long sentence and then ' +
+                 'the baseline utterance, to make sure that speech after ' +
+                 'an interruption works correctly.',
+    dependencies: ['Baseline'],
+    trials: 1,
+    run: function(testRunName, voiceName, callback) {
+      var callTime = new Date();
+      var startTime;
+      var errors = [];
+      logSpeakCall('When in the course of human events it becomes ' +
+                       'necessary for one people to dissolve the political ' +
+                       'bands which have connected them ', {
+        voiceName: voiceName,
+        onEvent: function(evt) {
+          logEvent(callTime, testRunName, evt);
+        }
+      });
+      window.setTimeout(function() {
+        logSpeakCall('Alpha Bravo Charlie Delta Echo', {
+          voiceName: voiceName,
+          onEvent: function(evt) {
+            logEvent(callTime, testRunName, evt);
+            if (isErrorEvent(evt)) {
+              callback(false, null, []);
+            } else if (evt.type == 'start') {
+              startTime = new Date();
+            } else if (evt.type == 'end') {
+              if (startTime == undefined)
+                startTime = callTime;
+              var endTime = new Date();
+              var delta = (endTime - startTime) / 1000;
+              var relative = delta / resultMap['Baseline'];
+              if (relative < 0.9) {
+                errors.push('Interrupting speech seems too short.');
+              } else if (relative > 1.1) {
+                errors.push('Interrupting speech seems too long.');
+              }
+              callback(errors.length == 0, delta, errors);
+            }
+          }
+        });
+      }, 4000);
+    }
+  },
+  {
+    name: 'Low volume',
+    description: '<b>Manual</b> test - verify that the volume is lower.',
+    dependencies: [],
+    trials: 1,
+    run: function(testRunName, voiceName, callback) {
+      var callTime = new Date();
+      logSpeakCall('Alpha Bravo Charlie Delta Echo', {
+        voiceName: voiceName,
+        volume: 0.5,
+        onEvent: function(evt) {
+          logEvent(callTime, testRunName, evt);
+          if (isErrorEvent(evt)) {
+            callback(false, null, []);
+          } else if (evt.type == 'end') {
+            callback(true, null, []);
+          }
+        }
+      });
+    }
+  },
+  {
+    name: 'High pitch',
+    description: '<b>Manual</b> test - verify that the pitch is ' +
+                 'moderately higher, but quite understandable.',
+    dependencies: [],
+    trials: 1,
+    run: function(testRunName, voiceName, callback) {
+      var callTime = new Date();
+      logSpeakCall('Alpha Bravo Charlie Delta Echo', {
+        voiceName: voiceName,
+        pitch: 1.2,
+        onEvent: function(evt) {
+          logEvent(callTime, testRunName, evt);
+          if (isErrorEvent(evt)) {
+            callback(false, null, []);
+          } else if (evt.type == 'end') {
+            callback(true, null, []);
+          }
+        }
+      });
+    }
+  },
+  {
+    name: 'Low pitch',
+    description: '<b>Manual</b> test - verify that the pitch is ' +
+                 'moderately lower, but quite understandable.',
+    dependencies: [],
+    trials: 1,
+    run: function(testRunName, voiceName, callback) {
+      var callTime = new Date();
+      logSpeakCall('Alpha Bravo Charlie Delta Echo', {
+        voiceName: voiceName,
+        pitch: 0.8,
+        onEvent: function(evt) {
+          logEvent(callTime, testRunName, evt);
+          if (isErrorEvent(evt)) {
+            callback(false, null, []);
+          } else if (evt.type == 'end') {
+            callback(true, null, []);
+          }
+        }
+      });
+    }
+  },
+  {
+    name: 'Word and sentence callbacks',
+    description: 'Checks to see if proper word and sentence callbacks ' +
+                 'are received.',
+    dependencies: ['Baseline'],
+    trials: 1,
+    run: function(testRunName, voiceName, callback) {
+      var callTime = new Date();
+      var startTime;
+      var errors = [];
+      var wordExpected = [{min: 5, max: 6},
+                          {min: 11, max: 12},
+                          {min: 19, max: 20},
+                          {min: 25, max: 26},
+                          {min: 30, max: 32},
+                          {min: 37, max: 38},
+                          {min: 43, max: 44},
+                          {min: 51, max: 52},
+                          {min: 57, max: 58}];
+      var sentenceExpected = [{min: 30, max: 32}]
+      var wordCount = 0;
+      var sentenceCount = 0;
+      var lastWordTime = callTime;
+      var lastSentenceTime = callTime;
+      var avgWordTime = resultMap['Baseline'] / 5;
+      logSpeakCall('Alpha Bravo Charlie Delta Echo. ' +
+                       'Alpha Bravo Charlie Delta Echo.', {
+        voiceName: voiceName,
+        onEvent: function(evt) {
+          logEvent(callTime, testRunName, evt);
+          if (isErrorEvent(evt)) {
+            callback(false, null, []);
+          } else if (evt.type == 'start') {
+            startTime = new Date();
+            lastWordTime = startTime;
+            lastSentenceTime = startTime;
+          } else if (evt.type == 'word') {
+            if (evt.charIndex > 0 && evt.charIndex < 62) {
+              var min = wordExpected[wordCount].min;
+              var max = wordExpected[wordCount].max;
+              if (evt.charIndex < min || evt.charIndex > max) {
+                errors.push('Got word at charIndex ' + evt.charIndex + ', ' +
+                            'was expecting next word callback charIndex ' +
+                            'in the range ' + min + ':' + max + '.');
+              }
+              if (wordCount != 4) {
+                var delta = (new Date() - lastWordTime) / 1000;
+                if (delta < 0.6 * avgWordTime) {
+                  errors.push('Word at charIndex ' + evt.charIndex +
+                              ' came after only ' + delta.toFixed(3) +
+                              ' s, which seems too short.');
+                } else if (delta > 1.3 * avgWordTime) {
+                  errors.push('Word at charIndex ' + evt.charIndex +
+                              ' came after ' + delta.toFixed(3) +
+                              ' s, which seems too long.');
+                }
+              }
+              wordCount++;
+            }
+            lastWordTime = new Date();
+          } else if (evt.type == 'sentence') {
+            if (evt.charIndex > 0 && evt.charIndex < 62) {
+              var min = sentenceExpected[sentenceCount].min;
+              var max = sentenceExpected[sentenceCount].max;
+              if (evt.charIndex < min || evt.charIndex > max) {
+                errors.push('Got sentence at charIndex ' + evt.charIndex +
+                            ', was expecting next callback charIndex ' +
+                            'in the range ' + min + ':' + max + '.');
+              }
+              var delta = (new Date() - lastSentenceTime) / 1000;
+              if (delta < 0.75 * resultMap['Baseline']) {
+                errors.push('Sentence at charIndex ' + evt.charIndex +
+                            ' came after only ' + delta.toFixed(3) +
+                            ' s, which seems too short.');
+              } else if (delta > 1.25 * resultMap['Baseline']) {
+                errors.push('Sentence at charIndex ' + evt.charIndex +
+                            ' came after ' + delta.toFixed(3) +
+                            ' s, which seems too long.');
+              }
+              sentenceCount++;
+            }
+            lastSentenceTime = new Date();
+          } else if (evt.type == 'end') {
+            if (wordCount == 0) {
+              errors.push('Didn\'t get any word callbacks.');
+            } else if (wordCount < wordExpected.length) {
+              errors.push('Not enough word callbacks.');
+            } else if (wordCount > wordExpected.length) {
+              errors.push('Too many word callbacks.');
+            }
+            if (sentenceCount == 0) {
+              errors.push('Didn\'t get any sentence callbacks.');
+            } else if (sentenceCount < sentenceExpected.length) {
+              errors.push('Not enough sentence callbacks.');
+            } else if (sentenceCount > sentenceExpected.length) {
+              errors.push('Too many sentence callbacks.');
+            }
+            if (startTime == undefined) {
+              errors.push('Error: no "start" event received!');
+              startTime = callTime;
+            }
+            var endTime = new Date();
+            var delta = (endTime - startTime) / 1000;
+            if (delta < 2.5) {
+              errors.push('Default speech rate seems too fast.');
+            } else if (delta > 7.0) {
+              errors.push('Default speech rate seems too slow.');
+            }
+            callback(errors.length == 0, delta, errors);
+          }
+        }
+      });
+    }
+  },
+  {
+    name: 'Baseline Queueing Test',
+    description: 'Establishes a baseline time to speak a ' +
+                 'sequence of three enqueued phrases, to compare ' +
+                 'other tests against.',
+    dependencies: [],
+    trials: 3,
+    run: function(testRunName, voiceName, callback) {
+      var callTime = new Date();
+      var startTime;
+      var errors = [];
+      logSpeakCall('Alpha Alpha', {
+        voiceName: voiceName,
+        onEvent: function(evt) {
+          logEvent(callTime, testRunName, evt);
+          if (isErrorEvent(evt)) {
+            callback(false, null, []);
+          } else if (evt.type == 'start') {
+            startTime = new Date();
+          }
+        }
+      });
+      logSpeakCall('Bravo bravo.', {
+        voiceName: voiceName,
+        enqueue: true,
+        onEvent: function(evt) {
+          logEvent(callTime, testRunName, evt);
+          if (isErrorEvent(evt)) {
+            callback(false, null, []);
+          }
+        }
+      });
+      logSpeakCall('Charlie charlie', {
+        voiceName: voiceName,
+        enqueue: true,
+        onEvent: function(evt) {
+          logEvent(callTime, testRunName, evt);
+          if (isErrorEvent(evt)) {
+            callback(false, null, []);
+          } else if (evt.type == 'end') {
+            if (startTime == undefined) {
+              errors.push('Error: no "start" event received!');
+              startTime = callTime;
+            }
+            var endTime = new Date();
+            var delta = (endTime - startTime) / 1000;
+            callback(errors.length == 0, delta, errors);
+          }
+        }
+      });
+    }
+  },
+  {
+    name: 'Interruption with Queueing',
+    description: 'Queue a sequence of three utterances, then before they ' +
+                 'are finished, interrupt and queue a sequence of three ' +
+                 'more utterances. Make sure that interrupting and ' +
+                 'cancelling the previous utterances doesn\'t interfere ' +
+                 'with the interrupting utterances.',
+    dependencies: ['Baseline Queueing Test'],
+    trials: 1,
+    run: function(testRunName, voiceName, callback) {
+      var callTime = new Date();
+      var startTime;
+      var errors = [];
+
+      logSpeakCall('Just when I\'m about to say something interesting,', {
+        voiceName: voiceName
+      });
+      logSpeakCall('it seems that I always get interrupted.', {
+        voiceName: voiceName,
+        enqueue: true,
+      });
+      logSpeakCall('How rude! Will you ever let me finish?', {
+        voiceName: voiceName,
+        enqueue: true,
+      });
+
+      window.setTimeout(function() {
+        logSpeakCall('Alpha Alpha', {
+          voiceName: voiceName,
+          onEvent: function(evt) {
+            logEvent(callTime, testRunName, evt);
+            if (isErrorEvent(evt)) {
+              callback(false, null, []);
+            } else if (evt.type == 'start') {
+              startTime = new Date();
+            }
+          }
+        });
+        logSpeakCall('Bravo bravo.', {
+          voiceName: voiceName,
+          enqueue: true,
+          onEvent: function(evt) {
+            logEvent(callTime, testRunName, evt);
+            if (isErrorEvent(evt)) {
+              callback(false, null, []);
+            }
+          }
+        });
+        logSpeakCall('Charlie charlie', {
+          voiceName: voiceName,
+          enqueue: true,
+          onEvent: function(evt) {
+            logEvent(callTime, testRunName, evt);
+            if (isErrorEvent(evt)) {
+              callback(false, null, []);
+            } else if (evt.type == 'end') {
+              if (startTime == undefined) {
+                errors.push('Error: no "start" event received!');
+                startTime = callTime;
+              }
+              var endTime = new Date();
+              var delta = (endTime - startTime) / 1000;
+              var relative = delta / resultMap['Baseline Queueing Test'];
+              if (relative < 0.9) {
+                errors.push('Interrupting speech seems too short.');
+              } else if (relative > 1.1) {
+                errors.push('Interrupting speech seems too long.');
+              }
+              callback(errors.length == 0, delta, errors);
+            }
+          }
+        });
+      }, 4000);
+    }
+  }
+];
+
+function updateDependencies() {
+  for (var i = 0; i < updateDependencyFunctions.length; i++) {
+    updateDependencyFunctions[i]();
+  }
+}
+
+function registerTest(test) {
+  var outer = document.createElement('div');
+  outer.className = 'outer';
+  $('container').appendChild(outer);
+
+  var buttonWrap = document.createElement('div');
+  buttonWrap.className = 'buttonWrap';
+  outer.appendChild(buttonWrap);
+
+  var button = document.createElement('button');
+  button.className = 'runTestButton';
+  button.innerText = test.name;
+  buttonWrap.appendChild(button);
+
+  var busy = document.createElement('img');
+  busy.src = 'pacman.gif';
+  busy.alt = 'Busy indicator';
+  buttonWrap.appendChild(busy);
+  busy.style.visibility = 'hidden';
+
+  var description = document.createElement('div');
+  description.className = 'description';
+  description.innerHTML = test.description;
+  outer.appendChild(description);
+
+  var resultsWrap = document.createElement('div');
+  resultsWrap.className = 'results';
+  outer.appendChild(resultsWrap);
+  var results = [];
+  for (var j = 0; j < test.trials; j++) {
+    var result = document.createElement('span');
+    resultsWrap.appendChild(result);
+    results.push(result);
+  }
+  var avg = document.createElement('span');
+  resultsWrap.appendChild(avg);
+
+  var messagesWrap = document.createElement('div');
+  messagesWrap.className = 'messages';
+  outer.appendChild(messagesWrap);
+
+  var totalTime;
+  var successCount;
+
+  function finishTrials() {
+    busy.style.visibility = 'hidden';
+    if (successCount == test.trials) {
+      console.log('Test succeeded.');
+      var success = document.createElement('div');
+      success.className = 'success';
+      success.innerText = 'Test succeeded.';
+      messagesWrap.appendChild(success);
+      if (totalTime > 0.0) {
+        var avgTime = totalTime / test.trials;
+        avg.className = 'result';
+        avg.innerText = 'Avg: ' + avgTime.toFixed(3) + ' s';
+        resultMap[test.name] = avgTime;
+        updateDependencies();
+      }
+    } else {
+      console.log('Test failed.');
+      var failure = document.createElement('div');
+      failure.className = 'failure';
+      failure.innerText = 'Test failed.';
+      messagesWrap.appendChild(failure);
+    }
+  }
+
+  function runTest(index, voiceName) {
+    if (emergencyStop) {
+      busy.style.visibility = 'hidden';
+      emergencyStop = false;
+      return;
+    }
+    var testRunName = 'Test run ' + testRunIndex + ', ' +
+                      test.name + ', trial ' + (index+1) + ' of ' +
+                      test.trials;
+    console.log('*** Beginning ' + testRunName +
+                ' with voice ' + voiceName);
+    test.run(testRunName, voiceName, function(success, resultTime, errors) {
+      if (success) {
+        successCount++;
+      }
+      for (var i = 0; i < errors.length; i++) {
+        console.log(errors[i]);
+        var error = document.createElement('div');
+        error.className = 'error';
+        error.innerText = errors[i];
+        messagesWrap.appendChild(error);
+      }
+      if (resultTime != null) {
+        results[index].className = 'result';
+        results[index].innerText = resultTime.toFixed(3) + ' s';
+        totalTime += resultTime;
+      }
+      index++;
+      if (index < test.trials) {
+        runTest(index, voiceName);
+      } else {
+        finishTrials();
+      }
+    });
+  }
+
+  button.addEventListener('click', function() {
+    var voiceIndex = $('voices').selectedIndex - 1;
+    if (voiceIndex < 0) {
+      alert('Please select a voice first!');
+      return;
+    }
+    testRunIndex++;
+    busy.style.visibility = 'visible';
+    totalTime = 0.0;
+    successCount = 0;
+    messagesWrap.innerHTML = '';
+    var voiceName = voiceArray[voiceIndex].voiceName;
+    runTest(0, voiceName);
+  }, false);
+
+  updateDependencyFunctions.push(function() {
+    for (var i = 0; i < test.dependencies.length; i++) {
+      if (resultMap[test.dependencies[i]] != undefined) {
+        button.disabled = false;
+        outer.className = 'outer';
+      } else {
+        button.disabled = true;
+        outer.className = 'outer disabled';
+      }
+    }
+  });
+}
+
+function load() {
+  var voice = localStorage['voice'];
+  chrome.tts.getVoices(function(va) {
+    voiceArray = va;
+    for (var i = 0; i < voiceArray.length; i++) {
+      var opt = document.createElement('option');
+      var name = voiceArray[i].voiceName;
+      if (name == localStorage['voice']) {
+        opt.setAttribute('selected', '');
+      }
+      opt.setAttribute('value', name);
+      opt.innerText = voiceArray[i].voiceName;
+      $('voices').appendChild(opt);
+    }
+  });
+  $('voices').addEventListener('change', function() {
+    var i = $('voices').selectedIndex;
+    localStorage['voice'] = $('voices').item(i).value;
+  }, false);
+  $('stop').addEventListener('click', stop);
+
+  for (var i = 0; i < tests.length; i++) {
+    registerTest(tests[i]);
+  }
+  updateDependencies();
+}
+
+function stop() {
+  console.log('*** Emergency stop!');
+  emergencyStop = true;
+  chrome.tts.stop();
+}
+
+document.addEventListener('DOMContentLoaded', load);
diff --git a/chrome/common/extensions/docs/examples/extensions/ttsdemo/128.png b/chrome/common/extensions/docs/examples/extensions/ttsdemo/128.png
new file mode 100644
index 0000000..7d7d565
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/ttsdemo/128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/ttsdemo/16.png b/chrome/common/extensions/docs/examples/extensions/ttsdemo/16.png
new file mode 100644
index 0000000..7ae8b1f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/ttsdemo/16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/ttsdemo/256.png b/chrome/common/extensions/docs/examples/extensions/ttsdemo/256.png
new file mode 100644
index 0000000..b603847
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/ttsdemo/256.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/ttsdemo/manifest.json b/chrome/common/extensions/docs/examples/extensions/ttsdemo/manifest.json
new file mode 100644
index 0000000..ade1281
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/ttsdemo/manifest.json
@@ -0,0 +1,19 @@
+{
+  "app": {
+    "launch": {
+       "local_path": "ttsdemo.html"
+    }
+  },
+  "description": "Demo Chrome's synthesized text-to-speech capabilities.",
+  "icons": {
+    "16": "16.png",
+    "128": "128.png",
+    "256": "256.png"
+  },
+  "minimum_chrome_version": "14",
+  "name": "TTS Demo",
+  "permissions": [ "tts" ],
+  "version": "2.1",
+
+  "manifest_version": 2
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/ttsdemo/ttsdemo.html b/chrome/common/extensions/docs/examples/extensions/ttsdemo/ttsdemo.html
new file mode 100644
index 0000000..9b9ac41
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/ttsdemo/ttsdemo.html
@@ -0,0 +1,172 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Chrome TTS Demo</title>
+  <style>
+    body {
+      font-family: arial, helvetica, sans-serif;
+    }
+    .banner {
+      width: 100%;
+      float: left;
+    }
+    .banner_left {
+      padding: 8px;
+      float: left;
+    }
+    .banner_right {
+      padding: 8px;
+    }
+    .body_wrapper {
+      width: 100%;
+      float: left;
+    }
+    .body_left {
+      border: 0;
+      padding: 0;
+      margin: 0;
+      width: 50%;
+      float: left;
+    }
+    .body_right {
+      border: 0;
+      padding: 0;
+      margin: 0;
+      width: 46%;
+      float: left;
+    }
+    .body_inner {
+      padding: 0 32px;
+    }
+    #srctext {
+      width: 100%;
+      font-size: 133%;
+    }
+    .large_button {
+      font-size: 166%;
+      padding: 6pt 12pt 6pt 12pt;
+    }
+    .box {
+      margin: 10px;
+      padding: 10px;
+      border: 1px solid #999;
+    }
+    .tabbable {
+      padding: 10px;
+      border: 1px solid #00C;
+    }
+    table {
+      margin-left: auto;
+      margin-right: auto;
+    }
+    #help {
+      text-align: left;
+    }
+    #voiceInfo {
+      text-align: left;
+      padding: 4px;
+      border: 1px solid #aaa;
+      width: 100%;
+      min-height: 100px;
+      overflow: auto;
+    }
+  </style>
+  <script src="ttsdemo.js"></script>
+</head>
+
+<body>
+
+<div class="banner">
+  <div class="banner_left">
+    <img src="128.png" class="logo" alt="">
+  </div>
+  <div class="banner_right">
+    <h1>Chrome Text-to-Speech Demo</h1>
+    <p>
+      Use this application to try out all of the text-to-speech voices in Chrome, or
+      <a href="https://chrome.google.com/webstore/search?q=tts">Search the Chrome Web Store</a>
+      for more TTS voices.
+    </p>
+  </div>
+</div>
+
+<div class="body_wrapper">
+  <div class="body_left">
+    <div class="body_inner">
+
+      Enter text here:
+      <textarea id="srctext" rows="6" cols="40">This is a demo of text-to-speech in Chrome.</textarea>
+
+      <p>
+        <button class="large_button" onclick="speakUserText()">Speak</button>
+        <button class="large_button" onclick="stop()">Stop</button>
+      </p>
+
+      <div class="box" id="ttsStatusBox">
+        TTS status: <b><span id="ttsStatus"></span></b>
+      </div>
+
+      <p>
+
+        Click on or tab to these boxes:
+
+      <p>
+
+      <span tabindex="0" class="tabbable" onfocus='speak("Alpha");'>Alpha</span>
+      <span tabindex="0" class="tabbable" onfocus='speak("Bravo");'>Bravo</span>
+      <span tabindex="0" class="tabbable" onfocus='speak("Charlie");'>Charlie</span>
+      <span tabindex="0" class="tabbable" onfocus='speak("Delta");'>Delta</span>
+      <span tabindex="0" class="tabbable" onfocus='speak("Echo");'>Echo</span>
+      <span tabindex="0" class="tabbable" onfocus='speak("Foxtrot");'>Foxtrot</span>
+
+    </div>
+  </div>
+  <div class="body_right">
+    <div class="body_inner">
+
+      <table class="simple">
+        <tr>
+        <td>Voice:</td>
+        <td><select id="voices">
+              <option value="">Unspecified</option>
+            </select></td>
+        </td>
+        </tr>
+        <tr>
+        <td>Lang:</td>
+        <td><select id="lang">
+          <option value="">Unspecified</option>
+          <option value="de">de (German)</option>
+          <option value="en-GB">en-GB (British English)</option>
+          <option value="en-US" selected>en-US (American English)</option>
+          <option value="es">es (Spanish)</option>
+          <option value="fr">fr (French)</option>
+          <option value="it">it (Italian)</option>
+        </select></td></tr>
+        <tr>
+        <td>Queuing mode:</td>
+        <td><select id="enqueue">
+          <option value="">Interrupt</option>
+          <option value="true">Enqueue</option>
+        </select></td></tr>
+        <tr>
+        <td>Rate:</td>
+        <td><input id="rate" type="range" min="0.5" max="4.0" value="1.0" step="0.1">
+        </td></tr>
+        <tr>
+        <td>Pitch:</td>
+        <td><input id="pitch" type="range" min="0.0" max="2.0" value="1.0" step="0.2">
+        </td></tr>
+        <tr>
+        <td>Volume:</td>
+        <td><input id="volume" type="range" min="0.0" max="1.0" value="1.0" step="0.1">
+        </td></tr>
+      </table>
+
+      <pre id="voiceInfo"></pre>
+    </div>
+  </div>
+</div>
+
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/ttsdemo/ttsdemo.js b/chrome/common/extensions/docs/examples/extensions/ttsdemo/ttsdemo.js
new file mode 100644
index 0000000..d4e0a6f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/ttsdemo/ttsdemo.js
@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2012 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.
+ */
+
+var text;
+var ttsStatus;
+var ttsStatusBox;
+var lang;
+var enqueue;
+var voices;
+var voiceInfo;
+var voiceArray;
+var utteranceIndex = 0;
+
+function load() {
+  text = document.getElementById('srctext');
+  ttsStatus = document.getElementById('ttsStatus');
+  ttsStatusBox = document.getElementById('ttsStatusBox');
+  lang = document.getElementById('lang');
+  enqueue = document.getElementById('enqueue');
+  voices = document.getElementById('voices');
+  voiceInfo = document.getElementById('voiceInfo');
+
+  chrome.tts.getVoices(function(va) {
+    voiceArray = va;
+    for (var i = 0; i < voiceArray.length; i++) {
+      var opt = document.createElement('option');
+      opt.setAttribute('value', voiceArray[i].voiceName);
+      opt.innerText = voiceArray[i].voiceName;
+      voices.appendChild(opt);
+    }
+  });
+  voices.addEventListener('change', function() {
+    var i = voices.selectedIndex - 1;
+    if (i >= 0) {
+      voiceInfo.innerText = JSON.stringify(voiceArray[i], null, 2);
+    } else {
+      voiceInfo.innerText = '';
+    }
+  }, false);
+}
+
+function speak(str, options, highlightText) {
+  if (!options) {
+    options = {};
+  }
+  if (enqueue.value) {
+    options.enqueue = Boolean(enqueue.value);
+  }
+  var voiceIndex = voices.selectedIndex - 1;
+  if (voiceIndex >= 0) {
+    options.voiceName = voiceArray[voiceIndex].voiceName;
+  }
+  var rateValue = Number(rate.value);
+  if (rateValue >= 0.1 && rateValue <= 10.0) {
+    options.rate = rateValue;
+  }
+  var pitchValue = Number(pitch.value);
+  if (pitchValue >= 0.0 && pitchValue <= 2.0) {
+    options.pitch = pitchValue;
+  }
+  var volumeValue = Number(volume.value);
+  if (volumeValue >= 0.0 && volumeValue <= 1.0) {
+    options.volume = volumeValue;
+  }
+  utteranceIndex++;
+  console.log(utteranceIndex + ': ' + JSON.stringify(options));
+  options.onEvent = function(event) {
+    console.log(utteranceIndex + ': ' + JSON.stringify(event));
+    if (highlightText) {
+      text.setSelectionRange(0, event.charIndex);
+    }
+    if (event.type == 'end' ||
+        event.type == 'interrupted' ||
+        event.type == 'cancelled' ||
+        event.type == 'error') {
+      chrome.tts.isSpeaking(function(isSpeaking) {
+        if (!isSpeaking) {
+          ttsStatus.innerHTML = 'Idle';
+          ttsStatusBox.style.background = '#fff';
+        }
+      });
+    }
+  };
+  chrome.tts.speak(
+      str, options, function() {
+    if (chrome.extension.lastError) {
+      console.log('TTS Error: ' + chrome.extension.lastError.message);
+    }
+  });
+  ttsStatus.innerHTML = 'Busy';
+  ttsStatusBox.style.background = '#ffc';
+}
+
+function stop() {
+  chrome.tts.stop();
+}
+
+function speakUserText() {
+  var options = {};
+  if (lang.value) {
+    options.lang = lang.value;
+  }
+  speak(text.value, options, true);
+}
+
+document.addEventListener('DOMContentLoaded', load);
diff --git a/chrome/common/extensions/docs/examples/howto/contentscript_xhr/contentscript.js b/chrome/common/extensions/docs/examples/howto/contentscript_xhr/contentscript.js
new file mode 100644
index 0000000..2b33246
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/howto/contentscript_xhr/contentscript.js
@@ -0,0 +1,72 @@
+/*
+* 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.
+*/
+
+/**
+ * Performs an XMLHttpRequest to Twitter's API to get trending topics.
+ *
+ * @param callback Function If the response from fetching url has a
+ *     HTTP status of 200, this function is called with a JSON decoded
+ *     response.  Otherwise, this function is called with null.
+ */
+function fetchTwitterFeed(callback) {
+  var xhr = new XMLHttpRequest();
+  xhr.onreadystatechange = function(data) {
+    if (xhr.readyState == 4) {
+      if (xhr.status == 200) {
+        var data = JSON.parse(xhr.responseText);
+        callback(data);
+      } else {
+        callback(null);
+      }
+    }
+  }
+  // Note that any URL fetched here must be matched by a permission in
+  // the manifest.json file!
+  var url = 'https://api.twitter.com/1/trends/daily.json?exclude=hashtags';
+  xhr.open('GET', url, true);
+  xhr.send();
+};
+
+/**
+ * Parses text from Twitter's API and generates a bar with trending topics at
+ * the top of the current page
+ *
+ * @param data Object JSON decoded response.  Null if the request failed.
+ */
+function onText(data) {
+  // Only render the bar if the data is parsed into a format we recognize.
+  if (data.trends) {
+    // Create the overlay at the top of the page and fill it with data.
+    var trends_dom = document.createElement('div');
+    var title_dom = document.createElement('strong');
+    title_dom.innerText = 'Topics currently trending on Twitter:';
+    trends_dom.appendChild(title_dom);
+    for (var key in data.trends) {
+      for (var i=0,trend; trend = data.trends[key][i]; i++) {
+        var link_dom = document.createElement('a');
+        link_dom.setAttribute('href', trend.url)
+        link_dom.innerText = trend.name;
+        link_dom.style.color = '#000';
+        trends_dom.appendChild(document.createTextNode(' '));
+        trends_dom.appendChild(link_dom);
+      }
+      break;
+    }
+    trends_dom.style.cssText = [
+      'background-color: #ffd700;',
+      'background-image: -webkit-repeating-linear-gradient(' +
+          '45deg, transparent, transparent 35px,' +
+          'rgba(0,0,0,.1) 35px, rgba(0,0,0,.1) 70px);',
+      'color: #000;',
+      'padding: 10px;',
+      'font: 14px Arial;'
+    ].join(' ');
+    document.body.style.cssText = 'position: relative';
+    document.body.parentElement.insertBefore(trends_dom, document.body);
+  }
+};
+
+fetchTwitterFeed(onText);
diff --git a/chrome/common/extensions/docs/examples/howto/contentscript_xhr/manifest.json b/chrome/common/extensions/docs/examples/howto/contentscript_xhr/manifest.json
new file mode 100644
index 0000000..9824414
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/howto/contentscript_xhr/manifest.json
@@ -0,0 +1,18 @@
+{
+  "name": "Content Script Cross-Domain XMLHttpRequest Example",
+  "version": "2.0.0",
+  "description": "Demonstrates making cross domain requests from a content script by putting Twitter trends on Google News.",
+  "permissions": [
+    "https://api.twitter.com/*"
+  ],
+  "icons": {
+    "48" : "sample-48.png",
+    "128" : "sample-128.png"
+  },
+  "content_scripts": [
+    {
+      "matches": ["http://news.google.com/*"],
+      "js" : ["contentscript.js"]
+    }
+  ]
+}
diff --git a/chrome/common/extensions/docs/examples/howto/contentscript_xhr/sample-128.png b/chrome/common/extensions/docs/examples/howto/contentscript_xhr/sample-128.png
new file mode 100644
index 0000000..d733b1e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/howto/contentscript_xhr/sample-128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/howto/contentscript_xhr/sample-48.png b/chrome/common/extensions/docs/examples/howto/contentscript_xhr/sample-48.png
new file mode 100644
index 0000000..3af1eb8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/howto/contentscript_xhr/sample-48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/howto/sandbox/LICENSE.handlebars b/chrome/common/extensions/docs/examples/howto/sandbox/LICENSE.handlebars
new file mode 100644
index 0000000..237cd03
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/howto/sandbox/LICENSE.handlebars
@@ -0,0 +1,20 @@
+Copyright (C) 2011 by Yehuda Katz
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/chrome/common/extensions/docs/examples/howto/sandbox/eventpage.html b/chrome/common/extensions/docs/examples/howto/sandbox/eventpage.html
new file mode 100644
index 0000000..8a29dbb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/howto/sandbox/eventpage.html
@@ -0,0 +1,14 @@
+<!--
+  - Copyright (c) 2012 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.
+  -->
+<!doctype html>
+<html>
+  <head>
+    <script src="eventpage.js"></script>
+  </head>
+  <body>
+    <iframe id="theFrame" src="sandbox.html"></iframe>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/howto/sandbox/eventpage.js b/chrome/common/extensions/docs/examples/howto/sandbox/eventpage.js
new file mode 100644
index 0000000..03800c6
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/howto/sandbox/eventpage.js
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 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.
+
+chrome.browserAction.onClicked.addListener(function() {
+  var iframe = document.getElementById('theFrame');
+  var message = {
+    command: 'render',
+    context: {thing: 'world'}
+  };
+  iframe.contentWindow.postMessage(message, '*');
+});
+
+
+window.addEventListener('message', function(event) {
+  if (event.data.html) {
+    var notification = webkitNotifications.createNotification(
+      'icon.png',
+      'Templated!',
+      'HTML Received for "' + event.data.name + '": `' + event.data.html + '`'
+    );
+    notification.show();
+  }
+});
diff --git a/chrome/common/extensions/docs/examples/howto/sandbox/handlebars-1.0.0.beta.6.js b/chrome/common/extensions/docs/examples/howto/sandbox/handlebars-1.0.0.beta.6.js
new file mode 100644
index 0000000..4f9e2c5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/howto/sandbox/handlebars-1.0.0.beta.6.js
@@ -0,0 +1,1553 @@
+// Copyright (C) 2011 by Yehuda Katz
+// Licensing details in LICENSE.handlebars
+
+// lib/handlebars/base.js
+var Handlebars = {};
+
+Handlebars.VERSION = "1.0.beta.6";
+
+Handlebars.helpers  = {};
+Handlebars.partials = {};
+
+Handlebars.registerHelper = function(name, fn, inverse) {
+  if(inverse) { fn.not = inverse; }
+  this.helpers[name] = fn;
+};
+
+Handlebars.registerPartial = function(name, str) {
+  this.partials[name] = str;
+};
+
+Handlebars.registerHelper('helperMissing', function(arg) {
+  if(arguments.length === 2) {
+    return undefined;
+  } else {
+    throw new Error("Could not find property '" + arg + "'");
+  }
+});
+
+var toString = Object.prototype.toString, functionType = "[object Function]";
+
+Handlebars.registerHelper('blockHelperMissing', function(context, options) {
+  var inverse = options.inverse || function() {}, fn = options.fn;
+
+
+  var ret = "";
+  var type = toString.call(context);
+
+  if(type === functionType) { context = context.call(this); }
+
+  if(context === true) {
+    return fn(this);
+  } else if(context === false || context == null) {
+    return inverse(this);
+  } else if(type === "[object Array]") {
+    if(context.length > 0) {
+      for(var i=0, j=context.length; i<j; i++) {
+        ret = ret + fn(context[i]);
+      }
+    } else {
+      ret = inverse(this);
+    }
+    return ret;
+  } else {
+    return fn(context);
+  }
+});
+
+Handlebars.registerHelper('each', function(context, options) {
+  var fn = options.fn, inverse = options.inverse;
+  var ret = "";
+
+  if(context && context.length > 0) {
+    for(var i=0, j=context.length; i<j; i++) {
+      ret = ret + fn(context[i]);
+    }
+  } else {
+    ret = inverse(this);
+  }
+  return ret;
+});
+
+Handlebars.registerHelper('if', function(context, options) {
+  var type = toString.call(context);
+  if(type === functionType) { context = context.call(this); }
+
+  if(!context || Handlebars.Utils.isEmpty(context)) {
+    return options.inverse(this);
+  } else {
+    return options.fn(this);
+  }
+});
+
+Handlebars.registerHelper('unless', function(context, options) {
+  var fn = options.fn, inverse = options.inverse;
+  options.fn = inverse;
+  options.inverse = fn;
+
+  return Handlebars.helpers['if'].call(this, context, options);
+});
+
+Handlebars.registerHelper('with', function(context, options) {
+  return options.fn(context);
+});
+
+Handlebars.registerHelper('log', function(context) {
+  Handlebars.log(context);
+});
+;
+// lib/handlebars/compiler/parser.js
+/* Jison generated parser */
+var handlebars = (function(){
+
+var parser = {trace: function trace() { },
+yy: {},
+symbols_: {"error":2,"root":3,"program":4,"EOF":5,"statements":6,"simpleInverse":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"OPEN_PARTIAL":24,"params":25,"hash":26,"param":27,"STRING":28,"INTEGER":29,"BOOLEAN":30,"hashSegments":31,"hashSegment":32,"ID":33,"EQUALS":34,"pathSegments":35,"SEP":36,"$accept":0,"$end":1},
+terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"OPEN_PARTIAL",28:"STRING",29:"INTEGER",30:"BOOLEAN",33:"ID",34:"EQUALS",36:"SEP"},
+productions_: [0,[3,2],[4,3],[4,1],[4,0],[6,1],[6,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[7,2],[17,3],[17,2],[17,2],[17,1],[25,2],[25,1],[27,1],[27,1],[27,1],[27,1],[26,1],[31,2],[31,1],[32,3],[32,3],[32,3],[32,3],[21,1],[35,3],[35,1]],
+performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
+
+var $0 = $$.length - 1;
+switch (yystate) {
+case 1: return $$[$0-1] 
+break;
+case 2: this.$ = new yy.ProgramNode($$[$0-2], $$[$0]) 
+break;
+case 3: this.$ = new yy.ProgramNode($$[$0]) 
+break;
+case 4: this.$ = new yy.ProgramNode([]) 
+break;
+case 5: this.$ = [$$[$0]] 
+break;
+case 6: $$[$0-1].push($$[$0]); this.$ = $$[$0-1] 
+break;
+case 7: this.$ = new yy.InverseNode($$[$0-2], $$[$0-1], $$[$0]) 
+break;
+case 8: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0]) 
+break;
+case 9: this.$ = $$[$0] 
+break;
+case 10: this.$ = $$[$0] 
+break;
+case 11: this.$ = new yy.ContentNode($$[$0]) 
+break;
+case 12: this.$ = new yy.CommentNode($$[$0]) 
+break;
+case 13: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]) 
+break;
+case 14: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]) 
+break;
+case 15: this.$ = $$[$0-1] 
+break;
+case 16: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]) 
+break;
+case 17: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], true) 
+break;
+case 18: this.$ = new yy.PartialNode($$[$0-1]) 
+break;
+case 19: this.$ = new yy.PartialNode($$[$0-2], $$[$0-1]) 
+break;
+case 20: 
+break;
+case 21: this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]] 
+break;
+case 22: this.$ = [[$$[$0-1]].concat($$[$0]), null] 
+break;
+case 23: this.$ = [[$$[$0-1]], $$[$0]] 
+break;
+case 24: this.$ = [[$$[$0]], null] 
+break;
+case 25: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; 
+break;
+case 26: this.$ = [$$[$0]] 
+break;
+case 27: this.$ = $$[$0] 
+break;
+case 28: this.$ = new yy.StringNode($$[$0]) 
+break;
+case 29: this.$ = new yy.IntegerNode($$[$0]) 
+break;
+case 30: this.$ = new yy.BooleanNode($$[$0]) 
+break;
+case 31: this.$ = new yy.HashNode($$[$0]) 
+break;
+case 32: $$[$0-1].push($$[$0]); this.$ = $$[$0-1] 
+break;
+case 33: this.$ = [$$[$0]] 
+break;
+case 34: this.$ = [$$[$0-2], $$[$0]] 
+break;
+case 35: this.$ = [$$[$0-2], new yy.StringNode($$[$0])] 
+break;
+case 36: this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])] 
+break;
+case 37: this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])] 
+break;
+case 38: this.$ = new yy.IdNode($$[$0]) 
+break;
+case 39: $$[$0-2].push($$[$0]); this.$ = $$[$0-2]; 
+break;
+case 40: this.$ = [$$[$0]] 
+break;
+}
+},
+table: [{3:1,4:2,5:[2,4],6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],24:[1,15]},{1:[3]},{5:[1,16]},{5:[2,3],7:17,8:18,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,19],20:[2,3],22:[1,13],23:[1,14],24:[1,15]},{5:[2,5],14:[2,5],15:[2,5],16:[2,5],19:[2,5],20:[2,5],22:[2,5],23:[2,5],24:[2,5]},{4:20,6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],24:[1,15]},{4:21,6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],24:[1,15]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],24:[2,9]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],24:[2,10]},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],24:[2,11]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],24:[2,12]},{17:22,21:23,33:[1,25],35:24},{17:26,21:23,33:[1,25],35:24},{17:27,21:23,33:[1,25],35:24},{17:28,21:23,33:[1,25],35:24},{21:29,33:[1,25],35:24},{1:[2,1]},{6:30,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],24:[1,15]},{5:[2,6],14:[2,6],15:[2,6],16:[2,6],19:[2,6],20:[2,6],22:[2,6],23:[2,6],24:[2,6]},{17:22,18:[1,31],21:23,33:[1,25],35:24},{10:32,20:[1,33]},{10:34,20:[1,33]},{18:[1,35]},{18:[2,24],21:40,25:36,26:37,27:38,28:[1,41],29:[1,42],30:[1,43],31:39,32:44,33:[1,45],35:24},{18:[2,38],28:[2,38],29:[2,38],30:[2,38],33:[2,38],36:[1,46]},{18:[2,40],28:[2,40],29:[2,40],30:[2,40],33:[2,40],36:[2,40]},{18:[1,47]},{18:[1,48]},{18:[1,49]},{18:[1,50],21:51,33:[1,25],35:24},{5:[2,2],8:18,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,2],22:[1,13],23:[1,14],24:[1,15]},{14:[2,20],15:[2,20],16:[2,20],19:[2,20],22:[2,20],23:[2,20],24:[2,20]},{5:[2,7],14:[2,7],15:[2,7],16:[2,7],19:[2,7],20:[2,7],22:[2,7],23:[2,7],24:[2,7]},{21:52,33:[1,25],35:24},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],24:[2,8]},{14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],24:[2,14]},{18:[2,22],21:40,26:53,27:54,28:[1,41],29:[1,42],30:[1,43],31:39,32:44,33:[1,45],35:24},{18:[2,23]},{18:[2,26],28:[2,26],29:[2,26],30:[2,26],33:[2,26]},{18:[2,31],32:55,33:[1,56]},{18:[2,27],28:[2,27],29:[2,27],30:[2,27],33:[2,27]},{18:[2,28],28:[2,28],29:[2,28],30:[2,28],33:[2,28]},{18:[2,29],28:[2,29],29:[2,29],30:[2,29],33:[2,29]},{18:[2,30],28:[2,30],29:[2,30],30:[2,30],33:[2,30]},{18:[2,33],33:[2,33]},{18:[2,40],28:[2,40],29:[2,40],30:[2,40],33:[2,40],34:[1,57],36:[2,40]},{33:[1,58]},{14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],24:[2,13]},{5:[2,16],14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],24:[2,16]},{5:[2,17],14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],24:[2,17]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],24:[2,18]},{18:[1,59]},{18:[1,60]},{18:[2,21]},{18:[2,25],28:[2,25],29:[2,25],30:[2,25],33:[2,25]},{18:[2,32],33:[2,32]},{34:[1,57]},{21:61,28:[1,62],29:[1,63],30:[1,64],33:[1,25],35:24},{18:[2,39],28:[2,39],29:[2,39],30:[2,39],33:[2,39],36:[2,39]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],24:[2,19]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],24:[2,15]},{18:[2,34],33:[2,34]},{18:[2,35],33:[2,35]},{18:[2,36],33:[2,36]},{18:[2,37],33:[2,37]}],
+defaultActions: {16:[2,1],37:[2,23],53:[2,21]},
+parseError: function parseError(str, hash) {
+    throw new Error(str);
+},
+parse: function parse(input) {
+    var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
+    this.lexer.setInput(input);
+    this.lexer.yy = this.yy;
+    this.yy.lexer = this.lexer;
+    if (typeof this.lexer.yylloc == "undefined")
+        this.lexer.yylloc = {};
+    var yyloc = this.lexer.yylloc;
+    lstack.push(yyloc);
+    if (typeof this.yy.parseError === "function")
+        this.parseError = this.yy.parseError;
+    function popStack(n) {
+        stack.length = stack.length - 2 * n;
+        vstack.length = vstack.length - n;
+        lstack.length = lstack.length - n;
+    }
+    function lex() {
+        var token;
+        token = self.lexer.lex() || 1;
+        if (typeof token !== "number") {
+            token = self.symbols_[token] || token;
+        }
+        return token;
+    }
+    var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
+    while (true) {
+        state = stack[stack.length - 1];
+        if (this.defaultActions[state]) {
+            action = this.defaultActions[state];
+        } else {
+            if (symbol == null)
+                symbol = lex();
+            action = table[state] && table[state][symbol];
+        }
+        if (typeof action === "undefined" || !action.length || !action[0]) {
+            if (!recovering) {
+                expected = [];
+                for (p in table[state])
+                    if (this.terminals_[p] && p > 2) {
+                        expected.push("'" + this.terminals_[p] + "'");
+                    }
+                var errStr = "";
+                if (this.lexer.showPosition) {
+                    errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + this.terminals_[symbol] + "'";
+                } else {
+                    errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
+                }
+                this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
+            }
+        }
+        if (action[0] instanceof Array && action.length > 1) {
+            throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
+        }
+        switch (action[0]) {
+        case 1:
+            stack.push(symbol);
+            vstack.push(this.lexer.yytext);
+            lstack.push(this.lexer.yylloc);
+            stack.push(action[1]);
+            symbol = null;
+            if (!preErrorSymbol) {
+                yyleng = this.lexer.yyleng;
+                yytext = this.lexer.yytext;
+                yylineno = this.lexer.yylineno;
+                yyloc = this.lexer.yylloc;
+                if (recovering > 0)
+                    recovering--;
+            } else {
+                symbol = preErrorSymbol;
+                preErrorSymbol = null;
+            }
+            break;
+        case 2:
+            len = this.productions_[action[1]][1];
+            yyval.$ = vstack[vstack.length - len];
+            yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
+            r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
+            if (typeof r !== "undefined") {
+                return r;
+            }
+            if (len) {
+                stack = stack.slice(0, -1 * len * 2);
+                vstack = vstack.slice(0, -1 * len);
+                lstack = lstack.slice(0, -1 * len);
+            }
+            stack.push(this.productions_[action[1]][0]);
+            vstack.push(yyval.$);
+            lstack.push(yyval._$);
+            newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
+            stack.push(newState);
+            break;
+        case 3:
+            return true;
+        }
+    }
+    return true;
+}
+};/* Jison generated lexer */
+var lexer = (function(){
+
+var lexer = ({EOF:1,
+parseError:function parseError(str, hash) {
+        if (this.yy.parseError) {
+            this.yy.parseError(str, hash);
+        } else {
+            throw new Error(str);
+        }
+    },
+setInput:function (input) {
+        this._input = input;
+        this._more = this._less = this.done = false;
+        this.yylineno = this.yyleng = 0;
+        this.yytext = this.matched = this.match = '';
+        this.conditionStack = ['INITIAL'];
+        this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
+        return this;
+    },
+input:function () {
+        var ch = this._input[0];
+        this.yytext+=ch;
+        this.yyleng++;
+        this.match+=ch;
+        this.matched+=ch;
+        var lines = ch.match(/\n/);
+        if (lines) this.yylineno++;
+        this._input = this._input.slice(1);
+        return ch;
+    },
+unput:function (ch) {
+        this._input = ch + this._input;
+        return this;
+    },
+more:function () {
+        this._more = true;
+        return this;
+    },
+pastInput:function () {
+        var past = this.matched.substr(0, this.matched.length - this.match.length);
+        return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
+    },
+upcomingInput:function () {
+        var next = this.match;
+        if (next.length < 20) {
+            next += this._input.substr(0, 20-next.length);
+        }
+        return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
+    },
+showPosition:function () {
+        var pre = this.pastInput();
+        var c = new Array(pre.length + 1).join("-");
+        return pre + this.upcomingInput() + "\n" + c+"^";
+    },
+next:function () {
+        if (this.done) {
+            return this.EOF;
+        }
+        if (!this._input) this.done = true;
+
+        var token,
+            match,
+            col,
+            lines;
+        if (!this._more) {
+            this.yytext = '';
+            this.match = '';
+        }
+        var rules = this._currentRules();
+        for (var i=0;i < rules.length; i++) {
+            match = this._input.match(this.rules[rules[i]]);
+            if (match) {
+                lines = match[0].match(/\n.*/g);
+                if (lines) this.yylineno += lines.length;
+                this.yylloc = {first_line: this.yylloc.last_line,
+                               last_line: this.yylineno+1,
+                               first_column: this.yylloc.last_column,
+                               last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length}
+                this.yytext += match[0];
+                this.match += match[0];
+                this.matches = match;
+                this.yyleng = this.yytext.length;
+                this._more = false;
+                this._input = this._input.slice(match[0].length);
+                this.matched += match[0];
+                token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]);
+                if (token) return token;
+                else return;
+            }
+        }
+        if (this._input === "") {
+            return this.EOF;
+        } else {
+            this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), 
+                    {text: "", token: null, line: this.yylineno});
+        }
+    },
+lex:function lex() {
+        var r = this.next();
+        if (typeof r !== 'undefined') {
+            return r;
+        } else {
+            return this.lex();
+        }
+    },
+begin:function begin(condition) {
+        this.conditionStack.push(condition);
+    },
+popState:function popState() {
+        return this.conditionStack.pop();
+    },
+_currentRules:function _currentRules() {
+        return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
+    },
+topState:function () {
+        return this.conditionStack[this.conditionStack.length-2];
+    },
+pushState:function begin(condition) {
+        this.begin(condition);
+    }});
+lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
+
+var YYSTATE=YY_START
+switch($avoiding_name_collisions) {
+case 0:
+                                   if(yy_.yytext.slice(-1) !== "\\") this.begin("mu");
+                                   if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu");
+                                   if(yy_.yytext) return 14;
+                                 
+break;
+case 1: return 14; 
+break;
+case 2: this.popState(); return 14; 
+break;
+case 3: return 24; 
+break;
+case 4: return 16; 
+break;
+case 5: return 20; 
+break;
+case 6: return 19; 
+break;
+case 7: return 19; 
+break;
+case 8: return 23; 
+break;
+case 9: return 23; 
+break;
+case 10: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; 
+break;
+case 11: return 22; 
+break;
+case 12: return 34; 
+break;
+case 13: return 33; 
+break;
+case 14: return 33; 
+break;
+case 15: return 36; 
+break;
+case 16: /*ignore whitespace*/ 
+break;
+case 17: this.popState(); return 18; 
+break;
+case 18: this.popState(); return 18; 
+break;
+case 19: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 28; 
+break;
+case 20: return 30; 
+break;
+case 21: return 30; 
+break;
+case 22: return 29; 
+break;
+case 23: return 33; 
+break;
+case 24: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 33; 
+break;
+case 25: return 'INVALID'; 
+break;
+case 26: return 5; 
+break;
+}
+};
+lexer.rules = [/^[^\x00]*?(?=(\{\{))/,/^[^\x00]+/,/^[^\x00]{2,}?(?=(\{\{))/,/^\{\{>/,/^\{\{#/,/^\{\{\//,/^\{\{\^/,/^\{\{\s*else\b/,/^\{\{\{/,/^\{\{&/,/^\{\{![\s\S]*?\}\}/,/^\{\{/,/^=/,/^\.(?=[} ])/,/^\.\./,/^[\/.]/,/^\s+/,/^\}\}\}/,/^\}\}/,/^"(\\["]|[^"])*"/,/^true(?=[}\s])/,/^false(?=[}\s])/,/^[0-9]+(?=[}\s])/,/^[a-zA-Z0-9_$-]+(?=[=}\s\/.])/,/^\[[^\]]*\]/,/^./,/^$/];
+lexer.conditions = {"mu":{"rules":[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"INITIAL":{"rules":[0,1,26],"inclusive":true}};return lexer;})()
+parser.lexer = lexer;
+return parser;
+})();
+if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
+exports.parser = handlebars;
+exports.parse = function () { return handlebars.parse.apply(handlebars, arguments); }
+exports.main = function commonjsMain(args) {
+    if (!args[1])
+        throw new Error('Usage: '+args[0]+' FILE');
+    if (typeof process !== 'undefined') {
+        var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8");
+    } else {
+        var cwd = require("file").path(require("file").cwd());
+        var source = cwd.join(args[1]).read({charset: "utf-8"});
+    }
+    return exports.parser.parse(source);
+}
+if (typeof module !== 'undefined' && require.main === module) {
+  exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args);
+}
+};
+;
+// lib/handlebars/compiler/base.js
+Handlebars.Parser = handlebars;
+
+Handlebars.parse = function(string) {
+  Handlebars.Parser.yy = Handlebars.AST;
+  return Handlebars.Parser.parse(string);
+};
+
+Handlebars.print = function(ast) {
+  return new Handlebars.PrintVisitor().accept(ast);
+};
+
+Handlebars.logger = {
+  DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
+
+  // override in the host environment
+  log: function(level, str) {}
+};
+
+Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
+;
+// lib/handlebars/compiler/ast.js
+(function() {
+
+  Handlebars.AST = {};
+
+  Handlebars.AST.ProgramNode = function(statements, inverse) {
+    this.type = "program";
+    this.statements = statements;
+    if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
+  };
+
+  Handlebars.AST.MustacheNode = function(params, hash, unescaped) {
+    this.type = "mustache";
+    this.id = params[0];
+    this.params = params.slice(1);
+    this.hash = hash;
+    this.escaped = !unescaped;
+  };
+
+  Handlebars.AST.PartialNode = function(id, context) {
+    this.type    = "partial";
+
+    // TODO: disallow complex IDs
+
+    this.id      = id;
+    this.context = context;
+  };
+
+  var verifyMatch = function(open, close) {
+    if(open.original !== close.original) {
+      throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
+    }
+  };
+
+  Handlebars.AST.BlockNode = function(mustache, program, close) {
+    verifyMatch(mustache.id, close);
+    this.type = "block";
+    this.mustache = mustache;
+    this.program  = program;
+  };
+
+  Handlebars.AST.InverseNode = function(mustache, program, close) {
+    verifyMatch(mustache.id, close);
+    this.type = "inverse";
+    this.mustache = mustache;
+    this.program  = program;
+  };
+
+  Handlebars.AST.ContentNode = function(string) {
+    this.type = "content";
+    this.string = string;
+  };
+
+  Handlebars.AST.HashNode = function(pairs) {
+    this.type = "hash";
+    this.pairs = pairs;
+  };
+
+  Handlebars.AST.IdNode = function(parts) {
+    this.type = "ID";
+    this.original = parts.join(".");
+
+    var dig = [], depth = 0;
+
+    for(var i=0,l=parts.length; i<l; i++) {
+      var part = parts[i];
+
+      if(part === "..") { depth++; }
+      else if(part === "." || part === "this") { this.isScoped = true; }
+      else { dig.push(part); }
+    }
+
+    this.parts    = dig;
+    this.string   = dig.join('.');
+    this.depth    = depth;
+    this.isSimple = (dig.length === 1) && (depth === 0);
+  };
+
+  Handlebars.AST.StringNode = function(string) {
+    this.type = "STRING";
+    this.string = string;
+  };
+
+  Handlebars.AST.IntegerNode = function(integer) {
+    this.type = "INTEGER";
+    this.integer = integer;
+  };
+
+  Handlebars.AST.BooleanNode = function(bool) {
+    this.type = "BOOLEAN";
+    this.bool = bool;
+  };
+
+  Handlebars.AST.CommentNode = function(comment) {
+    this.type = "comment";
+    this.comment = comment;
+  };
+
+})();;
+// lib/handlebars/utils.js
+Handlebars.Exception = function(message) {
+  var tmp = Error.prototype.constructor.apply(this, arguments);
+
+  for (var p in tmp) {
+    if (tmp.hasOwnProperty(p)) { this[p] = tmp[p]; }
+  }
+
+  this.message = tmp.message;
+};
+Handlebars.Exception.prototype = new Error;
+
+// Build out our basic SafeString type
+Handlebars.SafeString = function(string) {
+  this.string = string;
+};
+Handlebars.SafeString.prototype.toString = function() {
+  return this.string.toString();
+};
+
+(function() {
+  var escape = {
+    "<": "&lt;",
+    ">": "&gt;",
+    '"': "&quot;",
+    "'": "&#x27;",
+    "`": "&#x60;"
+  };
+
+  var badChars = /&(?!\w+;)|[<>"'`]/g;
+  var possible = /[&<>"'`]/;
+
+  var escapeChar = function(chr) {
+    return escape[chr] || "&amp;";
+  };
+
+  Handlebars.Utils = {
+    escapeExpression: function(string) {
+      // don't escape SafeStrings, since they're already safe
+      if (string instanceof Handlebars.SafeString) {
+        return string.toString();
+      } else if (string == null || string === false) {
+        return "";
+      }
+
+      if(!possible.test(string)) { return string; }
+      return string.replace(badChars, escapeChar);
+    },
+
+    isEmpty: function(value) {
+      if (typeof value === "undefined") {
+        return true;
+      } else if (value === null) {
+        return true;
+      } else if (value === false) {
+        return true;
+      } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
+        return true;
+      } else {
+        return false;
+      }
+    }
+  };
+})();;
+// lib/handlebars/compiler/compiler.js
+Handlebars.Compiler = function() {};
+Handlebars.JavaScriptCompiler = function() {};
+
+(function(Compiler, JavaScriptCompiler) {
+  Compiler.OPCODE_MAP = {
+    appendContent: 1,
+    getContext: 2,
+    lookupWithHelpers: 3,
+    lookup: 4,
+    append: 5,
+    invokeMustache: 6,
+    appendEscaped: 7,
+    pushString: 8,
+    truthyOrFallback: 9,
+    functionOrFallback: 10,
+    invokeProgram: 11,
+    invokePartial: 12,
+    push: 13,
+    assignToHash: 15,
+    pushStringParam: 16
+  };
+
+  Compiler.MULTI_PARAM_OPCODES = {
+    appendContent: 1,
+    getContext: 1,
+    lookupWithHelpers: 2,
+    lookup: 1,
+    invokeMustache: 3,
+    pushString: 1,
+    truthyOrFallback: 1,
+    functionOrFallback: 1,
+    invokeProgram: 3,
+    invokePartial: 1,
+    push: 1,
+    assignToHash: 1,
+    pushStringParam: 1
+  };
+
+  Compiler.DISASSEMBLE_MAP = {};
+
+  for(var prop in Compiler.OPCODE_MAP) {
+    var value = Compiler.OPCODE_MAP[prop];
+    Compiler.DISASSEMBLE_MAP[value] = prop;
+  }
+
+  Compiler.multiParamSize = function(code) {
+    return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]];
+  };
+
+  Compiler.prototype = {
+    compiler: Compiler,
+
+    disassemble: function() {
+      var opcodes = this.opcodes, opcode, nextCode;
+      var out = [], str, name, value;
+
+      for(var i=0, l=opcodes.length; i<l; i++) {
+        opcode = opcodes[i];
+
+        if(opcode === 'DECLARE') {
+          name = opcodes[++i];
+          value = opcodes[++i];
+          out.push("DECLARE " + name + " = " + value);
+        } else {
+          str = Compiler.DISASSEMBLE_MAP[opcode];
+
+          var extraParams = Compiler.multiParamSize(opcode);
+          var codes = [];
+
+          for(var j=0; j<extraParams; j++) {
+            nextCode = opcodes[++i];
+
+            if(typeof nextCode === "string") {
+              nextCode = "\"" + nextCode.replace("\n", "\\n") + "\"";
+            }
+
+            codes.push(nextCode);
+          }
+
+          str = str + " " + codes.join(" ");
+
+          out.push(str);
+        }
+      }
+
+      return out.join("\n");
+    },
+
+    guid: 0,
+
+    compile: function(program, options) {
+      this.children = [];
+      this.depths = {list: []};
+      this.options = options;
+
+      // These changes will propagate to the other compiler components
+      var knownHelpers = this.options.knownHelpers;
+      this.options.knownHelpers = {
+        'helperMissing': true,
+        'blockHelperMissing': true,
+        'each': true,
+        'if': true,
+        'unless': true,
+        'with': true,
+        'log': true
+      };
+      if (knownHelpers) {
+        for (var name in knownHelpers) {
+          this.options.knownHelpers[name] = knownHelpers[name];
+        }
+      }
+
+      return this.program(program);
+    },
+
+    accept: function(node) {
+      return this[node.type](node);
+    },
+
+    program: function(program) {
+      var statements = program.statements, statement;
+      this.opcodes = [];
+
+      for(var i=0, l=statements.length; i<l; i++) {
+        statement = statements[i];
+        this[statement.type](statement);
+      }
+      this.isSimple = l === 1;
+
+      this.depths.list = this.depths.list.sort(function(a, b) {
+        return a - b;
+      });
+
+      return this;
+    },
+
+    compileProgram: function(program) {
+      var result = new this.compiler().compile(program, this.options);
+      var guid = this.guid++;
+
+      this.usePartial = this.usePartial || result.usePartial;
+
+      this.children[guid] = result;
+
+      for(var i=0, l=result.depths.list.length; i<l; i++) {
+        depth = result.depths.list[i];
+
+        if(depth < 2) { continue; }
+        else { this.addDepth(depth - 1); }
+      }
+
+      return guid;
+    },
+
+    block: function(block) {
+      var mustache = block.mustache;
+      var depth, child, inverse, inverseGuid;
+
+      var params = this.setupStackForMustache(mustache);
+
+      var programGuid = this.compileProgram(block.program);
+
+      if(block.program.inverse) {
+        inverseGuid = this.compileProgram(block.program.inverse);
+        this.declare('inverse', inverseGuid);
+      }
+
+      this.opcode('invokeProgram', programGuid, params.length, !!mustache.hash);
+      this.declare('inverse', null);
+      this.opcode('append');
+    },
+
+    inverse: function(block) {
+      var params = this.setupStackForMustache(block.mustache);
+
+      var programGuid = this.compileProgram(block.program);
+
+      this.declare('inverse', programGuid);
+
+      this.opcode('invokeProgram', null, params.length, !!block.mustache.hash);
+      this.declare('inverse', null);
+      this.opcode('append');
+    },
+
+    hash: function(hash) {
+      var pairs = hash.pairs, pair, val;
+
+      this.opcode('push', '{}');
+
+      for(var i=0, l=pairs.length; i<l; i++) {
+        pair = pairs[i];
+        val  = pair[1];
+
+        this.accept(val);
+        this.opcode('assignToHash', pair[0]);
+      }
+    },
+
+    partial: function(partial) {
+      var id = partial.id;
+      this.usePartial = true;
+
+      if(partial.context) {
+        this.ID(partial.context);
+      } else {
+        this.opcode('push', 'depth0');
+      }
+
+      this.opcode('invokePartial', id.original);
+      this.opcode('append');
+    },
+
+    content: function(content) {
+      this.opcode('appendContent', content.string);
+    },
+
+    mustache: function(mustache) {
+      var params = this.setupStackForMustache(mustache);
+
+      this.opcode('invokeMustache', params.length, mustache.id.original, !!mustache.hash);
+
+      if(mustache.escaped && !this.options.noEscape) {
+        this.opcode('appendEscaped');
+      } else {
+        this.opcode('append');
+      }
+    },
+
+    ID: function(id) {
+      this.addDepth(id.depth);
+
+      this.opcode('getContext', id.depth);
+
+      this.opcode('lookupWithHelpers', id.parts[0] || null, id.isScoped || false);
+
+      for(var i=1, l=id.parts.length; i<l; i++) {
+        this.opcode('lookup', id.parts[i]);
+      }
+    },
+
+    STRING: function(string) {
+      this.opcode('pushString', string.string);
+    },
+
+    INTEGER: function(integer) {
+      this.opcode('push', integer.integer);
+    },
+
+    BOOLEAN: function(bool) {
+      this.opcode('push', bool.bool);
+    },
+
+    comment: function() {},
+
+    // HELPERS
+    pushParams: function(params) {
+      var i = params.length, param;
+
+      while(i--) {
+        param = params[i];
+
+        if(this.options.stringParams) {
+          if(param.depth) {
+            this.addDepth(param.depth);
+          }
+
+          this.opcode('getContext', param.depth || 0);
+          this.opcode('pushStringParam', param.string);
+        } else {
+          this[param.type](param);
+        }
+      }
+    },
+
+    opcode: function(name, val1, val2, val3) {
+      this.opcodes.push(Compiler.OPCODE_MAP[name]);
+      if(val1 !== undefined) { this.opcodes.push(val1); }
+      if(val2 !== undefined) { this.opcodes.push(val2); }
+      if(val3 !== undefined) { this.opcodes.push(val3); }
+    },
+
+    declare: function(name, value) {
+      this.opcodes.push('DECLARE');
+      this.opcodes.push(name);
+      this.opcodes.push(value);
+    },
+
+    addDepth: function(depth) {
+      if(depth === 0) { return; }
+
+      if(!this.depths[depth]) {
+        this.depths[depth] = true;
+        this.depths.list.push(depth);
+      }
+    },
+
+    setupStackForMustache: function(mustache) {
+      var params = mustache.params;
+
+      this.pushParams(params);
+
+      if(mustache.hash) {
+        this.hash(mustache.hash);
+      }
+
+      this.ID(mustache.id);
+
+      return params;
+    }
+  };
+
+  JavaScriptCompiler.prototype = {
+    // PUBLIC API: You can override these methods in a subclass to provide
+    // alternative compiled forms for name lookup and buffering semantics
+    nameLookup: function(parent, name, type) {
+      if (/^[0-9]+$/.test(name)) {
+        return parent + "[" + name + "]";
+      } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
+        return parent + "." + name;
+      }
+      else {
+        return parent + "['" + name + "']";
+      }
+    },
+
+    appendToBuffer: function(string) {
+      if (this.environment.isSimple) {
+        return "return " + string + ";";
+      } else {
+        return "buffer += " + string + ";";
+      }
+    },
+
+    initializeBuffer: function() {
+      return this.quotedString("");
+    },
+
+    namespace: "Handlebars",
+    // END PUBLIC API
+
+    compile: function(environment, options, context, asObject) {
+      this.environment = environment;
+      this.options = options || {};
+
+      this.name = this.environment.name;
+      this.isChild = !!context;
+      this.context = context || {
+        programs: [],
+        aliases: { self: 'this' },
+        registers: {list: []}
+      };
+
+      this.preamble();
+
+      this.stackSlot = 0;
+      this.stackVars = [];
+
+      this.compileChildren(environment, options);
+
+      var opcodes = environment.opcodes, opcode;
+
+      this.i = 0;
+
+      for(l=opcodes.length; this.i<l; this.i++) {
+        opcode = this.nextOpcode(0);
+
+        if(opcode[0] === 'DECLARE') {
+          this.i = this.i + 2;
+          this[opcode[1]] = opcode[2];
+        } else {
+          this.i = this.i + opcode[1].length;
+          this[opcode[0]].apply(this, opcode[1]);
+        }
+      }
+
+      return this.createFunctionContext(asObject);
+    },
+
+    nextOpcode: function(n) {
+      var opcodes = this.environment.opcodes, opcode = opcodes[this.i + n], name, val;
+      var extraParams, codes;
+
+      if(opcode === 'DECLARE') {
+        name = opcodes[this.i + 1];
+        val  = opcodes[this.i + 2];
+        return ['DECLARE', name, val];
+      } else {
+        name = Compiler.DISASSEMBLE_MAP[opcode];
+
+        extraParams = Compiler.multiParamSize(opcode);
+        codes = [];
+
+        for(var j=0; j<extraParams; j++) {
+          codes.push(opcodes[this.i + j + 1 + n]);
+        }
+
+        return [name, codes];
+      }
+    },
+
+    eat: function(opcode) {
+      this.i = this.i + opcode.length;
+    },
+
+    preamble: function() {
+      var out = [];
+
+      // this register will disambiguate helper lookup from finding a function in
+      // a context. This is necessary for mustache compatibility, which requires
+      // that context functions in blocks are evaluated by blockHelperMissing, and
+      // then proceed as if the resulting value was provided to blockHelperMissing.
+      this.useRegister('foundHelper');
+
+      if (!this.isChild) {
+        var namespace = this.namespace;
+        var copies = "helpers = helpers || " + namespace + ".helpers;";
+        if(this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
+        out.push(copies);
+      } else {
+        out.push('');
+      }
+
+      if (!this.environment.isSimple) {
+        out.push(", buffer = " + this.initializeBuffer());
+      } else {
+        out.push("");
+      }
+
+      // track the last context pushed into place to allow skipping the
+      // getContext opcode when it would be a noop
+      this.lastContext = 0;
+      this.source = out;
+    },
+
+    createFunctionContext: function(asObject) {
+      var locals = this.stackVars;
+      if (!this.isChild) {
+        locals = locals.concat(this.context.registers.list);
+      }
+
+      if(locals.length > 0) {
+        this.source[1] = this.source[1] + ", " + locals.join(", ");
+      }
+
+      // Generate minimizer alias mappings
+      if (!this.isChild) {
+        var aliases = []
+        for (var alias in this.context.aliases) {
+          this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
+        }
+      }
+
+      if (this.source[1]) {
+        this.source[1] = "var " + this.source[1].substring(2) + ";";
+      }
+
+      // Merge children
+      if (!this.isChild) {
+        this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
+      }
+
+      if (!this.environment.isSimple) {
+        this.source.push("return buffer;");
+      }
+
+      var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
+
+      for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
+        params.push("depth" + this.environment.depths.list[i]);
+      }
+
+      if (asObject) {
+        params.push(this.source.join("\n  "));
+
+        return Function.apply(this, params);
+      } else {
+        var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n  ' + this.source.join("\n  ") + '}';
+        Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n");
+        return functionSource;
+      }
+    },
+
+    appendContent: function(content) {
+      this.source.push(this.appendToBuffer(this.quotedString(content)));
+    },
+
+    append: function() {
+      var local = this.popStack();
+      this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
+      if (this.environment.isSimple) {
+        this.source.push("else { " + this.appendToBuffer("''") + " }");
+      }
+    },
+
+    appendEscaped: function() {
+      var opcode = this.nextOpcode(1), extra = "";
+      this.context.aliases.escapeExpression = 'this.escapeExpression';
+
+      if(opcode[0] === 'appendContent') {
+        extra = " + " + this.quotedString(opcode[1][0]);
+        this.eat(opcode);
+      }
+
+      this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")" + extra));
+    },
+
+    getContext: function(depth) {
+      if(this.lastContext !== depth) {
+        this.lastContext = depth;
+      }
+    },
+
+    lookupWithHelpers: function(name, isScoped) {
+      if(name) {
+        var topStack = this.nextStack();
+
+        this.usingKnownHelper = false;
+
+        var toPush;
+        if (!isScoped && this.options.knownHelpers[name]) {
+          toPush = topStack + " = " + this.nameLookup('helpers', name, 'helper');
+          this.usingKnownHelper = true;
+        } else if (isScoped || this.options.knownHelpersOnly) {
+          toPush = topStack + " = " + this.nameLookup('depth' + this.lastContext, name, 'context');
+        } else {
+          this.register('foundHelper', this.nameLookup('helpers', name, 'helper'));
+          toPush = topStack + " = foundHelper || " + this.nameLookup('depth' + this.lastContext, name, 'context');
+        }
+
+        toPush += ';';
+        this.source.push(toPush);
+      } else {
+        this.pushStack('depth' + this.lastContext);
+      }
+    },
+
+    lookup: function(name) {
+      var topStack = this.topStack();
+      this.source.push(topStack + " = (" + topStack + " === null || " + topStack + " === undefined || " + topStack + " === false ? " +
+        topStack + " : " + this.nameLookup(topStack, name, 'context') + ");");
+    },
+
+    pushStringParam: function(string) {
+      this.pushStack('depth' + this.lastContext);
+      this.pushString(string);
+    },
+
+    pushString: function(string) {
+      this.pushStack(this.quotedString(string));
+    },
+
+    push: function(name) {
+      this.pushStack(name);
+    },
+
+    invokeMustache: function(paramSize, original, hasHash) {
+      this.populateParams(paramSize, this.quotedString(original), "{}", null, hasHash, function(nextStack, helperMissingString, id) {
+        if (!this.usingKnownHelper) {
+          this.context.aliases.helperMissing = 'helpers.helperMissing';
+          this.context.aliases.undef = 'void 0';
+          this.source.push("else if(" + id + "=== undef) { " + nextStack + " = helperMissing.call(" + helperMissingString + "); }");
+          if (nextStack !== id) {
+            this.source.push("else { " + nextStack + " = " + id + "; }");
+          }
+        }
+      });
+    },
+
+    invokeProgram: function(guid, paramSize, hasHash) {
+      var inverse = this.programExpression(this.inverse);
+      var mainProgram = this.programExpression(guid);
+
+      this.populateParams(paramSize, null, mainProgram, inverse, hasHash, function(nextStack, helperMissingString, id) {
+        if (!this.usingKnownHelper) {
+          this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
+          this.source.push("else { " + nextStack + " = blockHelperMissing.call(" + helperMissingString + "); }");
+        }
+      });
+    },
+
+    populateParams: function(paramSize, helperId, program, inverse, hasHash, fn) {
+      var needsRegister = hasHash || this.options.stringParams || inverse || this.options.data;
+      var id = this.popStack(), nextStack;
+      var params = [], param, stringParam, stringOptions;
+
+      if (needsRegister) {
+        this.register('tmp1', program);
+        stringOptions = 'tmp1';
+      } else {
+        stringOptions = '{ hash: {} }';
+      }
+
+      if (needsRegister) {
+        var hash = (hasHash ? this.popStack() : '{}');
+        this.source.push('tmp1.hash = ' + hash + ';');
+      }
+
+      if(this.options.stringParams) {
+        this.source.push('tmp1.contexts = [];');
+      }
+
+      for(var i=0; i<paramSize; i++) {
+        param = this.popStack();
+        params.push(param);
+
+        if(this.options.stringParams) {
+          this.source.push('tmp1.contexts.push(' + this.popStack() + ');');
+        }
+      }
+
+      if(inverse) {
+        this.source.push('tmp1.fn = tmp1;');
+        this.source.push('tmp1.inverse = ' + inverse + ';');
+      }
+
+      if(this.options.data) {
+        this.source.push('tmp1.data = data;');
+      }
+
+      params.push(stringOptions);
+
+      this.populateCall(params, id, helperId || id, fn, program !== '{}');
+    },
+
+    populateCall: function(params, id, helperId, fn, program) {
+      var paramString = ["depth0"].concat(params).join(", ");
+      var helperMissingString = ["depth0"].concat(helperId).concat(params).join(", ");
+
+      var nextStack = this.nextStack();
+
+      if (this.usingKnownHelper) {
+        this.source.push(nextStack + " = " + id + ".call(" + paramString + ");");
+      } else {
+        this.context.aliases.functionType = '"function"';
+        var condition = program ? "foundHelper && " : ""
+        this.source.push("if(" + condition + "typeof " + id + " === functionType) { " + nextStack + " = " + id + ".call(" + paramString + "); }");
+      }
+      fn.call(this, nextStack, helperMissingString, id);
+      this.usingKnownHelper = false;
+    },
+
+    invokePartial: function(context) {
+      params = [this.nameLookup('partials', context, 'partial'), "'" + context + "'", this.popStack(), "helpers", "partials"];
+
+      if (this.options.data) {
+        params.push("data");
+      }
+
+      this.pushStack("self.invokePartial(" + params.join(", ") + ");");
+    },
+
+    assignToHash: function(key) {
+      var value = this.popStack();
+      var hash = this.topStack();
+
+      this.source.push(hash + "['" + key + "'] = " + value + ";");
+    },
+
+    // HELPERS
+
+    compiler: JavaScriptCompiler,
+
+    compileChildren: function(environment, options) {
+      var children = environment.children, child, compiler;
+
+      for(var i=0, l=children.length; i<l; i++) {
+        child = children[i];
+        compiler = new this.compiler();
+
+        this.context.programs.push('');     // Placeholder to prevent name conflicts for nested children
+        var index = this.context.programs.length;
+        child.index = index;
+        child.name = 'program' + index;
+        this.context.programs[index] = compiler.compile(child, options, this.context);
+      }
+    },
+
+    programExpression: function(guid) {
+      if(guid == null) { return "self.noop"; }
+
+      var child = this.environment.children[guid],
+          depths = child.depths.list;
+      var programParams = [child.index, child.name, "data"];
+
+      for(var i=0, l = depths.length; i<l; i++) {
+        depth = depths[i];
+
+        if(depth === 1) { programParams.push("depth0"); }
+        else { programParams.push("depth" + (depth - 1)); }
+      }
+
+      if(depths.length === 0) {
+        return "self.program(" + programParams.join(", ") + ")";
+      } else {
+        programParams.shift();
+        return "self.programWithDepth(" + programParams.join(", ") + ")";
+      }
+    },
+
+    register: function(name, val) {
+      this.useRegister(name);
+      this.source.push(name + " = " + val + ";");
+    },
+
+    useRegister: function(name) {
+      if(!this.context.registers[name]) {
+        this.context.registers[name] = true;
+        this.context.registers.list.push(name);
+      }
+    },
+
+    pushStack: function(item) {
+      this.source.push(this.nextStack() + " = " + item + ";");
+      return "stack" + this.stackSlot;
+    },
+
+    nextStack: function() {
+      this.stackSlot++;
+      if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
+      return "stack" + this.stackSlot;
+    },
+
+    popStack: function() {
+      return "stack" + this.stackSlot--;
+    },
+
+    topStack: function() {
+      return "stack" + this.stackSlot;
+    },
+
+    quotedString: function(str) {
+      return '"' + str
+        .replace(/\\/g, '\\\\')
+        .replace(/"/g, '\\"')
+        .replace(/\n/g, '\\n')
+        .replace(/\r/g, '\\r') + '"';
+    }
+  };
+
+  var reservedWords = (
+    "break else new var" +
+    " case finally return void" +
+    " catch for switch while" +
+    " continue function this with" +
+    " default if throw" +
+    " delete in try" +
+    " do instanceof typeof" +
+    " abstract enum int short" +
+    " boolean export interface static" +
+    " byte extends long super" +
+    " char final native synchronized" +
+    " class float package throws" +
+    " const goto private transient" +
+    " debugger implements protected volatile" +
+    " double import public let yield"
+  ).split(" ");
+
+  var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
+
+  for(var i=0, l=reservedWords.length; i<l; i++) {
+    compilerWords[reservedWords[i]] = true;
+  }
+
+  JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
+    if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) {
+      return true;
+    }
+    return false;
+  }
+
+})(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
+
+Handlebars.precompile = function(string, options) {
+  options = options || {};
+
+  var ast = Handlebars.parse(string);
+  var environment = new Handlebars.Compiler().compile(ast, options);
+  return new Handlebars.JavaScriptCompiler().compile(environment, options);
+};
+
+Handlebars.compile = function(string, options) {
+  options = options || {};
+
+  var compiled;
+  function compile() {
+    var ast = Handlebars.parse(string);
+    var environment = new Handlebars.Compiler().compile(ast, options);
+    var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
+    return Handlebars.template(templateSpec);
+  }
+
+  // Template is only compiled on first use and cached after that point.
+  return function(context, options) {
+    if (!compiled) {
+      compiled = compile();
+    }
+    return compiled.call(this, context, options);
+  };
+};
+;
+// lib/handlebars/runtime.js
+Handlebars.VM = {
+  template: function(templateSpec) {
+    // Just add water
+    var container = {
+      escapeExpression: Handlebars.Utils.escapeExpression,
+      invokePartial: Handlebars.VM.invokePartial,
+      programs: [],
+      program: function(i, fn, data) {
+        var programWrapper = this.programs[i];
+        if(data) {
+          return Handlebars.VM.program(fn, data);
+        } else if(programWrapper) {
+          return programWrapper;
+        } else {
+          programWrapper = this.programs[i] = Handlebars.VM.program(fn);
+          return programWrapper;
+        }
+      },
+      programWithDepth: Handlebars.VM.programWithDepth,
+      noop: Handlebars.VM.noop
+    };
+
+    return function(context, options) {
+      options = options || {};
+      return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
+    };
+  },
+
+  programWithDepth: function(fn, data, $depth) {
+    var args = Array.prototype.slice.call(arguments, 2);
+
+    return function(context, options) {
+      options = options || {};
+
+      return fn.apply(this, [context, options.data || data].concat(args));
+    };
+  },
+  program: function(fn, data) {
+    return function(context, options) {
+      options = options || {};
+
+      return fn(context, options.data || data);
+    };
+  },
+  noop: function() { return ""; },
+  invokePartial: function(partial, name, context, helpers, partials, data) {
+    options = { helpers: helpers, partials: partials, data: data };
+
+    if(partial === undefined) {
+      throw new Handlebars.Exception("The partial " + name + " could not be found");
+    } else if(partial instanceof Function) {
+      return partial(context, options);
+    } else if (!Handlebars.compile) {
+      throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
+    } else {
+      partials[name] = Handlebars.compile(partial);
+      return partials[name](context, options);
+    }
+  }
+};
+
+Handlebars.template = Handlebars.VM.template;
+;
diff --git a/chrome/common/extensions/docs/examples/howto/sandbox/icon.png b/chrome/common/extensions/docs/examples/howto/sandbox/icon.png
new file mode 100644
index 0000000..fc3390b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/howto/sandbox/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/howto/sandbox/manifest.json b/chrome/common/extensions/docs/examples/howto/sandbox/manifest.json
new file mode 100644
index 0000000..405ba9f
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/howto/sandbox/manifest.json
@@ -0,0 +1,18 @@
+{
+  "name": "Sandboxed Frame",
+  "version": "1.0",
+  "manifest_version": 2,
+  "permissions": ["notifications"],
+  "background": {
+    "page": "eventpage.html",
+    "persistent": false
+  },
+  "browser_action": {
+    "default_icon" : "icon.png",
+    "default_title": "Start Event Page"
+  },
+  "sandbox": {
+    "pages": ["sandbox.html"]
+  },
+  "web_accessible_resources": ["icon.png"]
+}
diff --git a/chrome/common/extensions/docs/examples/howto/sandbox/sandbox.html b/chrome/common/extensions/docs/examples/howto/sandbox/sandbox.html
new file mode 100644
index 0000000..d66fa22
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/howto/sandbox/sandbox.html
@@ -0,0 +1,44 @@
+<!--
+  - Copyright (c) 2012 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.
+  -->
+<!doctype html>
+<html>
+  <head>
+    <script src="handlebars-1.0.0.beta.6.js"></script>
+  </head>
+  <body>
+    <script id="hello-world-template" type="text/x-handlebars-template">
+      <div class="entry">
+        <h1>Hello, {{thing}}!</h1>
+      </div>
+    </script>
+    <script>
+      var templates = [];
+      var source = document.getElementById('hello-world-template').innerHTML;
+      templates['hello'] = Handlebars.compile(source);
+
+      // Set up message event handler:
+      window.addEventListener('message', function(event) {
+        var command = event.data.command;
+        var name = event.data.name || 'hello';
+        switch(command) {
+          case 'render':
+            event.source.postMessage({
+              name: name,
+              html: templates[name](event.data.context)
+            }, event.origin);
+            break;
+
+          // You could imagine additional functionality. For instance:
+          //
+          // case 'new':
+          //   templates[event.data.name] = Handlebars.compile(event.data.source);
+          //   event.source.postMessage({name: name, success: true}, event.origin);
+          //   break;
+        }
+      });
+    </script>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/howto/userscript-runat/runat.user.js b/chrome/common/extensions/docs/examples/howto/userscript-runat/runat.user.js
new file mode 100644
index 0000000..3947235
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/howto/userscript-runat/runat.user.js
@@ -0,0 +1,95 @@
+// ==UserScript==
+// @name           Userscript @run-at example.
+// @version        1.0.1
+// @namespace      runat
+// @description    This script demonstrates @runat by listing which resources
+//                 the current page has loaded.
+// @include        *
+// @run-at         document-start
+// ==/UserScript==
+
+/*
+ * 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.
+ */
+
+var urls = {};
+var count = 0;
+
+/**
+ * Called whenever the page loads a subresource.
+ * Logs all loaded URLs in the urls var.
+ */
+document.addEventListener('beforeload', function(evt) {
+  if (!urls[evt.url]) {
+    urls[evt.url] = 0;
+    count++;
+  }
+  urls[evt.url]++;
+}, true);
+
+/**
+ * Called when the window is finished loading.
+ * Loops through the urls var and prints its contents to the page DOM.
+ */
+window.addEventListener('load', function() {
+  if (count == 0) {
+    return;
+  }
+
+  // Create and style inserted DOM elements.
+  var urls_dom = document.createElement('ul');
+  urls_dom.style.cssText = [
+      'margin: 4px 0 !important;',
+      'padding: 0 !important;',
+      'width: 98%;',
+      'word-wrap: break-word;',
+      'max-height: 200px;',
+      'overflow: auto;'
+  ].join(' ');
+  var wrap_dom = document.createElement('div');
+  wrap_dom.style.cssText = [
+      'background-color: #ff7357;',
+      'background-image: -webkit-repeating-linear-gradient(' +
+          '-45deg, transparent, transparent 35px,' +
+          'rgba(0,0,0,.1) 35px, rgba(0,0,0,.1) 70px);',
+      'color: #000;',
+      'padding: 10px;',
+      'font: 14px Arial;'
+  ].join(' ');
+  var title_dom = document.createElement('strong');
+  title_dom.textContent = (count > 1) ?
+      'This page has loaded the following resources:' :
+      'This page loaded the following resource:';
+  wrap_dom.appendChild(title_dom);
+  wrap_dom.appendChild(urls_dom);
+
+  // Render each url as a list item containing a link.
+  for (var url in urls) {
+    var url_dom = document.createElement('li');
+    var link_dom = document.createElement('a');
+    var times_dom = document.createElement('span');
+    url_dom.style.cssText = [
+        'list-style-type: disc;',
+        'margin: 0 0 0 30px !important;',
+        'padding: 2px !important'
+    ].join(' ');
+    link_dom.setAttribute('href', url);
+    link_dom.setAttribute('target', '_blank');
+    link_dom.textContent = url;
+    link_dom.style.cssText = [
+        'color: #000 !important;'
+    ].join(' ');
+    times_dom.textContent = (urls[url] > 1) ?
+        ' (' + urls[url] + ' times)' :
+        ' (once)';
+    url_dom.appendChild(link_dom);
+    url_dom.appendChild(times_dom);
+    urls_dom.appendChild(url_dom);
+  }
+
+  // Insert the created DOM into the page.
+  document.body.style.position = 'relative';
+  document.body.parentElement.insertBefore(wrap_dom, document.body);
+}, true);
diff --git a/chrome/common/extensions/docs/examples/tutorials/analytics/analytics-extension-icon-128.png b/chrome/common/extensions/docs/examples/tutorials/analytics/analytics-extension-icon-128.png
new file mode 100644
index 0000000..de390dc
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/tutorials/analytics/analytics-extension-icon-128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/tutorials/analytics/analytics-extension-icon-19.png b/chrome/common/extensions/docs/examples/tutorials/analytics/analytics-extension-icon-19.png
new file mode 100644
index 0000000..08c2b2e
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/tutorials/analytics/analytics-extension-icon-19.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/tutorials/analytics/analytics-extension-icon-48.png b/chrome/common/extensions/docs/examples/tutorials/analytics/analytics-extension-icon-48.png
new file mode 100644
index 0000000..80395b8
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/tutorials/analytics/analytics-extension-icon-48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/tutorials/analytics/manifest.json b/chrome/common/extensions/docs/examples/tutorials/analytics/manifest.json
new file mode 100644
index 0000000..11f90a0
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/tutorials/analytics/manifest.json
@@ -0,0 +1,17 @@
+{
+  "name": "Event Tracking with Google Analytics",
+  "version": "2.0.0",
+  "description": "A sample extension which uses Google Analytics to track usage.",
+  "browser_action": {
+    "default_title": "Open the popup",
+    "default_icon": "analytics-extension-icon-19.png",
+    "default_popup" : "popup.html"
+  },
+  "icons": {
+    "48": "analytics-extension-icon-48.png",
+    "128": "analytics-extension-icon-128.png"
+  },
+
+  "manifest_version": 2,
+  "content_security_policy": "script-src 'self' https://ssl.google-analytics.com; object-src 'self'"
+}
diff --git a/chrome/common/extensions/docs/examples/tutorials/analytics/popup.html b/chrome/common/extensions/docs/examples/tutorials/analytics/popup.html
new file mode 100644
index 0000000..9a76c26
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/tutorials/analytics/popup.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2012 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.
+-->
+<html>
+  <head>
+    <style>
+      body {
+        width: 300px;
+        color: #000;
+        font-family: Arial;
+      }
+      #output {
+        color: #d00;
+        text-align: center;
+      }
+    </style>
+    <script src="popup.js"></script>
+  </head>
+  <body>
+    <h1>Popup</h1>
+    <p>Track the following actions:</p>
+    <button id='button1'>Button 1</button>
+    <button id='button2'>Button 2</button>
+    <button id='button3'>Button 3</button>
+    <button id='button4'>Button 4</button>
+    <button id='button5'>Button 5</button>
+    <button id='button6'>Button 6</button>
+  </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/tutorials/analytics/popup.js b/chrome/common/extensions/docs/examples/tutorials/analytics/popup.js
new file mode 100644
index 0000000..160fe15
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/tutorials/analytics/popup.js
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 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.
+
+/**
+ * Add your Analytics tracking ID here.
+ */
+var _AnalyticsCode = 'UA-XXXXXX-X';
+
+/**
+ * Below is a modified version of the Google Analytics asynchronous tracking
+ * code snippet.  It has been modified to pull the HTTPS version of ga.js
+ * instead of the default HTTP version.  It is recommended that you use this
+ * snippet instead of the standard tracking snippet provided when setting up
+ * a Google Analytics account.
+ */
+var _gaq = _gaq || [];
+_gaq.push(['_setAccount', _AnalyticsCode]);
+_gaq.push(['_trackPageview']);
+
+(function() {
+  var ga = document.createElement('script');
+  ga.type = 'text/javascript';
+  ga.async = true;
+  ga.src = 'https://ssl.google-analytics.com/ga.js';
+  var s = document.getElementsByTagName('script')[0];
+  s.parentNode.insertBefore(ga, s);
+})();
+
+/**
+ * Track a click on a button using the asynchronous tracking API.
+ *
+ * See http://code.google.com/apis/analytics/docs/tracking/asyncTracking.html
+ * for information on how to use the asynchronous tracking API.
+ */
+function trackButtonClick(e) {
+  _gaq.push(['_trackEvent', e.target.id, 'clicked']);
+}
+
+/**
+ * Now set up your event handlers for the popup's `button` elements once the
+ * popup's DOM has loaded.
+ */
+document.addEventListener('DOMContentLoaded', function () {
+  var buttons = document.querySelectorAll('button');
+  for (var i = 0; i < buttons.length; i++) {
+    buttons[i].addEventListener('click', trackButtonClick);
+  }
+});
diff --git a/chrome/common/extensions/docs/examples/tutorials/getstarted/icon.png b/chrome/common/extensions/docs/examples/tutorials/getstarted/icon.png
new file mode 100644
index 0000000..0fe39b0
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/tutorials/getstarted/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/tutorials/getstarted/manifest.json b/chrome/common/extensions/docs/examples/tutorials/getstarted/manifest.json
new file mode 100644
index 0000000..9496aca
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/tutorials/getstarted/manifest.json
@@ -0,0 +1,15 @@
+{
+  "manifest_version": 2,
+
+  "name": "One-click Kittens",
+  "description": "This extension demonstrates a 'browser action' with kittens.",
+  "version": "1.0",
+
+  "browser_action": {
+    "default_icon": "icon.png",
+    "default_popup": "popup.html"
+  },
+  "permissions": [
+    "https://secure.flickr.com/"
+  ]
+}
diff --git a/chrome/common/extensions/docs/examples/tutorials/getstarted/popup.html b/chrome/common/extensions/docs/examples/tutorials/getstarted/popup.html
new file mode 100644
index 0000000..f58540d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/tutorials/getstarted/popup.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Getting Started Extension's Popup</title>
+    <style>
+      body {
+        min-width: 357px;
+        overflow-x: hidden;
+      }
+
+      img {
+        margin: 5px;
+        border: 2px solid black;
+        vertical-align: middle;
+        width: 75px;
+        height: 75px;
+      }
+    </style>
+
+    <!--
+      - JavaScript and HTML must be in separate files: see our Content Security
+      - Policy documentation[1] for details and explanation.
+      -
+      - [1]: http://developer.chrome.com/extensions/contentSecurityPolicy.html
+     -->
+    <script src="popup.js"></script>
+  </head>
+  <body>
+  </body>
+</html>
+
diff --git a/chrome/common/extensions/docs/examples/tutorials/getstarted/popup.js b/chrome/common/extensions/docs/examples/tutorials/getstarted/popup.js
new file mode 100644
index 0000000..15cca2b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/tutorials/getstarted/popup.js
@@ -0,0 +1,83 @@
+// Copyright (c) 2012 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.
+
+/**
+ * Global variable containing the query we'd like to pass to Flickr. In this
+ * case, kittens!
+ *
+ * @type {string}
+ */
+var QUERY = 'kittens';
+
+var kittenGenerator = {
+  /**
+   * Flickr URL that will give us lots and lots of whatever we're looking for.
+   *
+   * See http://www.flickr.com/services/api/flickr.photos.search.html for
+   * details about the construction of this URL.
+   *
+   * @type {string}
+   * @private
+   */
+  searchOnFlickr_: 'https://secure.flickr.com/services/rest/?' +
+      'method=flickr.photos.search&' +
+      'api_key=90485e931f687a9b9c2a66bf58a3861a&' +
+      'text=' + encodeURIComponent(QUERY) + '&' +
+      'safe_search=1&' +
+      'content_type=1&' +
+      'sort=interestingness-desc&' +
+      'per_page=20',
+
+  /**
+   * Sends an XHR GET request to grab photos of lots and lots of kittens. The
+   * XHR's 'onload' event is hooks up to the 'showPhotos_' method.
+   *
+   * @public
+   */
+  requestKittens: function() {
+    var req = new XMLHttpRequest();
+    req.open("GET", this.kittensOnFlickr_, true);
+    req.onload = this.showPhotos_.bind(this);
+    req.send(null);
+  },
+
+  /**
+   * Handle the 'onload' event of our kitten XHR request, generated in
+   * 'requestKittens', by generating 'img' elements, and stuffing them into
+   * the document for display.
+   *
+   * @param {ProgressEvent} e The XHR ProgressEvent.
+   * @private
+   */
+  showPhotos_: function (e) {
+    var kittens = e.target.responseXML.querySelectorAll('photo');
+    for (var i = 0; i < kittens.length; i++) {
+      var img = document.createElement('img');
+      img.src = this.constructKittenURL_(kittens[i]);
+      img.setAttribute('alt', kittens[i].getAttribute('title'));
+      document.body.appendChild(img);
+    }
+  },
+
+  /**
+   * Given a photo, construct a URL using the method outlined at
+   * http://www.flickr.com/services/api/misc.urlKittenl
+   *
+   * @param {DOMElement} A kitten.
+   * @return {string} The kitten's URL.
+   * @private
+   */
+  constructKittenURL_: function (photo) {
+    return "http://farm" + photo.getAttribute("farm") +
+        ".static.flickr.com/" + photo.getAttribute("server") +
+        "/" + photo.getAttribute("id") +
+        "_" + photo.getAttribute("secret") +
+        "_s.jpg";
+  }
+};
+
+// Run our kitten generation script as soon as the document's DOM is ready.
+document.addEventListener('DOMContentLoaded', function () {
+  kittenGenerator.requestKittens();
+});
diff --git a/chrome/common/extensions/docs/server2/.gitignore b/chrome/common/extensions/docs/server2/.gitignore
new file mode 100644
index 0000000..76b510c
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/.gitignore
@@ -0,0 +1 @@
+third_party/
diff --git a/chrome/common/extensions/docs/server2/PRESUBMIT.py b/chrome/common/extensions/docs/server2/PRESUBMIT.py
new file mode 100644
index 0000000..dc3db5d
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/PRESUBMIT.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2012 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.
+
+"""Presubmit script for changes affecting extensions docs server
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details about the presubmit API built into gcl.
+"""
+
+# Run build_server so that files needed by tests are copied to the local
+# third_party directory.
+import os
+import sys
+SYS_PATH = sys.path[:]
+try:
+  SERVER2_PATH = os.path.join('chrome',
+                              'common',
+                              'extensions',
+                              'docs',
+                              'server2')
+  if os.sep + 'src' in os.getcwd():
+    # Is 'src' is in the path, we can find the server2/ directory from there.
+    sys.path.insert(0, os.path.join(os.getcwd().rsplit(os.sep + 'src', 1)[0],
+                                    'src',
+                                    SERVER2_PATH))
+  else:
+    # Otherwise, we have to guess we're in the server2/ directory.
+    sys.path.insert(0, '.')
+  import build_server
+  build_server.main()
+finally:
+  sys.path = SYS_PATH
+
+WHITELIST = [ r'.+_test.py$' ]
+# The integration tests are selectively run from the PRESUBMIT in
+# chrome/common/extensions.
+BLACKLIST = [ r'integration_test.py$' ]
+
+def CheckChangeOnUpload(input_api, output_api):
+  return input_api.canned_checks.RunUnitTestsInDirectory(
+      input_api, output_api, '.', whitelist=WHITELIST, blacklist=BLACKLIST)
+
+def CheckChangeOnCommit(input_api, output_api):
+  return input_api.canned_checks.RunUnitTestsInDirectory(
+      input_api, output_api, '.', whitelist=WHITELIST, blacklist=BLACKLIST)
diff --git a/chrome/common/extensions/docs/server2/README b/chrome/common/extensions/docs/server2/README
new file mode 100644
index 0000000..f467e4e
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/README
@@ -0,0 +1,51 @@
+--------
+Overview
+
+This is a Google App Engine server which serves the documentation for Chrome
+apps and extensions. At time of this writing, the primary URL is:
+http://developer.chrome.com/.
+
+
+---------------------
+Developing the Server
+
+You shouldn't need app engine locally to develop the server, preview.py should
+be sufficient. If for some reason you want to test against the app engine SDK:
+
+  1. Download the python Google App Engine SDK from:
+     https://developers.google.com/appengine/downloads
+
+  2. Run './start_dev_server.py <path/to/dev_appserver.py>'
+     (dev_appserver.py is part of the App Engine)
+
+  3. View docs at http://localhost:8080/(apps|extensions)/<doc_name>
+
+
+--------------------
+Deploying the Server
+
+You will need to have access to the http://chrome-apps-doc.appspot.com app.
+Contact aa@chromium.org, erikkay@chromium.org, mihaip@chromium.org,
+miket@chromium.org, kalman@chromium.org, or ernestd@chromium.org to obtain
+access.
+
+Once you have access:
+
+1. Increment the version in app.yaml so we can roll back if the update breaks.
+
+2. Run build_server.py. This copies some depenencies from /third_party into the
+   server directory so that they get uploaded to App Engine.
+
+3. Run appcfg.py (supplied with the App Engine SDK) to upload the server code:
+
+    appcfg.py update .
+
+4. When prompted for your credentials, enter the information for the account
+   that has access to the production app.
+
+5. Go to http://www.appspot.com, select the docs project, click "versions" in
+   the sidebar, and make the version you just deployed the "default" version.
+
+   If you get an error about too many versions when deploying, go into this
+   view and delete the version which was deployed the longest time ago.  Then
+   try to deploy again.
diff --git a/chrome/common/extensions/docs/server2/api_data_source.py b/chrome/common/extensions/docs/server2/api_data_source.py
new file mode 100644
index 0000000..a3f1b2a
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/api_data_source.py
@@ -0,0 +1,441 @@
+# Copyright (c) 2012 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.
+
+import copy
+import json
+import logging
+import os
+
+import compiled_file_system as compiled_fs
+from file_system import FileNotFoundError
+import third_party.json_schema_compiler.json_comment_eater as json_comment_eater
+import third_party.json_schema_compiler.model as model
+import third_party.json_schema_compiler.idl_schema as idl_schema
+import third_party.json_schema_compiler.idl_parser as idl_parser
+
+# Increment this version when there are changes to the data stored in any of
+# the caches used by APIDataSource. This allows the cache to be invalidated
+# without having to flush memcache on the production server.
+_VERSION = 3
+
+def _RemoveNoDocs(item):
+  if type(item) == dict:
+    if item.get('nodoc', False):
+      return True
+    to_remove = []
+    for key, value in item.items():
+      if _RemoveNoDocs(value):
+        to_remove.append(key)
+    for k in to_remove:
+      del item[k]
+  elif type(item) == list:
+    to_remove = []
+    for i in item:
+      if _RemoveNoDocs(i):
+        to_remove.append(i)
+    for i in to_remove:
+      item.remove(i)
+  return False
+
+def _CreateId(node, prefix):
+  if node.parent is not None and not isinstance(node.parent, model.Namespace):
+    return '-'.join([prefix, node.parent.simple_name, node.simple_name])
+  return '-'.join([prefix, node.simple_name])
+
+def _FormatValue(value):
+  """Inserts commas every three digits for integer values. It is magic.
+  """
+  s = str(value)
+  return ','.join([s[max(0, i - 3):i] for i in range(len(s), 0, -3)][::-1])
+
+class _JSCModel(object):
+  """Uses a Model from the JSON Schema Compiler and generates a dict that
+  a Handlebar template can use for a data source.
+  """
+  def __init__(self, json, ref_resolver, disable_refs):
+    self._ref_resolver = ref_resolver
+    self._disable_refs = disable_refs
+    clean_json = copy.deepcopy(json)
+    if _RemoveNoDocs(clean_json):
+      self._namespace = None
+    else:
+      self._namespace = model.Namespace(clean_json, clean_json['namespace'])
+
+  def _GetLink(self, ref):
+    if not self._disable_refs:
+      ref_data = self._ref_resolver.GetLink(self._namespace.name, ref)
+      if ref_data is not None:
+        return ref_data
+      logging.error('$ref %s could not be resolved.' % ref)
+    if '.' in ref:
+      type_name = ref.rsplit('.', 1)[-1]
+    else:
+      type_name = ref
+    return { 'href': '#type-%s' % type_name, 'text': ref }
+
+  def _FormatDescription(self, description):
+    if description is None or '$ref:' not in description:
+      return description
+    refs = description.split('$ref:')
+    formatted_description = [refs[0]]
+    for ref in refs[1:]:
+      parts = ref.split(' ', 1)
+      if len(parts) == 1:
+        ref = parts[0]
+        rest = ''
+      else:
+        ref, rest = parts
+        rest = ' ' + rest
+      if not ref[-1].isalnum():
+        rest = ref[-1] + rest
+        ref = ref[:-1]
+      ref_dict = self._GetLink(ref)
+      formatted_description.append('<a href="%(href)s">%(text)s</a>%(rest)s' %
+          { 'href': ref_dict['href'], 'text': ref_dict['text'], 'rest': rest })
+    return ''.join(formatted_description)
+
+  def ToDict(self):
+    if self._namespace is None:
+      return {}
+    return {
+      'name': self._namespace.name,
+      'types':  [self._GenerateType(t) for t in self._namespace.types.values()
+                 if t.type_ != model.PropertyType.ADDITIONAL_PROPERTIES],
+      'functions': self._GenerateFunctions(self._namespace.functions),
+      'events': self._GenerateEvents(self._namespace.events),
+      'properties': self._GenerateProperties(self._namespace.properties)
+    }
+
+  def _GenerateType(self, type_):
+    type_dict = {
+      'name': type_.simple_name,
+      'description': self._FormatDescription(type_.description),
+      'properties': self._GenerateProperties(type_.properties),
+      'functions': self._GenerateFunctions(type_.functions),
+      'events': self._GenerateEvents(type_.events),
+      'id': _CreateId(type_, 'type')
+    }
+    self._RenderTypeInformation(type_, type_dict)
+    return type_dict
+
+  def _GenerateFunctions(self, functions):
+    return [self._GenerateFunction(f) for f in functions.values()]
+
+  def _GenerateFunction(self, function):
+    function_dict = {
+      'name': function.simple_name,
+      'description': self._FormatDescription(function.description),
+      'callback': self._GenerateCallback(function.callback),
+      'parameters': [],
+      'returns': None,
+      'id': _CreateId(function, 'method')
+    }
+    if (function.parent is not None and
+        not isinstance(function.parent, model.Namespace)):
+      function_dict['parent_name'] = function.parent.simple_name
+    else:
+      function_dict['parent_name'] = None
+    if function.returns:
+      function_dict['returns'] = self._GenerateProperty(function.returns)
+    for param in function.params:
+      function_dict['parameters'].append(self._GenerateProperty(param))
+    if function_dict['callback']:
+      function_dict['parameters'].append(function_dict['callback'])
+    if len(function_dict['parameters']) > 0:
+      function_dict['parameters'][-1]['last'] = True
+    return function_dict
+
+  def _GenerateEvents(self, events):
+    return [self._GenerateEvent(e) for e in events.values()]
+
+  def _GenerateEvent(self, event):
+    event_dict = {
+      'name': event.simple_name,
+      'description': self._FormatDescription(event.description),
+      'parameters': [self._GenerateProperty(p) for p in event.params],
+      'callback': self._GenerateCallback(event.callback),
+      'filters': [self._GenerateProperty(f) for f in event.filters],
+      'conditions': [self._GetLink(condition)
+                     for condition in event.conditions],
+      'actions': [self._GetLink(action) for action in event.actions],
+      'supportsRules': event.supports_rules,
+      'id': _CreateId(event, 'event')
+    }
+    if (event.parent is not None and
+        not isinstance(event.parent, model.Namespace)):
+      event_dict['parent_name'] = event.parent.simple_name
+    else:
+      event_dict['parent_name'] = None
+    if event_dict['callback']:
+      event_dict['parameters'].append(event_dict['callback'])
+    if len(event_dict['parameters']) > 0:
+      event_dict['parameters'][-1]['last'] = True
+    return event_dict
+
+  def _GenerateCallback(self, callback):
+    if not callback:
+      return None
+    callback_dict = {
+      'name': callback.simple_name,
+      'description': self._FormatDescription(callback.description),
+      'simple_type': {'simple_type': 'function'},
+      'optional': callback.optional,
+      'parameters': []
+    }
+    for param in callback.params:
+      callback_dict['parameters'].append(self._GenerateProperty(param))
+    if (len(callback_dict['parameters']) > 0):
+      callback_dict['parameters'][-1]['last'] = True
+    return callback_dict
+
+  def _GenerateProperties(self, properties):
+    return [self._GenerateProperty(v) for v in properties.values()
+            if v.type_ != model.PropertyType.ADDITIONAL_PROPERTIES]
+
+  def _GenerateProperty(self, property_):
+    property_dict = {
+      'name': property_.simple_name,
+      'optional': property_.optional,
+      'description': self._FormatDescription(property_.description),
+      'properties': self._GenerateProperties(property_.properties),
+      'functions': self._GenerateFunctions(property_.functions),
+      'parameters': [],
+      'returns': None,
+      'id': _CreateId(property_, 'property')
+    }
+    for param in property_.params:
+      property_dict['parameters'].append(self._GenerateProperty(param))
+    if property_.returns:
+      property_dict['returns'] = self._GenerateProperty(property_.returns)
+    if (property_.parent is not None and
+        not isinstance(property_.parent, model.Namespace)):
+      property_dict['parent_name'] = property_.parent.simple_name
+    else:
+      property_dict['parent_name'] = None
+    if property_.has_value:
+      if isinstance(property_.value, int):
+        property_dict['value'] = _FormatValue(property_.value)
+      else:
+        property_dict['value'] = property_.value
+    else:
+      self._RenderTypeInformation(property_, property_dict)
+    return property_dict
+
+  def _RenderTypeInformation(self, property_, dst_dict):
+    if property_.type_ == model.PropertyType.CHOICES:
+      dst_dict['choices'] = [self._GenerateProperty(c)
+                             for c in property_.choices.values()]
+      # We keep track of which is last for knowing when to add "or" between
+      # choices in templates.
+      if len(dst_dict['choices']) > 0:
+        dst_dict['choices'][-1]['last'] = True
+    elif property_.type_ == model.PropertyType.REF:
+      dst_dict['link'] = self._GetLink(property_.ref_type)
+    elif property_.type_ == model.PropertyType.ARRAY:
+      dst_dict['array'] = self._GenerateProperty(property_.item_type)
+    elif property_.type_ == model.PropertyType.ENUM:
+      dst_dict['enum_values'] = []
+      for enum_value in property_.enum_values:
+        dst_dict['enum_values'].append({'name': enum_value})
+      if len(dst_dict['enum_values']) > 0:
+        dst_dict['enum_values'][-1]['last'] = True
+    elif property_.instance_of is not None:
+      dst_dict['simple_type'] = property_.instance_of.lower()
+    else:
+      dst_dict['simple_type'] = property_.type_.name.lower()
+
+class _LazySamplesGetter(object):
+  """This class is needed so that an extensions API page does not have to fetch
+  the apps samples page and vice versa.
+  """
+  def __init__(self, api_name, samples):
+    self._api_name = api_name
+    self._samples = samples
+
+  def get(self, key):
+    return self._samples.FilterSamples(key, self._api_name)
+
+class APIDataSource(object):
+  """This class fetches and loads JSON APIs from the FileSystem passed in with
+  |cache_factory|, so the APIs can be plugged into templates.
+  """
+  class Factory(object):
+    def __init__(self,
+                 cache_factory,
+                 base_path):
+      self._permissions_cache = cache_factory.Create(self._LoadPermissions,
+                                                     compiled_fs.PERMS,
+                                                     version=_VERSION)
+      self._json_cache = cache_factory.Create(
+          lambda api: self._LoadJsonAPI(api, False),
+          compiled_fs.JSON,
+          version=_VERSION)
+      self._idl_cache = cache_factory.Create(
+          lambda api: self._LoadIdlAPI(api, False),
+          compiled_fs.IDL,
+          version=_VERSION)
+
+      # These caches are used if an APIDataSource does not want to resolve the
+      # $refs in an API. This is needed to prevent infinite recursion in
+      # ReferenceResolver.
+      self._json_cache_no_refs = cache_factory.Create(
+          lambda api: self._LoadJsonAPI(api, True),
+          compiled_fs.JSON_NO_REFS,
+          version=_VERSION)
+      self._idl_cache_no_refs = cache_factory.Create(
+          lambda api: self._LoadIdlAPI(api, True),
+          compiled_fs.IDL_NO_REFS,
+          version=_VERSION)
+      self._idl_names_cache = cache_factory.Create(self._GetIDLNames,
+                                                   compiled_fs.IDL_NAMES,
+                                                   version=_VERSION)
+      self._names_cache = cache_factory.Create(self._GetAllNames,
+                                               compiled_fs.NAMES,
+                                               version=_VERSION)
+      self._base_path = base_path
+
+      # These must be set later via the SetFooDataSourceFactory methods.
+      self._ref_resolver_factory = None
+      self._samples_data_source_factory = None
+
+    def SetSamplesDataSourceFactory(self, samples_data_source_factory):
+      self._samples_data_source_factory = samples_data_source_factory
+
+    def SetReferenceResolverFactory(self, ref_resolver_factory):
+      self._ref_resolver_factory = ref_resolver_factory
+
+    def Create(self, request, disable_refs=False):
+      """Create an APIDataSource. |disable_refs| specifies whether $ref's in
+      APIs being processed by the |ToDict| method of _JSCModel follows $ref's
+      in the API. This prevents endless recursion in ReferenceResolver.
+      """
+      if self._samples_data_source_factory is None:
+        # Only error if there is a request, which means this APIDataSource is
+        # actually being used to render a page.
+        if request is not None:
+          logging.error('SamplesDataSource.Factory was never set in '
+                        'APIDataSource.Factory.')
+        samples = None
+      else:
+        samples = self._samples_data_source_factory.Create(request)
+      if not disable_refs and self._ref_resolver_factory is None:
+        logging.error('ReferenceResolver.Factory was never set in '
+                      'APIDataSource.Factory.')
+      return APIDataSource(self._permissions_cache,
+                           self._json_cache,
+                           self._idl_cache,
+                           self._json_cache_no_refs,
+                           self._idl_cache_no_refs,
+                           self._names_cache,
+                           self._idl_names_cache,
+                           self._base_path,
+                           samples,
+                           disable_refs)
+
+    def _LoadPermissions(self, json_str):
+      return json.loads(json_comment_eater.Nom(json_str))
+
+    def _LoadJsonAPI(self, api, disable_refs):
+      return _JSCModel(
+          json.loads(json_comment_eater.Nom(api))[0],
+          self._ref_resolver_factory.Create() if not disable_refs else None,
+          disable_refs).ToDict()
+
+    def _LoadIdlAPI(self, api, disable_refs):
+      idl = idl_parser.IDLParser().ParseData(api)
+      return _JSCModel(
+          idl_schema.IDLSchema(idl).process()[0],
+          self._ref_resolver_factory.Create() if not disable_refs else None,
+          disable_refs).ToDict()
+
+    def _GetIDLNames(self, apis):
+      return [
+        model.UnixName(os.path.splitext(api[len('%s/' % self._base_path):])[0])
+        for api in apis if api.endswith('.idl')
+      ]
+
+    def _GetAllNames(self, apis):
+      return [
+        model.UnixName(os.path.splitext(api[len('%s/' % self._base_path):])[0])
+        for api in apis
+      ]
+
+  def __init__(self,
+               permissions_cache,
+               json_cache,
+               idl_cache,
+               json_cache_no_refs,
+               idl_cache_no_refs,
+               names_cache,
+               idl_names_cache,
+               base_path,
+               samples,
+               disable_refs):
+    self._base_path = base_path
+    self._permissions_cache = permissions_cache
+    self._json_cache = json_cache
+    self._idl_cache = idl_cache
+    self._json_cache_no_refs = json_cache_no_refs
+    self._idl_cache_no_refs = idl_cache_no_refs
+    self._names_cache = names_cache
+    self._idl_names_cache = idl_names_cache
+    self._samples = samples
+    self._disable_refs = disable_refs
+
+  def _GetPermsFromFile(self, filename):
+    try:
+      perms = self._permissions_cache.GetFromFile('%s/%s' %
+          (self._base_path, filename))
+      return dict((model.UnixName(k), v) for k, v in perms.iteritems())
+    except FileNotFoundError:
+      return {}
+
+  def _GetFeature(self, path):
+    # Remove 'experimental_' from path name to match the keys in
+    # _permissions_features.json.
+    path = model.UnixName(path.replace('experimental_', ''))
+    for filename in ['_permission_features.json', '_manifest_features.json']:
+      api_perms = self._GetPermsFromFile(filename).get(path, None)
+      if api_perms is not None:
+        break
+    if api_perms and api_perms['channel'] in ('trunk', 'dev', 'beta'):
+      api_perms[api_perms['channel']] = True
+    return api_perms
+
+  def _GenerateHandlebarContext(self, handlebar_dict, path):
+    return_dict = {
+      'permissions': self._GetFeature(path),
+      'samples': _LazySamplesGetter(path, self._samples)
+    }
+    return_dict.update(handlebar_dict)
+    return return_dict
+
+  def _GetAsSubdirectory(self, name):
+    if name.startswith('experimental_'):
+      parts = name[len('experimental_'):].split('_', 1)
+      parts[1] = 'experimental_%s' % parts[1]
+      return '/'.join(parts)
+    return name.replace('_', '/', 1)
+
+  def get(self, key):
+    if key.endswith('.html') or key.endswith('.json') or key.endswith('.idl'):
+      path, ext = os.path.splitext(key)
+    else:
+      path = key
+    unix_name = model.UnixName(path)
+    idl_names = self._idl_names_cache.GetFromFileListing(self._base_path)
+    names = self._names_cache.GetFromFileListing(self._base_path)
+    if unix_name not in names and self._GetAsSubdirectory(unix_name) in names:
+      unix_name = self._GetAsSubdirectory(unix_name)
+
+    if self._disable_refs:
+      cache, ext = (
+          (self._idl_cache_no_refs, '.idl') if (unix_name in idl_names) else
+          (self._json_cache_no_refs, '.json'))
+    else:
+      cache, ext = ((self._idl_cache, '.idl') if (unix_name in idl_names) else
+                    (self._json_cache, '.json'))
+    return self._GenerateHandlebarContext(
+        cache.GetFromFile('%s/%s%s' % (self._base_path, unix_name, ext)),
+        path)
diff --git a/chrome/common/extensions/docs/server2/api_data_source_test.py b/chrome/common/extensions/docs/server2/api_data_source_test.py
new file mode 100755
index 0000000..9785ef9
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/api_data_source_test.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+import json
+import os
+import sys
+import unittest
+
+from api_data_source import (APIDataSource,
+                             _JSCModel,
+                             _FormatValue,
+                             _RemoveNoDocs)
+from compiled_file_system import CompiledFileSystem
+from file_system import FileNotFoundError
+from in_memory_object_store import InMemoryObjectStore
+from local_file_system import LocalFileSystem
+from reference_resolver import ReferenceResolver
+import third_party.json_schema_compiler.json_comment_eater as comment_eater
+import third_party.json_schema_compiler.model as model
+
+def _MakeLink(href, text):
+  return '<a href="%s">%s</a>' % (href, text)
+
+def _GetType(dict_, name):
+  for type_ in dict_['types']:
+    if type_['name'] == name:
+      return type_
+
+class FakeSamplesDataSource(object):
+  def Create(self, request):
+    return {}
+
+class FakeAPIAndListDataSource(object):
+  def __init__(self, json_data):
+    self._json = json_data
+
+  def get(self, key):
+    if key not in self._json:
+      raise FileNotFoundError(key)
+    return self._json[key]
+
+  def GetAllNames(self):
+    return self._json.keys()
+
+class APIDataSourceTest(unittest.TestCase):
+  def setUp(self):
+    self._base_path = os.path.join(sys.path[0], 'test_data', 'test_json')
+
+  def _ReadLocalFile(self, filename):
+    with open(os.path.join(self._base_path, filename), 'r') as f:
+      return f.read()
+
+  def _CreateRefResolver(self, filename):
+    data_source = FakeAPIAndListDataSource(
+        self._LoadJSON(filename))
+    return ReferenceResolver.Factory(data_source, data_source).Create()
+
+  def DISABLED_testSimple(self):
+    cache_factory = CompiledFileSystem.Factory(
+        LocalFileSystem(self._base_path),
+        InMemoryObjectStore('fake_branch'))
+    data_source_factory = APIDataSource.Factory(cache_factory,
+                                                '.')
+    data_source_factory.SetSamplesDataSourceFactory(FakeSamplesDataSource())
+    data_source = data_source_factory.Create({}, disable_refs=True)
+
+    # Take the dict out of the list.
+    expected = json.loads(self._ReadLocalFile('expected_test_file.json'))
+    expected['permissions'] = None
+    test1 = data_source.get('test_file')
+    test1.pop('samples')
+    self.assertEqual(expected, test1)
+    test2 = data_source.get('testFile')
+    test2.pop('samples')
+    self.assertEqual(expected, test2)
+    test3 = data_source.get('testFile.html')
+    test3.pop('samples')
+    self.assertEqual(expected, test3)
+    self.assertRaises(FileNotFoundError, data_source.get, 'junk')
+
+  def _LoadJSON(self, filename):
+    return json.loads(comment_eater.Nom(self._ReadLocalFile(filename)))
+
+  def testCreateId(self):
+    data_source = FakeAPIAndListDataSource(
+        self._LoadJSON('test_file_data_source.json'))
+    dict_ = _JSCModel(self._LoadJSON('test_file.json')[0],
+                      self._CreateRefResolver('test_file_data_source.json'),
+                      False).ToDict()
+    self.assertEquals('type-TypeA', dict_['types'][0]['id'])
+    self.assertEquals('property-TypeA-b',
+                      dict_['types'][0]['properties'][0]['id'])
+    self.assertEquals('method-get', dict_['functions'][0]['id'])
+    self.assertEquals('event-EventA', dict_['events'][0]['id'])
+
+  def testToDict(self):
+    filename = 'test_file.json'
+    expected_json = self._LoadJSON('expected_' + filename)
+    data_source = FakeAPIAndListDataSource(
+        self._LoadJSON('test_file_data_source.json'))
+    dict_ = _JSCModel(self._LoadJSON(filename)[0],
+                      self._CreateRefResolver('test_file_data_source.json'),
+                      False).ToDict()
+    self.assertEquals(expected_json, dict_)
+
+  def testFormatValue(self):
+    self.assertEquals('1,234,567', _FormatValue(1234567))
+    self.assertEquals('67', _FormatValue(67))
+    self.assertEquals('234,567', _FormatValue(234567))
+
+  def testFormatDescription(self):
+    dict_ = _JSCModel(self._LoadJSON('ref_test.json')[0],
+                      self._CreateRefResolver('ref_test_data_source.json'),
+                      False).ToDict()
+    self.assertEquals(_MakeLink('#type-type2', 'type2'),
+                      _GetType(dict_, 'type1')['description'])
+    self.assertEquals(
+        'A %s, or %s' % (_MakeLink('#type-type3', 'type3'),
+                         _MakeLink('#type-type2', 'type2')),
+        _GetType(dict_, 'type2')['description'])
+    self.assertEquals(
+        '%s != %s' % (_MakeLink('other.html#type-type2', 'other.type2'),
+                      _MakeLink('ref_test.html#type-type2', 'type2')),
+        _GetType(dict_, 'type3')['description'])
+
+  def testRemoveNoDocs(self):
+    d = self._LoadJSON('nodoc_test.json')
+    _RemoveNoDocs(d)
+    self.assertEqual(self._LoadJSON('expected_nodoc.json'), d)
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/chrome/common/extensions/docs/server2/api_list_data_source.py b/chrome/common/extensions/docs/server2/api_list_data_source.py
new file mode 100644
index 0000000..e73e2c2
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/api_list_data_source.py
@@ -0,0 +1,82 @@
+# Copyright (c) 2012 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.
+
+import logging
+import os
+
+from file_system import FileNotFoundError
+import compiled_file_system as compiled_fs
+import third_party.json_schema_compiler.model as model
+from docs_server_utils import SanitizeAPIName
+
+# These files are special cases that shouldn't be in the API list.
+IGNORED_FILES = [
+  'devtools'
+]
+
+class APIListDataSource(object):
+  """ This class creates a list of chrome.* APIs and chrome.experimental.* APIs
+  that are used in the api_index.html and experimental.html pages.
+  """
+  class Factory(object):
+    def __init__(self, cache_factory, file_system, api_path, public_path):
+      self._cache = cache_factory.Create(self._ListAPIs, compiled_fs.LIST)
+      self._file_system = file_system
+      def Normalize(string):
+        return string if string.endswith('/') else (string + '/')
+      self._api_path = Normalize(api_path)
+      self._public_path = Normalize(public_path)
+
+    def _GetAPIsInSubdirectory(self, api_names, doc_type):
+      public_templates = self._file_system.ReadSingle(
+          self._public_path + doc_type + '/')
+      template_names = [os.path.splitext(name)[0]
+                        for name in public_templates]
+      experimental_apis = []
+      chrome_apis = []
+      for template_name in sorted(template_names):
+        if template_name in IGNORED_FILES:
+          continue
+        if model.UnixName(template_name) in api_names:
+          if template_name.startswith('experimental'):
+            experimental_apis.append({
+              'name': template_name.replace('_', '.')
+            })
+          else:
+            chrome_apis.append({ 'name': template_name.replace('_', '.') })
+      if len(chrome_apis):
+        chrome_apis[-1]['last'] = True
+      if len(experimental_apis):
+        experimental_apis[-1]['last'] = True
+      return {
+        'chrome': chrome_apis,
+        'experimental': experimental_apis
+      }
+
+    def _ListAPIs(self, apis):
+      api_names = set(SanitizeAPIName(name, self._api_path) for name in apis)
+      return {
+        'apps': self._GetAPIsInSubdirectory(api_names, 'apps'),
+        'extensions': self._GetAPIsInSubdirectory(api_names, 'extensions')
+      }
+
+    def Create(self):
+      return APIListDataSource(self._cache, self._api_path)
+
+  def __init__(self, cache, api_path):
+    self._cache = cache
+    self._api_path = api_path
+
+  def GetAllNames(self):
+    names = []
+    for i in ['apps', 'extensions']:
+      for j in ['chrome', 'experimental']:
+       names.extend(self.get(i).get(j))
+    return [api_name['name'] for api_name in names]
+
+  def get(self, key):
+    try:
+      return self._cache.GetFromFileListing(self._api_path)[key]
+    except FileNotFoundError as e:
+      raise ValueError('%s: Error listing files for "%s".' % (e, key))
diff --git a/chrome/common/extensions/docs/server2/app.yaml b/chrome/common/extensions/docs/server2/app.yaml
new file mode 100644
index 0000000..c85b3b7
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/app.yaml
@@ -0,0 +1,9 @@
+application: chrome-apps-doc
+version: 2-0-8
+runtime: python27
+api_version: 1
+threadsafe: false
+
+handlers:
+- url: /.*
+  script: appengine_main.py
diff --git a/chrome/common/extensions/docs/server2/appengine_blobstore.py b/chrome/common/extensions/docs/server2/appengine_blobstore.py
new file mode 100644
index 0000000..5781556
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/appengine_blobstore.py
@@ -0,0 +1,48 @@
+# Copyright (c) 2012 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.
+
+import blob_reference_store as datastore
+from blob_reference_store import BlobReferenceStore
+from appengine_wrappers import blobstore
+from appengine_wrappers import files
+
+BLOBSTORE_GITHUB = 'BlobstoreGithub'
+
+class AppEngineBlobstore(object):
+  """A wrapper around the blobstore API, which stores the blob keys in
+  datastore.
+  """
+  def __init__(self):
+    self._datastore = BlobReferenceStore('blobstore')
+
+  def Set(self, key, blob, namespace):
+    """Add a blob to the blobstore. |version| is used as part of the key so
+    multiple blobs with the same name can be differentiated.
+    """
+    key = namespace + '.' + key
+    filename = files.blobstore.create()
+    with files.open(filename, 'a') as f:
+      f.write(blob)
+    files.finalize(filename)
+    blob_key = files.blobstore.get_blob_key(filename)
+    self._datastore.Set(datastore.BLOB_REFERENCE_BLOBSTORE, key, blob_key)
+
+  def Get(self, key, namespace):
+    """Get a blob with version |version|.
+    """
+    key = namespace + '.' + key
+    blob_key = self._datastore.Get(datastore.BLOB_REFERENCE_BLOBSTORE, key)
+    if blob_key is None:
+      return None
+    blob_reader = blobstore.BlobReader(blob_key)
+    return blob_reader.read()
+
+  def Delete(self, key, namespace):
+    """Delete the blob with version |version| if it is found.
+    """
+    key = namespace + '.' + key
+    blob_key = self._datastore.Delete(datastore.BLOB_REFERENCE_BLOBSTORE, key)
+    if blob_key is None:
+      return
+    blob_key.delete()
diff --git a/chrome/common/extensions/docs/server2/appengine_main.py b/chrome/common/extensions/docs/server2/appengine_main.py
new file mode 100755
index 0000000..5381017
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/appengine_main.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+import os
+import sys
+
+# Add the original server location to sys.path so we are able to import
+# modules from there.
+SERVER_PATH = 'chrome/common/extensions/docs/server2'
+if os.path.abspath(SERVER_PATH) not in sys.path:
+  sys.path.append(os.path.abspath(SERVER_PATH))
+
+from google.appengine.ext import webapp
+from google.appengine.ext.webapp.util import run_wsgi_app
+
+from handler import Handler
+
+def main():
+  run_wsgi_app(webapp.WSGIApplication([('/.*', Handler)], debug=False))
+
+if __name__ == '__main__':
+  main()
diff --git a/chrome/common/extensions/docs/server2/appengine_url_fetcher.py b/chrome/common/extensions/docs/server2/appengine_url_fetcher.py
new file mode 100644
index 0000000..1b768b7
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/appengine_url_fetcher.py
@@ -0,0 +1,43 @@
+# Copyright (c) 2012 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.
+
+from appengine_wrappers import urlfetch
+from future import Future
+
+class _AsyncFetchDelegate(object):
+  def __init__(self, rpc):
+    self._rpc = rpc
+
+  def Get(self):
+    return self._rpc.get_result()
+
+class AppEngineUrlFetcher(object):
+  """A wrapper around the App Engine urlfetch module that allows for easy
+  async fetches.
+  """
+  def __init__(self, base_path):
+    self._base_path = base_path
+
+  def Fetch(self, url):
+    """Fetches a file synchronously.
+    """
+    if self._base_path is not None:
+      return urlfetch.fetch(self._base_path + '/' + url,
+                            headers={ 'Cache-Control': 'max-age=0' })
+    else:
+      return urlfetch.fetch(url, headers={ 'Cache-Control': 'max-age=0' })
+
+  def FetchAsync(self, url):
+    """Fetches a file asynchronously, and returns a Future with the result.
+    """
+    rpc = urlfetch.create_rpc()
+    if self._base_path is not None:
+      urlfetch.make_fetch_call(rpc,
+                               self._base_path + '/' + url,
+                               headers={ 'Cache-Control': 'max-age=0' })
+    else:
+      urlfetch.make_fetch_call(rpc,
+                               url,
+                               headers={ 'Cache-Control': 'max-age=0' })
+    return Future(delegate=_AsyncFetchDelegate(rpc))
diff --git a/chrome/common/extensions/docs/server2/appengine_wrappers.py b/chrome/common/extensions/docs/server2/appengine_wrappers.py
new file mode 100644
index 0000000..0ec3866
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/appengine_wrappers.py
@@ -0,0 +1,190 @@
+# Copyright (c) 2012 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.
+
+# This will attempt to import the actual App Engine modules, and if it fails,
+# they will be replaced with fake modules. This is useful during testing.
+try:
+  import google.appengine.ext.blobstore as blobstore
+  from google.appengine.ext.blobstore.blobstore import BlobReferenceProperty
+  import google.appengine.ext.db as db
+  import google.appengine.ext.webapp as webapp
+  import google.appengine.api.files as files
+  import google.appengine.api.memcache as memcache
+  import google.appengine.api.urlfetch as urlfetch
+  # Default to a 5 minute cache timeout.
+  CACHE_TIMEOUT = 300
+except ImportError:
+  # Cache for one second because zero means cache forever.
+  CACHE_TIMEOUT = 1
+  import re
+  from StringIO import StringIO
+
+  FAKE_URL_FETCHER_CONFIGURATION = None
+
+  def ConfigureFakeUrlFetch(configuration):
+    """|configuration| is a dictionary mapping strings to fake urlfetch classes.
+    A fake urlfetch class just needs to have a fetch method. The keys of the
+    dictionary are treated as regex, and they are matched with the URL to
+    determine which fake urlfetch is used.
+    """
+    global FAKE_URL_FETCHER_CONFIGURATION
+    FAKE_URL_FETCHER_CONFIGURATION = dict(
+        (re.compile(k), v) for k, v in configuration.iteritems())
+
+  def _GetConfiguration(key):
+    if not FAKE_URL_FETCHER_CONFIGURATION:
+      raise ValueError('No fake fetch paths have been configured. '
+                       'See ConfigureFakeUrlFetch in appengine_wrappers.py.')
+    for k, v in FAKE_URL_FETCHER_CONFIGURATION.iteritems():
+      if k.match(key):
+        return v
+    return None
+
+  class _RPC(object):
+    def __init__(self, result=None):
+      self.result = result
+
+    def get_result(self):
+      return self.result
+
+  class FakeUrlFetch(object):
+    """A fake urlfetch module that uses the current
+    |FAKE_URL_FETCHER_CONFIGURATION| to map urls to fake fetchers.
+    """
+    class _Response(object):
+      def __init__(self, content):
+        self.content = content
+        self.headers = { 'content-type': 'none' }
+        self.status_code = 200
+
+    def fetch(self, url, **kwargs):
+      response = self._Response(_GetConfiguration(url).fetch(url))
+      if response.content is None:
+        response.status_code = 404
+      return response
+
+    def create_rpc(self):
+      return _RPC()
+
+    def make_fetch_call(self, rpc, url, **kwargs):
+      rpc.result = self.fetch(url)
+  urlfetch = FakeUrlFetch()
+
+  class NotImplemented(object):
+    def __getattr__(self, attr):
+      raise NotImplementedError()
+
+  _BLOBS = {}
+  class FakeBlobstore(object):
+    class BlobReader(object):
+      def __init__(self, blob_key):
+        self._data = _BLOBS[blob_key].getvalue()
+
+      def read(self):
+        return self._data
+
+  blobstore = FakeBlobstore()
+
+  class FakeFileInterface(object):
+    """This class allows a StringIO object to be used in a with block like a
+    file.
+    """
+    def __init__(self, io):
+      self._io = io
+
+    def __exit__(self, *args):
+      pass
+
+    def write(self, data):
+      self._io.write(data)
+
+    def __enter__(self, *args):
+      return self._io
+
+  class FakeFiles(object):
+    _next_blobstore_key = 0
+    class blobstore(object):
+      @staticmethod
+      def create():
+        FakeFiles._next_blobstore_key += 1
+        return FakeFiles._next_blobstore_key
+
+      @staticmethod
+      def get_blob_key(filename):
+        return filename
+
+    def open(self, filename, mode):
+      _BLOBS[filename] = StringIO()
+      return FakeFileInterface(_BLOBS[filename])
+
+    def GetBlobKeys(self):
+      return _BLOBS.keys()
+
+    def finalize(self, filename):
+      pass
+
+  files = FakeFiles()
+
+  class InMemoryMemcache(object):
+    """A fake memcache that does nothing.
+    """
+    class Client(object):
+      def set_multi_async(self, mapping, namespace='', time=0):
+        return
+
+      def get_multi_async(self, keys, namespace='', time=0):
+        return _RPC(result=dict((k, None) for k in keys))
+
+    def set(self, key, value, namespace='', time=0):
+      return
+
+    def get(self, key, namespace='', time=0):
+      return None
+
+    def delete(self, key, namespace):
+      return
+
+  memcache = InMemoryMemcache()
+
+  class webapp(object):
+    class RequestHandler(object):
+      """A fake webapp.RequestHandler class for Handler to extend.
+      """
+      def __init__(self, request, response):
+        self.request = request
+        self.response = response
+
+      def redirect(self, path):
+        self.request.path = path
+
+  class _Db_Result(object):
+    def __init__(self, data):
+      self._data = data
+
+    class _Result(object):
+      def __init__(self, value):
+        self.value = value
+
+    def get(self):
+      return self._Result(self._data)
+
+  class db(object):
+    _store = {}
+    class StringProperty(object):
+      pass
+
+    class Model(object):
+      def __init__(self, key_='', value=''):
+        self._key = key_
+        self._value = value
+
+      @staticmethod
+      def gql(query, key):
+        return _Db_Result(db._store.get(key, None))
+
+      def put(self):
+        db._store[self._key] = self._value
+
+  class BlobReferenceProperty(object):
+    pass
diff --git a/chrome/common/extensions/docs/server2/blob_reference_store.py b/chrome/common/extensions/docs/server2/blob_reference_store.py
new file mode 100644
index 0000000..97c6827
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/blob_reference_store.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2012 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.
+
+from appengine_wrappers import db
+from appengine_wrappers import BlobReferenceProperty
+
+BLOB_REFERENCE_BLOBSTORE = 'BlobReferenceBlobstore'
+
+class _Model(db.Model):
+  key_ = db.StringProperty()
+  value = BlobReferenceProperty()
+
+class BlobReferenceStore(object):
+  """A wrapper around the datastore API that can store blob keys.
+  """
+  def __init__(self, branch):
+    self._branch = branch
+
+  def _Query(self, namespace, key):
+    return _Model.gql('WHERE key_ = :1', self._MakeKey(namespace, key)).get()
+
+  def _MakeKey(self, namespace, key):
+    return '.'.join([self._branch, namespace, key])
+
+  def Set(self, namespace, key, value):
+    _Model(key_=self._MakeKey(namespace, key), value=value).put()
+
+  def Get(self, namespace, key):
+    result = self._Query(namespace, key)
+    if not result:
+      return None
+    return result.value
+
+  def Delete(self, namespace, key):
+    result = self._Query(namespace, key)
+    if not result:
+      return None
+    blob_key = result.value
+    result.delete()
+    return blob_key
diff --git a/chrome/common/extensions/docs/server2/branch_utility.py b/chrome/common/extensions/docs/server2/branch_utility.py
new file mode 100644
index 0000000..ba4125f
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/branch_utility.py
@@ -0,0 +1,77 @@
+# Copyright (c) 2012 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.
+
+import json
+
+import object_store
+import operator
+
+class BranchUtility(object):
+  def __init__(self, base_path, default_branches, fetcher, object_store):
+    self._base_path = base_path
+    self._default_branches = default_branches
+    self._fetcher = fetcher
+    self._object_store = object_store
+
+  def GetAllBranchNames(self):
+    # TODO(aa): Do we need to include 'local'?
+    return ['dev', 'beta', 'stable', 'trunk', 'local']
+
+  def GetAllBranchNumbers(self):
+    return [(branch, self.GetBranchNumberForChannelName(branch))
+            for branch in self.GetAllBranchNames()]
+
+  def SplitChannelNameFromPath(self, path):
+    try:
+      first, second = path.split('/', 1)
+    except ValueError:
+      first = path
+      second = ''
+    if first in ['trunk', 'dev', 'beta', 'stable']:
+      return (first, second, False)
+    else:
+      doc_type = path.split('/')[0]
+      if doc_type in self._default_branches:
+        return (self._default_branches[doc_type], path, False)
+      return (self._default_branches['extensions'], path, True)
+
+  def GetBranchNumberForChannelName(self, channel_name):
+    """Returns the branch number for a channel name. If the |channel_name| is
+    'trunk' or 'local', then |channel_name| will be returned unchanged. These
+    are returned unchanged because 'trunk' has a separate URL from the other
+    branches and should be handled differently. 'local' is also a special branch
+    for development that should be handled differently.
+    """
+    if channel_name in ['trunk', 'local']:
+      return channel_name
+
+    branch_number = self._object_store.Get(channel_name + '.' + self._base_path,
+                                           object_store.BRANCH_UTILITY).Get()
+    if branch_number is not None:
+      return branch_number
+
+    fetch_data = self._fetcher.Fetch(self._base_path).content
+    version_json = json.loads(fetch_data)
+    branch_numbers = {}
+    for entry in version_json:
+      if entry['os'] not in ['win', 'linux', 'mac', 'cros']:
+        continue
+      for version in entry['versions']:
+        if version['channel'] != channel_name:
+          continue
+        branch = version['version'].split('.')[2]
+        if branch not in branch_numbers:
+          branch_numbers[branch] = 0
+        else:
+          branch_numbers[branch] += 1
+
+    sorted_branches = sorted(branch_numbers.iteritems(),
+                             None,
+                             operator.itemgetter(1),
+                             True)
+    self._object_store.Set(channel_name + '.' + self._base_path,
+                           sorted_branches[0][0],
+                           object_store.BRANCH_UTILITY)
+
+    return sorted_branches[0][0]
diff --git a/chrome/common/extensions/docs/server2/branch_utility_test.py b/chrome/common/extensions/docs/server2/branch_utility_test.py
new file mode 100755
index 0000000..ccc92f5
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/branch_utility_test.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+import os
+import sys
+import unittest
+
+from in_memory_object_store import InMemoryObjectStore
+from branch_utility import BranchUtility
+from fake_url_fetcher import FakeUrlFetcher
+
+class BranchUtilityTest(unittest.TestCase):
+  def setUp(self):
+    self._branch_util = BranchUtility(
+        os.path.join('branch_utility', 'first.json'),
+        { 'extensions': 'stable', 'apps': 'trunk' },
+        FakeUrlFetcher(os.path.join(sys.path[0], 'test_data')),
+        InMemoryObjectStore(''))
+
+  def testSplitChannelNameFromPath(self):
+    self.assertEquals(('dev', 'extensions/stuff.html', False),
+                      self._branch_util.SplitChannelNameFromPath(
+                      'dev/extensions/stuff.html'))
+    self.assertEquals(('beta', 'extensions/stuff.html', False),
+                      self._branch_util.SplitChannelNameFromPath(
+                      'beta/extensions/stuff.html'))
+    self.assertEquals(('trunk', 'extensions/stuff.html', False),
+                      self._branch_util.SplitChannelNameFromPath(
+                      'trunk/extensions/stuff.html'))
+    self.assertEquals(('stable', 'extensions/stuff.html', False),
+                      self._branch_util.SplitChannelNameFromPath(
+                      'extensions/stuff.html'))
+    self.assertEquals(('trunk', 'apps/stuff.html', False),
+                      self._branch_util.SplitChannelNameFromPath(
+                      'apps/stuff.html'))
+    self.assertEquals(('stable', 'extensions/dev/stuff.html', False),
+                      self._branch_util.SplitChannelNameFromPath(
+                      'extensions/dev/stuff.html'))
+    self.assertEquals(('stable', 'stuff.html', True),
+                      self._branch_util.SplitChannelNameFromPath(
+                      'stuff.html'))
+
+  def testGetBranchNumberForChannelName(self):
+    self.assertEquals('1145',
+                      self._branch_util.GetBranchNumberForChannelName('dev'))
+    self.assertEquals('1084',
+                      self._branch_util.GetBranchNumberForChannelName('beta'))
+    self.assertEquals('1084',
+                      self._branch_util.GetBranchNumberForChannelName('stable'))
+    self.assertEquals('trunk',
+                      self._branch_util.GetBranchNumberForChannelName('trunk'))
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/chrome/common/extensions/docs/server2/build_server.py b/chrome/common/extensions/docs/server2/build_server.py
new file mode 100755
index 0000000..5a99253
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/build_server.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+# This script is used to copy all dependencies into the local directory.
+# The package of files can then be uploaded to App Engine.
+import os
+import shutil
+import stat
+import sys
+
+SRC_DIR = os.path.join(sys.path[0], os.pardir, os.pardir, os.pardir, os.pardir,
+    os.pardir)
+THIRD_PARTY_DIR = os.path.join(SRC_DIR, 'third_party')
+LOCAL_THIRD_PARTY_DIR = os.path.join(sys.path[0], 'third_party')
+TOOLS_DIR = os.path.join(SRC_DIR, 'tools')
+SCHEMA_COMPILER_FILES = ['model.py', 'idl_schema.py', 'schema_util.py']
+
+def MakeInit(path):
+  path = os.path.join(path, '__init__.py')
+  with open(os.path.join(path), 'w') as f:
+    os.utime(os.path.join(path), None)
+
+def OnError(function, path, excinfo):
+  os.chmod(path, stat.S_IWUSR)
+  function(path)
+
+def CopyThirdParty(src, dest, files=None):
+  dest_path = os.path.join(LOCAL_THIRD_PARTY_DIR, dest)
+  if not files:
+    shutil.copytree(src, dest_path)
+    MakeInit(dest_path)
+    return
+  try:
+    os.makedirs(dest_path)
+  except Exception:
+    pass
+  MakeInit(dest_path)
+  for filename in files:
+    shutil.copy(os.path.join(src, filename), os.path.join(dest_path, filename))
+
+def main():
+  if os.path.isdir(LOCAL_THIRD_PARTY_DIR):
+    try:
+      shutil.rmtree(LOCAL_THIRD_PARTY_DIR, False, OnError)
+    except OSError:
+      print('*-------------------------------------------------------------*\n'
+            '| If you are receiving an upload error, try removing          |\n'
+            '| chrome/common/extensions/docs/server2/third_party manually. |\n'
+            '*-------------------------------------------------------------*\n')
+
+
+  CopyThirdParty(os.path.join(THIRD_PARTY_DIR, 'handlebar'), 'handlebar')
+  CopyThirdParty(os.path.join(SRC_DIR, 'ppapi', 'generators'),
+                 'json_schema_compiler')
+  CopyThirdParty(os.path.join(THIRD_PARTY_DIR, 'ply'),
+                 os.path.join('json_schema_compiler', 'ply'))
+  CopyThirdParty(os.path.join(TOOLS_DIR, 'json_schema_compiler'),
+                 'json_schema_compiler',
+                 SCHEMA_COMPILER_FILES)
+  CopyThirdParty(TOOLS_DIR, 'json_schema_compiler', ['json_comment_eater.py'])
+  MakeInit(LOCAL_THIRD_PARTY_DIR)
+
+  # To be able to use the Handlebar class we need this import in __init__.py.
+  with open(os.path.join(LOCAL_THIRD_PARTY_DIR,
+                         'handlebar',
+                         '__init__.py'), 'a') as f:
+    f.write('from handlebar import Handlebar\n')
+
+if __name__ == '__main__':
+  main()
diff --git a/chrome/common/extensions/docs/server2/compiled_file_system.py b/chrome/common/extensions/docs/server2/compiled_file_system.py
new file mode 100644
index 0000000..9acf585
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/compiled_file_system.py
@@ -0,0 +1,120 @@
+# Copyright (c) 2012 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.
+
+import os
+
+import object_store
+
+APPS                      = 'Apps'
+APPS_FS                   = 'AppsFileSystem'
+CRON                      = 'Cron'
+EXTENSIONS                = 'Extensions'
+EXTENSIONS_FS             = 'ExtensionsFileSystem'
+CRON_FILE_LISTING         = 'Cron.FileListing'
+CRON_GITHUB_INVALIDATION  = 'Cron.GithubInvalidation'
+CRON_INVALIDATION         = 'Cron.Invalidation'
+HANDLEBAR                 = 'Handlebar'
+IDL                       = 'IDL'
+IDL_NO_REFS               = 'IDLNoRefs'
+IDL_NAMES                 = 'IDLNames'
+INTRO                     = 'Intro'
+JSON                      = 'JSON'
+JSON_NO_REFS              = 'JSONNoRefs'
+LIST                      = 'List'
+NAMES                     = 'Names'
+PERMS                     = 'Perms'
+SIDENAV                   = 'Sidenav'
+STATIC                    = 'Static'
+ZIP                       = 'Zip'
+
+class _CacheEntry(object):
+  def __init__(self, cache_data, version):
+    self._cache_data = cache_data
+    self.version = version
+
+class CompiledFileSystem(object):
+  """This class caches FileSystem data that has been processed.
+  """
+  class Factory(object):
+    """A class to build a CompiledFileSystem.
+    """
+    def __init__(self, file_system, object_store):
+      self._file_system = file_system
+      self._object_store = object_store
+
+    def Create(self, populate_function, namespace, version=None):
+      """Create a CompiledFileSystem that populates the cache by calling
+      |populate_function| on the raw filesystem data. The keys to the cache
+      are put in the namespace specified by |namespace|, and optionally adding
+      |version|.
+      """
+      return CompiledFileSystem(self._file_system,
+                             populate_function,
+                             self._object_store,
+                             namespace,
+                             version=version)
+
+  def __init__(self,
+               file_system,
+               populate_function,
+               object_store,
+               namespace,
+               version=None):
+    self._file_system = file_system
+    self._populate_function = populate_function
+    self._object_store = object_store
+    self._namespace = 'CompiledFileSystem.' + namespace
+    if version is not None:
+      self._namespace = '%s.%s' % (self._namespace, version)
+
+  def _MakeKey(self, key):
+    return self._namespace + '.' + key
+
+  def _RecursiveList(self, files):
+    all_files = files[:]
+    dirs = {}
+    for filename in files:
+      if filename.endswith('/'):
+        all_files.remove(filename)
+        dirs.update(self._file_system.Read([filename]).Get())
+    for dir_, files in dirs.iteritems():
+      all_files.extend(self._RecursiveList([dir_ + f for f in files]))
+    return all_files
+
+  def GetFromFile(self, path):
+    """Calls |populate_function| on the contents of the file at |path|.
+    """
+    version = self._file_system.Stat(path).version
+    cache_entry = self._object_store.Get(self._MakeKey(path),
+                                         object_store.FILE_SYSTEM_CACHE,
+                                         time=0).Get()
+    if (cache_entry is not None) and (version == cache_entry.version):
+      return cache_entry._cache_data
+    cache_data = self._populate_function(self._file_system.ReadSingle(path))
+    self._object_store.Set(self._MakeKey(path),
+                           _CacheEntry(cache_data, version),
+                           object_store.FILE_SYSTEM_CACHE,
+                           time=0)
+    return cache_data
+
+  def GetFromFileListing(self, path):
+    """Calls |populate_function| on the listing of the files at |path|.
+    Assumes that the path given is to a directory.
+    """
+    if not path.endswith('/'):
+      path += '/'
+    version = self._file_system.Stat(path).version
+    cache_entry = self._object_store.Get(
+        self._MakeKey(path),
+        object_store.FILE_SYSTEM_CACHE_LISTING,
+        time=0).Get()
+    if (cache_entry is not None) and (version == cache_entry.version):
+        return cache_entry._cache_data
+    cache_data = self._populate_function(self._RecursiveList(
+        [path + f for f in self._file_system.ReadSingle(path)]))
+    self._object_store.Set(self._MakeKey(path),
+                           _CacheEntry(cache_data, version),
+                           object_store.FILE_SYSTEM_CACHE_LISTING,
+                           time=0)
+    return cache_data
diff --git a/chrome/common/extensions/docs/server2/converter_html_parser.py b/chrome/common/extensions/docs/server2/converter_html_parser.py
new file mode 100644
index 0000000..9e0db14
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/converter_html_parser.py
@@ -0,0 +1,58 @@
+# Copyright (c) 2012 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.
+
+from HTMLParser import HTMLParser
+from StringIO import StringIO
+
+class _ConverterHTMLParser(HTMLParser):
+  def __init__(self, io):
+    HTMLParser.__init__(self)
+    self._io = io
+    self._tag_stack = []
+
+  def handle_starttag(self, tag, attrs):
+    attrs_dict = dict(attrs)
+    self._tag_stack.append({'tag': tag})
+    class_attr = dict(attrs).get('class', None)
+    if class_attr is not None:
+      if class_attr == 'doc-family extensions':
+        self._io.write('{{^is_apps}}\n')
+        self._tag_stack[-1]['close'] = True
+      if class_attr == 'doc-family apps':
+        self._io.write('{{?is_apps}}\n')
+        self._tag_stack[-1]['close'] = True
+    self._io.write(self.get_starttag_text())
+
+  def handle_startendtag(self, tag, attrs):
+    self._io.write(self.get_starttag_text())
+
+  def handle_endtag(self, tag):
+    self._io.write('</' + tag + '>')
+    if len(self._tag_stack) == 0:
+      return
+    if self._tag_stack[-1]['tag'] == tag:
+      if self._tag_stack[-1].get('close', False):
+        self._io.write('\n{{/is_apps}}')
+      self._tag_stack.pop()
+
+  def handle_data(self, data):
+    self._io.write(data)
+
+  def handle_comment(self, data):
+    self._io.write('<!--' + data + '-->')
+
+  def handle_entityref(self, name):
+    self._io.write('&' + name + ';')
+
+  def handle_charref(self, name):
+    self._io.write('&#' + name + ';')
+
+  def handle_decl(self, data):
+    self._io.write('<!' + data + '>')
+
+def HandleDocFamily(html):
+  output = StringIO()
+  parser = _ConverterHTMLParser(output)
+  parser.feed(html)
+  return output.getvalue()
diff --git a/chrome/common/extensions/docs/server2/cron.yaml b/chrome/common/extensions/docs/server2/cron.yaml
new file mode 100644
index 0000000..c36e8c1
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/cron.yaml
@@ -0,0 +1,16 @@
+cron:
+- description: Load everything for trunk.
+  url: /cron/trunk
+  schedule: every 5 minutes
+
+- description: Load everything for dev.
+  url: /cron/dev
+  schedule: every 5 minutes
+
+- description: Load everything for beta.
+  url: /cron/beta
+  schedule: every 5 minutes
+
+- description: Load everything for stable.
+  url: /cron/stable
+  schedule: every 5 minutes
diff --git a/chrome/common/extensions/docs/server2/docs_server_utils.py b/chrome/common/extensions/docs/server2/docs_server_utils.py
new file mode 100644
index 0000000..a3fe3fc
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/docs_server_utils.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2012 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.
+
+import os
+
+def FormatKey(key):
+  """Normalize a key by making sure it has a .html extension, and convert any
+  '.'s to '_'s.
+  """
+  if key.endswith('.html'):
+    key = key[:-len('.html')]
+  safe_key = key.replace('.', '_')
+  return '%s.html' % safe_key
+
+def SanitizeAPIName(name, api_path=''):
+  """Sanitizes API filenames that are in subdirectories.
+  """
+  filename = os.path.splitext(name)[0][len(api_path):].replace(os.sep, '_')
+  if 'experimental' in filename:
+    filename = 'experimental_' + filename.replace('experimental_', '')
+  return filename
diff --git a/chrome/common/extensions/docs/server2/example_zipper.py b/chrome/common/extensions/docs/server2/example_zipper.py
new file mode 100644
index 0000000..37a1693
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/example_zipper.py
@@ -0,0 +1,42 @@
+# Copyright (c) 2012 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.
+
+import os
+from io import BytesIO
+import re
+from zipfile import ZipFile
+
+import compiled_file_system as compiled_fs
+
+class ExampleZipper(object):
+  """This class creates a zip file given a samples directory.
+  """
+  def __init__(self, file_system, cache_factory, base_path):
+    self._base_path = base_path
+    self._zip_cache = cache_factory.Create(self._MakeZipFile,
+                                           compiled_fs.ZIP)
+    self._file_system = file_system
+
+  def _MakeZipFile(self, files):
+    zip_path = os.path.commonprefix(files).rsplit('/', 1)[-2]
+    prefix = zip_path.rsplit('/', 1)[-2]
+    if zip_path + '/manifest.json' not in files:
+      return None
+    zip_bytes = BytesIO()
+    zip_file = ZipFile(zip_bytes, mode='w')
+    try:
+      for name, file_contents in (
+          self._file_system.Read(files, binary=True).Get().iteritems()):
+        zip_file.writestr(name[len(prefix):].strip('/'), file_contents)
+    finally:
+      zip_file.close()
+    return zip_bytes.getvalue()
+
+  def Create(self, path):
+    """ Creates a new zip file from the recursive contents of |path|
+    as returned by |_zip_cache|.
+    Paths within the zip file are given relative to and including |path|.
+    """
+    return self._zip_cache.GetFromFileListing(
+        self._base_path + '/' + path)
diff --git a/chrome/common/extensions/docs/server2/example_zipper_test.py b/chrome/common/extensions/docs/server2/example_zipper_test.py
new file mode 100755
index 0000000..f1a5185
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/example_zipper_test.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+import os
+import sys
+import unittest
+
+from compiled_file_system import CompiledFileSystem
+from example_zipper import ExampleZipper
+from in_memory_object_store import InMemoryObjectStore
+from local_file_system import LocalFileSystem
+from memcache_file_system import MemcacheFileSystem
+
+class ExampleZipperTest(unittest.TestCase):
+  def setUp(self):
+    object_store = InMemoryObjectStore('')
+    self._file_system = MemcacheFileSystem(
+        LocalFileSystem(os.path.join(sys.path[0], 'test_data')),
+        object_store)
+    self._example_zipper = ExampleZipper(
+        self._file_system,
+        CompiledFileSystem.Factory(self._file_system, object_store),
+        'example_zipper')
+
+  def testCreateZip(self):
+    # Cache manifest.json as unicode and make sure ExampleZipper doesn't error.
+    self._file_system.ReadSingle('example_zipper/basic/manifest.json')
+    self.assertTrue(len(self._example_zipper.Create('basic')) > 0)
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/chrome/common/extensions/docs/server2/fake_fetchers.py b/chrome/common/extensions/docs/server2/fake_fetchers.py
new file mode 100644
index 0000000..3d1a720
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/fake_fetchers.py
@@ -0,0 +1,120 @@
+# Copyright (c) 2012 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.
+
+# These are fake fetchers that are used for testing and the preview server.
+# They return canned responses for URLs. appengine_wrappers.py uses the fake
+# fetchers if the App Engine imports fail.
+
+import os
+import re
+
+import appengine_wrappers
+from file_system import FileNotFoundError
+import url_constants
+
+class _FakeFetcher(object):
+  def __init__(self, base_path):
+    self._base_path = base_path
+
+  def _ReadFile(self, path, mode='rb'):
+    with open(os.path.join(self._base_path, path), mode) as f:
+      return f.read()
+
+  def _ListDir(self, path):
+    return os.listdir(os.path.join(self._base_path, path))
+
+  def _IsDir(self, path):
+    return os.path.isdir(os.path.join(self._base_path, path))
+
+  def _Stat(self, path):
+    return os.stat(os.path.join(self._base_path, path)).st_mtime
+
+class FakeOmahaProxy(_FakeFetcher):
+  def fetch(self, url):
+    return self._ReadFile(os.path.join('server2',
+                                       'test_data',
+                                       'branch_utility',
+                                       'first.json'))
+
+class FakeSubversionServer(_FakeFetcher):
+  def __init__(self, base_path):
+    _FakeFetcher.__init__(self, base_path)
+    self._base_pattern = re.compile(r'.*chrome/common/extensions/(.*)')
+
+  def fetch(self, url):
+    path = os.path.join(os.pardir, self._base_pattern.match(url).group(1))
+    if self._IsDir(path):
+      html = ['<html>Revision 000000']
+      try:
+        for f in self._ListDir(path):
+          if f.startswith('.'):
+            continue
+          if self._IsDir(os.path.join(path, f)):
+            html.append('<a>' + f + '/</a>')
+          else:
+            html.append('<a>' + f + '</a>')
+        html.append('</html>')
+        return '\n'.join(html)
+      except OSError:
+        raise FileNotFoundError(path)
+    try:
+      return self._ReadFile(path)
+    except IOError:
+      raise FileNotFoundError(path)
+
+class FakeViewvcServer(_FakeFetcher):
+  def __init__(self, base_path):
+    _FakeFetcher.__init__(self, base_path)
+    self._base_pattern = re.compile(r'.*chrome/common/extensions/(.*)')
+
+  def fetch(self, url):
+    path = os.path.join(os.pardir, self._base_pattern.match(url).group(1))
+    if self._IsDir(path):
+      html = ['<html><td>Directory revision:</td><td><a>%s</a></td>' %
+              self._Stat(path)]
+      for f in self._ListDir(path):
+        if f.startswith('.'):
+          continue
+        html.append('<td><a name="%s"></a></td>' % f)
+        stat = self._Stat(os.path.join(path, f))
+        html.append('<td><a title="%s"><strong>%s</strong></a></td>' %
+            ('dir' if self._IsDir(os.path.join(path, f)) else 'file', stat))
+      html.append('</html>')
+      return '\n'.join(html)
+    try:
+      return self._ReadFile(path)
+    except IOError:
+      raise FileNotFoundError(path)
+
+class FakeGithubStat(_FakeFetcher):
+  def fetch(self, url):
+    return '{ "commit": { "tree": { "sha": 0} } }'
+
+class FakeGithubZip(_FakeFetcher):
+  def fetch(self, url):
+    try:
+      return self._ReadFile(os.path.join('server2',
+                                         'test_data',
+                                         'github_file_system',
+                                         'apps_samples.zip'),
+                            mode='rb')
+    except IOError:
+      return None
+
+class FakeIssuesFetcher(_FakeFetcher):
+  def fetch(self, url):
+    return 'Status,Summary,ID'
+
+def ConfigureFakeFetchers(docs):
+  '''Configure the fake fetcher paths relative to the docs directory.
+  '''
+  appengine_wrappers.ConfigureFakeUrlFetch({
+    url_constants.OMAHA_PROXY_URL: FakeOmahaProxy(docs),
+    '%s/.*' % url_constants.SVN_URL: FakeSubversionServer(docs),
+    '%s/.*' % url_constants.VIEWVC_URL: FakeViewvcServer(docs),
+    '%s/commits/.*' % url_constants.GITHUB_URL: FakeGithubStat(docs),
+    '%s/zipball' % url_constants.GITHUB_URL: FakeGithubZip(docs),
+    re.escape(url_constants.OPEN_ISSUES_CSV_URL): FakeIssuesFetcher(docs),
+    re.escape(url_constants.CLOSED_ISSUES_CSV_URL): FakeIssuesFetcher(docs)
+  })
diff --git a/chrome/common/extensions/docs/server2/fake_url_fetcher.py b/chrome/common/extensions/docs/server2/fake_url_fetcher.py
new file mode 100644
index 0000000..ea4f523
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/fake_url_fetcher.py
@@ -0,0 +1,49 @@
+# Copyright (c) 2012 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.
+
+import os
+
+from future import Future
+
+class _Response(object):
+  def __init__(self):
+    self.content = ''
+    self.headers = { 'content-type': 'none' }
+    self.status_code = 200
+
+class FakeUrlFetcher(object):
+  def __init__(self, base_path):
+    self._base_path = base_path
+
+  def _ReadFile(self, filename):
+    with open(os.path.join(self._base_path, filename), 'r') as f:
+      return f.read()
+
+  def _ListDir(self, directory):
+    # In some tests, we need to test listing a directory from the HTML returned
+    # from SVN. This reads an HTML file that has the directories HTML.
+    if not os.path.isdir(os.path.join(self._base_path, directory)):
+      return self._ReadFile(directory[:-1])
+    files = os.listdir(os.path.join(self._base_path, directory))
+    html = '<html><title>Revision: 00000</title>\n'
+    for filename in files:
+      if filename.startswith('.'):
+        continue
+      if os.path.isdir(os.path.join(self._base_path, directory, filename)):
+        html += '<a>' + filename + '/</a>\n'
+      else:
+        html += '<a>' + filename + '</a>\n'
+    html += '</html>'
+    return html
+
+  def FetchAsync(self, url):
+    return Future(value=self.Fetch(url))
+
+  def Fetch(self, url):
+    result = _Response()
+    if url.endswith('/'):
+      result.content = self._ListDir(url)
+    else:
+      result.content = self._ReadFile(url)
+    return result
diff --git a/chrome/common/extensions/docs/server2/file_system.py b/chrome/common/extensions/docs/server2/file_system.py
new file mode 100644
index 0000000..540abe8
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/file_system.py
@@ -0,0 +1,52 @@
+# Copyright (c) 2012 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.
+
+import os
+
+class FileNotFoundError(Exception):
+  def __init__(self, filename):
+    Exception.__init__(self, filename)
+
+class StatInfo(object):
+  """The result of calling Stat on a FileSystem.
+  """
+  def __init__(self, version, child_versions=None):
+    self.version = version
+    self.child_versions = child_versions
+
+def _ProcessFileData(data, path):
+  if os.path.splitext(path)[-1] not in ['.js', '.html', '.json']:
+    return data
+  try:
+    return unicode(data, 'utf-8')
+  except:
+    return unicode(data, 'latin-1')
+
+class FileSystem(object):
+  """A FileSystem interface that can read files and directories.
+  """
+
+  def Read(self, paths, binary=False):
+    """Reads each file in paths and returns a dictionary mapping the path to the
+    contents. If a path in paths ends with a '/', it is assumed to be a
+    directory, and a list of files in the directory is mapped to the path.
+
+    If binary=False, the contents of each file will be unicode parsed as utf-8,
+    and failing that as latin-1 (some extension docs use latin-1). If
+    binary=True then the contents will be a str.
+    """
+    raise NotImplementedError()
+
+  def ReadSingle(self, path):
+    """Reads a single file from the FileSystem.
+    """
+    return self.Read([path]).Get()[path]
+
+  # TODO(cduvall): Allow Stat to take a list of paths like Read.
+  def Stat(self, path):
+    """Returns a |StatInfo| object containing the version of |path|. If |path|
+    is a directory, |StatInfo| will have the versions of all the children of
+    the directory in |StatInfo.child_versions|.
+    """
+    raise NotImplementedError()
diff --git a/chrome/common/extensions/docs/server2/future.py b/chrome/common/extensions/docs/server2/future.py
new file mode 100644
index 0000000..98deef6
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/future.py
@@ -0,0 +1,30 @@
+# Copyright (c) 2012 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.
+
+_no_value = object()
+
+class Future(object):
+  """Stores a value, error, or delegate to be used later.
+  """
+  def __init__(self, value=_no_value, error=None, delegate=None):
+    self._value = value
+    self._error = error
+    self._delegate = delegate
+    if (self._value is _no_value and
+        self._error is None and
+        self._delegate is None):
+      raise ValueError('Must have either a value, error, or delegate.')
+
+  def Get(self):
+    """Gets the stored value, error, or delegate contents.
+    """
+    if self._value is not _no_value:
+      return self._value
+    if self._error is not None:
+      raise self._error
+    try:
+      self._value = self._delegate.Get()
+    except Exception as self._error:
+      raise
+    return self._value
diff --git a/chrome/common/extensions/docs/server2/github_file_system.py b/chrome/common/extensions/docs/server2/github_file_system.py
new file mode 100644
index 0000000..b32c5a1
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/github_file_system.py
@@ -0,0 +1,123 @@
+# Copyright (c) 2012 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.
+
+import json
+import logging
+import os
+
+import appengine_blobstore as blobstore
+import object_store
+from file_system import FileSystem, StatInfo, FileNotFoundError
+from StringIO import StringIO
+from future import Future
+from zipfile import ZipFile, BadZipfile
+
+ZIP_KEY = 'zipball'
+
+def _MakeKey(version):
+  return ZIP_KEY + '.' + str(version)
+
+class _AsyncFetchFutureZip(object):
+  def __init__(self, fetcher, blobstore, key_to_set, key_to_delete=None):
+    self._fetch = fetcher.FetchAsync(ZIP_KEY)
+    self._blobstore = blobstore
+    self._key_to_set = key_to_set
+    self._key_to_delete = key_to_delete
+
+  def Get(self):
+    try:
+      blob = self._fetch.Get().content
+    except FileNotFoundError as e:
+      logging.error('Bad github zip file: %s' % e)
+      return None
+    self._blobstore.Set(_MakeKey(self._key_to_set),
+                        blob,
+                        blobstore.BLOBSTORE_GITHUB)
+    if self._key_to_delete is not None:
+      self._blobstore.Delete(_MakeKey(self._key_to_delete),
+                             blobstore.BLOBSTORE_GITHUB)
+    try:
+      return ZipFile(StringIO(blob))
+    except BadZipfile as e:
+      logging.error('Bad github zip file: %s' % e)
+      return None
+
+class GithubFileSystem(FileSystem):
+  """FileSystem implementation which fetches resources from github.
+  """
+  def __init__(self, fetcher, object_store, blobstore):
+    self._fetcher = fetcher
+    self._object_store = object_store
+    self._blobstore = blobstore
+    self._version = None
+    self._GetZip(self.Stat(ZIP_KEY).version)
+
+  def _GetZip(self, version):
+    blob = self._blobstore.Get(_MakeKey(version), blobstore.BLOBSTORE_GITHUB)
+    if blob is not None:
+      try:
+        self._zip_file = Future(value=ZipFile(StringIO(blob)))
+      except BadZipfile as e:
+        self._blobstore.Delete(_MakeKey(version), blobstore.BLOBSTORE_GITHUB)
+        logging.error('Bad github zip file: %s' % e)
+        self._zip_file = Future(value=None)
+    else:
+      self._zip_file = Future(
+          delegate=_AsyncFetchFutureZip(self._fetcher,
+                                        self._blobstore,
+                                        version,
+                                        key_to_delete=self._version))
+    self._version = version
+
+  def _ReadFile(self, path):
+    zip_file = self._zip_file.Get()
+    if zip_file is None:
+      logging.error('Bad github zip file.')
+      return ''
+    prefix = zip_file.namelist()[0][:-1]
+    return zip_file.read(prefix + path)
+
+  def _ListDir(self, path):
+    zip_file = self._zip_file.Get()
+    if zip_file is None:
+      logging.error('Bad github zip file.')
+      return []
+    filenames = zip_file.namelist()
+    # Take out parent directory name (GoogleChrome-chrome-app-samples-c78a30f)
+    filenames = [f[len(filenames[0]) - 1:] for f in filenames]
+    # Remove the path of the directory we're listing from the filenames.
+    filenames = [f[len(path):] for f in filenames
+                 if f != path and f.startswith(path)]
+    # Remove all files not directly in this directory.
+    return [f for f in filenames if f[:-1].count('/') == 0]
+
+  def Read(self, paths, binary=False):
+    version = self.Stat(ZIP_KEY).version
+    if version != self._version:
+      self._GetZip(version)
+    result = {}
+    for path in paths:
+      if path.endswith('/'):
+        result[path] = self._ListDir(path)
+      else:
+        result[path] = self._ReadFile(path)
+    return Future(value=result)
+
+  def Stat(self, path):
+    version = self._object_store.Get(path, object_store.GITHUB_STAT).Get()
+    if version is not None:
+      return StatInfo(version)
+    version = (json.loads(
+        self._fetcher.Fetch('commits/HEAD').content).get('commit', {})
+                                                    .get('tree', {})
+                                                    .get('sha', None))
+    # Check if the JSON was valid, and set to 0 if not.
+    if version is not None:
+      self._object_store.Set(path, version, object_store.GITHUB_STAT)
+    else:
+      logging.warning('Problem fetching commit hash from github.')
+      version = 0
+      # Cache for a minute so we don't try to keep fetching bad data.
+      self._object_store.Set(path, version, object_store.GITHUB_STAT, time=60)
+    return StatInfo(version)
diff --git a/chrome/common/extensions/docs/server2/github_file_system_test.py b/chrome/common/extensions/docs/server2/github_file_system_test.py
new file mode 100755
index 0000000..3d2767c
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/github_file_system_test.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+import json
+import os
+import sys
+import unittest
+
+from appengine_blobstore import AppEngineBlobstore
+from appengine_url_fetcher import AppEngineUrlFetcher
+from appengine_wrappers import files
+from fake_fetchers import ConfigureFakeFetchers
+from github_file_system import GithubFileSystem
+from in_memory_object_store import InMemoryObjectStore
+import url_constants
+
+class GithubFileSystemTest(unittest.TestCase):
+  def setUp(self):
+    ConfigureFakeFetchers(os.path.join(sys.path[0], os.pardir))
+    self._base_path = os.path.join(sys.path[0],
+                                   'test_data',
+                                   'github_file_system')
+    self._file_system = GithubFileSystem(
+        AppEngineUrlFetcher(url_constants.GITHUB_URL),
+        InMemoryObjectStore('github'),
+        AppEngineBlobstore())
+
+  def _ReadLocalFile(self, filename):
+    with open(os.path.join(self._base_path, filename), 'r') as f:
+      return f.read()
+
+  def testList(self):
+    self.assertEqual(json.loads(self._ReadLocalFile('expected_list.json')),
+                     self._file_system.Read(['/']).Get())
+
+  def testRead(self):
+   self.assertEqual(self._ReadLocalFile('expected_read.txt'),
+                    self._file_system.ReadSingle('/analytics/launch.js'))
+
+  def testStat(self):
+    self.assertEqual(0, self._file_system.Stat('zipball').version)
+
+  def testKeyGeneration(self):
+    self.assertEqual(0, len(files.GetBlobKeys()))
+    self._file_system.ReadSingle('/analytics/launch.js')
+    self.assertEqual(1, len(files.GetBlobKeys()))
+    self._file_system.ReadSingle('/analytics/main.css')
+    self.assertEqual(1, len(files.GetBlobKeys()))
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/chrome/common/extensions/docs/server2/handler.py b/chrome/common/extensions/docs/server2/handler.py
new file mode 100644
index 0000000..2a36a7e
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/handler.py
@@ -0,0 +1,365 @@
+# Copyright (c) 2012 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.
+
+import logging
+import os
+from StringIO import StringIO
+import sys
+
+from appengine_wrappers import webapp
+from appengine_wrappers import memcache
+from appengine_wrappers import urlfetch
+
+from api_data_source import APIDataSource
+from api_list_data_source import APIListDataSource
+from appengine_blobstore import AppEngineBlobstore
+from in_memory_object_store import InMemoryObjectStore
+from appengine_url_fetcher import AppEngineUrlFetcher
+from branch_utility import BranchUtility
+from example_zipper import ExampleZipper
+from compiled_file_system import CompiledFileSystem
+import compiled_file_system as compiled_fs
+from github_file_system import GithubFileSystem
+from intro_data_source import IntroDataSource
+from known_issues_data_source import KnownIssuesDataSource
+from local_file_system import LocalFileSystem
+from memcache_file_system import MemcacheFileSystem
+from reference_resolver import ReferenceResolver
+from samples_data_source import SamplesDataSource
+from server_instance import ServerInstance
+from sidenav_data_source import SidenavDataSource
+from subversion_file_system import SubversionFileSystem
+from template_data_source import TemplateDataSource
+from third_party.json_schema_compiler.model import UnixName
+import url_constants
+
+# The branch that the server will default to when no branch is specified in the
+# URL. This is necessary because it is not possible to pass flags to the script
+# handler.
+# Production settings:
+DEFAULT_BRANCHES = { 'extensions': 'stable', 'apps': 'trunk' }
+# Dev settings:
+# DEFAULT_BRANCHES = { 'extensions': 'local', 'apps': 'local' }
+
+BRANCH_UTILITY_MEMCACHE = InMemoryObjectStore('branch_utility')
+BRANCH_UTILITY = BranchUtility(url_constants.OMAHA_PROXY_URL,
+                               DEFAULT_BRANCHES,
+                               AppEngineUrlFetcher(None),
+                               BRANCH_UTILITY_MEMCACHE)
+
+GITHUB_MEMCACHE = InMemoryObjectStore('github')
+GITHUB_FILE_SYSTEM = GithubFileSystem(
+    AppEngineUrlFetcher(url_constants.GITHUB_URL),
+    GITHUB_MEMCACHE,
+    AppEngineBlobstore())
+GITHUB_COMPILED_FILE_SYSTEM = CompiledFileSystem.Factory(GITHUB_FILE_SYSTEM,
+                                                         GITHUB_MEMCACHE)
+
+EXTENSIONS_PATH = 'chrome/common/extensions'
+DOCS_PATH = 'docs'
+API_PATH = 'api'
+TEMPLATE_PATH = DOCS_PATH + '/templates'
+INTRO_PATH = TEMPLATE_PATH + '/intros'
+ARTICLE_PATH = TEMPLATE_PATH + '/articles'
+PUBLIC_TEMPLATE_PATH = TEMPLATE_PATH + '/public'
+PRIVATE_TEMPLATE_PATH = TEMPLATE_PATH + '/private'
+EXAMPLES_PATH = DOCS_PATH + '/examples'
+JSON_PATH = TEMPLATE_PATH + '/json'
+
+# Global cache of instances because Handler is recreated for every request.
+SERVER_INSTANCES = {}
+
+def _GetURLFromBranch(branch):
+    if branch == 'trunk':
+      return url_constants.SVN_TRUNK_URL + '/src'
+    return url_constants.SVN_BRANCH_URL + '/' + branch + '/src'
+
+def _SplitFilenameUnix(files):
+  return [UnixName(os.path.splitext(f.split('/')[-1])[0]) for f in files]
+
+def _CreateMemcacheFileSystem(branch, branch_memcache):
+  svn_url = _GetURLFromBranch(branch) + '/' + EXTENSIONS_PATH
+  stat_fetcher = AppEngineUrlFetcher(
+      svn_url.replace(url_constants.SVN_URL, url_constants.VIEWVC_URL))
+  fetcher = AppEngineUrlFetcher(svn_url)
+  return MemcacheFileSystem(SubversionFileSystem(fetcher, stat_fetcher),
+                            branch_memcache)
+
+APPS_BRANCH = BRANCH_UTILITY.GetBranchNumberForChannelName(
+    DEFAULT_BRANCHES['apps'])
+APPS_MEMCACHE = InMemoryObjectStore(APPS_BRANCH)
+APPS_FILE_SYSTEM = _CreateMemcacheFileSystem(APPS_BRANCH, APPS_MEMCACHE)
+APPS_COMPILED_FILE_SYSTEM = CompiledFileSystem.Factory(
+    APPS_FILE_SYSTEM,
+    APPS_MEMCACHE).Create(_SplitFilenameUnix, compiled_fs.APPS_FS)
+
+EXTENSIONS_BRANCH = BRANCH_UTILITY.GetBranchNumberForChannelName(
+    DEFAULT_BRANCHES['extensions'])
+EXTENSIONS_MEMCACHE = InMemoryObjectStore(EXTENSIONS_BRANCH)
+EXTENSIONS_FILE_SYSTEM = _CreateMemcacheFileSystem(EXTENSIONS_BRANCH,
+                                                   EXTENSIONS_MEMCACHE)
+EXTENSIONS_COMPILED_FILE_SYSTEM = CompiledFileSystem.Factory(
+    EXTENSIONS_FILE_SYSTEM,
+    EXTENSIONS_MEMCACHE).Create(_SplitFilenameUnix, compiled_fs.EXTENSIONS_FS)
+
+KNOWN_ISSUES_DATA_SOURCE = KnownIssuesDataSource(
+    InMemoryObjectStore('KnownIssues'),
+    AppEngineUrlFetcher(None))
+
+def _MakeInstanceKey(branch, number):
+  return '%s/%s' % (branch, number)
+
+def _GetInstanceForBranch(channel_name, local_path):
+  branch = BRANCH_UTILITY.GetBranchNumberForChannelName(channel_name)
+
+  # The key for the server is a tuple of |channel_name| with |branch|, since
+  # sometimes stable and beta point to the same branch.
+  instance_key = _MakeInstanceKey(channel_name, branch)
+  instance = SERVER_INSTANCES.get(instance_key, None)
+  if instance is not None:
+    return instance
+
+  branch_memcache = InMemoryObjectStore(branch)
+  if branch == 'local':
+    file_system = LocalFileSystem(local_path)
+  else:
+    file_system = _CreateMemcacheFileSystem(branch, branch_memcache)
+
+  cache_factory = CompiledFileSystem.Factory(file_system, branch_memcache)
+  api_list_data_source_factory = APIListDataSource.Factory(cache_factory,
+                                                           file_system,
+                                                           API_PATH,
+                                                           PUBLIC_TEMPLATE_PATH)
+  intro_data_source_factory = IntroDataSource.Factory(
+      cache_factory,
+      [INTRO_PATH, ARTICLE_PATH])
+  api_data_source_factory = APIDataSource.Factory(
+      cache_factory,
+      API_PATH)
+  ref_resolver_factory = ReferenceResolver.Factory(
+      api_data_source_factory.Create(None, disable_refs=True),
+      api_list_data_source_factory.Create())
+  api_data_source_factory.SetReferenceResolverFactory(ref_resolver_factory)
+  samples_data_source_factory = SamplesDataSource.Factory(
+      channel_name,
+      file_system,
+      GITHUB_FILE_SYSTEM,
+      cache_factory,
+      GITHUB_COMPILED_FILE_SYSTEM,
+      ref_resolver_factory,
+      EXAMPLES_PATH)
+  api_data_source_factory.SetSamplesDataSourceFactory(
+      samples_data_source_factory)
+  sidenav_data_source_factory = SidenavDataSource.Factory(cache_factory,
+                                                          JSON_PATH)
+  template_data_source_factory = TemplateDataSource.Factory(
+      channel_name,
+      api_data_source_factory,
+      api_list_data_source_factory,
+      intro_data_source_factory,
+      samples_data_source_factory,
+      KNOWN_ISSUES_DATA_SOURCE,
+      sidenav_data_source_factory,
+      cache_factory,
+      PUBLIC_TEMPLATE_PATH,
+      PRIVATE_TEMPLATE_PATH)
+  example_zipper = ExampleZipper(file_system,
+                                 cache_factory,
+                                 DOCS_PATH)
+
+  instance = ServerInstance(template_data_source_factory,
+                            example_zipper,
+                            cache_factory)
+  SERVER_INSTANCES[instance_key] = instance
+  return instance
+
+def _CleanBranches():
+  keys = [_MakeInstanceKey(branch, number)
+          for branch, number in BRANCH_UTILITY.GetAllBranchNumbers()]
+  for key in SERVER_INSTANCES.keys():
+    if key not in keys:
+      SERVER_INSTANCES.pop(key)
+
+class _MockResponse(object):
+  def __init__(self):
+    self.status = 200
+    self.out = StringIO()
+    self.headers = {}
+
+  def set_status(self, status):
+    self.status = status
+
+class _MockRequest(object):
+  def __init__(self, path):
+    self.headers = {}
+    self.path = path
+    self.url = 'http://localhost' + path
+
+class Handler(webapp.RequestHandler):
+  def __init__(self, request, response, local_path=EXTENSIONS_PATH):
+    self._local_path = local_path
+    super(Handler, self).__init__(request, response)
+
+  def _HandleGet(self, path):
+    channel_name, real_path, default = BRANCH_UTILITY.SplitChannelNameFromPath(
+        path)
+    # TODO: Detect that these are directories and serve index.html out of them.
+    if real_path.strip('/') == 'apps':
+      real_path = 'apps/index.html'
+    if real_path.strip('/') == 'extensions':
+      real_path = 'extensions/index.html'
+
+    if (not real_path.startswith('extensions/') and
+        not real_path.startswith('apps/') and
+        not real_path.startswith('static/')):
+      if self._RedirectBadPaths(real_path, channel_name, default):
+        return
+
+    _CleanBranches()
+    _GetInstanceForBranch(channel_name, self._local_path).Get(real_path,
+                                                              self.request,
+                                                              self.response)
+
+  def _Render(self, files, channel):
+    original_response = self.response
+    for f in files:
+      if f.endswith('404.html'):
+        continue
+      path = channel + f.split(PUBLIC_TEMPLATE_PATH)[-1]
+      self.request = _MockRequest(path)
+      self.response = _MockResponse()
+      try:
+        self._HandleGet(path)
+      except Exception as e:
+        logging.error('Error rendering %s: %s' % (path, str(e)))
+    self.response = original_response
+
+  class _ValueHolder(object):
+    """Class to allow a value to be changed within a lambda.
+    """
+    def __init__(self, starting_value):
+      self._value = starting_value
+
+    def Set(self, value):
+      self._value = value
+
+    def Get(self):
+      return self._value
+
+  def _HandleCron(self, path):
+    # Cache population strategy:
+    #
+    # We could list all files in PUBLIC_TEMPLATE_PATH then render them. However,
+    # this would be inefficient in the common case where files haven't changed
+    # since the last cron.
+    #
+    # Instead, let the CompiledFileSystem give us clues when to re-render: we
+    # use the CFS to check whether the templates, examples, or API folders have
+    # been changed. If there has been a change, I will call the compilation
+    # function. The same is then done separately with the apps samples page,
+    # since it pulls its data from Github.
+    channel = path.split('/')[-1]
+    branch = BRANCH_UTILITY.GetBranchNumberForChannelName(channel)
+    logging.info('Running cron job for %s.' % branch)
+    branch_memcache = InMemoryObjectStore(branch)
+    file_system = _CreateMemcacheFileSystem(branch, branch_memcache)
+    factory = CompiledFileSystem.Factory(file_system, branch_memcache)
+
+    needs_render = self._ValueHolder(False)
+    invalidation_cache = factory.Create(lambda _: needs_render.Set(True),
+                                        compiled_fs.CRON_INVALIDATION)
+    for path in [TEMPLATE_PATH, EXAMPLES_PATH, API_PATH]:
+      invalidation_cache.GetFromFile(path + '/')
+
+    if needs_render.Get():
+      file_listing_cache = factory.Create(lambda x: x,
+                                          compiled_fs.CRON_FILE_LISTING)
+      self._Render(file_listing_cache.GetFromFileListing(PUBLIC_TEMPLATE_PATH),
+                   channel)
+    else:
+      # If |needs_render| was True, this page was already rendered, and we don't
+      # need to render again.
+      github_invalidation_cache = GITHUB_COMPILED_FILE_SYSTEM.Create(
+          lambda _: needs_render.Set(True),
+          compiled_fs.CRON_GITHUB_INVALIDATION)
+      if needs_render.Get():
+        self._Render([PUBLIC_TEMPLATE_PATH + '/apps/samples.html'], channel)
+
+    self.response.out.write('Success')
+
+  def _RedirectSpecialCases(self, path):
+    google_dev_url = 'http://developer.google.com/chrome'
+    if path == '/' or path == '/index.html':
+      self.redirect(google_dev_url)
+      return True
+
+    if path == '/apps.html':
+      self.redirect('/apps/about_apps.html')
+      return True
+
+    return False
+
+  def _RedirectBadPaths(self, path, channel_name, default):
+    if '/' in path or path == '404.html':
+      return False
+    apps_templates = APPS_COMPILED_FILE_SYSTEM.GetFromFileListing(
+        PUBLIC_TEMPLATE_PATH + '/apps')
+    extensions_templates = EXTENSIONS_COMPILED_FILE_SYSTEM.GetFromFileListing(
+        PUBLIC_TEMPLATE_PATH + '/extensions')
+    unix_path = UnixName(os.path.splitext(path)[0])
+    if default:
+      apps_path = '/apps/%s' % path
+      extensions_path = '/extensions/%s' % path
+    else:
+      apps_path = '/%s/apps/%s' % (channel_name, path)
+      extensions_path = '/%s/extensions/%s' % (channel_name, path)
+    if unix_path in extensions_templates:
+      self.redirect(extensions_path)
+    elif unix_path in apps_templates:
+      self.redirect(apps_path)
+    else:
+      self.redirect(extensions_path)
+    return True
+
+  def _RedirectFromCodeDotGoogleDotCom(self, path):
+    if (not self.request.url.startswith(('http://code.google.com',
+                                         'https://code.google.com'))):
+      return False
+
+    newUrl = 'http://developer.chrome.com/'
+
+    # switch to https if necessary
+    if (self.request.url.startswith('https')):
+      newUrl = newUrl.replace('http', 'https', 1)
+
+    path = path.split('/')
+    if len(path) > 0 and path[0] == 'chrome':
+      path.pop(0)
+    for channel in BRANCH_UTILITY.GetAllBranchNames():
+      if channel in path:
+        position = path.index(channel)
+        path.pop(position)
+        path.insert(0, channel)
+    newUrl += '/'.join(path)
+    self.redirect(newUrl)
+    return True
+
+  def get(self):
+    path = self.request.path
+    if self._RedirectSpecialCases(path):
+      return
+
+    if path.startswith('/cron'):
+      self._HandleCron(path)
+      return
+
+    # Redirect paths like "directory" to "directory/". This is so relative
+    # file paths will know to treat this as a directory.
+    if os.path.splitext(path)[1] == '' and path[-1] != '/':
+      self.redirect(path + '/')
+      return
+
+    path = path.strip('/')
+    if not self._RedirectFromCodeDotGoogleDotCom(path):
+      self._HandleGet(path)
diff --git a/chrome/common/extensions/docs/server2/in_memory_object_store.py b/chrome/common/extensions/docs/server2/in_memory_object_store.py
new file mode 100644
index 0000000..15a3778
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/in_memory_object_store.py
@@ -0,0 +1,92 @@
+# Copyright (c) 2012 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.
+
+import time
+
+from appengine_wrappers import CACHE_TIMEOUT
+from future import Future
+from object_store import ObjectStore
+from memcache_object_store import MemcacheObjectStore
+
+class _CacheEntry(object):
+  def __init__(self, value, expire_time):
+    self.value = value
+    self._never_expires = (expire_time == 0)
+    self._expiry = time.time() + expire_time
+
+  def HasExpired(self):
+    if self._never_expires:
+      return False
+    return time.time() > self._expiry
+
+class _AsyncGetFuture(object):
+  """A future for memcache gets.
+
+  Properties:
+  - |cache| the in-memory cache used by InMemoryObjectStore
+  - |time| the cache timeout
+  - |namespace| the namespace of the cache items
+  - |future| the |Future| from the backing |ObjectStore|
+  - |initial_mapping| a mapping of cache items already in memory
+  """
+  def __init__(self, cache, time, namespace, future, initial_mapping):
+    self._cache = cache
+    self._time = time
+    self._namespace = namespace
+    self._future = future
+    self._mapping = initial_mapping
+
+  def Get(self):
+    if self._future is not None:
+      result = self._future.Get()
+      self._cache[self._namespace].update(
+          dict((k, _CacheEntry(v, self._time)) for k, v in result.iteritems()))
+      self._mapping.update(result)
+    return self._mapping
+
+class InMemoryObjectStore(ObjectStore):
+  def __init__(self, branch):
+    self._branch = branch
+    self._cache = {}
+    self._object_store = MemcacheObjectStore()
+
+  def _MakeNamespace(self, namespace):
+    return 'ObjectStore.%s.%s' % (self._branch, namespace)
+
+  def SetMulti(self, mapping, namespace, time=CACHE_TIMEOUT):
+    namespace = self._MakeNamespace(namespace)
+    for k, v in mapping.iteritems():
+      if namespace not in self._cache:
+        self._cache[namespace] = {}
+      self._cache[namespace][k] = _CacheEntry(v, time)
+      # TODO(cduvall): Use a batch set? App Engine kept throwing:
+      # ValueError: Values may not be more than 1000000 bytes in length
+      # for the batch set.
+      self._object_store.Set(k, v, namespace, time=time)
+
+  def GetMulti(self, keys, namespace, time=CACHE_TIMEOUT):
+    namespace = self._MakeNamespace(namespace)
+    keys = keys[:]
+    mapping = {}
+    if namespace not in self._cache:
+      self._cache[namespace] = {}
+    for key in keys:
+      cache_entry = self._cache[namespace].get(key, None)
+      if cache_entry is None or cache_entry.HasExpired():
+        mapping[key] = None
+      else:
+        mapping[key] = cache_entry.value
+        keys.remove(key)
+    future = self._object_store.GetMulti(keys, namespace, time=time)
+    return Future(delegate=_AsyncGetFuture(self._cache,
+                                           time,
+                                           namespace,
+                                           future,
+                                           mapping))
+
+  def Delete(self, key, namespace):
+    namespace = self._MakeNamespace(namespace)
+    if namespace in self._cache and key in self._cache[namespace]:
+      self._cache[namespace].pop(key)
+    self._object_store.Delete(key, namespace)
diff --git a/chrome/common/extensions/docs/server2/integration_test.py b/chrome/common/extensions/docs/server2/integration_test.py
new file mode 100755
index 0000000..da02e75
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/integration_test.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+import logging
+import optparse
+import os
+import sys
+from StringIO import StringIO
+import re
+import unittest
+
+# Run build_server so that files needed by tests are copied to the local
+# third_party directory.
+import build_server
+build_server.main()
+
+BASE_PATH = None
+EXPLICIT_TEST_FILES = None
+
+from fake_fetchers import ConfigureFakeFetchers
+ConfigureFakeFetchers(os.path.join(sys.path[0], os.pardir))
+
+# Import Handler later because it immediately makes a request to github. We need
+# the fake urlfetch to be in place first.
+from handler import Handler
+
+class _MockResponse(object):
+  def __init__(self):
+    self.status = 200
+    self.out = StringIO()
+    self.headers = {}
+
+  def set_status(self, status):
+    self.status = status
+
+class _MockRequest(object):
+  def __init__(self, path):
+    self.headers = {}
+    self.path = path
+    self.url = 'http://localhost' + path
+
+class IntegrationTest(unittest.TestCase):
+  def _TestSamplesLocales(self, sample_path, failures):
+    # Use US English, Spanish, and Arabic.
+    for lang in ['en-US', 'es', 'ar']:
+      request = _MockRequest(sample_path)
+      request.headers['Accept-Language'] = lang + ';q=0.8'
+      response = _MockResponse()
+      try:
+        Handler(request, response, local_path=BASE_PATH).get()
+        if 200 != response.status:
+          failures.append(
+              'Samples page with language %s does not have 200 status.'
+              ' Status was %d.' %  (lang, response.status))
+        if not response.out.getvalue():
+          failures.append(
+              'Rendering samples page with language %s produced no output.' %
+                  lang)
+      except Exception as e:
+        failures.append('Error rendering samples page with language %s: %s' %
+            (lang, e))
+
+  def _RunPublicTemplatesTest(self):
+    base_path = os.path.join(BASE_PATH, 'docs', 'templates', 'public')
+    if EXPLICIT_TEST_FILES is None:
+      test_files = []
+      for path, dirs, files in os.walk(base_path):
+        for dir_ in dirs:
+          if dir_.startswith('.'):
+            dirs.remove(dir_)
+        for name in files:
+          if name.startswith('.') or name == '404.html':
+            continue
+          test_files.append(os.path.join(path, name)[len(base_path + os.sep):])
+    else:
+      test_files = EXPLICIT_TEST_FILES
+    test_files = [f.replace(os.sep, '/') for f in test_files]
+    failures = []
+    for filename in test_files:
+      request = _MockRequest(filename)
+      response = _MockResponse()
+      try:
+        Handler(request, response, local_path=BASE_PATH).get()
+        if 200 != response.status:
+          failures.append('%s does not have 200 status. Status was %d.' %
+              (filename, response.status))
+        if not response.out.getvalue():
+          failures.append('Rendering %s produced no output.' % filename)
+        if filename.endswith('samples.html'):
+          self._TestSamplesLocales(filename, failures)
+      except Exception as e:
+        failures.append('Error rendering %s: %s' % (filename, e))
+    if failures:
+      self.fail('\n'.join(failures))
+
+  def testAllPublicTemplates(self):
+    logging.getLogger().setLevel(logging.ERROR)
+    logging_error = logging.error
+    try:
+      logging.error = self.fail
+      self._RunPublicTemplatesTest()
+    finally:
+      logging.error = logging_error
+
+  def testNonexistentFile(self):
+    logging.getLogger().setLevel(logging.CRITICAL)
+    request = _MockRequest('extensions/junk.html')
+    bad_response = _MockResponse()
+    Handler(request, bad_response, local_path=BASE_PATH).get()
+    self.assertEqual(404, bad_response.status)
+    request_404 = _MockRequest('404.html')
+    response_404 = _MockResponse()
+    Handler(request_404, response_404, local_path=BASE_PATH).get()
+    self.assertEqual(200, response_404.status)
+    self.assertEqual(response_404.out.getvalue(), bad_response.out.getvalue())
+
+  def testCron(self):
+    if EXPLICIT_TEST_FILES is not None:
+      return
+    logging_error = logging.error
+    try:
+      logging.error = self.fail
+      request = _MockRequest('/cron/trunk')
+      response = _MockResponse()
+      Handler(request, response, local_path=BASE_PATH).get()
+      self.assertEqual(200, response.status)
+      self.assertEqual('Success', response.out.getvalue())
+    finally:
+      logging.error = logging_error
+
+if __name__ == '__main__':
+  parser = optparse.OptionParser()
+  parser.add_option('-p',
+                    '--path',
+                    default=os.path.join(
+                        os.path.abspath(os.path.dirname(__file__)),
+                        os.pardir,
+                        os.pardir))
+  parser.add_option('-a',
+                    '--all',
+                    action='store_true',
+                    default=False)
+  (opts, args) = parser.parse_args()
+
+  if not opts.all:
+    EXPLICIT_TEST_FILES = args
+  BASE_PATH = opts.path
+  suite = unittest.TestSuite(tests=[
+    IntegrationTest('testNonexistentFile'),
+    IntegrationTest('testCron'),
+    IntegrationTest('testAllPublicTemplates')
+  ])
+  result = unittest.TestResult()
+  suite.run(result)
+  if result.failures:
+    print('*----------------------------------*')
+    print('| integration_test.py has failures |')
+    print('*----------------------------------*')
+    for test, failure in result.failures:
+      print(test)
+      print(failure)
+    exit(1)
+  exit(0)
diff --git a/chrome/common/extensions/docs/server2/intro_data_source.py b/chrome/common/extensions/docs/server2/intro_data_source.py
new file mode 100644
index 0000000..7099251
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/intro_data_source.py
@@ -0,0 +1,109 @@
+# Copyright (c) 2012 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.
+
+from HTMLParser import HTMLParser
+import re
+import logging
+
+from docs_server_utils import FormatKey
+from file_system import FileNotFoundError
+import compiled_file_system as compiled_fs
+from third_party.handlebar import Handlebar
+
+# Increment this version if there are changes to the table of contents dict that
+# IntroDataSource caches.
+_VERSION = 2
+
+_H1_REGEX = re.compile('<h1[^>.]*?>.*?</h1>', flags=re.DOTALL)
+
+class _IntroParser(HTMLParser):
+  """ An HTML parser which will parse table of contents and page title info out
+  of an intro.
+  """
+  def __init__(self):
+    HTMLParser.__init__(self)
+    self.toc = []
+    self.page_title = None
+    self._recent_tag = None
+    self._current_heading = {}
+
+  def handle_starttag(self, tag, attrs):
+    id_ = ''
+    if tag not in ['h1', 'h2', 'h3']:
+      return
+    if tag != 'h1' or self.page_title is None:
+      self._recent_tag = tag
+    for attr in attrs:
+      if attr[0] == 'id':
+        id_ = attr[1]
+    if tag == 'h2':
+      self._current_heading = { 'link': id_, 'subheadings': [], 'title': '' }
+      self.toc.append(self._current_heading)
+    elif tag == 'h3':
+      self._current_heading = { 'link': id_, 'title': '' }
+      self.toc[-1]['subheadings'].append(self._current_heading)
+
+  def handle_endtag(self, tag):
+    if tag in ['h1', 'h2', 'h3']:
+      self._recent_tag = None
+
+  def handle_data(self, data):
+    if self._recent_tag is None:
+      return
+    if self._recent_tag == 'h1':
+      if self.page_title is None:
+        self.page_title = data
+      else:
+        self.page_title += data
+    elif self._recent_tag in ['h2', 'h3']:
+      self._current_heading['title'] += data
+
+class IntroDataSource(object):
+  """This class fetches the intros for a given API. From this intro, a table
+  of contents dictionary is created, which contains the headings in the intro.
+  """
+  class Factory(object):
+    def __init__(self, cache_factory, base_paths):
+      self._cache = cache_factory.Create(self._MakeIntroDict,
+                                         compiled_fs.INTRO,
+                                         version=_VERSION)
+      self._base_paths = base_paths
+
+    def _MakeIntroDict(self, intro):
+      apps_parser = _IntroParser()
+      apps_parser.feed(Handlebar(intro).render({ 'is_apps': True }).text)
+      extensions_parser = _IntroParser()
+      extensions_parser.feed(Handlebar(intro).render({ 'is_apps': False }).text)
+      # TODO(cduvall): Use the normal template rendering system, so we can check
+      # errors.
+      if extensions_parser.page_title != apps_parser.page_title:
+        logging.error(
+            'Title differs for apps and extensions: Apps: %s, Extensions: %s.' %
+                (extensions_parser.page_title, apps_parser.page_title))
+      intro = re.sub(_H1_REGEX, '', intro, count=1)
+      return {
+        'intro': Handlebar(intro),
+        'title': apps_parser.page_title,
+        # TODO(cduvall): Take this out, this is so the old TOCs don't break.
+        'toc': extensions_parser.toc,
+        'apps_toc': apps_parser.toc,
+        'extensions_toc': extensions_parser.toc,
+      }
+
+    def Create(self):
+      return IntroDataSource(self._cache, self._base_paths)
+
+  def __init__(self, cache, base_paths):
+    self._cache = cache
+    self._base_paths = base_paths
+
+  def get(self, key):
+    real_path = FormatKey(key)
+    error = None
+    for base_path in self._base_paths:
+      try:
+        return self._cache.GetFromFile(base_path + '/' + real_path)
+      except FileNotFoundError as error:
+        pass
+    raise ValueError(str(error) + ': No intro found for "%s".' % key)
diff --git a/chrome/common/extensions/docs/server2/known_issues_data_source.py b/chrome/common/extensions/docs/server2/known_issues_data_source.py
new file mode 100644
index 0000000..d527150
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/known_issues_data_source.py
@@ -0,0 +1,49 @@
+# Copyright (c) 2012 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.
+
+import csv
+import logging
+import operator
+from StringIO import StringIO
+
+import object_store
+from url_constants import OPEN_ISSUES_CSV_URL, CLOSED_ISSUES_CSV_URL
+
+class KnownIssuesDataSource:
+  """A data source that fetches Apps issues from code.google.com, and returns a
+  list of open and closed issues.
+  """
+  def __init__(self, object_store, url_fetch):
+    self._object_store = object_store
+    self._url_fetch = url_fetch
+
+  def GetIssues(self, url):
+    cached_data = self._object_store.Get(url, object_store.KNOWN_ISSUES).Get()
+    if cached_data is not None:
+      return cached_data
+
+    issues = []
+    response = self._url_fetch.Fetch(url)
+    if response.status_code != 200:
+      logging.warning('Fetching %s gave status code %s.' %
+          (KNOWN_ISSUES_CSV_URL, response.status_code))
+      # Return None so we can do {{^known_issues.open}} in the template.
+      return None
+    issues_reader = csv.DictReader(StringIO(response.content))
+
+    for issue_dict in issues_reader:
+      id = issue_dict.get('ID', '')
+      title = issue_dict.get('Summary', '')
+      if not id or not title:
+        continue
+      issues.append({ 'id': id, 'title': title })
+    issues = sorted(issues, key=operator.itemgetter('title'))
+    self._object_store.Set(url, issues, object_store.KNOWN_ISSUES)
+    return issues
+
+  def get(self, key):
+    return {
+      'open': self.GetIssues(OPEN_ISSUES_CSV_URL),
+      'closed': self.GetIssues(CLOSED_ISSUES_CSV_URL)
+    }.get(key, None)
diff --git a/chrome/common/extensions/docs/server2/local_file_system.py b/chrome/common/extensions/docs/server2/local_file_system.py
new file mode 100644
index 0000000..7babeca
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/local_file_system.py
@@ -0,0 +1,68 @@
+# Copyright (c) 2012 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.
+
+import os
+
+import file_system
+from future import Future
+
+class LocalFileSystem(file_system.FileSystem):
+  """FileSystem implementation which fetches resources from the local
+  filesystem.
+  """
+  def __init__(self, base_path):
+    self._base_path = self._ConvertToFilepath(base_path)
+
+  def _ConvertToFilepath(self, path):
+    return path.replace('/', os.sep)
+
+  def _ReadFile(self, filename, binary):
+    try:
+      mode = 'rb' if binary else 'r'
+      with open(os.path.join(self._base_path, filename), mode) as f:
+        contents = f.read()
+        if binary:
+          return contents
+        return file_system._ProcessFileData(contents, filename)
+    except IOError:
+      raise file_system.FileNotFoundError(filename)
+
+  def _ListDir(self, dir_name):
+    all_files = []
+    full_path = os.path.join(self._base_path, dir_name)
+    try:
+      files = os.listdir(full_path)
+    except OSError:
+      raise file_system.FileNotFoundError(dir_name)
+    for path in files:
+      if path.startswith('.'):
+        continue
+      if os.path.isdir(os.path.join(full_path, path)):
+        all_files.append(path + '/')
+      else:
+        all_files.append(path)
+    return all_files
+
+  def Read(self, paths, binary=False):
+    result = {}
+    for path in paths:
+      if path.endswith('/'):
+        result[path] = self._ListDir(self._ConvertToFilepath(path))
+      else:
+        result[path] = self._ReadFile(self._ConvertToFilepath(path), binary)
+    return Future(value=result)
+
+  def _CreateStatInfo(self, path):
+    if path.endswith('/'):
+      versions = dict((filename, os.stat(os.path.join(path, filename)).st_mtime)
+                      for filename in os.listdir(path))
+    else:
+      versions = None
+    return file_system.StatInfo(os.stat(path).st_mtime, versions)
+
+  def Stat(self, path):
+    try:
+      return self._CreateStatInfo(os.path.join(self._base_path, path))
+    except OSError:
+      raise file_system.FileNotFoundError(path)
diff --git a/chrome/common/extensions/docs/server2/local_file_system_test.py b/chrome/common/extensions/docs/server2/local_file_system_test.py
new file mode 100755
index 0000000..d9f8b49
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/local_file_system_test.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+import os
+import sys
+import unittest
+
+from local_file_system import LocalFileSystem
+
+class LocalFileSystemTest(unittest.TestCase):
+  def setUp(self):
+    self._file_system = LocalFileSystem(os.path.join(sys.path[0],
+                                                     'test_data',
+                                                     'file_system'))
+
+  def testReadFiles(self):
+    expected = {
+      'test1.txt': 'test1\n',
+      'test2.txt': 'test2\n',
+      'test3.txt': 'test3\n',
+    }
+    self.assertEqual(
+        expected,
+        self._file_system.Read(['test1.txt', 'test2.txt', 'test3.txt']).Get())
+
+  def testListDir(self):
+    expected = ['dir/']
+    for i in range(7):
+      expected.append('file%d.html' % i)
+    self.assertEqual(expected,
+                     sorted(self._file_system.ReadSingle('list/')))
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/chrome/common/extensions/docs/server2/memcache_file_system.py b/chrome/common/extensions/docs/server2/memcache_file_system.py
new file mode 100644
index 0000000..ae281dc
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/memcache_file_system.py
@@ -0,0 +1,106 @@
+# Copyright (c) 2012 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.
+
+from file_system import FileSystem, StatInfo, FileNotFoundError
+from future import Future
+import object_store
+
+class _AsyncUncachedFuture(object):
+  def __init__(self,
+               uncached,
+               current_result,
+               file_system,
+               object_store,
+               namespace):
+    self._uncached = uncached
+    self._current_result = current_result
+    self._file_system = file_system
+    self._object_store = object_store
+    self._namespace = namespace
+
+  def Get(self):
+    mapping = {}
+    new_items = self._uncached.Get()
+    for item in new_items:
+      version = self._file_system.Stat(item).version
+      mapping[item] = (new_items[item], version)
+      self._current_result[item] = new_items[item]
+    self._object_store.SetMulti(mapping, self._namespace, time=0)
+    return self._current_result
+
+class MemcacheFileSystem(FileSystem):
+  """FileSystem implementation which memcaches the results of Read.
+  """
+  def __init__(self, file_system, object_store):
+    self._file_system = file_system
+    self._object_store = object_store
+
+  def Stat(self, path, stats=None):
+    """Stats the directory given, or if a file is given, stats the files parent
+    directory to get info about the file.
+    """
+    # TODO(kalman): store the whole stat info, not just the version.
+    version = self._object_store.Get(path, object_store.FILE_SYSTEM_STAT).Get()
+    if version is not None:
+      return StatInfo(version)
+
+    # Always stat the parent directory, since it will have the stat of the child
+    # anyway, and this gives us an entire directory's stat info at once.
+    if path.endswith('/'):
+      dir_path = path
+    else:
+      dir_path = path.rsplit('/', 1)[0] + '/'
+
+    dir_stat = self._file_system.Stat(dir_path)
+    if path == dir_path:
+      version = dir_stat.version
+    else:
+      version = dir_stat.child_versions.get(path.split('/')[-1], None)
+      if version is None:
+        raise FileNotFoundError(path)
+    mapping = { path: version }
+
+    for child_path, child_version in dir_stat.child_versions.iteritems():
+      child_path = dir_path + child_path
+      mapping[child_path] = child_version
+    self._object_store.SetMulti(mapping, object_store.FILE_SYSTEM_STAT)
+    return StatInfo(version)
+
+  def Read(self, paths, binary=False):
+    """Reads a list of files. If a file is in memcache and it is not out of
+    date, it is returned. Otherwise, the file is retrieved from the file system.
+    """
+    result = {}
+    uncached = []
+    namespace = object_store.FILE_SYSTEM_READ
+    if binary:
+      namespace = '%s.binary' % namespace
+    results = self._object_store.GetMulti(paths, namespace, time=0).Get()
+    result_values = [x[1] for x in sorted(results.iteritems())]
+    stats = self._object_store.GetMulti(paths,
+                                        object_store.FILE_SYSTEM_STAT).Get()
+    stat_values = [x[1] for x in sorted(stats.iteritems())]
+    for path, cached_result, stat in zip(sorted(paths),
+                                         result_values,
+                                         stat_values):
+      if cached_result is None:
+        uncached.append(path)
+        continue
+      data, version = cached_result
+      # TODO(cduvall): Make this use a multi stat.
+      if stat is None:
+        stat = self.Stat(path).version
+      if stat != version:
+        uncached.append(path)
+        continue
+      result[path] = data
+
+    if not uncached:
+      return Future(value=result)
+    return Future(delegate=_AsyncUncachedFuture(
+        self._file_system.Read(uncached, binary=binary),
+        result,
+        self,
+        self._object_store,
+        namespace))
diff --git a/chrome/common/extensions/docs/server2/memcache_file_system_test.py b/chrome/common/extensions/docs/server2/memcache_file_system_test.py
new file mode 100755
index 0000000..85b26e9
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/memcache_file_system_test.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+import os
+import sys
+import unittest
+
+import object_store
+from in_memory_object_store import InMemoryObjectStore
+from file_system import FileSystem, StatInfo
+from future import Future
+from local_file_system import LocalFileSystem
+from memcache_file_system import MemcacheFileSystem
+
+class _FakeFileSystem(FileSystem):
+  def __init__(self):
+    self.stat_value = 0
+    self._stat_count = 0
+    self._read_count = 0
+
+  def CheckAndReset(self, read_count=0, stat_count=0):
+    try:
+      return (self._read_count == read_count and
+              self._stat_count == stat_count)
+    finally:
+      self._read_count = 0
+      self._stat_count = 0
+
+  def Stat(self, path):
+    self._stat_count += 1
+    children = dict((path.strip('/') + str(i), self.stat_value)
+                    for i in range(5))
+    if not path.endswith('/'):
+      children[path.rsplit('/', 1)[-1]] = self.stat_value
+    return StatInfo(self.stat_value, children)
+
+  def Read(self, paths, binary=False):
+    self._read_count += 1
+    return Future(value=dict((path, path) for path in paths))
+
+class MemcacheFileSystemTest(unittest.TestCase):
+  def setUp(self):
+    self._object_store = InMemoryObjectStore('')
+    self._local_fs = LocalFileSystem(os.path.join(sys.path[0],
+                                                  'test_data',
+                                                  'file_system'))
+
+  def _SetReadCacheItem(self, key, value, stat):
+    self._object_store.Set(key, (value, stat), object_store.FILE_SYSTEM_READ)
+
+  def _SetStatCacheItem(self, key, value):
+    self._object_store.Set(key, value, object_store.FILE_SYSTEM_STAT)
+
+  def _DeleteReadCacheItem(self, key):
+    self._object_store.Delete(key, object_store.FILE_SYSTEM_READ)
+
+  def _DeleteStatCacheItem(self, key):
+    self._object_store.Delete(key, object_store.FILE_SYSTEM_STAT)
+
+  def testReadFiles(self):
+    file_system = MemcacheFileSystem(self._local_fs, self._object_store)
+    expected = {
+      './test1.txt': 'test1\n',
+      './test2.txt': 'test2\n',
+      './test3.txt': 'test3\n',
+    }
+    self.assertEqual(
+        expected,
+        file_system.Read(['./test1.txt', './test2.txt', './test3.txt']).Get())
+
+  def testListDir(self):
+    file_system = MemcacheFileSystem(self._local_fs, self._object_store)
+    expected = ['dir/']
+    for i in range(7):
+      expected.append('file%d.html' % i)
+    self._SetReadCacheItem('list/', expected, file_system.Stat('list/').version)
+    self.assertEqual(expected,
+                     sorted(file_system.ReadSingle('list/')))
+    expected.remove('file0.html')
+    self._SetReadCacheItem('list/', expected, file_system.Stat('list/').version)
+    self.assertEqual(expected,
+                     sorted(file_system.ReadSingle('list/')))
+
+  def testCaching(self):
+    fake_fs = _FakeFileSystem()
+    file_system = MemcacheFileSystem(fake_fs, self._object_store)
+    self.assertEqual('bob/bob0', file_system.ReadSingle('bob/bob0'))
+    self.assertTrue(fake_fs.CheckAndReset(read_count=1, stat_count=1))
+
+    # Resource has been cached, so test resource is not re-fetched.
+    self.assertEqual('bob/bob0', file_system.ReadSingle('bob/bob0'))
+    self.assertTrue(fake_fs.CheckAndReset())
+
+    # Test if the Stat version is the same the resource is not re-fetched.
+    self._DeleteStatCacheItem('bob/bob0')
+    self.assertEqual('bob/bob0', file_system.ReadSingle('bob/bob0'))
+    self.assertTrue(fake_fs.CheckAndReset(stat_count=1))
+
+    # Test if there is a newer version, the resource is re-fetched.
+    self._DeleteStatCacheItem('bob/bob0')
+    fake_fs.stat_value += 1
+    self.assertEqual('bob/bob0', file_system.ReadSingle('bob/bob0'))
+    self.assertTrue(fake_fs.CheckAndReset(read_count=1, stat_count=1))
+
+    # Test directory and subdirectory stats are cached.
+    self._DeleteStatCacheItem('bob/bob0')
+    self._DeleteReadCacheItem('bob/bob0')
+    self._DeleteStatCacheItem('bob/bob1')
+    self.assertEqual('bob/bob1', file_system.ReadSingle('bob/bob1'))
+    self.assertEqual('bob/bob0', file_system.ReadSingle('bob/bob0'))
+    self.assertTrue(fake_fs.CheckAndReset(read_count=2, stat_count=1))
+    self.assertEqual('bob/bob1', file_system.ReadSingle('bob/bob1'))
+    self.assertTrue(fake_fs.CheckAndReset())
+
+    # Test a more recent parent directory doesn't force a refetch of children.
+    self._DeleteReadCacheItem('bob/bob0')
+    self._DeleteReadCacheItem('bob/bob1')
+    self.assertEqual('bob/bob1', file_system.ReadSingle('bob/bob1'))
+    self.assertEqual('bob/bob2', file_system.ReadSingle('bob/bob2'))
+    self.assertEqual('bob/bob3', file_system.ReadSingle('bob/bob3'))
+    self.assertTrue(fake_fs.CheckAndReset(read_count=3))
+    self._SetStatCacheItem('bob/', 10)
+    self.assertEqual('bob/bob1', file_system.ReadSingle('bob/bob1'))
+    self.assertEqual('bob/bob2', file_system.ReadSingle('bob/bob2'))
+    self.assertEqual('bob/bob3', file_system.ReadSingle('bob/bob3'))
+    self.assertTrue(fake_fs.CheckAndReset())
+
+    self._DeleteStatCacheItem('bob/bob0')
+    self.assertEqual('bob/bob0', file_system.ReadSingle('bob/bob0'))
+    self.assertTrue(fake_fs.CheckAndReset(read_count=1, stat_count=1))
+    self.assertEqual('bob/bob0', file_system.ReadSingle('bob/bob0'))
+    self.assertTrue(fake_fs.CheckAndReset())
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/chrome/common/extensions/docs/server2/memcache_object_store.py b/chrome/common/extensions/docs/server2/memcache_object_store.py
new file mode 100644
index 0000000..71751b8
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/memcache_object_store.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2012 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.
+
+from appengine_wrappers import memcache
+from object_store import ObjectStore, CACHE_TIMEOUT
+
+class _AsyncMemcacheGetFuture(object):
+  def __init__(self, rpc):
+    self._rpc = rpc
+
+  def Get(self):
+    return self._rpc.get_result()
+
+class MemcacheObjectStore(ObjectStore):
+  def SetMulti(self, mapping, namespace, time=CACHE_TIMEOUT):
+    memcache.Client().set_multi_async(mapping, namespace=namespace, time=time)
+
+  def GetMulti(self, keys, namespace, time=CACHE_TIMEOUT):
+    rpc = memcache.Client().get_multi_async(keys, namespace=namespace)
+    return _AsyncMemcacheGetFuture(rpc)
+
+  def Delete(self, key, namespace):
+    memcache.delete(key, namespace=namespace)
diff --git a/chrome/common/extensions/docs/server2/object_store.py b/chrome/common/extensions/docs/server2/object_store.py
new file mode 100644
index 0000000..391cadb
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/object_store.py
@@ -0,0 +1,55 @@
+# Copyright (c) 2012 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.
+
+from appengine_wrappers import CACHE_TIMEOUT
+from future import Future
+
+BRANCH_UTILITY            = 'BranchUtility'
+FILE_SYSTEM_CACHE         = 'CompiledFileSystem'
+FILE_SYSTEM_CACHE_LISTING = 'CompiledFileSystemListing'
+FILE_SYSTEM_READ          = 'Read'
+FILE_SYSTEM_STAT          = 'Stat'
+GITHUB_STAT               = 'GithubStat'
+KNOWN_ISSUES              = 'KnownIssues'
+
+class _SingleGetFuture(object):
+  def __init__(self, multi_get, key):
+    self._future = multi_get
+    self._key = key
+
+  def Get(self):
+    return self._future.Get()[self._key]
+
+class ObjectStore(object):
+  """A class for caching picklable objects.
+  """
+  def Set(self, key, value, namespace, time=CACHE_TIMEOUT):
+    """Sets key -> value in the object store, with the specified timeout.
+    """
+    self.SetMulti({ key: value }, namespace, time=time)
+
+  def SetMulti(self, mapping, namespace, time=CACHE_TIMEOUT):
+    """Sets the mapping of keys to values in the object store with the specified
+    timeout.
+    """
+    raise NotImplementedError()
+
+  def Get(self, key, namespace, time=CACHE_TIMEOUT):
+    """Gets a |Future| with the value of |key| in the object store, or None
+    if |key| is not in the object store.
+    """
+    return Future(delegate=_SingleGetFuture(
+        self.GetMulti([key], namespace, time=time),
+        key))
+
+  def GetMulti(self, keys, namespace, time=CACHE_TIMEOUT):
+    """Gets a |Future| with values mapped to |keys| from the object store, with
+    any keys not in the object store mapped to None.
+    """
+    raise NotImplementedError()
+
+  def Delete(self, key, namespace):
+    """Deletes a key from the object store.
+    """
+    raise NotImplementedError()
diff --git a/chrome/common/extensions/docs/server2/preview.py b/chrome/common/extensions/docs/server2/preview.py
new file mode 100755
index 0000000..ff6585c
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/preview.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+# This helps you preview the apps and extensions docs.
+#
+#    ./preview.py --help
+#
+# There are two modes: server- and render- mode. The default is server, in which
+# a webserver is started on a port (default 8000). Navigating to paths on
+# http://localhost:8000, for example
+#
+#     http://localhost:8000/extensions/tabs.html
+#
+# will render the documentation for the extension tabs API.
+#
+# On the other hand, render mode statically renders docs to stdout. Use this
+# to save the output (more convenient than needing to save the page in a
+# browser), handy when uploading the docs somewhere (e.g. for a review),
+# and for profiling the server. For example,
+#
+#    ./preview.py -r extensions/tabs.html
+#
+# will output the documentation for the tabs API on stdout and exit immediately.
+#
+# Note: absolute paths into static content (e.g. /static/css/site.css) will be
+# relative paths (e.g. static/css/site.css) for convenient sandboxing.
+
+from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+import optparse
+import os
+import shutil
+from StringIO import StringIO
+import sys
+import urlparse
+
+import build_server
+# Copy all the files necessary to run the server. These are cleaned up when the
+# server quits.
+build_server.main()
+
+from fake_fetchers import ConfigureFakeFetchers
+
+class _Response(object):
+  def __init__(self):
+    self.status = 200
+    self.out = StringIO()
+    self.headers = {}
+
+  def set_status(self, status):
+    self.status = status
+
+class _Request(object):
+  def __init__(self, path):
+    self.headers = {}
+    self.path = path
+    self.url = 'http://localhost' + path
+
+def _Render(path, local_path):
+  response = _Response()
+  Handler(_Request(urlparse.urlparse(path).path),
+          response,
+          local_path=local_path).get()
+  content = response.out.getvalue()
+  if isinstance(content, unicode):
+    content = content.encode('utf-8', 'replace')
+  return (content, response.status)
+
+def _GetLocalPath():
+  if os.sep in sys.argv[0]:
+    return os.path.join(sys.argv[0].rsplit(os.sep, 1)[0], os.pardir, os.pardir)
+  return os.path.join(os.pardir, os.pardir)
+
+class RequestHandler(BaseHTTPRequestHandler):
+  """A HTTPRequestHandler that outputs the docs page generated by Handler.
+  """
+  def do_GET(self):
+    (content, status) = _Render(self.path, RequestHandler.local_path)
+    self.send_response(status)
+    self.end_headers()
+    self.wfile.write(content)
+
+if __name__ == '__main__':
+  parser = optparse.OptionParser(
+      description='Runs a server to preview the extension documentation.',
+      usage='usage: %prog [option]...')
+  parser.add_option('-p', '--port', default='8000',
+      help='port to run the server on')
+  parser.add_option('-d', '--directory', default=_GetLocalPath(),
+      help='extensions directory to serve from - '
+           'should be chrome/common/extensions within a Chromium checkout')
+  parser.add_option('-r', '--render', default='',
+      help='statically render a page and print to stdout rather than starting '
+           'the server, e.g. apps/storage.html. The path may optionally end '
+           'with #n where n is the number of times to render the page before '
+           'printing it, e.g. apps/storage.html#50, to use for profiling.')
+
+  (opts, argv) = parser.parse_args()
+
+  if (not os.path.isdir(opts.directory) or
+      not os.path.isdir(os.path.join(opts.directory, 'docs')) or
+      not os.path.isdir(os.path.join(opts.directory, 'api'))):
+    print('Specified directory does not exist or does not contain extension '
+          'docs.')
+    exit()
+
+  ConfigureFakeFetchers(os.path.join(opts.directory, 'docs'))
+  from handler import Handler
+
+  if opts.render:
+    if opts.render.find('#') >= 0:
+      (path, iterations) = opts.render.rsplit('#', 1)
+      extra_iterations = int(iterations) - 1
+    else:
+      path = opts.render
+      extra_iterations = 0
+
+    (content, status) = _Render(path, opts.directory)
+    if status != 200:
+      print('Error status: %s' % status)
+      exit(1)
+
+    for _ in range(extra_iterations):
+      _Render(path, opts.directory)
+
+    # Static paths will show up as /stable/static/foo but this only makes sense
+    # from a webserver.
+    print(content.replace('/stable/static', 'static'))
+    exit()
+
+  print('Starting previewserver on port %s' % opts.port)
+  print('Reading from %s' % opts.directory)
+  print('')
+  print('The extension documentation can be found at:')
+  print('')
+  print('  http://localhost:%s/extensions/' % opts.port)
+  print('')
+  print('The apps documentation can be found at:')
+  print('')
+  print('  http://localhost:%s/apps/' % opts.port)
+  print('')
+
+  RequestHandler.local_path = opts.directory
+  server = HTTPServer(('', int(opts.port)), RequestHandler)
+  try:
+    server.serve_forever()
+  finally:
+    server.socket.close()
diff --git a/chrome/common/extensions/docs/server2/reference_resolver.py b/chrome/common/extensions/docs/server2/reference_resolver.py
new file mode 100644
index 0000000..f273266
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/reference_resolver.py
@@ -0,0 +1,76 @@
+# Copyright (c) 2012 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.
+
+from file_system import FileNotFoundError
+
+def _ClassifySchemaNode(node_name, api):
+  if '.' in node_name:
+    node_name, rest = node_name.split('.', 1)
+  else:
+    rest = None
+  for key, group in [('types', 'type'),
+                     ('functions', 'method'),
+                     ('events', 'event'),
+                     ('properties', 'property')]:
+    for item in api.get(key, []):
+      if item['name'] == node_name:
+        if rest is not None:
+          ret = _ClassifySchemaNode(rest, item)
+          if ret is not None:
+            return ret
+        else:
+          return group
+  return None
+
+class ReferenceResolver(object):
+  """Resolves references to $ref's by searching through the APIs to find the
+  correct node.
+  """
+  class Factory(object):
+    def __init__(self, api_data_source, api_list_data_source):
+      self._api_data_source = api_data_source
+      self._api_list_data_source = api_list_data_source
+
+    def Create(self):
+      return ReferenceResolver(self._api_data_source,
+                               self._api_list_data_source)
+
+  def __init__(self, api_data_source, api_list_data_source):
+    self._api_data_source = api_data_source
+    self._api_list_data_source = api_list_data_source
+
+  def GetLink(self, namespace_name, ref):
+    # Try to resolve the ref in the current namespace first.
+    try:
+      api = self._api_data_source.get(namespace_name)
+      category = _ClassifySchemaNode(ref, api)
+      if category is not None:
+        return {
+          'href': '#%s-%s' % (category, ref.replace('.', '-')),
+          'text': ref
+        }
+    except FileNotFoundError:
+      pass
+    parts = ref.split('.')
+    api_list = self._api_list_data_source.GetAllNames()
+    for i, part in enumerate(parts):
+      if '.'.join(parts[:i]) not in api_list:
+        continue
+      try:
+        api_name = '.'.join(parts[:i])
+        api = self._api_data_source.get(api_name)
+      except FileNotFoundError:
+        continue
+      name = '.'.join(parts[i:])
+      category = _ClassifySchemaNode(name, api)
+      if category is None:
+        continue
+      text = ref
+      if text.startswith('%s.' % namespace_name):
+        text = text[len('%s.' % namespace_name):]
+      return {
+        'href': '%s.html#%s-%s' % (api_name, category, name.replace('.', '-')),
+        'text': text
+      }
+    return None
diff --git a/chrome/common/extensions/docs/server2/reference_resolver_test.py b/chrome/common/extensions/docs/server2/reference_resolver_test.py
new file mode 100755
index 0000000..efa8947
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/reference_resolver_test.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+import json
+import os
+import sys
+import unittest
+
+from reference_resolver import ReferenceResolver
+from file_system import FileNotFoundError
+
+class FakeAPIDataSource(object):
+  def __init__(self, json_data):
+    self._json = json_data
+
+  def get(self, key):
+    if key not in self._json:
+      raise FileNotFoundError(key)
+    return self._json[key]
+
+  def GetAllNames(self):
+    return self._json.keys()
+
+class APIDataSourceTest(unittest.TestCase):
+  def setUp(self):
+    self._base_path = os.path.join(sys.path[0], 'test_data', 'test_json')
+
+  def _ReadLocalFile(self, filename):
+    with open(os.path.join(self._base_path, filename), 'r') as f:
+      return f.read()
+
+  def testGetLink(self):
+    data_source = FakeAPIDataSource(
+        json.loads(self._ReadLocalFile('fake_data_source.json')))
+    resolver = ReferenceResolver(data_source, data_source)
+    self.assertEqual({
+      'href': 'foo.html#type-foo_t1',
+      'text': 'foo.foo_t1'
+    }, resolver.GetLink('baz', 'foo.foo_t1'))
+    self.assertEqual({
+      'href': 'baz.html#event-baz_e1',
+      'text': 'baz_e1'
+    }, resolver.GetLink('baz', 'baz.baz_e1'))
+    self.assertEqual({
+      'href': '#event-baz_e1',
+      'text': 'baz_e1'
+    }, resolver.GetLink('baz', 'baz_e1'))
+    self.assertEqual({
+      'href': 'foo.html#method-foo_f1',
+      'text': 'foo.foo_f1'
+    }, resolver.GetLink('baz', 'foo.foo_f1'))
+    self.assertEqual({
+      'href': 'foo.html#property-foo_p3',
+      'text': 'foo.foo_p3'
+    }, resolver.GetLink('baz', 'foo.foo_p3'))
+    self.assertEqual({
+      'href': 'bar.bon.html#type-bar_bon_t3',
+      'text': 'bar.bon.bar_bon_t3'
+    }, resolver.GetLink('baz', 'bar.bon.bar_bon_t3'))
+    self.assertEqual({
+      'href': '#property-bar_bon_p3',
+      'text': 'bar_bon_p3'
+    }, resolver.GetLink('bar.bon', 'bar_bon_p3'))
+    self.assertEqual({
+      'href': 'bar.bon.html#property-bar_bon_p3',
+      'text': 'bar_bon_p3'
+    }, resolver.GetLink('bar.bon', 'bar.bon.bar_bon_p3'))
+    self.assertEqual({
+      'href': 'bar.html#event-bar_e2',
+      'text': 'bar_e2'
+    }, resolver.GetLink('bar', 'bar.bar_e2'))
+    self.assertEqual({
+      'href': 'bar.html#type-bon',
+      'text': 'bon'
+    }, resolver.GetLink('bar', 'bar.bon'))
+    self.assertEqual({
+      'href': '#event-foo_t3-foo_t3_e1',
+      'text': 'foo_t3.foo_t3_e1'
+    }, resolver.GetLink('foo', 'foo_t3.foo_t3_e1'))
+    self.assertEqual({
+      'href': 'foo.html#event-foo_t3-foo_t3_e1',
+      'text': 'foo_t3.foo_t3_e1'
+    }, resolver.GetLink('foo', 'foo.foo_t3.foo_t3_e1'))
+    self.assertEqual(
+        None,
+        resolver.GetLink('bar', 'bar.bon.bar_e3'))
+    self.assertEqual(
+        None,
+        resolver.GetLink('baz.bon', 'bar_p3'))
+    self.assertEqual(
+        None,
+        resolver.GetLink('a', 'falafel.faf'))
+    self.assertEqual(
+        None,
+        resolver.GetLink('foo', 'bar_p3'))
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/chrome/common/extensions/docs/server2/samples_data_source.py b/chrome/common/extensions/docs/server2/samples_data_source.py
new file mode 100644
index 0000000..900c10a
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/samples_data_source.py
@@ -0,0 +1,235 @@
+# Copyright (c) 2012 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.
+
+import hashlib
+import json
+import logging
+import re
+
+import compiled_file_system as compiled_fs
+from file_system import FileNotFoundError
+import third_party.json_schema_compiler.json_comment_eater as json_comment_eater
+import third_party.json_schema_compiler.model as model
+import url_constants
+
+DEFAULT_ICON_PATH = '/images/sample-default-icon.png'
+
+class SamplesDataSource(object):
+  """Constructs a list of samples and their respective files and api calls.
+  """
+
+  class Factory(object):
+    """A factory to create SamplesDataSource instances bound to individual
+    Requests.
+    """
+    def __init__(self,
+                 channel,
+                 file_system,
+                 github_file_system,
+                 cache_factory,
+                 github_cache_factory,
+                 ref_resolver_factory,
+                 samples_path):
+      self._file_system = file_system
+      self._github_file_system = github_file_system
+      self._static_path = ((('/' + channel) if channel != 'local' else '') +
+                           '/static')
+      self._extensions_cache = cache_factory.Create(self._MakeSamplesList,
+                                                    compiled_fs.EXTENSIONS)
+      self._apps_cache = github_cache_factory.Create(
+          lambda x: self._MakeSamplesList(x, is_apps=True),
+          compiled_fs.APPS)
+      self._ref_resolver = ref_resolver_factory.Create()
+      self._samples_path = samples_path
+
+    def Create(self, request):
+      """Returns a new SamplesDataSource bound to |request|.
+      """
+      return SamplesDataSource(self._extensions_cache,
+                               self._apps_cache,
+                               self._samples_path,
+                               request)
+
+    def _GetAPIItems(self, js_file):
+      return set(re.findall('(chrome\.[a-zA-Z0-9\.]+)', js_file))
+
+    def _GetDataFromManifest(self, path, file_system):
+      manifest = file_system.ReadSingle(path + '/manifest.json')
+      try:
+        manifest_json = json.loads(json_comment_eater.Nom(manifest))
+      except ValueError as e:
+        logging.error('Error parsing manifest.json for %s: %s' % (path, e))
+        return None
+      l10n_data = {
+        'name': manifest_json.get('name', ''),
+        'description': manifest_json.get('description', ''),
+        'icon': manifest_json.get('icons', {}).get('128', None),
+        'default_locale': manifest_json.get('default_locale', None),
+        'locales': {}
+      }
+      if not l10n_data['default_locale']:
+        return l10n_data
+      locales_path = path + '/_locales/'
+      locales_dir = file_system.ReadSingle(locales_path)
+      if locales_dir:
+        locales_files = file_system.Read(
+            [locales_path + f + 'messages.json' for f in locales_dir]).Get()
+        try:
+          locales_json = [(locale_path, json.loads(contents))
+                          for locale_path, contents in
+                          locales_files.iteritems()]
+        except ValueError as e:
+          logging.error('Error parsing locales files for %s: %s' % (path, e))
+        else:
+          for path, json_ in locales_json:
+            l10n_data['locales'][path[len(locales_path):].split('/')[0]] = json_
+      return l10n_data
+
+    def _MakeSamplesList(self, files, is_apps=False):
+      file_system = self._github_file_system if is_apps else self._file_system
+      samples_list = []
+      for filename in sorted(files):
+        if filename.rsplit('/')[-1] != 'manifest.json':
+          continue
+        # This is a little hacky, but it makes a sample page.
+        sample_path = filename.rsplit('/', 1)[-2]
+        sample_files = [path for path in files
+                        if path.startswith(sample_path + '/')]
+        js_files = [path for path in sample_files if path.endswith('.js')]
+        try:
+          js_contents = file_system.Read(js_files).Get()
+        except FileNotFoundError as e:
+          logging.warning('Error fetching samples files: %s. Was a file '
+                          'deleted from a sample? This warning should go away '
+                          'in 5 minutes.' % e)
+          continue
+        api_items = set()
+        for js in js_contents.values():
+          api_items.update(self._GetAPIItems(js))
+
+        api_calls = []
+        for item in api_items:
+          if len(item.split('.')) < 3:
+            continue
+          if item.endswith('.removeListener') or item.endswith('.hasListener'):
+            continue
+          if item.endswith('.addListener'):
+            item = item[:-len('.addListener')]
+          if item.startswith('chrome.'):
+            item = item[len('chrome.'):]
+          ref_data = self._ref_resolver.GetLink('samples', item)
+          if ref_data is None:
+            continue
+          api_calls.append({
+            'name': ref_data['text'],
+            'link': ref_data['href']
+          })
+        try:
+          manifest_data = self._GetDataFromManifest(sample_path, file_system)
+        except FileNotFoundError as e:
+          logging.warning('Error getting data from samples manifest: %s. If '
+                          'this file was deleted from a sample this message '
+                          'should go away in 5 minutes.' % e)
+          continue
+        if manifest_data is None:
+          continue
+
+        sample_base_path = sample_path.split('/', 1)[1]
+        if is_apps:
+          url = url_constants.GITHUB_BASE + '/' + sample_base_path
+          icon_base = url_constants.RAW_GITHUB_BASE + '/' + sample_base_path
+          download_url = url
+        else:
+          url = sample_base_path
+          icon_base = sample_base_path
+          download_url = sample_base_path + '.zip'
+
+        if manifest_data['icon'] is None:
+          icon_path = self._static_path + DEFAULT_ICON_PATH
+        else:
+          icon_path = icon_base + '/' + manifest_data['icon']
+        manifest_data.update({
+          'icon': icon_path,
+          'id': hashlib.md5(url).hexdigest(),
+          'download_url': download_url,
+          'url': url,
+          'files': [f.replace(sample_path + '/', '') for f in sample_files],
+          'api_calls': api_calls
+        })
+        samples_list.append(manifest_data)
+
+      return samples_list
+
+  def __init__(self, extensions_cache, apps_cache, samples_path, request):
+    self._extensions_cache = extensions_cache
+    self._apps_cache = apps_cache
+    self._samples_path = samples_path
+    self._request = request
+
+  def _GetAcceptedLanguages(self):
+    accept_language = self._request.headers.get('Accept-Language', None)
+    if accept_language is None:
+      return []
+    return [lang_with_q.split(';')[0].strip()
+            for lang_with_q in accept_language.split(',')]
+
+  def FilterSamples(self, key, api_name):
+    """Fetches and filters the list of samples specified by |key|, returning
+    only the samples that use the API |api_name|. |key| is either 'apps' or
+    'extensions'.
+    """
+    api_search = api_name + '_'
+    samples_list = []
+    try:
+      for sample in self.get(key):
+        api_calls_unix = [model.UnixName(call['name'])
+                          for call in sample['api_calls']]
+        for call in api_calls_unix:
+          if api_search in call:
+            samples_list.append(sample)
+            break
+    except NotImplementedError:
+      # If we're testing, the GithubFileSystem can't fetch samples.
+      # Bug: http://crbug.com/141910
+      return []
+    return samples_list
+
+  def _CreateSamplesDict(self, key):
+    if key == 'apps':
+      samples_list = self._apps_cache.GetFromFileListing('/')
+    else:
+      samples_list = self._extensions_cache.GetFromFileListing(
+          self._samples_path + '/')
+    return_list = []
+    for dict_ in samples_list:
+      name = dict_['name']
+      description = dict_['description']
+      if name.startswith('__MSG_') or description.startswith('__MSG_'):
+        try:
+          # Copy the sample dict so we don't change the dict in the cache.
+          sample_data = dict_.copy()
+          name_key = name[len('__MSG_'):-len('__')]
+          description_key = description[len('__MSG_'):-len('__')]
+          locale = sample_data['default_locale']
+          for lang in self._GetAcceptedLanguages():
+            if lang in sample_data['locales']:
+              locale = lang
+              break
+          locale_data = sample_data['locales'][locale]
+          sample_data['name'] = locale_data[name_key]['message']
+          sample_data['description'] = locale_data[description_key]['message']
+        except Exception as e:
+          logging.error(e)
+          # Revert the sample to the original dict.
+          sample_data = dict_
+        return_list.append(sample_data)
+      else:
+        return_list.append(dict_)
+    return return_list
+
+  def get(self, key):
+    return {
+      'apps': lambda: self._CreateSamplesDict('apps'),
+      'extensions': lambda: self._CreateSamplesDict('extensions')
+    }.get(key, lambda: {})()
diff --git a/chrome/common/extensions/docs/server2/samples_data_source_test.py b/chrome/common/extensions/docs/server2/samples_data_source_test.py
new file mode 100755
index 0000000..37ee0c7
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/samples_data_source_test.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+import json
+import os
+import sys
+import unittest
+
+from samples_data_source import SamplesDataSource
+
+class _FakeRequest(object):
+  pass
+
+class SamplesDataSourceTest(unittest.TestCase):
+  def setUp(self):
+    self._base_path = os.path.join(sys.path[0],
+                                   'test_data',
+                                   'samples_data_source')
+
+  def _ReadLocalFile(self, filename):
+    with open(os.path.join(self._base_path, filename), 'r') as f:
+      return f.read()
+
+  def _FakeGet(self, key):
+    return json.loads(self._ReadLocalFile(key))
+
+  def testFilterSamples(self):
+    sds = SamplesDataSource({}, {}, 'fake_path', _FakeRequest())
+    sds.get = self._FakeGet
+    self.assertEquals(json.loads(self._ReadLocalFile('expected.json')),
+                      sds.FilterSamples('samples.json', 'bobaloo'))
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/chrome/common/extensions/docs/server2/server_instance.py b/chrome/common/extensions/docs/server2/server_instance.py
new file mode 100644
index 0000000..b6f6168
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/server_instance.py
@@ -0,0 +1,69 @@
+# Copyright (c) 2012 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.
+
+from fnmatch import fnmatch
+import mimetypes
+import os
+
+from file_system import FileNotFoundError
+import compiled_file_system as compiled_fs
+
+STATIC_DIR_PREFIX = 'docs'
+DOCS_PATH = 'docs'
+
+class ServerInstance(object):
+  """This class is used to hold a data source and fetcher for an instance of a
+  server. Each new branch will get its own ServerInstance.
+  """
+  def __init__(self,
+               template_data_source_factory,
+               example_zipper,
+               cache_factory):
+    self._template_data_source_factory = template_data_source_factory
+    self._example_zipper = example_zipper
+    self._cache = cache_factory.Create(lambda x: x, compiled_fs.STATIC)
+    mimetypes.init()
+
+  def _FetchStaticResource(self, path, response):
+    """Fetch a resource in the 'static' directory.
+    """
+    try:
+      result = self._cache.GetFromFile(STATIC_DIR_PREFIX + '/' + path)
+    except FileNotFoundError:
+      return None
+    base, ext = os.path.splitext(path)
+    response.headers['content-type'] = mimetypes.types_map[ext]
+    return result
+
+  def Get(self, path, request, response):
+    # TODO(cduvall): bundle up all the request-scoped data into a single object.
+    templates = self._template_data_source_factory.Create(request, path)
+
+    content = None
+    if fnmatch(path, 'extensions/examples/*.zip'):
+      try:
+        content = self._example_zipper.Create(
+            path[len('extensions/'):-len('.zip')])
+        response.headers['content-type'] = mimetypes.types_map['.zip']
+      except FileNotFoundError:
+        content = None
+    elif path.startswith('extensions/examples/'):
+      try:
+        content = self._cache.GetFromFile(
+            '%s/%s' % (DOCS_PATH, path[len('extensions/'):]))
+        response.headers['content-type'] = 'text/plain'
+      except FileNotFoundError:
+        content = None
+    elif path.startswith('static/'):
+      content = self._FetchStaticResource(path, response)
+    elif path.endswith('.html'):
+      content = templates.Render(path)
+
+    response.headers['x-frame-options'] = 'sameorigin'
+    if content:
+      response.headers['cache-control'] = 'max-age=300'
+      response.out.write(content)
+    else:
+      response.set_status(404);
+      response.out.write(templates.Render('404'))
diff --git a/chrome/common/extensions/docs/server2/sidenav_data_source.py b/chrome/common/extensions/docs/server2/sidenav_data_source.py
new file mode 100644
index 0000000..a1c658d
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/sidenav_data_source.py
@@ -0,0 +1,66 @@
+# Copyright (c) 2012 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.
+
+import copy
+import json
+import logging
+
+import compiled_file_system as compiled_fs
+from file_system import FileNotFoundError
+from third_party.json_schema_compiler.model import UnixName
+
+class SidenavDataSource(object):
+  """This class reads in and caches a JSON file representing the side navigation
+  menu.
+  """
+  class Factory(object):
+    def __init__(self, cache_factory, json_path):
+      self._cache = cache_factory.Create(self._CreateSidenavDict,
+                                         compiled_fs.SIDENAV)
+      self._json_path = json_path
+
+    def Create(self, path):
+      """Create a SidenavDataSource, binding it to |path|. |path| is the url
+      of the page that is being rendered. It is used to determine which item
+      in the sidenav should be highlighted.
+      """
+      return SidenavDataSource(self._cache, self._json_path, path)
+
+    def _AddLevels(self, items, level):
+      """Levels represent how deeply this item is nested in the sidenav. We
+      start at 2 because the top <ul> is the only level 1 element.
+      """
+      for item in items:
+        item['level'] = level
+        if 'items' in item:
+          self._AddLevels(item['items'], level + 1)
+
+    def _CreateSidenavDict(self, json_str):
+      items = json.loads(json_str)
+      self._AddLevels(items, 2);
+      return items
+
+  def __init__(self, cache, json_path, path):
+    self._cache = cache
+    self._json_path = json_path
+    self._file_name = path.split('/')[-1]
+
+  def _AddSelected(self, items):
+    for item in items:
+      if item.get('fileName', '') == self._file_name:
+        item['selected'] = True
+        return True
+      if 'items' in item:
+        if self._AddSelected(item['items']):
+          item['child_selected'] = True
+    return False
+
+  def get(self, key):
+    try:
+      sidenav = copy.deepcopy(self._cache.GetFromFile(
+          '%s/%s_sidenav.json' % (self._json_path, key)))
+      self._AddSelected(sidenav)
+      return sidenav
+    except FileNotFoundError as e:
+      logging.error('%s: Error reading sidenav "%s".' % (e, key))
diff --git a/chrome/common/extensions/docs/server2/sidenav_data_source_test.py b/chrome/common/extensions/docs/server2/sidenav_data_source_test.py
new file mode 100755
index 0000000..7f13641
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/sidenav_data_source_test.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+import os
+import sys
+import unittest
+
+from compiled_file_system import CompiledFileSystem
+from in_memory_object_store import InMemoryObjectStore
+from local_file_system import LocalFileSystem
+from sidenav_data_source import SidenavDataSource
+
+class SamplesDataSourceTest(unittest.TestCase):
+  def setUp(self):
+    self._base_path = os.path.join(sys.path[0],
+                                   'test_data',
+                                   'sidenav_data_source')
+    self._cache_factory = CompiledFileSystem.Factory(
+        LocalFileSystem(self._base_path),
+        InMemoryObjectStore('fake_branch'))
+
+  def _CheckLevels(self, items, level=2):
+    for item in items:
+      self.assertEqual(level, item['level'])
+      if 'items' in item:
+        self._CheckLevels(item['items'], level=level + 1)
+
+  def testLevels(self):
+    sidenav_data_source = SidenavDataSource.Factory(self._cache_factory,
+                                                    self._base_path).Create('')
+    sidenav_json = sidenav_data_source.get('test')
+    self._CheckLevels(sidenav_json)
+
+  def testSelected(self):
+    sidenav_data_source = SidenavDataSource.Factory(
+        self._cache_factory,
+        self._base_path).Create('www.b.com')
+    sidenav_json = sidenav_data_source.get('test')
+    # This will be prettier once JSON is loaded with an OrderedDict.
+    for item in sidenav_json:
+      if item['title'] == 'Jim':
+        self.assertTrue(item.get('child_selected', False))
+        for next_item in item['items']:
+          if next_item['title'] == 'B':
+            self.assertTrue(next_item.get('selected', False))
+            return
+    # If we didn't return already, we should fail.
+    self.fail()
+
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/chrome/common/extensions/docs/server2/start_dev_server.py b/chrome/common/extensions/docs/server2/start_dev_server.py
new file mode 100755
index 0000000..687413d
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/start_dev_server.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+import os
+import signal
+import shutil
+import subprocess
+import sys
+
+import build_server
+
+SERVER_PATH = sys.path[0]
+SRC_PATH = os.path.join(SERVER_PATH, os.pardir, os.pardir, os.pardir, os.pardir,
+    os.pardir)
+FILENAMES = ['app.yaml', 'appengine_main.py']
+
+def CleanUp(signal, frame):
+  for filename in FILENAMES:
+    os.remove(os.path.join(SRC_PATH, filename))
+
+if len(sys.argv) < 2:
+  print 'usage: start_dev_server.py <location of dev_appserver.py> [options]'
+  exit(0)
+
+signal.signal(signal.SIGINT, CleanUp)
+
+build_server.main()
+for filename in FILENAMES:
+  shutil.copy(os.path.join(SERVER_PATH, filename),
+      os.path.join(SRC_PATH, filename))
+args = [sys.executable] + sys.argv[1:] + [SRC_PATH]
+subprocess.call(args)
diff --git a/chrome/common/extensions/docs/server2/subversion_file_system.py b/chrome/common/extensions/docs/server2/subversion_file_system.py
new file mode 100644
index 0000000..001805c
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/subversion_file_system.py
@@ -0,0 +1,104 @@
+# Copyright (c) 2012 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.
+
+import re
+import xml.dom.minidom as xml
+from xml.parsers.expat import ExpatError
+
+import file_system
+from future import Future
+
+class _AsyncFetchFuture(object):
+  def __init__(self, paths, fetcher, binary):
+    # A list of tuples of the form (path, Future).
+    self._fetches = [(path, fetcher.FetchAsync(path)) for path in paths]
+    self._value = {}
+    self._error = None
+    self._binary = binary
+
+  def _ListDir(self, directory):
+    dom = xml.parseString(directory)
+    files = [elem.childNodes[0].data for elem in dom.getElementsByTagName('a')]
+    if '..' in files:
+      files.remove('..')
+    return files
+
+  def Get(self):
+    for path, future in self._fetches:
+      result = future.Get()
+      if result.status_code == 404:
+        raise file_system.FileNotFoundError(path)
+      elif path.endswith('/'):
+        self._value[path] = self._ListDir(result.content)
+      elif not self._binary:
+        self._value[path] = file_system._ProcessFileData(result.content, path)
+      else:
+        self._value[path] = result.content
+    if self._error is not None:
+      raise self._error
+    return self._value
+
+class SubversionFileSystem(file_system.FileSystem):
+  """Class to fetch resources from src.chromium.org.
+  """
+  def __init__(self, fetcher, stat_fetcher):
+    self._fetcher = fetcher
+    self._stat_fetcher = stat_fetcher
+
+  def Read(self, paths, binary=False):
+    return Future(delegate=_AsyncFetchFuture(paths, self._fetcher, binary))
+
+  def _ParseHTML(self, html):
+    """Unfortunately, the viewvc page has a stray </div> tag, so this takes care
+    of all mismatched tags.
+    """
+    try:
+      return xml.parseString(html)
+    except ExpatError as e:
+      return self._ParseHTML('\n'.join(
+          line for (i, line) in enumerate(html.split('\n'))
+          if e.lineno != i + 1))
+
+  def _CreateStatInfo(self, html):
+    dom = self._ParseHTML(html)
+    # Brace yourself, this is about to get ugly. The page returned from viewvc
+    # was not the prettiest.
+    tds = dom.getElementsByTagName('td')
+    a_list = []
+    found = False
+    dir_revision = None
+    for td in tds:
+      if found:
+        dir_revision = td.getElementsByTagName('a')[0].firstChild.nodeValue
+        found = False
+      a_list.extend(td.getElementsByTagName('a'))
+      if (td.firstChild is not None and
+          td.firstChild.nodeValue == 'Directory revision:'):
+        found = True
+    child_revisions = {}
+    for i, a in enumerate(a_list):
+      if i + 1 >= len(a_list):
+        break
+      next_a = a_list[i + 1]
+      name = a.getAttribute('name')
+      if name:
+        rev = next_a.getElementsByTagName('strong')[0]
+        if 'file' in next_a.getAttribute('title'):
+          child_revisions[name] = rev.firstChild.nodeValue
+        else:
+          child_revisions[name + '/'] = rev.firstChild.nodeValue
+    return file_system.StatInfo(dir_revision, child_revisions)
+
+  def Stat(self, path):
+    directory = path.rsplit('/', 1)[0]
+    result = self._stat_fetcher.Fetch(directory + '/')
+    if result.status_code == 404:
+      raise file_system.FileNotFoundError(path)
+    stat_info = self._CreateStatInfo(result.content)
+    if not path.endswith('/'):
+      filename = path.rsplit('/', 1)[-1]
+      if filename not in stat_info.child_versions:
+        raise file_system.FileNotFoundError(path)
+      stat_info.version = stat_info.child_versions[filename]
+    return stat_info
diff --git a/chrome/common/extensions/docs/server2/subversion_file_system_test.py b/chrome/common/extensions/docs/server2/subversion_file_system_test.py
new file mode 100755
index 0000000..8a78268
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/subversion_file_system_test.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+import json
+import os
+import sys
+import unittest
+
+from fake_url_fetcher import FakeUrlFetcher
+from subversion_file_system import SubversionFileSystem
+
+class SubversionFileSystemTest(unittest.TestCase):
+  def setUp(self):
+    self._base_path = os.path.join(sys.path[0], 'test_data', 'file_system')
+    fetcher = FakeUrlFetcher(self._base_path)
+    self._file_system = SubversionFileSystem(fetcher, fetcher)
+
+  def _ReadLocalFile(self, filename):
+    with open(os.path.join(self._base_path, filename), 'r') as f:
+      return f.read()
+
+  def testReadFiles(self):
+    expected = {
+      'test1.txt': 'test1\n',
+      'test2.txt': 'test2\n',
+      'test3.txt': 'test3\n',
+    }
+    self.assertEqual(
+        expected,
+        self._file_system.Read(['test1.txt', 'test2.txt', 'test3.txt']).Get())
+
+  def testListDir(self):
+    expected = ['dir/']
+    for i in range(7):
+      expected.append('file%d.html' % i)
+    self.assertEqual(expected,
+                     sorted(self._file_system.ReadSingle('list/')))
+
+  def testStat(self):
+    stat_info = self._file_system.Stat('stat/')
+    self.assertEquals('151113', stat_info.version)
+    self.assertEquals(json.loads(self._ReadLocalFile('stat_result.json')),
+                      stat_info.child_versions)
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/chrome/common/extensions/docs/server2/template_data_source.py b/chrome/common/extensions/docs/server2/template_data_source.py
new file mode 100644
index 0000000..9ac3bba
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/template_data_source.py
@@ -0,0 +1,150 @@
+# Copyright (c) 2012 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.
+
+import logging
+
+from docs_server_utils import FormatKey
+from file_system import FileNotFoundError
+import compiled_file_system as compiled_fs
+from third_party.handlebar import Handlebar
+
+EXTENSIONS_URL = '/chrome/extensions'
+
+def _MakeChannelDict(channel_name):
+  return {
+    'showWarning': channel_name != 'stable',
+    'channels': [
+      { 'name': 'Stable', 'path': 'stable' },
+      { 'name': 'Dev',    'path': 'dev' },
+      { 'name': 'Beta',   'path': 'beta' },
+      { 'name': 'Trunk',  'path': 'trunk' }
+    ],
+    'current': channel_name
+  }
+
+class TemplateDataSource(object):
+  """Renders Handlebar templates, providing them with the context in which to
+  render.
+
+  Also acts as a data source itself, providing partial Handlebar templates to
+  those it renders.
+
+  Each instance of TemplateDataSource is bound to a Request so that it can
+  render templates with request-specific data (such as Accept-Language); use
+  a Factory to cheaply construct these.
+  """
+
+  class Factory(object):
+    """A factory to create lightweight TemplateDataSource instances bound to
+    individual Requests.
+    """
+    def __init__(self,
+                 channel_name,
+                 api_data_source_factory,
+                 api_list_data_source_factory,
+                 intro_data_source_factory,
+                 samples_data_source_factory,
+                 known_issues_data_source,
+                 sidenav_data_source_factory,
+                 cache_factory,
+                 public_template_path,
+                 private_template_path):
+      self._branch_info = _MakeChannelDict(channel_name)
+      self._api_data_source_factory = api_data_source_factory
+      self._api_list_data_source_factory = api_list_data_source_factory
+      self._intro_data_source_factory = intro_data_source_factory
+      self._samples_data_source_factory = samples_data_source_factory
+      self._known_issues_data_source = known_issues_data_source
+      self._sidenav_data_source_factory = sidenav_data_source_factory
+      self._cache = cache_factory.Create(Handlebar, compiled_fs.HANDLEBAR)
+      self._public_template_path = public_template_path
+      self._private_template_path = private_template_path
+      self._static_resources = (
+          (('/' + channel_name) if channel_name != 'local' else '') + '/static')
+
+    def Create(self, request, path):
+      """Returns a new TemplateDataSource bound to |request|.
+      """
+      branch_info = self._branch_info.copy()
+      branch_info['showWarning'] = (not path.startswith('apps') and
+                                    branch_info['showWarning'])
+      return TemplateDataSource(
+          branch_info,
+          self._api_data_source_factory.Create(request),
+          self._api_list_data_source_factory.Create(),
+          self._intro_data_source_factory.Create(),
+          self._samples_data_source_factory.Create(request),
+          self._known_issues_data_source,
+          self._sidenav_data_source_factory.Create(path),
+          self._cache,
+          self._public_template_path,
+          self._private_template_path,
+          self._static_resources,
+          request)
+
+  def __init__(self,
+               branch_info,
+               api_data_source,
+               api_list_data_source,
+               intro_data_source,
+               samples_data_source,
+               known_issues_data_source,
+               sidenav_data_source,
+               cache,
+               public_template_path,
+               private_template_path,
+               static_resources,
+               request):
+    self._branch_info = branch_info
+    self._api_list_data_source = api_list_data_source
+    self._intro_data_source = intro_data_source
+    self._samples_data_source = samples_data_source
+    self._api_data_source = api_data_source
+    self._known_issues_data_source = known_issues_data_source
+    self._sidenav_data_source = sidenav_data_source
+    self._cache = cache
+    self._public_template_path = public_template_path
+    self._private_template_path = private_template_path
+    self._static_resources = static_resources
+    self._request = request
+
+  def Render(self, template_name):
+    """This method will render a template named |template_name|, fetching all
+    the partial templates needed from |self._cache|. Partials are retrieved
+    from the TemplateDataSource with the |get| method.
+    """
+    template = self.GetTemplate(self._public_template_path, template_name)
+    if not template:
+      return ''
+      # TODO error handling
+    render_data = template.render({
+      'api_list': self._api_list_data_source,
+      'apis': self._api_data_source,
+      'branchInfo': self._branch_info,
+      'intros': self._intro_data_source,
+      'known_issues': self._known_issues_data_source,
+      'sidenavs': self._sidenav_data_source,
+      'partials': self,
+      'samples': self._samples_data_source,
+      'static': self._static_resources,
+      'apps_title': 'Apps',
+      'extensions_title': 'Extensions',
+      'true': True,
+      'false': False
+    })
+    if render_data.errors:
+      logging.error('Handlebar error(s) rendering %s:\n%s' %
+          (template_name, '  \n'.join(render_data.errors)))
+    return render_data.text
+
+  def get(self, key):
+    return self.GetTemplate(self._private_template_path, key)
+
+  def GetTemplate(self, base_path, template_name):
+    real_path = FormatKey(template_name)
+    try:
+      return self._cache.GetFromFile(base_path + '/' + real_path)
+    except FileNotFoundError as e:
+      logging.info(e)
+      return None
diff --git a/chrome/common/extensions/docs/server2/template_data_source_test.py b/chrome/common/extensions/docs/server2/template_data_source_test.py
new file mode 100755
index 0000000..705b7b1
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/template_data_source_test.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+import json
+import os
+import sys
+import unittest
+
+from in_memory_object_store import InMemoryObjectStore
+from compiled_file_system import CompiledFileSystem
+from local_file_system import LocalFileSystem
+from template_data_source import TemplateDataSource
+from third_party.handlebar import Handlebar
+
+class _FakeRequest(object):
+    pass
+
+class _FakeFactory(object):
+  def __init__(self, input_dict=None):
+    if input_dict is None:
+      self._input_dict = {}
+    else:
+      self._input_dict = input_dict
+
+  def Create(self, *args):
+    return self._input_dict
+
+class TemplateDataSourceTest(unittest.TestCase):
+  def setUp(self):
+    self._base_path = os.path.join(sys.path[0],
+                                   'test_data',
+                                   'template_data_source')
+    self._fake_api_data_source_factory = _FakeFactory()
+    self._fake_api_list_data_source_factory = _FakeFactory()
+    self._fake_intro_data_source_factory = _FakeFactory()
+    self._fake_samples_data_source_factory = _FakeFactory()
+    self._fake_sidenav_data_source_factory = _FakeFactory()
+    self._fake_known_issues_data_source = {}
+    self._object_store = InMemoryObjectStore('fake_branch')
+
+  def _ReadLocalFile(self, filename):
+    with open(os.path.join(self._base_path, filename), 'r') as f:
+      return f.read()
+
+  def _RenderTest(self, name, data_source):
+    template_name = name + '_tmpl.html'
+    template = Handlebar(self._ReadLocalFile(template_name))
+    self.assertEquals(
+        self._ReadLocalFile(name + '_expected.html'),
+        data_source.Render(template_name))
+
+  def _CreateTemplateDataSource(self, input_dict, cache_factory):
+    return (TemplateDataSource.Factory('fake_channel',
+                                       _FakeFactory(input_dict),
+                                       self._fake_api_list_data_source_factory,
+                                       self._fake_intro_data_source_factory,
+                                       self._fake_samples_data_source_factory,
+                                       self._fake_known_issues_data_source,
+                                       self._fake_sidenav_data_source_factory,
+                                       cache_factory,
+                                       '.',
+                                       '.')
+            .Create(_FakeRequest(), 'extensions/foo'))
+
+  def testSimple(self):
+    self._base_path = os.path.join(self._base_path, 'simple')
+    fetcher = LocalFileSystem(self._base_path)
+    cache_factory = CompiledFileSystem.Factory(fetcher, self._object_store)
+    t_data_source = self._CreateTemplateDataSource(
+        self._fake_api_data_source_factory, cache_factory)
+    template_a1 = Handlebar(self._ReadLocalFile('test1.html'))
+    self.assertEqual(template_a1.render({}, {'templates': {}}).text,
+        t_data_source.get('test1').render({}, {'templates': {}}).text)
+
+    template_a2 = Handlebar(self._ReadLocalFile('test2.html'))
+    self.assertEqual(template_a2.render({}, {'templates': {}}).text,
+        t_data_source.get('test2').render({}, {'templates': {}}).text)
+
+    self.assertEqual(None, t_data_source.get('junk.html'))
+
+  def testPartials(self):
+    self._base_path = os.path.join(self._base_path, 'partials')
+    fetcher = LocalFileSystem(self._base_path)
+    cache_factory = CompiledFileSystem.Factory(fetcher, self._object_store)
+    t_data_source = self._CreateTemplateDataSource(
+        self._fake_api_data_source_factory, cache_factory)
+    self.assertEqual(
+        self._ReadLocalFile('test_expected.html'),
+        t_data_source.get('test_tmpl').render(
+            json.loads(self._ReadLocalFile('input.json')), t_data_source).text)
+
+  def testRender(self):
+    self._base_path = os.path.join(self._base_path, 'render')
+    fetcher = LocalFileSystem(self._base_path)
+    context = json.loads(self._ReadLocalFile('test1.json'))
+    cache_factory = CompiledFileSystem.Factory(fetcher, self._object_store)
+    self._RenderTest(
+        'test1',
+        self._CreateTemplateDataSource(
+            json.loads(self._ReadLocalFile('test1.json')),
+                           cache_factory))
+    self._RenderTest(
+        'test2',
+        self._CreateTemplateDataSource(
+            json.loads(self._ReadLocalFile('test2.json')),
+                           cache_factory))
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/chrome/common/extensions/docs/server2/test_data/branch_utility/first.json b/chrome/common/extensions/docs/server2/test_data/branch_utility/first.json
new file mode 100644
index 0000000..b37b0ff
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/branch_utility/first.json
@@ -0,0 +1,261 @@
+[
+  {
+    "os":"cros",
+    "versions":[
+      {
+        "base_webkit_revision":"r?",
+        "v8_ver":null,
+        "wk_ver":null,
+        "base_trunk_revision":"r?",
+        "prev_version":"0.0.0.0",
+        "version":"0.0.0.0",
+        "date":"00\/00\/00",
+        "prev_date":"00\/00\/00",
+        "true_branch":"??",
+        "channel":"canary",
+        "branch_revision":"NA"
+      },
+      {
+        "base_webkit_revision":"116185",
+        "v8_ver":"3.10.8.7",
+        "wk_ver":"536.11",
+        "base_trunk_revision":135598,
+        "prev_version":"20.0.1132.7",
+        "version":"20.0.1132.11",
+        "date":"05\/18\/12",
+        "prev_date":"05\/15\/12",
+        "true_branch":"1132",
+        "channel":"dev",
+        "branch_revision":137611
+      },
+      {
+        "base_webkit_revision":"112327",
+        "v8_ver":"3.9.24.23",
+        "wk_ver":"536.5",
+        "base_trunk_revision":129376,
+        "prev_version":"19.0.1084.47",
+        "version":"19.0.1084.48",
+        "date":"05\/17\/12",
+        "prev_date":"05\/14\/12",
+        "true_branch":"1084",
+        "channel":"beta",
+        "branch_revision":137367
+      },
+      {
+        "base_webkit_revision":"106313",
+        "v8_ver":"3.8.9.19",
+        "wk_ver":"535.19",
+        "base_trunk_revision":119867,
+        "prev_version":"18.0.1025.165",
+        "version":"18.0.1025.168",
+        "date":"05\/01\/12",
+        "prev_date":"04\/23\/12",
+        "true_branch":"1234",
+        "channel":"stable",
+        "branch_revision":134367
+      }
+    ]
+  },
+  {
+    "os":"win",
+    "versions":[
+      {
+        "base_webkit_revision":"117602",
+        "v8_ver":"3.11.3.0",
+        "wk_ver":"537.1",
+        "base_trunk_revision":138089,
+        "prev_version":"21.0.1145.0",
+        "version":"21.0.1146.0",
+        "date":"05\/22\/12",
+        "prev_date":"05\/21\/12",
+        "true_branch":"trunk",
+        "channel":"canary",
+        "branch_revision":"NA"
+      },
+      {
+        "base_webkit_revision":"117602",
+        "v8_ver":"3.11.3.0",
+        "wk_ver":"537.1",
+        "base_trunk_revision":138079,
+        "prev_version":"20.0.1132.11",
+        "version":"21.0.1145.0",
+        "date":"05\/22\/12",
+        "prev_date":"05\/17\/12",
+        "true_branch":"1132",
+        "channel":"dev",
+        "branch_revision":"NA"
+      },
+      {
+        "base_webkit_revision":"112327",
+        "v8_ver":"3.9.24.21",
+        "wk_ver":"536.5",
+        "base_trunk_revision":129376,
+        "prev_version":"19.0.1084.41",
+        "version":"19.0.1084.46",
+        "date":"05\/10\/12",
+        "prev_date":"05\/03\/12",
+        "true_branch":"1084",
+        "channel":"beta",
+        "branch_revision":135956
+      },
+      {
+        "base_webkit_revision":"112327",
+        "v8_ver":"3.9.24.21",
+        "wk_ver":"536.5",
+        "base_trunk_revision":129376,
+        "prev_version":"18.0.1025.168",
+        "version":"19.0.1084.46",
+        "date":"05\/15\/12",
+        "prev_date":"04\/30\/12",
+        "true_branch":"1234",
+        "channel":"stable",
+        "branch_revision":135956
+      }
+    ]
+  },
+  {
+    "os":"mac",
+    "versions":[
+      {
+        "base_webkit_revision":"117602",
+        "v8_ver":"3.11.3.0",
+        "wk_ver":"537.1",
+        "base_trunk_revision":138079,
+        "prev_version":"21.0.1144.0",
+        "version":"21.0.1145.0",
+        "date":"05\/21\/12",
+        "prev_date":"05\/20\/12",
+        "true_branch":"trunk",
+        "channel":"canary",
+        "branch_revision":"NA"
+      },
+      {
+        "base_webkit_revision":"117602",
+        "v8_ver":"3.11.3.0",
+        "wk_ver":"537.1",
+        "base_trunk_revision":138079,
+        "prev_version":"20.0.1132.11",
+        "version":"21.0.1145.0",
+        "date":"05\/22\/12",
+        "prev_date":"05\/17\/12",
+        "true_branch":"1132",
+        "channel":"dev",
+        "branch_revision":"NA"
+      },
+      {
+        "base_webkit_revision":"112327",
+        "v8_ver":"3.9.24.21",
+        "wk_ver":"536.5",
+        "base_trunk_revision":129376,
+        "prev_version":"19.0.1084.41",
+        "version":"19.0.1084.46",
+        "date":"05\/10\/12",
+        "prev_date":"05\/03\/12",
+        "true_branch":"1084",
+        "channel":"beta",
+        "branch_revision":135956
+      },
+      {
+        "base_webkit_revision":"112327",
+        "v8_ver":"3.9.24.21",
+        "wk_ver":"536.5",
+        "base_trunk_revision":129376,
+        "prev_version":"18.0.1025.168",
+        "version":"19.0.1084.46",
+        "date":"05\/15\/12",
+        "prev_date":"04\/30\/12",
+        "true_branch":"1234",
+        "channel":"stable",
+        "branch_revision":135956
+      }
+    ]
+  },
+  {
+    "os":"cf",
+    "versions":[
+      {
+        "base_webkit_revision":"116185",
+        "v8_ver":"3.10.8.7",
+        "wk_ver":"536.11",
+        "base_trunk_revision":135598,
+        "prev_version":"20.0.1132.8",
+        "version":"20.0.1132.11",
+        "date":"05\/17\/12",
+        "prev_date":"05\/16\/12",
+        "true_branch":"1132",
+        "channel":"dev",
+        "branch_revision":137611
+      },
+      {
+        "base_webkit_revision":"112327",
+        "v8_ver":"3.9.24.21",
+        "wk_ver":"536.5",
+        "base_trunk_revision":129376,
+        "prev_version":"19.0.1084.41",
+        "version":"19.0.1084.46",
+        "date":"05\/10\/12",
+        "prev_date":"05\/03\/12",
+        "true_branch":"1084",
+        "channel":"beta",
+        "branch_revision":135956
+      },
+      {
+        "base_webkit_revision":"112327",
+        "v8_ver":"3.9.24.21",
+        "wk_ver":"536.5",
+        "base_trunk_revision":129376,
+        "prev_version":"18.0.1025.168",
+        "version":"19.0.1084.46",
+        "date":"05\/15\/12",
+        "prev_date":"04\/30\/12",
+        "true_branch":"1084",
+        "channel":"stable",
+        "branch_revision":135956
+      }
+    ]
+  },
+  {
+    "os":"linux",
+    "versions":[
+      {
+        "base_webkit_revision":"117602",
+        "v8_ver":"3.11.3.0",
+        "wk_ver":"537.1",
+        "base_trunk_revision":138079,
+        "prev_version":"20.0.1132.11",
+        "version":"21.0.1145.0",
+        "date":"05\/21\/12",
+        "prev_date":"05\/17\/12",
+        "true_branch":"trunk",
+        "channel":"dev",
+        "branch_revision":"NA"
+      },
+      {
+        "base_webkit_revision":"112327",
+        "v8_ver":"3.9.24.21",
+        "wk_ver":"536.5",
+        "base_trunk_revision":129376,
+        "prev_version":"19.0.1084.41",
+        "version":"19.0.1084.46",
+        "date":"05\/10\/12",
+        "prev_date":"05\/03\/12",
+        "true_branch":"1084",
+        "channel":"beta",
+        "branch_revision":135956
+      },
+      {
+        "base_webkit_revision":"112327",
+        "v8_ver":"3.9.24.21",
+        "wk_ver":"536.5",
+        "base_trunk_revision":129376,
+        "prev_version":"18.0.1025.168",
+        "version":"19.0.1084.46",
+        "date":"05\/15\/12",
+        "prev_date":"04\/30\/12",
+        "true_branch":"1084",
+        "channel":"stable",
+        "branch_revision":135956
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/docs/server2/test_data/example_zipper/basic/icon.png b/chrome/common/extensions/docs/server2/test_data/example_zipper/basic/icon.png
new file mode 100644
index 0000000..84c4be3
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/example_zipper/basic/icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/server2/test_data/example_zipper/basic/manifest.json b/chrome/common/extensions/docs/server2/test_data/example_zipper/basic/manifest.json
new file mode 100644
index 0000000..a42819b
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/example_zipper/basic/manifest.json
@@ -0,0 +1,15 @@
+{
+  "name": "My Bookmarks",
+  "version": "1.0",
+  "description": "A browser action with a popup dump of all bookmarks, including search, add, edit and delete.",
+  "permissions": [
+    "bookmarks", "tabs"
+  ],
+  "browser_action": {
+      "default_title": "My Bookmarks.",
+      "default_icon": "icon.png",
+      "default_popup": "popup.html"
+  },
+  "manifest_version": 2,
+  "content_security_policy": "script-src 'self' https://ajax.googleapis.com; object-src 'self'"
+}
diff --git a/chrome/common/extensions/docs/server2/test_data/example_zipper/basic/popup.html b/chrome/common/extensions/docs/server2/test_data/example_zipper/basic/popup.html
new file mode 100644
index 0000000..9baa86e
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/example_zipper/basic/popup.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<link type="text/css" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/base/jquery-ui.css" rel="stylesheet">
+<style>
+div, td, th { color: black; }
+</style>
+<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
+<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js"></script>
+<script src="popup.js"></script>
+<script>
+</script>
+</head>
+<body style="width: 400px">
+<div>Search Bookmarks: <input id="search"></div>
+<div id="bookmarks"></div>
+<div id="editdialog"></div>
+<div id="deletedialog"></div>
+<div id="adddialog"></div>
+</body>
+</html>
diff --git a/chrome/common/extensions/docs/server2/test_data/example_zipper/basic/popup.js b/chrome/common/extensions/docs/server2/test_data/example_zipper/basic/popup.js
new file mode 100644
index 0000000..ca72daa
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/example_zipper/basic/popup.js
@@ -0,0 +1,128 @@
+// Copyright (c) 2012 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.
+
+// Search the bookmarks when entering the search keyword.
+$(function() {
+  $('#search').change(function() {
+     $('#bookmarks').empty();
+     dumpBookmarks($('#search').val());
+  });
+});
+// Traverse the bookmark tree, and print the folder and nodes.
+function dumpBookmarks(query) {
+  var bookmarkTreeNodes = chrome.bookmarks.getTree(
+    function(bookmarkTreeNodes) {
+      $('#bookmarks').append(dumpTreeNodes(bookmarkTreeNodes, query));
+    });
+}
+function dumpTreeNodes(bookmarkNodes, query) {
+  var list = $('<ul>');
+  var i;
+  for (i = 0; i < bookmarkNodes.length; i++) {
+    list.append(dumpNode(bookmarkNodes[i], query));
+  }
+  return list;
+}
+function dumpNode(bookmarkNode, query) {
+  if (bookmarkNode.title) {
+    if (query && !bookmarkNode.children) {
+      if (String(bookmarkNode.title).indexOf(query) == -1) {
+        return $('<span></span>');
+      }
+    }
+    var anchor = $('<a>');
+    anchor.attr('href', bookmarkNode.url);
+    anchor.text(bookmarkNode.title);
+    /*
+     * When clicking on a bookmark in the extension, a new tab is fired with
+     * the bookmark url.
+     */
+    anchor.click(function() {
+      chrome.tabs.create({url: bookmarkNode.url});
+    });
+    var span = $('<span>');
+    var options = bookmarkNode.children ?
+      $('<span>[<a href="#" id="addlink">Add</a>]</span>') :
+      $('<span>[<a id="editlink" href="#">Edit</a> <a id="deletelink" ' +
+        'href="#">Delete</a>]</span>');
+    var edit = bookmarkNode.children ? $('<table><tr><td>Name</td><td>' +
+      '<input id="title"></td></tr><tr><td>URL</td><td><input id="url">' +
+      '</td></tr></table>') : $('<input>');
+    // Show add and edit links when hover over.
+        span.hover(function() {
+        span.append(options);
+        $('#deletelink').click(function() {
+          $('#deletedialog').empty().dialog({
+                 autoOpen: false,
+                 title: 'Confirm Deletion',
+                 resizable: false,
+                 height: 140,
+                 modal: true,
+                 overlay: {
+                   backgroundColor: '#000',
+                   opacity: 0.5
+                 },
+                 buttons: {
+                   'Yes, Delete It!': function() {
+                      chrome.bookmarks.remove(String(bookmarkNode.id));
+                      span.parent().remove();
+                      $(this).dialog('destroy');
+                    },
+                    Cancel: function() {
+                      $(this).dialog('destroy');
+                    }
+                 }
+               }).dialog('open');
+         });
+        $('#addlink').click(function() {
+          $('#adddialog').empty().append(edit).dialog({autoOpen: false,
+            closeOnEscape: true, title: 'Add New Bookmark', modal: true,
+            buttons: {
+            'Add' : function() {
+               chrome.bookmarks.create({parentId: bookmarkNode.id,
+                 title: $('#title').val(), url: $('#url').val()});
+               $('#bookmarks').empty();
+               $(this).dialog('destroy');
+               window.dumpBookmarks();
+             },
+            'Cancel': function() {
+               $(this).dialog('destroy');
+            }
+          }}).dialog('open');
+        });
+        $('#editlink').click(function() {
+         edit.val(anchor.text());
+         $('#editdialog').empty().append(edit).dialog({autoOpen: false,
+           closeOnEscape: true, title: 'Edit Title', modal: true,
+           show: 'slide', buttons: {
+              'Save': function() {
+                 chrome.bookmarks.update(String(bookmarkNode.id), {
+                   title: edit.val()
+                 });
+                 anchor.text(edit.val());
+                 options.show();
+                 $(this).dialog('destroy');
+              },
+             'Cancel': function() {
+                 $(this).dialog('destroy');
+             }
+         }}).dialog('open');
+        });
+        options.fadeIn();
+      },
+      // unhover
+      function() {
+        options.remove();
+      }).append(anchor);
+  }
+  var li = $(bookmarkNode.title ? '<li>' : '<div>').append(span);
+  if (bookmarkNode.children && bookmarkNode.children.length > 0) {
+    li.append(dumpTreeNodes(bookmarkNode.children, query));
+  }
+  return li;
+}
+
+document.addEventListener('DOMContentLoaded', function () {
+  dumpBookmarks();
+});
diff --git a/chrome/common/extensions/docs/server2/test_data/file_system.zip b/chrome/common/extensions/docs/server2/test_data/file_system.zip
new file mode 100644
index 0000000..40e6424
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/file_system.zip
Binary files differ
diff --git a/chrome/common/extensions/docs/server2/test_data/file_system/list/dir/empty.txt b/chrome/common/extensions/docs/server2/test_data/file_system/list/dir/empty.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/file_system/list/dir/empty.txt
diff --git a/chrome/common/extensions/docs/server2/test_data/file_system/list/file0.html b/chrome/common/extensions/docs/server2/test_data/file_system/list/file0.html
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/file_system/list/file0.html
diff --git a/chrome/common/extensions/docs/server2/test_data/file_system/list/file1.html b/chrome/common/extensions/docs/server2/test_data/file_system/list/file1.html
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/file_system/list/file1.html
diff --git a/chrome/common/extensions/docs/server2/test_data/file_system/list/file2.html b/chrome/common/extensions/docs/server2/test_data/file_system/list/file2.html
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/file_system/list/file2.html
diff --git a/chrome/common/extensions/docs/server2/test_data/file_system/list/file3.html b/chrome/common/extensions/docs/server2/test_data/file_system/list/file3.html
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/file_system/list/file3.html
diff --git a/chrome/common/extensions/docs/server2/test_data/file_system/list/file4.html b/chrome/common/extensions/docs/server2/test_data/file_system/list/file4.html
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/file_system/list/file4.html
diff --git a/chrome/common/extensions/docs/server2/test_data/file_system/list/file5.html b/chrome/common/extensions/docs/server2/test_data/file_system/list/file5.html
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/file_system/list/file5.html
diff --git a/chrome/common/extensions/docs/server2/test_data/file_system/list/file6.html b/chrome/common/extensions/docs/server2/test_data/file_system/list/file6.html
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/file_system/list/file6.html
diff --git a/chrome/common/extensions/docs/server2/test_data/file_system/stat b/chrome/common/extensions/docs/server2/test_data/file_system/stat
new file mode 100644
index 0000000..0269677
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/file_system/stat
@@ -0,0 +1,2535 @@
+
+
+
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<!-- ViewVC :: http://www.viewvc.org/ -->
+<head>
+<title>[chrome] Index of /trunk/src/chrome/common/extensions/api</title>
+<meta name="generator" content="ViewVC 1.0.9" />
+<link rel="stylesheet" href="/viewvc/*docroot*/styles.css" type="text/css" />
+
+</head>
+<body>
+<div class="vc_navheader">
+
+<form method="get" action="/viewvc/">
+
+<table style="padding:0.1em;">
+<tr>
+<td>
+<strong>
+
+<a href="/viewvc/chrome/">
+
+[chrome]</a>
+/
+
+<a href="/viewvc/chrome/trunk/">
+
+trunk</a>
+/
+
+<a href="/viewvc/chrome/trunk/src/">
+
+src</a>
+/
+
+<a href="/viewvc/chrome/trunk/src/chrome/">
+
+chrome</a>
+/
+
+<a href="/viewvc/chrome/trunk/src/chrome/common/">
+
+common</a>
+/
+
+<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/">
+
+extensions</a>
+/
+
+
+
+api
+
+
+</strong>
+
+</td>
+<td style="text-align:right;">
+
+
+<strong>Repository:</strong>
+<select name="root" onchange="submit()">
+
+
+<option value="*viewroots*">Repository Listing</option>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+<optgroup label="Subversion Repositories"><option selected="selected">chrome</option><option>multivm</option><option>native_client</option></optgroup>
+
+</select>
+<input type="submit" value="Go" />
+
+</td>
+</tr>
+</table>
+
+</form>
+
+</div>
+<div style="float: right; padding: 5px;"><a href="http://www.viewvc.org/"><img src="/viewvc/*docroot*/images/logo.png" alt="ViewVC logotype" width="128" height="48" /></a></div>
+<h1>Index of /trunk/src/chrome/common/extensions/api</h1>
+
+
+<table class="auto">
+<tr><td>Files shown:</td><td><strong>86</strong>
+
+
+</td></tr>
+
+<tr>
+<td>Directory revision:</td>
+<td><a href="/viewvc/chrome?view=rev&amp;revision=151113">151113</a> (of <a href="/viewvc/chrome?view=rev">151144</a>)</td>
+</tr>
+
+<tr>
+<td>Sticky Revision:</td>
+<td><form method="get" action="/viewvc/chrome" style="display: inline">
+<div style="display: inline">
+<input type="hidden" name="orig_pathtype" value="DIR" /><input type="hidden" name="orig_view" value="dir" /><input type="hidden" name="orig_path" value="trunk/src/chrome/common/extensions/api" /><input type="hidden" name="view" value="redirect_pathrev" />
+
+<input type="text" name="pathrev" value="" size="6"/>
+
+<input type="submit" value="Set" />
+</div>
+</form>
+
+</td>
+</tr>
+
+
+</table>
+
+
+<p><a name="dirlist"></a></p>
+<hr />
+
+<table cellspacing="1" cellpadding="2">
+<thead>
+<tr>
+<th class="vc_header_sort" colspan="2">
+<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/?sortdir=down#dirlist">File</a>
+
+<img class="vc_sortarrow" alt=""
+width="13" height="13"
+src="/viewvc/*docroot*/images/up.png" />
+
+</th>
+<th class="vc_header">
+<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/?sortby=rev#dirlist">Rev.</a>
+
+</th>
+<th class="vc_header">
+<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/?sortby=date#dirlist">Age</a>
+
+</th>
+<th class="vc_header">
+<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/?sortby=author#dirlist">Author</a>
+
+</th>
+
+<th class="vc_header">
+<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/?sortby=log#dirlist">Last log entry</a>
+
+</th>
+
+</tr>
+</thead>
+<tbody>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/">
+<img src="/viewvc/*docroot*/images/back_small.png" alt="" width="16" height="16"
+/>&nbsp;Parent&nbsp;Directory</a>
+</td>
+<td>&nbsp;</td>
+<td>&nbsp;</td>
+<td>&nbsp;</td>
+
+<td>&nbsp;</td>
+
+</tr>
+
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="devtools" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/devtools/" title="View directory contents">
+
+<img src="/viewvc/*docroot*/images/dir.png" alt="" width="16" height="16" />
+devtools/</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/devtools/?view=log" title="View directory revision log"><strong>149291</strong></a></td>
+
+<td>&nbsp;10 days</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Extensions Docs Server: fix rendering of the Devtools API.
+
+This change splits d...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="OWNERS" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/OWNERS?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+OWNERS</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/OWNERS?revision=150079&amp;view=markup" title="View file contents"><strong>150079</strong></a></td>
+
+<td>&nbsp;4 days</td>
+<td>&nbsp;battre@chromium.org</td>
+
+
+<td>&nbsp;Create OWNERS file for chrome/renderer/resources/extensions
+
+Creating OWNERS fil...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="_manifest_features.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/_manifest_features.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+_manifest_features.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/_manifest_features.json?revision=150827&amp;view=markup" title="View file contents"><strong>150827</strong></a></td>
+
+<td>&nbsp;29 hours</td>
+<td>&nbsp;aa@chromium.org</td>
+
+
+<td>&nbsp;Add an 'author' field to the manifest definition.
+
+
+Review URL: <a href="https://chromium">https://chromium</a>...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="_permission_features.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/_permission_features.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+_permission_features.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/_permission_features.json?revision=150186&amp;view=markup" title="View file contents"><strong>150186</strong></a></td>
+
+<td>&nbsp;3 days</td>
+<td>&nbsp;miket@chromium.org</td>
+
+
+<td>&nbsp;Move serial out of experimental.
+
+This CL is unremarkable except that I figured ...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="alarms.idl" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/alarms.idl?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+alarms.idl</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/alarms.idl?revision=143049&amp;view=markup" title="View file contents"><strong>143049</strong></a></td>
+
+<td>&nbsp;7 weeks</td>
+<td>&nbsp;mpcomplete@chromium.org</td>
+
+
+<td>&nbsp;Fix how the IDL parser handles comments, and fix the chrome.alarms docs.
+
+This i...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="api.gyp" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/api.gyp?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+api.gyp</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/api.gyp?revision=151113&amp;view=markup" title="View file contents"><strong>151113</strong></a></td>
+
+<td>&nbsp;2 hours</td>
+<td>&nbsp;mitchellwrosen@chromium.org</td>
+
+
+<td>&nbsp;Use JSON schema compiler in chrome.management code
+
+BUG=121174
+
+
+Review URL: htt...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="app.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/app.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+app.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/app.json?revision=133722&amp;view=markup" title="View file contents"><strong>133722</strong></a></td>
+
+<td>&nbsp;3 months</td>
+<td>&nbsp;jstritar@chromium.org</td>
+
+
+<td>&nbsp;Add an API for hosted apps to check their install and running states.
+
+BUG=10721...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="app_window.idl" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/app_window.idl?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+app_window.idl</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/app_window.idl?revision=147710&amp;view=markup" title="View file contents"><strong>147710</strong></a></td>
+
+<td>&nbsp;3 weeks</td>
+<td>&nbsp;asargent@chromium.org</td>
+
+
+<td>&nbsp;Move chrome.appWindow to chrome.app.window.
+
+BUG=134573
+TEST=In platform apps, y...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="bookmarks.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/bookmarks.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+bookmarks.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/bookmarks.json?revision=149307&amp;view=markup" title="View file contents"><strong>149307</strong></a></td>
+
+<td>&nbsp;9 days</td>
+<td>&nbsp;mitchellwrosen@chromium.org</td>
+
+
+<td>&nbsp;Refactor chrome.bookmarks API to use JSON schema compiler.
+
+BUG=121174
+
+
+Review ...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="browser_action.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/browser_action.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+browser_action.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/browser_action.json?revision=148043&amp;view=markup" title="View file contents"><strong>148043</strong></a></td>
+
+<td>&nbsp;2 weeks</td>
+<td>&nbsp;yoz@chromium.org</td>
+
+
+<td>&nbsp;Add browserAction.enable/disable as alias for pageAction.show/hide.
+
+Depends on ...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="browsing_data.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/browsing_data.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+browsing_data.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/browsing_data.json?revision=142133&amp;view=markup" title="View file contents"><strong>142133</strong></a></td>
+
+<td>&nbsp;8 weeks</td>
+<td>&nbsp;mkwst@chromium.org</td>
+
+
+<td>&nbsp;`chrome.browsingData` extension API can now remove data from protected origins.
+...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="chromeos_info_private.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/chromeos_info_private.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+chromeos_info_private.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/chromeos_info_private.json?revision=150737&amp;view=markup" title="View file contents"><strong>150737</strong></a></td>
+
+<td>&nbsp;41 hours</td>
+<td>&nbsp;dpolukhin@chromium.org</td>
+
+
+<td>&nbsp;Add board and isOwner properties to chromeosInfoPrivate
+
+BUG=chrome-os-partner:9...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="cloud_print_private.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/cloud_print_private.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+cloud_print_private.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/cloud_print_private.json?revision=149303&amp;view=markup" title="View file contents"><strong>149303</strong></a></td>
+
+<td>&nbsp;9 days</td>
+<td>&nbsp;vitalybuka@chromium.org</td>
+
+
+<td>&nbsp;Renamed chrome_auth to cloud_print.
+No functionality changed except naming.
+Serv...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="content_settings.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/content_settings.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+content_settings.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/content_settings.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="context_menus.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/context_menus.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+context_menus.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/context_menus.json?revision=140112&amp;view=markup" title="View file contents"><strong>140112</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;yoz@chromium.org</td>
+
+
+<td>&nbsp;Dispatch a new event chrome.contextMenus.onClicked.
+
+Disallow onclick create par...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="cookies.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/cookies.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+cookies.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/cookies.json?revision=124878&amp;view=markup" title="View file contents"><strong>124878</strong></a></td>
+
+<td>&nbsp;5 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Allow comments in extension config files.
+
+Added a script to remove comments fro...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="debugger.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/debugger.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+debugger.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/debugger.json?revision=147894&amp;view=markup" title="View file contents"><strong>147894</strong></a></td>
+
+<td>&nbsp;2 weeks</td>
+<td>&nbsp;mitchellwrosen@chromium.org</td>
+
+
+<td>&nbsp;Refactor chrome.debugger api to use the JSON schema compiler. 
+
+BUG=121174
+
+
+Rev...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="declarative_web_request.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/declarative_web_request.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+declarative_web_request.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/declarative_web_request.json?revision=150421&amp;view=markup" title="View file contents"><strong>150421</strong></a></td>
+
+<td>&nbsp;3 days</td>
+<td>&nbsp;yoz@chromium.org</td>
+
+
+<td>&nbsp;Support also excluding content types in declarative webrequest conditions.
+
+BUG=...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="devtools.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/devtools.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+devtools.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/devtools.json?revision=124878&amp;view=markup" title="View file contents"><strong>124878</strong></a></td>
+
+<td>&nbsp;5 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Allow comments in extension config files.
+
+Added a script to remove comments fro...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="downloads.idl" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/downloads.idl?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+downloads.idl</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/downloads.idl?revision=146201&amp;view=markup" title="View file contents"><strong>146201</strong></a></td>
+
+<td>&nbsp;4 weeks</td>
+<td>&nbsp;benjhayden@chromium.org</td>
+
+
+<td>&nbsp;Switch the downloads API over to IDL/json_schema_compiler
+
+Modify ppapi/generato...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="echo_private.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/echo_private.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+echo_private.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/echo_private.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="events.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/events.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+events.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/events.json?revision=143168&amp;view=markup" title="View file contents"><strong>143168</strong></a></td>
+
+<td>&nbsp;7 weeks</td>
+<td>&nbsp;battre@chromium.org</td>
+
+
+<td>&nbsp;Document signatures for Event.[add|remove|has]Listener()
+
+
+BUG=132950
+TEST=no
+
+
+...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="experimental_accessibility.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_accessibility.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_accessibility.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_accessibility.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="experimental_app.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_app.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_app.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_app.json?revision=148848&amp;view=markup" title="View file contents"><strong>148848</strong></a></td>
+
+<td>&nbsp;13 days</td>
+<td>&nbsp;asargent@chromium.org</td>
+
+
+<td>&nbsp;Updates to documentation for experimental.apps
+
+-Put nodoc on experimental.apps....</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="experimental_bluetooth.idl" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_bluetooth.idl?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_bluetooth.idl</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_bluetooth.idl?revision=150898&amp;view=markup" title="View file contents"><strong>150898</strong></a></td>
+
+<td>&nbsp;25 hours</td>
+<td>&nbsp;bryeung@chromium.org</td>
+
+
+<td>&nbsp;Bluetooth API: improve discovery
+
+This CL: 
+- eliminates unnecessary dispatches
+...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="bookmark_manager.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/bookmark_manager.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+bookmark_manager.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/bookmark_manager.json?revision=140864&amp;view=markup" title="View file contents"><strong>140864</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;ananta@chromium.org</td>
+
+
+<td>&nbsp;Disable the new window context menu option in the bookmarks extension for Window...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="experimental_commands.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_commands.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_commands.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_commands.json?revision=149147&amp;view=markup" title="View file contents"><strong>149147</strong></a></td>
+
+<td>&nbsp;10 days</td>
+<td>&nbsp;finnur@chromium.org</td>
+
+
+<td>&nbsp;Renaming the 'keybinding' permission to 'commands'.
+
+BUG=135562
+TEST=Covered by ...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="experimental_discovery.idl" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_discovery.idl?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_discovery.idl</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_discovery.idl?revision=149243&amp;view=markup" title="View file contents"><strong>149243</strong></a></td>
+
+<td>&nbsp;10 days</td>
+<td>&nbsp;beaudoin@chromium.org</td>
+
+
+<td>&nbsp;Discovery API supports specifying a tile in a URL.
+
+BUG=none
+TEST=none
+
+
+Review ...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="experimental_dns.idl" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_dns.idl?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_dns.idl</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_dns.idl?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="experimental_identity.idl" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_identity.idl?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_identity.idl</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_identity.idl?revision=147523&amp;view=markup" title="View file contents"><strong>147523</strong></a></td>
+
+<td>&nbsp;3 weeks</td>
+<td>&nbsp;estade@chromium.org</td>
+
+
+<td>&nbsp;convert identity api to .idl
+
+BUG=135685
+TEST=manual + existing extension tests
+...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="experimental_idltest.idl" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_idltest.idl?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_idltest.idl</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_idltest.idl?revision=138707&amp;view=markup" title="View file contents"><strong>138707</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;asargent@chromium.org</td>
+
+
+<td>&nbsp;Add support for 'nocompile' to IDL schema compiler.
+
+The json schema stuff alrea...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="experimental_infobars.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_infobars.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_infobars.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_infobars.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="experimental_input_virtual_keyboard.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_input_virtual_keyboard.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_input_virtual_keyboard.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_input_virtual_keyboard.json?revision=138223&amp;view=markup" title="View file contents"><strong>138223</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;yusukes@google.com</td>
+
+
+<td>&nbsp;Remove virtual keyboard support:
+
+1. common.gypi: use_virtual_keyboard define
+2....</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="experimental_media_galleries.idl" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_media_galleries.idl?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_media_galleries.idl</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_media_galleries.idl?revision=150164&amp;view=markup" title="View file contents"><strong>150164</strong></a></td>
+
+<td>&nbsp;3 days</td>
+<td>&nbsp;vandebo@chromium.org</td>
+
+
+<td>&nbsp;Move MediaGalleries.getMediaFileSystems back to experimental for M22
+
+BUG=110823...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="experimental_offscreen_tabs.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_offscreen_tabs.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_offscreen_tabs.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_offscreen_tabs.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="experimental_processes.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_processes.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_processes.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_processes.json?revision=137690&amp;view=markup" title="View file contents"><strong>137690</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;nasko@chromium.org</td>
+
+
+<td>&nbsp;Improving the process model extension API
+
+BUG=32302
+TEST=Unit tests.
+
+
+Review U...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="push_messaging.idl" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/push_messaging.idl?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+push_messaging.idl</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/push_messaging.idl?revision=149536&amp;view=markup" title="View file contents"><strong>149536</strong></a></td>
+
+<td>&nbsp;8 days</td>
+<td>&nbsp;dcheng@chromium.org</td>
+
+
+<td>&nbsp;Add skeleton implementation of ExtensionPushMessagingEventRouter.
+
+BUG=139663
+TE...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="experimental_record.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_record.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_record.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_record.json?revision=145342&amp;view=markup" title="View file contents"><strong>145342</strong></a></td>
+
+<td>&nbsp;5 weeks</td>
+<td>&nbsp;clintstaley@chromium.org</td>
+
+
+<td>&nbsp;Fixes to repeat-count checking and error checking for failed subbrowser run
+
+BUG...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="experimental_rlz.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_rlz.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_rlz.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_rlz.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="experimental_speech_input.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_speech_input.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_speech_input.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_speech_input.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="experimental_usb.idl" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_usb.idl?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+experimental_usb.idl</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/experimental_usb.idl?revision=144221&amp;view=markup" title="View file contents"><strong>144221</strong></a></td>
+
+<td>&nbsp;6 weeks</td>
+<td>&nbsp;bryeung@chromium.org</td>
+
+
+<td>&nbsp;Publish documentation for the experimental apis
+
+TEST=none
+BUG=130199,134556
+
+Co...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="extension.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/extension.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+extension.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/extension.json?revision=146855&amp;view=markup" title="View file contents"><strong>146855</strong></a></td>
+
+<td>&nbsp;3 weeks</td>
+<td>&nbsp;mpcomplete@chromium.org</td>
+
+
+<td>&nbsp;Fix extension docs for sendMessage/onMessage.
+
+BUG=136654,136857
+TEST=
+
+Review U...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="extension_api.cc" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/extension_api.cc?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+extension_api.cc</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/extension_api.cc?revision=149819&amp;view=markup" title="View file contents"><strong>149819</strong></a></td>
+
+<td>&nbsp;7 days</td>
+<td>&nbsp;vabr@chromium.org</td>
+
+
+<td>&nbsp;Correct const accessors in base/values.(h|cc), Part II (ListValue)
+
+For problem ...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="extension_api.h" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/extension_api.h?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+extension_api.h</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/extension_api.h?revision=146163&amp;view=markup" title="View file contents"><strong>146163</strong></a></td>
+
+<td>&nbsp;4 weeks</td>
+<td>&nbsp;ajwong@chromium.org</td>
+
+
+<td>&nbsp;Remove the rest of #pragma once in one big CL.
+
+For context see this thread:
+  h...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="extension_api_unittest.cc" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/extension_api_unittest.cc?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+extension_api_unittest.cc</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/extension_api_unittest.cc?revision=149819&amp;view=markup" title="View file contents"><strong>149819</strong></a></td>
+
+<td>&nbsp;7 days</td>
+<td>&nbsp;vabr@chromium.org</td>
+
+
+<td>&nbsp;Correct const accessors in base/values.(h|cc), Part II (ListValue)
+
+For problem ...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="file_browser_handler.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/file_browser_handler.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+file_browser_handler.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/file_browser_handler.json?revision=149101&amp;view=markup" title="View file contents"><strong>149101</strong></a></td>
+
+<td>&nbsp;10 days</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Extensions Docs Server: Integration testing
+
+A test that makes sure all the page...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="file_browser_handler_internal.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/file_browser_handler_internal.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+file_browser_handler_internal.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/file_browser_handler_internal.json?revision=142683&amp;view=markup" title="View file contents"><strong>142683</strong></a></td>
+
+<td>&nbsp;7 weeks</td>
+<td>&nbsp;tbarzic@chromium.org</td>
+
+
+<td>&nbsp;Reland 142611 - Return FileEntry object from fileBrowserHandler.selectFile funct...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="file_browser_private.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/file_browser_private.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+file_browser_private.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/file_browser_private.json?revision=150806&amp;view=markup" title="View file contents"><strong>150806</strong></a></td>
+
+<td>&nbsp;30 hours</td>
+<td>&nbsp;yoshiki@chromium.org</td>
+
+
+<td>&nbsp;FileBrowser: Hide DriveApps on Open-with menu when the selected file is not on d...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="file_system.idl" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/file_system.idl?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+file_system.idl</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/file_system.idl?revision=150515&amp;view=markup" title="View file contents"><strong>150515</strong></a></td>
+
+<td>&nbsp;2 days</td>
+<td>&nbsp;thorogood@chromium.org</td>
+
+
+<td>&nbsp;Updates file type selector for fileSystem API
+
+Allows developers to explicitly s...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="font_settings.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/font_settings.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+font_settings.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/font_settings.json?revision=147621&amp;view=markup" title="View file contents"><strong>147621</strong></a></td>
+
+<td>&nbsp;3 weeks</td>
+<td>&nbsp;falken@chromium.org</td>
+
+
+<td>&nbsp;Move Font Settings API out of experimental.
+
+BUG=114148
+TEST=browser_tests and e...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="history.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/history.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+history.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/history.json?revision=129086&amp;view=markup" title="View file contents"><strong>129086</strong></a></td>
+
+<td>&nbsp;4 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Add callbacks to history.addUrl and history.deleteUrl
+
+Added optional callbacks ...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="i18n.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/i18n.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+i18n.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/i18n.json?revision=149045&amp;view=markup" title="View file contents"><strong>149045</strong></a></td>
+
+<td>&nbsp;11 days</td>
+<td>&nbsp;mitchellwrosen@chromium.org</td>
+
+
+<td>&nbsp;Use JSON schema compiler in i18n API code
+
+TBR=<a href="mailto:ben&#64;chromium.org">ben&#64;chromium.org</a>
+
+BUG=121174
+
+Rev...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="idle.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/idle.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+idle.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/idle.json?revision=124878&amp;view=markup" title="View file contents"><strong>124878</strong></a></td>
+
+<td>&nbsp;5 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Allow comments in extension config files.
+
+Added a script to remove comments fro...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="input_ime.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/input_ime.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+input_ime.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/input_ime.json?revision=149101&amp;view=markup" title="View file contents"><strong>149101</strong></a></td>
+
+<td>&nbsp;10 days</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Extensions Docs Server: Integration testing
+
+A test that makes sure all the page...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="input_method_private.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/input_method_private.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+input_method_private.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/input_method_private.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="managed_mode_private.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/managed_mode_private.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+managed_mode_private.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/managed_mode_private.json?revision=142720&amp;view=markup" title="View file contents"><strong>142720</strong></a></td>
+
+<td>&nbsp;7 weeks</td>
+<td>&nbsp;bauerb@chromium.org</td>
+
+
+<td>&nbsp;Add ManagedModePolicyProvider and extension API to get and set policies.
+
+
+BUG=1...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="management.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/management.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+management.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/management.json?revision=151113&amp;view=markup" title="View file contents"><strong>151113</strong></a></td>
+
+<td>&nbsp;2 hours</td>
+<td>&nbsp;mitchellwrosen@chromium.org</td>
+
+
+<td>&nbsp;Use JSON schema compiler in chrome.management code
+
+BUG=121174
+
+
+Review URL: htt...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="media_player_private.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/media_player_private.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+media_player_private.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/media_player_private.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="metrics_private.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/metrics_private.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+metrics_private.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/metrics_private.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="omnibox.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/omnibox.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+omnibox.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/omnibox.json?revision=124878&amp;view=markup" title="View file contents"><strong>124878</strong></a></td>
+
+<td>&nbsp;5 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Allow comments in extension config files.
+
+Added a script to remove comments fro...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="page_action.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/page_action.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+page_action.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/page_action.json?revision=137065&amp;view=markup" title="View file contents"><strong>137065</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Add optional callback to (browser|page)Action.setIcon
+
+BrowserActionApiTest.Dyna...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="page_actions.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/page_actions.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+page_actions.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/page_actions.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="page_capture.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/page_capture.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+page_capture.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/page_capture.json?revision=147890&amp;view=markup" title="View file contents"><strong>147890</strong></a></td>
+
+<td>&nbsp;2 weeks</td>
+<td>&nbsp;hebert.christopherj@chromium.org</td>
+
+
+<td>&nbsp;Extension Docs Server Version 2: Various fixes.
+
+Callbacks no longer appear when...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="permissions.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/permissions.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+permissions.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/permissions.json?revision=124878&amp;view=markup" title="View file contents"><strong>124878</strong></a></td>
+
+<td>&nbsp;5 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Allow comments in extension config files.
+
+Added a script to remove comments fro...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="privacy.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/privacy.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+privacy.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/privacy.json?revision=136588&amp;view=markup" title="View file contents"><strong>136588</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;bryeung@chromium.org</td>
+
+
+<td>&nbsp;Make all extension api types fully qualified.
+
+BUG=123073
+TEST= (unit_tests --gt...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="proxy.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/proxy.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+proxy.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/proxy.json?revision=136588&amp;view=markup" title="View file contents"><strong>136588</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;bryeung@chromium.org</td>
+
+
+<td>&nbsp;Make all extension api types fully qualified.
+
+BUG=123073
+TEST= (unit_tests --gt...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="runtime.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/runtime.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+runtime.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/runtime.json?revision=148956&amp;view=markup" title="View file contents"><strong>148956</strong></a></td>
+
+<td>&nbsp;11 days</td>
+<td>&nbsp;mpcomplete@chromium.org</td>
+
+
+<td>&nbsp;Fix the chrome.runtime.onInstalled docs to note it fires for updates as well.
+
+B...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="script_badge.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/script_badge.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+script_badge.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/script_badge.json?revision=146930&amp;view=markup" title="View file contents"><strong>146930</strong></a></td>
+
+<td>&nbsp;3 weeks</td>
+<td>&nbsp;jyasskin@chromium.org</td>
+
+
+<td>&nbsp;Implement scriptBadge.requestToAct.
+
+Currently it just shows the script badge an...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="serial.idl" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/serial.idl?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+serial.idl</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/serial.idl?revision=150186&amp;view=markup" title="View file contents"><strong>150186</strong></a></td>
+
+<td>&nbsp;3 days</td>
+<td>&nbsp;miket@chromium.org</td>
+
+
+<td>&nbsp;Move serial out of experimental.
+
+This CL is unremarkable except that I figured ...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="socket.idl" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/socket.idl?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+socket.idl</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/socket.idl?revision=150190&amp;view=markup" title="View file contents"><strong>150190</strong></a></td>
+
+<td>&nbsp;3 days</td>
+<td>&nbsp;thorogood@chromium.org</td>
+
+
+<td>&nbsp;Adds socket.getInfo to the socket API
+
+Allows clients to retrieve the state of a...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="storage.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/storage.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+storage.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/storage.json?revision=149837&amp;view=markup" title="View file contents"><strong>149837</strong></a></td>
+
+<td>&nbsp;7 days</td>
+<td>&nbsp;joaodasilva@chromium.org</td>
+
+
+<td>&nbsp;Disable the managed storage API behind a flag for M22.
+
+BUG=108992
+
+
+Review URL:...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="system_private.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/system_private.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+system_private.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/system_private.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="tabs.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/tabs.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+tabs.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/tabs.json?revision=149631&amp;view=markup" title="View file contents"><strong>149631</strong></a></td>
+
+<td>&nbsp;8 days</td>
+<td>&nbsp;kalman@chromium.org</td>
+
+
+<td>&nbsp;Make ActiveTabPermissionManager also grant the tabs permission for
+that tab; onl...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="terminal_private.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/terminal_private.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+terminal_private.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/terminal_private.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="test.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/test.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+test.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/test.json?revision=145505&amp;view=markup" title="View file contents"><strong>145505</strong></a></td>
+
+<td>&nbsp;5 weeks</td>
+<td>&nbsp;kalman@chromium.org</td>
+
+
+<td>&nbsp;Expose the extension/app's id on chrome.runtime.id and, and make this
+an unprivi...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="top_sites.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/top_sites.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+top_sites.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/top_sites.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="tts.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/tts.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+tts.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/tts.json?revision=134209&amp;view=markup" title="View file contents"><strong>134209</strong></a></td>
+
+<td>&nbsp;3 months</td>
+<td>&nbsp;dtseng@chromium.org</td>
+
+
+<td>&nbsp;Uses a system-wide notion of isSpeaking in the Mac extension TTS api.
+
+
+BUG=none...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="tts_engine.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/tts_engine.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+tts_engine.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/tts_engine.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="types.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/types.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+types.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/types.json?revision=146093&amp;view=markup" title="View file contents"><strong>146093</strong></a></td>
+
+<td>&nbsp;4 weeks</td>
+<td>&nbsp;bauerb@chromium.org</td>
+
+
+<td>&nbsp;Add "regular_only" scope to allowed scopes for ChromeSetting.clear().
+
+
+BUG=1363...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="wallpaper_private.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/wallpaper_private.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+wallpaper_private.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/wallpaper_private.json?revision=148400&amp;view=markup" title="View file contents"><strong>148400</strong></a></td>
+
+<td>&nbsp;2 weeks</td>
+<td>&nbsp;bshe@chromium.org</td>
+
+
+<td>&nbsp;Wallpaper manager backend APIs
+
+
+BUG=118684
+TEST=none
+
+
+Review URL: <a href="https://chro">https://chro</a>...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="web_navigation.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/web_navigation.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+web_navigation.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/web_navigation.json?revision=148204&amp;view=markup" title="View file contents"><strong>148204</strong></a></td>
+
+<td>&nbsp;2 weeks</td>
+<td>&nbsp;jochen@chromium.org</td>
+
+
+<td>&nbsp;Revert "Revert 148074 - Pass the render process id to the FrameNavigationState."...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="web_request.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/web_request.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+web_request.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/web_request.json?revision=138481&amp;view=markup" title="View file contents"><strong>138481</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;vabr@chromium.org</td>
+
+
+<td>&nbsp;Making webRequest.addEventListener internal
+
+Changes required to hide addEventLi...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="web_request_internal.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/web_request_internal.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+web_request_internal.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/web_request_internal.json?revision=138481&amp;view=markup" title="View file contents"><strong>138481</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;vabr@chromium.org</td>
+
+
+<td>&nbsp;Making webRequest.addEventListener internal
+
+Changes required to hide addEventLi...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="web_socket_proxy_private.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/web_socket_proxy_private.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+web_socket_proxy_private.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/web_socket_proxy_private.json?revision=136747&amp;view=markup" title="View file contents"><strong>136747</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;cduvall@chromium.org</td>
+
+
+<td>&nbsp;Files generated by the JSON schema compiler are named incorrectly
+
+Files are now...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="webstore.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/webstore.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+webstore.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/webstore.json?revision=127930&amp;view=markup" title="View file contents"><strong>127930</strong></a></td>
+
+<td>&nbsp;4 months</td>
+<td>&nbsp;kalman@chromium.org</td>
+
+
+<td>&nbsp;Convert the webstore API to use the schema_generated_bindings infrastructure.
+
+
+...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_odd">
+<td colspan="2">
+
+<a name="webstore_private.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/webstore_private.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+webstore_private.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/webstore_private.json?revision=150455&amp;view=markup" title="View file contents"><strong>150455</strong></a></td>
+
+<td>&nbsp;2 days</td>
+<td>&nbsp;mihaip@chromium.org</td>
+
+
+<td>&nbsp;Remove chrome.webstorePrivate.silentlyInstall.
+
+BUG=117168
+
+Review URL: https://...</td>
+
+
+
+</tr>
+
+<tr class="vc_row_even">
+<td colspan="2">
+
+<a name="windows.json" href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/windows.json?view=log" title="View file revision log">
+
+<img src="/viewvc/*docroot*/images/text.png" alt="" width="16" height="16" />
+windows.json</a>
+
+</td>
+
+
+
+<td>&nbsp;<a href="/viewvc/chrome/trunk/src/chrome/common/extensions/api/windows.json?revision=140947&amp;view=markup" title="View file contents"><strong>140947</strong></a></td>
+
+<td>&nbsp;2 months</td>
+<td>&nbsp;jeremya@chromium.org</td>
+
+
+<td>&nbsp;Remove chrome.windows.* support for platform apps.
+
+Use chrome.appWindow.* inste...</td>
+
+
+
+</tr>
+
+</tbody>
+</table>
+
+</div>
+
+
+
+<hr />
+<table>
+<tr>
+<td><address><a href="mailto:cvs-admin@insert.your.domain.here">No admin address has been configured</a></address></td>
+<td style="text-align: right;"><strong><a href="/viewvc/*docroot*/help_dirview.html">ViewVC Help</a></strong></td>
+</tr>
+<tr>
+<td>Powered by <a href="http://viewvc.tigris.org/">ViewVC 1.0.9</a></td>
+<td style="text-align: right;">&nbsp;</td>
+</tr>
+</table>
+</body>
+</html>
+
+
diff --git a/chrome/common/extensions/docs/server2/test_data/file_system/stat_result.json b/chrome/common/extensions/docs/server2/test_data/file_system/stat_result.json
new file mode 100644
index 0000000..4c2bc75
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/file_system/stat_result.json
@@ -0,0 +1,89 @@
+{
+  "extension_api.h": "146163",
+  "_permission_features.json": "150186",
+  "file_system.idl": "150515",
+  "input_ime.json": "149101",
+  "context_menus.json": "140112",
+  "app.json": "133722",
+  "browser_action.json": "148043",
+  "test.json": "145505",
+  "chromeos_info_private.json": "150737",
+  "font_settings.json": "147621",
+  "management.json": "151113",
+  "bookmark_manager.json": "140864",
+  "experimental_commands.json": "149147",
+  "web_navigation.json": "148204",
+  "echo_private.json": "136747",
+  "i18n.json": "149045",
+  "storage.json": "149837",
+  "experimental_media_galleries.idl": "150164",
+  "web_request_internal.json": "138481",
+  "app_window.idl": "147710",
+  "web_request.json": "138481",
+  "windows.json": "140947",
+  "terminal_private.json": "136747",
+  "experimental_rlz.json": "136747",
+  "system_private.json": "136747",
+  "content_settings.json": "136747",
+  "file_browser_handler.json": "149101",
+  "devtools.json": "124878",
+  "experimental_dns.idl": "136747",
+  "experimental_speech_input.json": "136747",
+  "api.gyp": "151113",
+  "experimental_identity.idl": "147523",
+  "runtime.json": "148956",
+  "alarms.idl": "143049",
+  "declarative_web_request.json": "150421",
+  "web_socket_proxy_private.json": "136747",
+  "devtools/": "149291",
+  "experimental_idltest.idl": "138707",
+  "script_badge.json": "146930",
+  "extension_api_unittest.cc": "149819",
+  "experimental_offscreen_tabs.json": "136747",
+  "events.json": "143168",
+  "tts.json": "134209",
+  "metrics_private.json": "136747",
+  "experimental_usb.idl": "144221",
+  "webstore.json": "127930",
+  "managed_mode_private.json": "142720",
+  "downloads.idl": "146201",
+  "serial.idl": "150186",
+  "page_capture.json": "147890",
+  "_manifest_features.json": "150827",
+  "browsing_data.json": "142133",
+  "top_sites.json": "136747",
+  "page_actions.json": "136747",
+  "page_action.json": "137065",
+  "experimental_discovery.idl": "149243",
+  "history.json": "129086",
+  "bookmarks.json": "149307",
+  "permissions.json": "124878",
+  "experimental_app.json": "148848",
+  "media_player_private.json": "136747",
+  "experimental_input_virtual_keyboard.json": "138223",
+  "privacy.json": "136588",
+  "tabs.json": "149631",
+  "tts_engine.json": "136747",
+  "experimental_bluetooth.idl": "150898",
+  "idle.json": "124878",
+  "input_method_private.json": "136747",
+  "experimental_accessibility.json": "136747",
+  "push_messaging.idl": "149536",
+  "webstore_private.json": "150455",
+  "extension.json": "146855",
+  "extension_api.cc": "149819",
+  "file_browser_private.json": "150806",
+  "cloud_print_private.json": "149303",
+  "omnibox.json": "124878",
+  "experimental_processes.json": "137690",
+  "proxy.json": "136588",
+  "file_browser_handler_internal.json": "142683",
+  "types.json": "146093",
+  "experimental_record.json": "145342",
+  "debugger.json": "147894",
+  "socket.idl": "150190",
+  "OWNERS": "150079",
+  "cookies.json": "124878",
+  "experimental_infobars.json": "136747",
+  "wallpaper_private.json": "148400"
+}
diff --git a/chrome/common/extensions/docs/server2/test_data/file_system/test1.txt b/chrome/common/extensions/docs/server2/test_data/file_system/test1.txt
new file mode 100644
index 0000000..a5bce3f
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/file_system/test1.txt
@@ -0,0 +1 @@
+test1
diff --git a/chrome/common/extensions/docs/server2/test_data/file_system/test2.txt b/chrome/common/extensions/docs/server2/test_data/file_system/test2.txt
new file mode 100644
index 0000000..180cf83
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/file_system/test2.txt
@@ -0,0 +1 @@
+test2
diff --git a/chrome/common/extensions/docs/server2/test_data/file_system/test3.txt b/chrome/common/extensions/docs/server2/test_data/file_system/test3.txt
new file mode 100644
index 0000000..df6b0d2
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/file_system/test3.txt
@@ -0,0 +1 @@
+test3
diff --git a/chrome/common/extensions/docs/server2/test_data/github_file_system/apps_samples.zip b/chrome/common/extensions/docs/server2/test_data/github_file_system/apps_samples.zip
new file mode 100644
index 0000000..e6af3dd
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/github_file_system/apps_samples.zip
Binary files differ
diff --git a/chrome/common/extensions/docs/server2/test_data/github_file_system/expected_list.json b/chrome/common/extensions/docs/server2/test_data/github_file_system/expected_list.json
new file mode 100644
index 0000000..78ef296
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/github_file_system/expected_list.json
@@ -0,0 +1,41 @@
+{
+  "/": [
+    ".gitignore", 
+    "README.md", 
+    "analytics/", 
+    "appsquare/", 
+    "browser-tag/", 
+    "calculator/", 
+    "camera-capture/", 
+    "clock/", 
+    "context-menu/", 
+    "diff/", 
+    "dojo/", 
+    "filesystem-access/", 
+    "frameless-window/", 
+    "gdocs/", 
+    "hello-world/", 
+    "identity/", 
+    "io2012-presentation/", 
+    "ioio/", 
+    "mdns-browser/", 
+    "mini-code-edit/", 
+    "nodejs-net.coffee", 
+    "sandbox/", 
+    "sandboxed-content/", 
+    "serial-control-signals/", 
+    "serial/", 
+    "servo/", 
+    "singleton/", 
+    "storage/", 
+    "telnet/", 
+    "text-editor/", 
+    "udp/", 
+    "usb/", 
+    "weather/", 
+    "webgl/", 
+    "webintents/", 
+    "windows/", 
+    "zephyr_hxm/"
+  ]
+}
diff --git a/chrome/common/extensions/docs/server2/test_data/github_file_system/expected_read.txt b/chrome/common/extensions/docs/server2/test_data/github_file_system/expected_read.txt
new file mode 100644
index 0000000..6f17cf8
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/github_file_system/expected_read.txt
@@ -0,0 +1,10 @@
+/**
+ * Listens for the app launching then creates the window
+ *
+ * @see http://developer.chrome.com/trunk/apps/app.runtime.html
+ * @see http://developer.chrome.com/trunk/apps/app.window.html
+ */
+chrome.app.runtime.onLaunched.addListener(function() {
+  chrome.app.window.create('main.html',
+    {width: 480, height: 225});
+});
diff --git a/chrome/common/extensions/docs/server2/test_data/samples_data_source/expected.json b/chrome/common/extensions/docs/server2/test_data/samples_data_source/expected.json
new file mode 100644
index 0000000..c6ff5fb
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/samples_data_source/expected.json
@@ -0,0 +1,17 @@
+[
+  {
+    "api_calls": [
+      { "name": "bobaloo" },
+      { "name": "jimmy.bobaloo.loopoo" },
+      { "name": "jimmy.jimmy" },
+      { "name": "hallo.bobaloo" }
+    ]
+  },
+  {
+    "api_calls": [
+      { "name": "timmy.bobaloo.affa" },
+      { "name": "jimmy.baloo.loopoo" },
+      { "name": "jimmy.jimmy" }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/docs/server2/test_data/samples_data_source/samples.json b/chrome/common/extensions/docs/server2/test_data/samples_data_source/samples.json
new file mode 100644
index 0000000..3dbdc4d
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/samples_data_source/samples.json
@@ -0,0 +1,24 @@
+[
+  {
+    "api_calls": [
+      { "name": "bobaloo" },
+      { "name": "jimmy.bobaloo.loopoo" },
+      { "name": "jimmy.jimmy" },
+      { "name": "hallo.bobaloo" }
+    ]
+  },
+  {
+    "api_calls": [
+      { "name": "timmy.bobaloo.affa" },
+      { "name": "jimmy.baloo.loopoo" },
+      { "name": "jimmy.jimmy" }
+    ]
+  },
+  {
+    "api_calls": [
+      { "name": "torta.lamb.paco" },
+      { "name": "jimmy.baloo.loopoo" },
+      { "name": "jimmy.jimmy" }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/docs/server2/test_data/sidenav_data_source/test_sidenav.json b/chrome/common/extensions/docs/server2/test_data/sidenav_data_source/test_sidenav.json
new file mode 100644
index 0000000..6860f8e
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/sidenav_data_source/test_sidenav.json
@@ -0,0 +1,32 @@
+[
+  {
+    "title": "Bob",
+    "fileName": "www.bob.com"
+  },
+  {
+    "title": "Jim",
+    "fileName": "www.jim.com",
+    "items": [
+      {
+        "title": "A",
+        "fileName": "www.a.com"
+      },
+      {
+        "title": "B",
+        "fileName": "www.b.com",
+        "items": [
+          {
+            "title": "C",
+            "fileName": "www.c.com",
+            "items": [
+              {
+                "title": "D",
+                "fileName": "www.d.com"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/input.json b/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/input.json
new file mode 100644
index 0000000..e089d8b
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/input.json
@@ -0,0 +1,7 @@
+{
+  "name": {
+    "first": "Bob",
+    "last": "Jones"
+  },
+  "part": "elbow"
+}
diff --git a/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/name_tmpl.html b/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/name_tmpl.html
new file mode 100644
index 0000000..50f86c0
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/name_tmpl.html
@@ -0,0 +1 @@
+{{name.first}} {{name.last}}
diff --git a/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/test_expected.html b/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/test_expected.html
new file mode 100644
index 0000000..b9b52dd
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/test_expected.html
@@ -0,0 +1 @@
+Hello Bob Jones how is your elbow?
diff --git a/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/test_tmpl.html b/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/test_tmpl.html
new file mode 100644
index 0000000..f755402
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/template_data_source/partials/test_tmpl.html
@@ -0,0 +1 @@
+Hello {{+name_tmpl}} how is your {{part}}?
diff --git a/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test1.json b/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test1.json
new file mode 100644
index 0000000..ac0fdb3
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test1.json
@@ -0,0 +1,6 @@
+{
+  "arg1": "This",
+  "arg2": "is",
+  "arg3": "a",
+  "arg4": "test"
+}
diff --git a/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test1_expected.html b/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test1_expected.html
new file mode 100644
index 0000000..484ba93
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test1_expected.html
@@ -0,0 +1 @@
+This is a test.
diff --git a/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test1_tmpl.html b/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test1_tmpl.html
new file mode 100644
index 0000000..5b05713
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test1_tmpl.html
@@ -0,0 +1 @@
+{{apis.arg1}} {{apis.arg2}} {{apis.arg3}} {{apis.arg4}}.
diff --git a/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test2.json b/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test2.json
new file mode 100644
index 0000000..3d4021c
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test2.json
@@ -0,0 +1,12 @@
+{
+  "arg1": "This",
+  "arg2": {
+    "arg": 2
+  },
+  "arg3": "test",
+  "arg4": [
+    "incredible",
+    "amazing",
+    "awesome"
+  ]
+}
diff --git a/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test2_expected.html b/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test2_expected.html
new file mode 100644
index 0000000..b456ad0
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test2_expected.html
@@ -0,0 +1 @@
+This 2nd test is an incredible, amazing, awesome, test.
diff --git a/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test2_tmpl.html b/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test2_tmpl.html
new file mode 100644
index 0000000..413a131
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/template_data_source/render/test2_tmpl.html
@@ -0,0 +1 @@
+{{apis.arg1}} {{apis.arg2.arg}}nd {{apis.arg3}} is an {{#apis.arg4}}{{@}}, {{/}}test.
diff --git a/chrome/common/extensions/docs/server2/test_data/template_data_source/simple/test1.html b/chrome/common/extensions/docs/server2/test_data/template_data_source/simple/test1.html
new file mode 100644
index 0000000..0075e9c
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/template_data_source/simple/test1.html
@@ -0,0 +1 @@
+Hello {{test}}
diff --git a/chrome/common/extensions/docs/server2/test_data/template_data_source/simple/test2.html b/chrome/common/extensions/docs/server2/test_data/template_data_source/simple/test2.html
new file mode 100644
index 0000000..1a78ab1
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/template_data_source/simple/test2.html
@@ -0,0 +1 @@
+Hello second {{test}}
diff --git a/chrome/common/extensions/docs/server2/test_data/test_json/expected_nodoc.json b/chrome/common/extensions/docs/server2/test_data/test_json/expected_nodoc.json
new file mode 100644
index 0000000..1e3d6cd
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/test_json/expected_nodoc.json
@@ -0,0 +1,30 @@
+[
+  {
+    "name": "B",
+    "list": [
+      {
+        "name": "B2"
+      }
+    ]
+  },
+  {
+    "name": "D",
+    "nodoc": false
+  },
+  {
+    "name": "E",
+    "items1": [
+      {
+        "name": "E1",
+        "items": [
+          {
+            "name": "E1.3"
+          }
+        ]
+      },
+      {
+        "name": "E2"
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/docs/server2/test_data/test_json/expected_test_file.json b/chrome/common/extensions/docs/server2/test_data/test_json/expected_test_file.json
new file mode 100644
index 0000000..0d91661
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/test_json/expected_test_file.json
@@ -0,0 +1,221 @@
+{
+  "functions": [
+    {
+      "name": "get",
+      "parent_name": null,
+      "callback": {
+        "simple_type": {
+          "simple_type": "function"
+        },
+        "last": true,
+        "name": "callback",
+        "parameters": [
+          {
+            "functions": [],
+            "parameters": [],
+            "returns": null,
+            "last": true,
+            "name": "results",
+            "parent_name": "callback",
+            "id": "property-callback-results",
+            "array": {
+              "functions": [],
+              "parameters": [],
+              "returns": null,
+              "name": "resultsElement",
+              "parent_name": "results",
+              "id": "property-results-resultsElement",
+              "link": {
+                "text": "TypeA",
+                "href": "#type-TypeA"
+              },
+              "optional": false,
+              "properties": [],
+              "description": null
+            },
+            "optional": false,
+            "properties": [],
+            "description": null
+          }
+        ],
+        "optional": false,
+        "description": null
+      },
+      "returns": null,
+      "parameters": [
+        {
+          "functions": [],
+          "parameters": [],
+          "returns": null,
+          "name": "a",
+          "parent_name": "get",
+          "id": "property-get-a",
+          "choices": [
+            {
+              "functions": [],
+              "parameters": [],
+              "returns": null,
+              "simple_type": "string",
+              "name": "a",
+              "parent_name": "a",
+              "id": "property-a-a",
+              "optional": true,
+              "properties": [],
+              "description": null
+            },
+            {
+              "functions": [],
+              "parameters": [],
+              "returns": null,
+              "last": true,
+              "name": "a",
+              "parent_name": "a",
+              "id": "property-a-a",
+              "array": {
+                "functions": [],
+                "parameters": [],
+                "returns": null,
+                "simple_type": "string",
+                "name": "aElement",
+                "parent_name": "a",
+                "id": "property-a-aElement",
+                "optional": false,
+                "properties": [],
+                "description": null
+              },
+              "optional": true,
+              "properties": [],
+              "description": null
+            }
+          ],
+          "optional": false,
+          "properties": [],
+          "description": "a param"
+        },
+        {
+          "simple_type": {
+            "simple_type": "function"
+          },
+          "last": true,
+          "name": "callback",
+          "parameters": [
+            {
+              "functions": [],
+              "parameters": [],
+              "returns": null,
+              "last": true,
+              "name": "results",
+              "parent_name": "callback",
+              "id": "property-callback-results",
+              "array": {
+                "functions": [],
+                "parameters": [],
+                "returns": null,
+                "name": "resultsElement",
+                "parent_name": "results",
+                "id": "property-results-resultsElement",
+                "link": {
+                  "text": "TypeA",
+                  "href": "#type-TypeA"
+                },
+                "optional": false,
+                "properties": [],
+                "description": null
+              },
+              "optional": false,
+              "properties": [],
+              "description": null
+            }
+          ],
+          "optional": false,
+          "description": null
+        }
+      ],
+      "id": "method-get",
+      "description": "Gets stuff."
+    }
+  ],
+  "properties": [],
+  "name": "tester",
+  "types": [
+    {
+      "properties": [
+        {
+          "functions": [],
+          "parameters": [],
+          "returns": null,
+          "name": "b",
+          "parent_name": "TypeA",
+          "id": "property-TypeA-b",
+          "array": {
+            "functions": [],
+            "parameters": [],
+            "returns": null,
+            "name": "bElement",
+            "parent_name": "b",
+            "id": "property-b-bElement",
+            "link": {
+              "text": "TypeA",
+              "href": "#type-TypeA"
+            },
+            "optional": false,
+            "properties": [],
+            "description": null
+          },
+          "optional": true,
+          "properties": [],
+          "description": "List of TypeA."
+        }
+      ],
+      "name": "TypeA",
+      "simple_type": "object",
+      "functions": [],
+      "events": [],
+      "id": "type-TypeA",
+      "description": "A cool thing."
+    }
+  ],
+  "events": [
+    {
+      "parent_name": null,
+      "callback": null,
+      "name": "EventA",
+      "parameters": [
+        {
+          "functions": [],
+          "parameters": [],
+          "returns": null,
+          "simple_type": "string",
+          "name": "id",
+          "parent_name": "EventA",
+          "id": "property-EventA-id",
+          "optional": false,
+          "properties": [],
+          "description": null
+        },
+        {
+          "functions": [],
+          "parameters": [],
+          "returns": null,
+          "last": true,
+          "name": "bookmark",
+          "parent_name": "EventA",
+          "id": "property-EventA-bookmark",
+          "link": {
+            "text": "TypeA",
+            "href": "#type-TypeA"
+          },
+          "optional": false,
+          "properties": [],
+          "description": null
+        }
+      ],
+      "supportsRules": false,
+      "filters": [],
+      "conditions": [],
+      "id": "event-EventA",
+      "actions": [],
+      "description": "A cool event."
+    }
+  ]
+}
diff --git a/chrome/common/extensions/docs/server2/test_data/test_json/fake_data_source.json b/chrome/common/extensions/docs/server2/test_data/test_json/fake_data_source.json
new file mode 100644
index 0000000..df27193
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/test_json/fake_data_source.json
@@ -0,0 +1,96 @@
+{
+  "baz": {
+    "events": [
+      { "name": "baz_e1" },
+      { "name": "baz_e2" },
+      { "name": "baz_e3" }
+    ],
+    "functions": [
+      { "name": "baz_f1" },
+      { "name": "baz_f2" },
+      { "name": "baz_f3" }
+    ],
+    "types": [
+      { "name": "baz_t1" },
+      { "name": "baz_t2" },
+      { "name": "baz_t3" }
+    ],
+    "properties": [
+      { "name": "baz_p1" },
+      { "name": "baz_p2" },
+      { "name": "baz_p3" }
+    ]
+  },
+  "bar.bon": {
+    "events": [
+      { "name": "bar_bon_e1" },
+      { "name": "bar_bon_e2" },
+      { "name": "bar_bon_e3" }
+    ],
+    "functions": [
+      { "name": "bar_bon_f1" },
+      { "name": "bar_bon_f2" },
+      { "name": "bar_bon_f3" }
+    ],
+    "types": [
+      { "name": "bar_bon_t1" },
+      { "name": "bar_bon_t2" },
+      { "name": "bar_bon_t3" }
+    ],
+    "properties": [
+      { "name": "bar_bon_p1" },
+      { "name": "bar_bon_p2" },
+      { "name": "bar_bon_p3" }
+    ]
+  },
+  "bar": {
+    "events": [
+      { "name": "bar_e1" },
+      { "name": "bar_e2" },
+      { "name": "bar_e3" }
+    ],
+    "functions": [
+      { "name": "bar_f1" },
+      { "name": "bar_f2" },
+      { "name": "bar_f3" }
+    ],
+    "types": [
+      { "name": "bar_t1" },
+      { "name": "bar_t2" },
+      { "name": "bar_t3" },
+      { "name": "bon" }
+    ],
+    "properties": [
+      { "name": "bar_p1" },
+      { "name": "bar_p2" },
+      { "name": "bar_p3" }
+    ]
+  },
+  "foo": {
+    "events": [
+      { "name": "foo_e1" },
+      { "name": "foo_e2" },
+      { "name": "foo_e3" }
+    ],
+    "functions": [
+      { "name": "foo_f1" },
+      { "name": "foo_f2" },
+      { "name": "foo_f3" }
+    ],
+    "types": [
+      { "name": "foo_t1" },
+      { "name": "foo_t2" },
+      {
+        "name": "foo_t3",
+        "events": [
+          { "name": "foo_t3_e1" }
+        ]
+      }
+    ],
+    "properties": [
+      { "name": "foo_p1" },
+      { "name": "foo_p2" },
+      { "name": "foo_p3" }
+    ]
+  }
+}
diff --git a/chrome/common/extensions/docs/server2/test_data/test_json/nodoc_test.json b/chrome/common/extensions/docs/server2/test_data/test_json/nodoc_test.json
new file mode 100644
index 0000000..53b9059
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/test_json/nodoc_test.json
@@ -0,0 +1,62 @@
+[
+  {
+    "name": "A",
+    "nodoc": true
+  },
+  {
+    "name": "B",
+    "list": [
+      {
+        "name": "B1",
+        "nodoc": true
+      },
+      {
+        "name": "B2"
+      },
+      {
+        "name": "B3",
+        "nodoc": true
+      }
+    ]
+  },
+  {
+    "name": "C",
+    "nodoc": true
+  },
+  {
+    "name": "D",
+    "nodoc": false
+  },
+  {
+    "name": "E",
+    "dict": {
+      "name": "Ed",
+      "nodoc": true
+    },
+    "items1": [
+      {
+        "name": "E1",
+        "items": [
+          {
+            "name": "E1.1",
+            "nodoc": true
+          },
+          {
+            "name": "E1.2",
+            "nodoc": true
+          },
+          {
+            "name": "E1.3"
+          }
+        ]
+      },
+      {
+        "name": "E2"
+      },
+      {
+        "name": "E3",
+        "nodoc": true
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/docs/server2/test_data/test_json/ref_test.json b/chrome/common/extensions/docs/server2/test_data/test_json/ref_test.json
new file mode 100644
index 0000000..6e07fe1
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/test_json/ref_test.json
@@ -0,0 +1,52 @@
+[
+  {
+    "namespace": "ref_test",
+    "types": [
+      {
+        "id": "type1",
+        "type": "string",
+        "description": "$ref:type2"
+      },
+      {
+        "id": "type2",
+        "type": "string",
+        "description": "A $ref:type3, or $ref:type2"
+      },
+      {
+        "id": "type3",
+        "type": "string",
+        "description": "$ref:other.type2 != $ref:ref_test.type2"
+      }
+    ],
+    "events": [
+      {
+        "name": "event1",
+        "type": "function",
+        "description": "We like $ref:type1",
+        "parameters": [
+          {
+            "name": "param1",
+            "type": "string"
+          }
+        ]
+      }
+    ],
+    "properties": {
+      "prop1": {
+        "$ref": "type3"
+      }
+    },
+    "functions": [
+      {
+        "name": "func1",
+        "type": "function",
+        "parameters": [
+          {
+            "name": "param1",
+            "type": "string"
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/docs/server2/test_data/test_json/ref_test_data_source.json b/chrome/common/extensions/docs/server2/test_data/test_json/ref_test_data_source.json
new file mode 100644
index 0000000..1121609
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/test_json/ref_test_data_source.json
@@ -0,0 +1,23 @@
+{
+  "ref_test": {
+    "types": [
+      { "name": "type1" },
+      { "name": "type2" },
+      { "name": "type3" }
+    ],
+    "events": [
+      { "name": "event1" }
+    ],
+    "properties": [
+      { "name": "prop1" }
+    ],
+    "functions": [
+      { "name": "func1" }
+    ]
+  },
+  "other": {
+    "types": [
+      { "name": "type2" }
+    ]
+  }
+}
diff --git a/chrome/common/extensions/docs/server2/test_data/test_json/test_file.json b/chrome/common/extensions/docs/server2/test_data/test_json/test_file.json
new file mode 100644
index 0000000..ea39b62
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/test_json/test_file.json
@@ -0,0 +1,54 @@
+[
+  {
+    "namespace": "tester",
+    "types": [
+      {
+        "id": "TypeA",
+        "type": "object",
+        "description": "A cool thing.",
+        "properties": {
+          "a": {"nodoc": true, "type": "string", "minimum": 0},
+          "b": {"type": "array", "optional": true, "items": {"$ref": "TypeA"}, "description": "List of TypeA."}
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "get",
+        "type": "function",
+        "description": "Gets stuff.",
+        "parameters": [
+          {
+            "name": "a",
+            "description": "a param",
+            "choices": [
+              {"type": "string"},
+              {"type": "array", "items": {"type": "string"}, "minItems": 1}
+            ]
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {"name": "results", "type": "array", "items": { "$ref": "TypeA"} }
+            ]
+          }
+        ]
+      }
+    ],
+    "events": [
+      {
+        "name": "EventA",
+        "type": "function",
+        "description": "A cool event.",
+        "parameters": [
+          {"type": "string", "name": "id"},
+          {
+            "$ref": "TypeA",
+            "name": "bookmark"
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/chrome/common/extensions/docs/server2/test_data/test_json/test_file_data_source.json b/chrome/common/extensions/docs/server2/test_data/test_json/test_file_data_source.json
new file mode 100644
index 0000000..13bd52a
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/test_data/test_json/test_file_data_source.json
@@ -0,0 +1,13 @@
+{
+  "tester": {
+    "types": [
+      { "name": "TypeA" }
+    ],
+    "functions": [
+      { "name": "get" }
+    ],
+    "events": [
+      { "name": "EventA" }
+    ]
+  }
+}
diff --git a/chrome/common/extensions/docs/server2/url_constants.py b/chrome/common/extensions/docs/server2/url_constants.py
new file mode 100644
index 0000000..ea1bb0d
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/url_constants.py
@@ -0,0 +1,19 @@
+# Copyright (c) 2012 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.
+
+GITHUB_URL = 'https://api.github.com/repos/GoogleChrome/chrome-app-samples'
+GITHUB_BASE = 'https://github.com/GoogleChrome/chrome-app-samples/tree/master'
+RAW_GITHUB_BASE = ('https://github.com/GoogleChrome/chrome-app-samples/raw/'
+                   'master')
+OMAHA_PROXY_URL = 'http://omahaproxy.appspot.com/json'
+SVN_URL = 'http://src.chromium.org/chrome'
+VIEWVC_URL = 'http://src.chromium.org/viewvc/chrome'
+SVN_TRUNK_URL = SVN_URL + '/trunk'
+SVN_BRANCH_URL = SVN_URL + '/branches'
+OPEN_ISSUES_CSV_URL = (
+    'http://code.google.com/p/chromium/issues/csv?can=1&'
+    'q=Hotlist%3DKnownIssue%20Feature%3DApps+is%3Aopen')
+CLOSED_ISSUES_CSV_URL = (
+    'http://code.google.com/p/chromium/issues/csv?can=1&'
+    'q=Hotlist%3DKnownIssue+Feature%3DApps+-is%3Aopen')
diff --git a/chrome/common/extensions/docs/static/css/api.css b/chrome/common/extensions/docs/static/css/api.css
new file mode 100644
index 0000000..d9b58a6
--- /dev/null
+++ b/chrome/common/extensions/docs/static/css/api.css
@@ -0,0 +1,38 @@
+/* Copyright (c) 2012 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.
+ */
+
+.type_name,
+.variable,
+.property {
+  font-style: italic;
+}
+
+.api_reference div.summary {
+  border: 1px solid #93B4D9;
+  font-family: "Courier New", courier, monospace;
+  padding: 0.5em 0.5em 0.5em 2em;
+  text-indent: -1.5em;
+  background-color: #CADEF4;
+  margin-top: 1em;
+}
+
+/* This style is used because types with functions prefix the function with the
+ * type name, using a lowercase first letter.
+ */
+.api_reference div.lower:first-letter {
+  text-transform: lowercase;
+}
+
+.api_reference div.description {
+  margin-left: 2em;
+}
+
+div.summary .subdued {
+  color: #7594B8;
+}
+
+.optional {
+  color: #7D7D7D;
+}
diff --git a/chrome/common/extensions/docs/static/css/index.css b/chrome/common/extensions/docs/static/css/index.css
new file mode 100644
index 0000000..dc561f5
--- /dev/null
+++ b/chrome/common/extensions/docs/static/css/index.css
@@ -0,0 +1,21 @@
+/* Copyright (c) 2012 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.
+ */
+
+#gc-pagecontent {
+  margin: 0;
+}
+
+#gc-pagecontent #index td {
+  padding: 6px 12px;
+}
+
+#howDoIStart {
+  width: 330px;
+}
+
+#howDoIStart ol {
+  margin: 0 0 1em 15px;
+  padding: 0;
+}
diff --git a/chrome/common/extensions/docs/static/css/prettify.css b/chrome/common/extensions/docs/static/css/prettify.css
new file mode 100644
index 0000000..bba79c7
--- /dev/null
+++ b/chrome/common/extensions/docs/static/css/prettify.css
@@ -0,0 +1,51 @@
+/* Pretty printing styles. Used with prettify.js. */
+
+/* SPAN elements with the classes below are added by prettyprint. */
+.pln { color: #000 }  /* plain text */
+
+@media screen {
+  .str { color: #080 }  /* string content */
+  .kwd { color: #008 }  /* a keyword */
+  .com { color: #800 }  /* a comment */
+  .typ { color: #606 }  /* a type name */
+  .lit { color: #066 }  /* a literal value */
+  /* punctuation, lisp open bracket, lisp close bracket */
+  .pun, .opn, .clo { color: #660 }
+  .tag { color: #008 }  /* a markup tag name */
+  .atn { color: #606 }  /* a markup attribute name */
+  .atv { color: #080 }  /* a markup attribute value */
+  .dec, .var { color: #606 }  /* a declaration; a variable name */
+  .fun { color: red }  /* a function name */
+}
+
+/* Use higher contrast and text-weight for printable form. */
+@media print, projection {
+  .str { color: #060 }
+  .kwd { color: #006; font-weight: bold }
+  .com { color: #600; font-style: italic }
+  .typ { color: #404; font-weight: bold }
+  .lit { color: #044 }
+  .pun, .opn, .clo { color: #440 }
+  .tag { color: #006; font-weight: bold }
+  .atn { color: #404 }
+  .atv { color: #060 }
+}
+
+pre.prettyprint { }
+
+/* Specify class=linenums on a pre to get line numbering */
+ol.linenums { margin-top: 0; margin-bottom: 0 } /* IE indents via margin-left */
+li.L0,
+li.L1,
+li.L2,
+li.L3,
+li.L5,
+li.L6,
+li.L7,
+li.L8 { list-style-type: none }
+/* Alternate shading for lines */
+li.L1,
+li.L3,
+li.L5,
+li.L7,
+li.L9 { background: #eee }
diff --git a/chrome/common/extensions/docs/static/css/print.css b/chrome/common/extensions/docs/static/css/print.css
new file mode 100644
index 0000000..b2820b1
--- /dev/null
+++ b/chrome/common/extensions/docs/static/css/print.css
@@ -0,0 +1,11 @@
+/* Chrome extensions developer guide - styles for printing */
+
+/* Make preformatted text wrap
+  (ref: http://myy.helia.fi/~karte/pre-wrap-css3-mozilla-opera-ie.html) */
+pre {
+  white-space: pre-wrap; /* css-3 */
+  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
+  white-space: -pre-wrap; /* Opera 4-6 */
+  white-space: -o-pre-wrap; /* Opera 7 */
+  word-wrap: break-word; /* Internet Explorer 5.5+ */
+}
diff --git a/chrome/common/extensions/docs/static/css/samples.css b/chrome/common/extensions/docs/static/css/samples.css
new file mode 100644
index 0000000..640b13b
--- /dev/null
+++ b/chrome/common/extensions/docs/static/css/samples.css
@@ -0,0 +1,38 @@
+#controls {
+  margin: 10px 0;
+  background: #EEE;
+  padding: 10px;
+  border-radius: 10px;
+}
+
+#search_input {
+  width: 30em;
+}
+
+.label {
+  font-weight: bold;
+}
+
+td.label {
+  min-width: 150px;
+  text-align: right;
+  vertical-align: top;
+  padding-right: 10px;
+}
+
+.sample {
+  position: relative;
+  padding-left: 80px;
+}
+
+img.icon {
+  position: absolute;
+  width: 64px;
+  height: 64px;
+  left: 0;
+  top: 40px;
+}
+
+#controls td {
+  border: none;
+}
diff --git a/chrome/common/extensions/docs/static/css/site.css b/chrome/common/extensions/docs/static/css/site.css
new file mode 100644
index 0000000..0528999
--- /dev/null
+++ b/chrome/common/extensions/docs/static/css/site.css
@@ -0,0 +1,528 @@
+/* Copyright (c) 2012 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.
+ */
+
+.hidden {
+  display: none;
+}
+
+body {
+  color: #333;
+  font: 13px/22px 'Open Sans',arial,sans-serif;
+  font-weight: 400;
+  background-color: white;
+  margin: auto;
+  padding: 0;
+}
+
+p {
+  margin: 1em 0 0 0;
+  color: #767676;
+  font-size: 14px;
+}
+
+p.note,
+p.caution,
+p.warning {
+  margin: 1em 0 0 0;
+  padding: .2em .5em .2em .9em;
+  background-color: #F5F5F5;
+  border-top: 1px solid;
+  border-bottom: 1px solid;
+  overflow: hidden;
+}
+
+p.note {
+  border-color: #36C;
+}
+p.caution {
+  border-color: #FC3;
+}
+p.warning {
+  border-color: #A03;
+}
+
+p.warning em,
+p.warning strong {
+  color: #A03;
+}
+
+a, a:link {
+  text-decoration: none;
+  color: #39F;
+  font-weight: 600;
+}
+
+a:visited {
+  color: #236bb2;
+}
+
+a:active,
+a:hover {
+  color: #3bf;
+}
+
+#toc a {
+  color: black;
+}
+
+ol, ul {
+  color: #767676;
+}
+
+#header {
+  margin: auto;
+  max-width: 1160px;
+  padding: 0 50px;
+  position: relative;
+  height: 60px;
+}
+
+#logo {
+  position: relative;
+  padding-top: 10px;
+  z-index: 2;
+}
+
+#logo img {
+  padding: 10px 0;
+}
+
+#cse {
+  width: 400px;
+  padding-top: 5px;
+  z-index: 1;
+  position: absolute;
+  right: 50px;
+}
+
+/*
+ * Without a border, the results from the custom search are hard to tell apart
+ * from the rest of the content
+ */
+.gsc-resultsbox-visible {
+  border-left: 1px solid #E9E9E9;
+  border-right: 1px solid #E9E9E9;
+  border-bottom: 1px solid #E9E9E9;
+}
+
+li {
+  margin: .3em 0 0 1.5em;
+  padding: 0;
+}
+
+ol li {
+  margin-top: 1em;
+}
+
+img {
+  border: none;
+  padding: 10px 0;
+}
+
+p img {
+  padding: 0 2px;
+}
+
+code,
+pre {
+  font-family: monospace;
+  color: #080;
+}
+
+code {
+  font-size: 10pt;
+}
+
+pre {
+  font-size: 10pt;
+  background-color: #F5F5F5;
+  margin: 1em 0 0 0;
+  padding: .99em;
+  overflow: auto;
+  word-wrap: break-word;
+  position: relative;
+}
+
+pre a {
+  text-decoration: underline!important;
+}
+
+pre b {
+  background: yellow;
+}
+
+pre[data-filename]::after {
+  content: attr(data-filename);
+  background-color: darkGray;
+  right: 0;
+  top: 0;
+  position: absolute;
+  font-size: 16px;
+  color: #FFF;
+  padding: 2px 25px;
+  text-transform: uppercase;
+}
+
+dt {
+  font-weight: bold;
+  margin: .75em 0 0 0;
+}
+
+dl {
+  margin: 0;
+}
+
+dd {
+  margin: .4em 0 0 2em;
+  padding: 0;
+  font-weight: normal;
+}
+
+.displayModeWarning {
+  background-color: #FF7735;
+  color: white;
+  font-weight: bold;
+  padding: 6px 8px;
+}
+
+span.displayModeWarning {
+  margin-right: 2px;
+}
+
+#gc-container {
+  margin: auto;
+  max-width: 1160px;
+  padding: 0 50px;
+  position: relative;
+  height: auto;
+}
+
+#gc-topnav {
+  font-size: 1em;
+  margin: auto;
+  max-width: 1160px;
+  padding: 0;
+  white-space: nowrap;
+  background-color: white;
+  border-top: 1px solid #E5E5E5;
+  border-bottom: 1px solid #E5E5E5;
+}
+
+#gc-topnav h1 {
+  font-size: 1.5em;
+  line-height: 1.3em;
+  font-weight: bold;
+  background-color: transparent;
+  border: 0;
+  margin: 0;
+  padding: 0 0 0 14px;
+  float: left;
+  border-top: 10px solid white;
+}
+
+#gc-topnav li {
+  font-weight: 600;
+  height: 45px;
+  text-transform: uppercase;
+  white-space: nowrap;
+  display: inline-block;
+  margin: 0;
+  padding: 0;
+}
+
+#gc-topnav ul {
+  font-size: 12px;
+  height: 45px;
+  text-align: right;
+  list-style: none;
+  margin: 0;
+}
+
+#gc-topnav li a {
+  color: #333;
+}
+#gc-topnav li a:hover {
+  color: #39f;
+}
+
+#gc-topnav div {
+  border-top: 10px solid white;
+  margin: 0 30px;
+  position: relative;
+  height: 29px;
+}
+
+.pageData {
+  display: none;
+}
+
+#gc-pagecontent {
+  margin: 30px 0 100px 250px;
+}
+
+#gc-pagecontent h2 {
+  border-top: 1px solid #CCC;
+  font-size: 170%;
+  font-weight: normal;
+  margin: 2em 0 0 0;
+  padding: 40px 0;
+}
+
+#gc-pagecontent h3 {
+  font-size: 130%;
+  font-weight: bold;
+  margin: 1.5em 0 0 0;
+  top: 0em;
+}
+
+#gc-pagecontent h4 {
+  font-size: 110%;
+  margin: .7em 0 0 0;
+  position: relative;
+  top: .4em;
+}
+
+#gc-pagecontent h5 {
+  font-size: 100%;
+  margin: 0;
+}
+
+#gc-pagecontent h1.page_title,
+#gc-pagecontent h2.page_title {
+  line-height: 130%;
+  font-size: 320%;
+  margin: 0;
+  padding: .8em 0 0;
+  border: none;
+  background: none;
+  font-weight: normal;
+}
+
+#gc-pagecontent table {
+  border-collapse: collapse;
+}
+#gc-pagecontent th {
+  text-align: left;
+  padding: 6px 12px;
+}
+#gc-pagecontent td {
+  padding: 6px 12px;
+  border: 1px solid #36C;
+  vertical-align: top;
+}
+
+/* Provide a "simple" version of the table to use just for layout. */
+#gc-pagecontent table.simple,
+#gc-pagecontent table.simple th,
+#gc-pagecontent table.simple td {
+  border-collapse: separate;
+  border-style: none;
+}
+#gc-pagecontent table.simple {
+  padding: 0;
+}
+#gc-pagecontent table.simple th,
+#gc-pagecontent table.simple td {
+  padding: 1px;
+}
+
+#gc-footer {
+  clear: both;
+  margin: auto;
+  color: #666;
+  max-width: 1160px;
+  padding: 0 50px;
+}
+
+#gc-footer .text {
+  text-align: center;
+  padding: 30px 0;
+  margin: 0 0 0 0;
+}
+
+#gc-sidebar {
+  margin: 0;
+  margin-top: 2.5em;
+  width: 180px;
+  float: left;
+}
+
+/* Sidebar link/button styling. */
+#gc-sidebar span,
+#gc-sidebar a {
+  color: #767676;
+  display: block;
+  position: relative;
+  font-weight: normal;
+}
+#gc-sidebar a:visited {
+  color: #767676;
+}
+#gc-sidebar a:hover,
+#gc-sidebar a.selected {
+  color: #39F;
+}
+#gc-sidebar a.button {
+  color: #767676;
+}
+#gc-sidebar span.level2,
+#gc-sidebar a.level2 {
+  font-weight: bold;
+}
+
+#gc-sidebar .toggleIndicator {
+  position: absolute;
+  right: 0;
+  top: 3px;
+  background: url(../images/toggle_sprite.png) no-repeat 0 0;
+  height: 8px;
+  width: 8px;
+}
+#gc-sidebar .toggleIndicator.toggled {
+  background-position: 0 -9px;
+}
+
+/* Sidebar list styling. */
+#gc-sidebar ul {
+  list-style: none;
+  padding: 0;
+}
+#gc-sidebar ul.level2 {
+  margin-left: 10px;
+  padding-top: 10px;
+}
+#gc-sidebar ul.level3 {
+  margin-left: 20px;
+  padding-top: 10px;
+  list-style: url(../images/sidearrow.png);
+}
+
+/* Sidebar list element styling. */
+#gc-sidebar li {
+  margin: 0;
+  padding: 8px 0;
+  line-height: 120%;
+}
+#gc-sidebar li.level2 {
+  border-top: 1px solid #E5E5E5;
+}
+
+#gc-toc div.line {
+  border-top: thin solid #FAFAFA;
+  height: 1px;
+  margin: 1.3em 1em 0 0;
+  padding: 0;
+}
+
+#toc {
+  background-color: #F5F5F5;
+  float: right;
+  margin: 5px 0px 0px 0px;
+  padding: 5px;
+  width: 250px;
+  word-break: break-word;
+  /* We want this element to have a visual left-margin of 20px, but margins on
+     floated elements don't affect the borders and background of the elements
+     they float over. So we add this border to force the issue. */
+  border-left: 20px solid white;
+  border-bottom: 20px solid white;
+  z-index: 3;
+  position: relative;
+}
+
+#toc * {
+  padding: 0;
+  list-style: none;
+  font-weight: 600;
+}
+
+#toc h2 {
+  font-weight: bold;
+  font-size: 100%;
+  margin: 0;
+  border: none;
+  padding: 0;
+}
+
+#toc ol {
+  margin: 1em 0 0 0;
+}
+
+#toc ol li {
+  margin: .5em 0 .5em 1em;
+  line-height: 1.2em;
+}
+
+#toc ol li ol {
+  margin: 0;
+}
+
+#toc ol li ol li {
+  margin: .5em 0 .5em 1em;
+}
+
+.filtered_item {
+  line-height: 6px;
+}
+
+#filtered_apis {
+  margin-top: 5px;
+}
+
+#skipto {
+  display: none;
+}
+
+input.gsc-search-button.gsc-search-button-v2 {
+  padding: 8px 30px;
+  margin-top: 5px;
+}
+
+div.gsc-control-cse {
+  margin: 0;
+  padding: 0;
+  position: relative;
+  z-index: 2;
+}
+
+td.gsc-input {
+  padding: 0 0;
+}
+
+div.gsc-input-box {
+  height: 30px;
+  width: 250px;
+  float: right;
+}
+
+#known_issues {
+  background: #F5F5F5;
+  padding-top: 1px;
+  padding-bottom: 16px;
+  padding-right: 16px;
+  padding-left: 16px;
+}
+
+#search_results td {
+  border: none;
+}
+
+#search_results {
+  float: left;
+}
+
+/* List with largeish images floated to the right. */
+.imaged li {
+  clear: right;
+}
+
+.imaged li img {
+  float: right;
+  margin-bottom: 1em;
+}
+
+.imaged + p {
+  clear: right;
+}
diff --git a/chrome/common/extensions/docs/static/images/a11y/focus-outline-2.png b/chrome/common/extensions/docs/static/images/a11y/focus-outline-2.png
new file mode 100644
index 0000000..b8c96c1
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/a11y/focus-outline-2.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/a11y/focus-outline.png b/chrome/common/extensions/docs/static/images/a11y/focus-outline.png
new file mode 100644
index 0000000..aea6551
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/a11y/focus-outline.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/a11y/standard-html-controls.png b/chrome/common/extensions/docs/static/images/a11y/standard-html-controls.png
new file mode 100644
index 0000000..518ee8e
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/a11y/standard-html-controls.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/active-tab-after.png b/chrome/common/extensions/docs/static/images/active-tab-after.png
new file mode 100644
index 0000000..9a74dbb
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/active-tab-after.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/active-tab-before.png b/chrome/common/extensions/docs/static/images/active-tab-before.png
new file mode 100644
index 0000000..0ae78dc
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/active-tab-before.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/applifecycle.png b/chrome/common/extensions/docs/static/images/applifecycle.png
new file mode 100644
index 0000000..2b04cdb
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/applifecycle.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/bookmarks.png b/chrome/common/extensions/docs/static/images/bookmarks.png
new file mode 100644
index 0000000..d8e56f8
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/bookmarks.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/browser-action.png b/chrome/common/extensions/docs/static/images/browser-action.png
new file mode 100644
index 0000000..82ebe33
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/browser-action.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/calculator-128.png b/chrome/common/extensions/docs/static/images/calculator-128.png
new file mode 100644
index 0000000..85963f3
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/calculator-128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/calculator-16.png b/chrome/common/extensions/docs/static/images/calculator-16.png
new file mode 100644
index 0000000..0d9135e
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/calculator-16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/chrome_logo.gif b/chrome/common/extensions/docs/static/images/chrome_logo.gif
new file mode 100644
index 0000000..c6bcc7b
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/chrome_logo.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/console-button.gif b/chrome/common/extensions/docs/static/images/console-button.gif
new file mode 100644
index 0000000..387b4e8
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/console-button.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/container.png b/chrome/common/extensions/docs/static/images/container.png
new file mode 100644
index 0000000..d83ade0
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/container.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/csperrors.png b/chrome/common/extensions/docs/static/images/csperrors.png
new file mode 100644
index 0000000..9644703
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/csperrors.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/customframe.png b/chrome/common/extensions/docs/static/images/customframe.png
new file mode 100644
index 0000000..e5ebaca
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/customframe.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/devtools-1.gif b/chrome/common/extensions/docs/static/images/devtools-1.gif
new file mode 100644
index 0000000..012500a
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/devtools-1.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/devtools-2.gif b/chrome/common/extensions/docs/static/images/devtools-2.gif
new file mode 100644
index 0000000..ec85912
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/devtools-2.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/devtools-3.gif b/chrome/common/extensions/docs/static/images/devtools-3.gif
new file mode 100644
index 0000000..40deb03
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/devtools-3.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/devtools-audits-category.png b/chrome/common/extensions/docs/static/images/devtools-audits-category.png
new file mode 100644
index 0000000..b05fc7d
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/devtools-audits-category.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/devtools-audits-results.png b/chrome/common/extensions/docs/static/images/devtools-audits-results.png
new file mode 100644
index 0000000..9185055
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/devtools-audits-results.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/devtools-localvars.gif b/chrome/common/extensions/docs/static/images/devtools-localvars.gif
new file mode 100644
index 0000000..2640c7d
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/devtools-localvars.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/devtools-panels.png b/chrome/common/extensions/docs/static/images/devtools-panels.png
new file mode 100644
index 0000000..93cbdfa
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/devtools-panels.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/editor.png b/chrome/common/extensions/docs/static/images/editor.png
new file mode 100644
index 0000000..f584a06
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/editor.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/fetchedicon.png b/chrome/common/extensions/docs/static/images/fetchedicon.png
new file mode 100644
index 0000000..11d5607
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/fetchedicon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/filebrowserhandler.png b/chrome/common/extensions/docs/static/images/filebrowserhandler.png
new file mode 100644
index 0000000..746ddad
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/filebrowserhandler.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/fileicons.png b/chrome/common/extensions/docs/static/images/fileicons.png
new file mode 100644
index 0000000..92a364a
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/fileicons.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/gettingstarted-1.jpg b/chrome/common/extensions/docs/static/images/gettingstarted-1.jpg
new file mode 100644
index 0000000..aa61c6d
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/gettingstarted-1.jpg
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/gettingstarted-icon.png b/chrome/common/extensions/docs/static/images/gettingstarted-icon.png
new file mode 100644
index 0000000..e7ab026
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/gettingstarted-icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/gettingstarted-popup.jpg b/chrome/common/extensions/docs/static/images/gettingstarted-popup.jpg
new file mode 100644
index 0000000..d57e188
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/gettingstarted-popup.jpg
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/hotdogmenu.png b/chrome/common/extensions/docs/static/images/hotdogmenu.png
new file mode 100644
index 0000000..9d3b99b
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/hotdogmenu.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/i18n-after-1.gif b/chrome/common/extensions/docs/static/images/i18n-after-1.gif
new file mode 100644
index 0000000..76650e2
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/i18n-after-1.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/i18n-after-2.gif b/chrome/common/extensions/docs/static/images/i18n-after-2.gif
new file mode 100644
index 0000000..031d119
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/i18n-after-2.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/i18n-before.gif b/chrome/common/extensions/docs/static/images/i18n-before.gif
new file mode 100644
index 0000000..7691e91
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/i18n-before.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/i18n-hierarchy.gif b/chrome/common/extensions/docs/static/images/i18n-hierarchy.gif
new file mode 100644
index 0000000..2854072
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/i18n-hierarchy.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/i18n-strings.gif b/chrome/common/extensions/docs/static/images/i18n-strings.gif
new file mode 100644
index 0000000..d82df88
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/i18n-strings.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/index/gmail-small.png b/chrome/common/extensions/docs/static/images/index/gmail-small.png
new file mode 100644
index 0000000..0e70bc0
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/index/gmail-small.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/infobar.png b/chrome/common/extensions/docs/static/images/infobar.png
new file mode 100644
index 0000000..d375fa1
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/infobar.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/jstransferrequests.png b/chrome/common/extensions/docs/static/images/jstransferrequests.png
new file mode 100644
index 0000000..0211322
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/jstransferrequests.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/listoffiles.png b/chrome/common/extensions/docs/static/images/listoffiles.png
new file mode 100644
index 0000000..153cd46
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/listoffiles.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/load_after.png b/chrome/common/extensions/docs/static/images/load_after.png
new file mode 100644
index 0000000..cbeb8b3
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/load_after.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/mvc.png b/chrome/common/extensions/docs/static/images/mvc.png
new file mode 100644
index 0000000..efc1009
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/mvc.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/noframe.png b/chrome/common/extensions/docs/static/images/noframe.png
new file mode 100644
index 0000000..1b422a7
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/noframe.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/notification-linux.png b/chrome/common/extensions/docs/static/images/notification-linux.png
new file mode 100644
index 0000000..e0d06a4
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/notification-linux.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/notification-mac.png b/chrome/common/extensions/docs/static/images/notification-mac.png
new file mode 100644
index 0000000..dfa1d92
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/notification-mac.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/notification-windows.png b/chrome/common/extensions/docs/static/images/notification-windows.png
new file mode 100644
index 0000000..673d917
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/notification-windows.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/ntp-blank.png b/chrome/common/extensions/docs/static/images/ntp-blank.png
new file mode 100644
index 0000000..79ad292
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/ntp-blank.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/ntp-default.png b/chrome/common/extensions/docs/static/images/ntp-default.png
new file mode 100644
index 0000000..4aa15c1
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/ntp-default.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/omnibox.png b/chrome/common/extensions/docs/static/images/omnibox.png
new file mode 100644
index 0000000..c8d3bb4
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/omnibox.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/overview/arch-1.gif b/chrome/common/extensions/docs/static/images/overview/arch-1.gif
new file mode 100644
index 0000000..16d4a84
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/overview/arch-1.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/overview/arch-2.gif b/chrome/common/extensions/docs/static/images/overview/arch-2.gif
new file mode 100644
index 0000000..e6281c5
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/overview/arch-2.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/overview/arch-3.gif b/chrome/common/extensions/docs/static/images/overview/arch-3.gif
new file mode 100644
index 0000000..968f388
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/overview/arch-3.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/overview/arch-cs.gif b/chrome/common/extensions/docs/static/images/overview/arch-cs.gif
new file mode 100644
index 0000000..619ea2a
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/overview/arch-cs.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/overview/browser-action-with-popup.png b/chrome/common/extensions/docs/static/images/overview/browser-action-with-popup.png
new file mode 100644
index 0000000..b1ca45e
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/overview/browser-action-with-popup.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/overview/browser-action.png b/chrome/common/extensions/docs/static/images/overview/browser-action.png
new file mode 100644
index 0000000..8bf54df
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/overview/browser-action.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/overview/page-action.png b/chrome/common/extensions/docs/static/images/overview/page-action.png
new file mode 100644
index 0000000..257a233
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/overview/page-action.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/package-success.gif b/chrome/common/extensions/docs/static/images/package-success.gif
new file mode 100644
index 0000000..175a049
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/package-success.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/page-action.png b/chrome/common/extensions/docs/static/images/page-action.png
new file mode 100644
index 0000000..3e3b391
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/page-action.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/perms-hw1.png b/chrome/common/extensions/docs/static/images/perms-hw1.png
new file mode 100644
index 0000000..5e88b0d
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/perms-hw1.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/perms-hw2-disabled.png b/chrome/common/extensions/docs/static/images/perms-hw2-disabled.png
new file mode 100644
index 0000000..1e880ff
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/perms-hw2-disabled.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/perms-hw2.png b/chrome/common/extensions/docs/static/images/perms-hw2.png
new file mode 100644
index 0000000..82cee5d
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/perms-hw2.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/perms-optional.png b/chrome/common/extensions/docs/static/images/perms-optional.png
new file mode 100644
index 0000000..91b3f27
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/perms-optional.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/play-button.gif b/chrome/common/extensions/docs/static/images/play-button.gif
new file mode 100644
index 0000000..9039660
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/play-button.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/sample-default-icon.png b/chrome/common/extensions/docs/static/images/sample-default-icon.png
new file mode 100644
index 0000000..04f4319
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/sample-default-icon.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/sidearrow.png b/chrome/common/extensions/docs/static/images/sidearrow.png
new file mode 100644
index 0000000..6da307a
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/sidearrow.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/tabs.png b/chrome/common/extensions/docs/static/images/tabs.png
new file mode 100644
index 0000000..1a039d2
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/tabs.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/themes-1.gif b/chrome/common/extensions/docs/static/images/themes-1.gif
new file mode 100644
index 0000000..fb4bff8
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/themes-1.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/themes-2.gif b/chrome/common/extensions/docs/static/images/themes-2.gif
new file mode 100644
index 0000000..17f35ae
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/themes-2.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/themes-3.gif b/chrome/common/extensions/docs/static/images/themes-3.gif
new file mode 100644
index 0000000..ed55160
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/themes-3.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/todos.png b/chrome/common/extensions/docs/static/images/todos.png
new file mode 100644
index 0000000..8b7e4ae
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/todos.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/toggle_sprite.png b/chrome/common/extensions/docs/static/images/toggle_sprite.png
new file mode 100644
index 0000000..d41a5bd
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/toggle_sprite.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/tut_analytics/screenshot01.png b/chrome/common/extensions/docs/static/images/tut_analytics/screenshot01.png
new file mode 100644
index 0000000..52792f6
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/tut_analytics/screenshot01.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/tut_analytics/screenshot02.png b/chrome/common/extensions/docs/static/images/tut_analytics/screenshot02.png
new file mode 100644
index 0000000..e590de6
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/tut_analytics/screenshot02.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/tut_analytics/screenshot03.png b/chrome/common/extensions/docs/static/images/tut_analytics/screenshot03.png
new file mode 100644
index 0000000..5a4efc8
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/tut_analytics/screenshot03.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/tut_analytics/screenshot04.png b/chrome/common/extensions/docs/static/images/tut_analytics/screenshot04.png
new file mode 100644
index 0000000..4c82be0
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/tut_analytics/screenshot04.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/update-success.gif b/chrome/common/extensions/docs/static/images/update-success.gif
new file mode 100644
index 0000000..66e3e8b
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/update-success.gif
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/uploader.png b/chrome/common/extensions/docs/static/images/uploader.png
new file mode 100644
index 0000000..92a364a
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/uploader.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/webrequestapi.png b/chrome/common/extensions/docs/static/images/webrequestapi.png
new file mode 100644
index 0000000..7ae2cee
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/webrequestapi.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/windows.png b/chrome/common/extensions/docs/static/images/windows.png
new file mode 100644
index 0000000..6abfb01
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/windows.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/images/writecompleted.png b/chrome/common/extensions/docs/static/images/writecompleted.png
new file mode 100644
index 0000000..0615b8d
--- /dev/null
+++ b/chrome/common/extensions/docs/static/images/writecompleted.png
Binary files differ
diff --git a/chrome/common/extensions/docs/static/js/branch.js b/chrome/common/extensions/docs/static/js/branch.js
new file mode 100644
index 0000000..3b70fc3
--- /dev/null
+++ b/chrome/common/extensions/docs/static/js/branch.js
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 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.
+
+(function() {
+  var branchChooser = document.getElementById('branchChooser');
+  // No branch chooser on stable (for example).
+  if (!branchChooser)
+    return;
+
+  branchChooser.addEventListener('change', function() {
+    var value = event.target.value;
+    if (!value)
+      return;
+    var current_branch = window.bootstrap.branchInfo.current;
+    var path = window.location.pathname.split('/');
+    if (path[0] == '')
+      path = path.slice(1);
+    var index = path.indexOf(current_branch);
+    if (index != -1)
+      path[index] = value;
+    else
+      path.splice(0, 0, value);
+    window.location = '/' + path.join('/');
+  });
+})()
diff --git a/chrome/common/extensions/docs/static/js/filter.js b/chrome/common/extensions/docs/static/js/filter.js
new file mode 100644
index 0000000..02b5aa3
--- /dev/null
+++ b/chrome/common/extensions/docs/static/js/filter.js
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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.
+
+// This is used for the search box on the left navigation bar. The APIs are
+// filtered and displayed based on what the user is typing in the box.
+(function() {
+  var search_box = document.getElementById('api_search');
+  var filtered_apis = document.getElementById('filtered_apis');
+
+  function filterAPIs() {
+    var search_text = search_box.value.toLowerCase();
+    var apis = window.bootstrap.api_names;
+    if (!search_text) {
+      filtered_apis.style.display = 'none';
+      return;
+    }
+    var api_list = ''
+    for (var i = 0; i < apis.length; ++i) {
+      if (apis[i].name.toLowerCase().indexOf(search_text) != -1)
+        api_list += '<li class="filtered_item"><a href="' + apis[i].name +
+            '.html">' + apis[i].name + '</a></li>'
+    }
+    if (api_list != filtered_apis.innerHtml)
+      filtered_apis.innerHTML = api_list;
+    if (!api_list)
+      filtered_apis.style.display = 'none';
+    else
+      filtered_apis.style.display = '';
+  }
+
+  filtered_apis.style.display = 'none';
+  search_box.addEventListener('search', filterAPIs);
+  search_box.addEventListener('keyup', filterAPIs);
+})();
diff --git a/chrome/common/extensions/docs/static/js/prettify.js b/chrome/common/extensions/docs/static/js/prettify.js
new file mode 100644
index 0000000..eef5ad7
--- /dev/null
+++ b/chrome/common/extensions/docs/static/js/prettify.js
@@ -0,0 +1,28 @@
+var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
+(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
+[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
+f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
+(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
+{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
+t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
+"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
+l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
+q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
+q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
+"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
+a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
+for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
+m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
+a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
+j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
+"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
+H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
+J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
+I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
+["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
+/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
+["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
+hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
+!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
+250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
+PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();
diff --git a/chrome/common/extensions/docs/static/js/samples.js b/chrome/common/extensions/docs/static/js/samples.js
new file mode 100644
index 0000000..cdc9496
--- /dev/null
+++ b/chrome/common/extensions/docs/static/js/samples.js
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 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.
+
+(function() {
+  var search_box = document.getElementById('search_input');
+  var samples = document.getElementsByClassName('sample');
+
+  function filterSamples() {
+    var search_text = search_box.value.toLowerCase();
+    for (var i = 0; i < samples.length; ++i) {
+      var sample = samples[i]
+      if (sample.getAttribute('tags').toLowerCase().indexOf(search_text) < 0)
+        sample.style.display = 'none';
+      else
+        sample.style.display = '';
+    }
+  }
+  search_box.addEventListener('search', filterSamples);
+  search_box.addEventListener('keyup', filterSamples);
+
+  var api_filter_items = document.getElementById('api_filter_items');
+  api_filter_items.addEventListener('click', function(event) {
+    if (event.target instanceof HTMLAnchorElement) {
+      search_box.value = 'chrome.' + event.target.innerText;
+      filterSamples();
+    }
+  });
+})();
diff --git a/chrome/common/extensions/docs/static/js/sidebar.js b/chrome/common/extensions/docs/static/js/sidebar.js
new file mode 100644
index 0000000..ec97cb7
--- /dev/null
+++ b/chrome/common/extensions/docs/static/js/sidebar.js
@@ -0,0 +1,85 @@
+// Copyright (c) 2012 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.
+
+/**
+ * Adds toggle controls to the sidebar list.
+ *
+ * Controls are inserted as the first children of list items in the sidebar
+ * which contain only text (not a link).  Handlers are set up so that when a
+ * toggle control is clicked, any <ul> elements who are siblings of the
+ * control are hidden/revealed as appropriate given the control's state.
+ *
+ * If a list item possesses the class 'selected' its ancestor <ul> is
+ * revealed by default (it represents the current page).
+ */
+(function() {
+  var sidebar = document.getElementById('gc-sidebar');
+  if (!sidebar)
+    return;
+
+  // Figure out which link matches the current page so it can be styled
+  // differently.
+  var pathParts = document.location.pathname.replace(/\/+$/, '').split('/')
+  Array.prototype.forEach.call(sidebar.getElementsByTagName('a'),
+                               function(link) {
+    if (link.getAttribute('href') != pathParts[pathParts.length - 1])
+      return;
+    link.className = 'selected';
+    link.removeAttribute('href');
+  });
+
+  // Go through the items on the sidebar and add toggles.
+  Array.prototype.forEach.call(sidebar.querySelectorAll('[toggleable]'),
+                               function(toggleable) {
+    var buttonContent = toggleable.previousElementSibling;
+    if (!buttonContent) {
+      console.warn('Cannot toggle %s, no previous sibling', toggleable);
+      return;
+    }
+
+    var button = document.createElement('a');
+    button.className = 'button';
+    var toggleIndicator = document.createElement('div');
+    toggleIndicator.className = 'toggleIndicator';
+
+    var isToggled = false;
+    function toggle() {
+      if (isToggled) {
+        toggleable.classList.add('hidden');
+        toggleIndicator.classList.remove('toggled');
+      } else {
+        toggleable.classList.remove('hidden');
+        toggleIndicator.classList.add('toggled');
+      }
+      isToggled = !isToggled;
+    }
+
+    // TODO(kalman): needs a button role for accessibility?
+    button.setAttribute('href', 'javascript:void(0)');
+    button.addEventListener('click', toggle);
+    buttonContent.parentElement.insertBefore(button, buttonContent);
+    button.appendChild(buttonContent);
+    button.appendChild(toggleIndicator);
+
+    // Leave the toggle open if the selected link is a child.
+    if (toggleable.querySelector('.selected'))
+      toggle();
+    else
+      toggleable.classList.add('hidden');
+  });
+
+  // Each level of the sidebar is displayed differently. Rather than trying
+  // to nest CSS selectors in a crazy way, programmatically assign levels
+  // to them.
+  function addNestLevels(node, currentLevel) {
+    node.classList.add('level' + currentLevel);
+    if (node.tagName == 'UL')
+      currentLevel++;
+    Array.prototype.forEach.call(node.children, function(child) {
+      addNestLevels(child, currentLevel);
+    });
+  }
+
+  addNestLevels(sidebar, 1);
+})()
diff --git a/chrome/common/extensions/docs/templates/articles/a11y.html b/chrome/common/extensions/docs/templates/articles/a11y.html
new file mode 100644
index 0000000..1a6036c
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/a11y.html
@@ -0,0 +1,466 @@
+<h1>Accessibility (a11y)</h1>
+
+
+<p>
+When you design an extension,
+try to make it as accessible as possible
+to people with disabilities such as
+visual impairment, hearing loss, and limited dexterity.
+</p>
+
+<p>
+Everyone &mdash; not just people with special needs &mdash;
+can benefit from the alternative access modes
+that accessible extensions provide.
+For example, keyboard shortcuts are important
+for blind people and people with limited dexterity,
+but they also help power users get things done
+more quickly without using a mouse.
+Captions and transcripts give deaf people access to audio content,
+but they are also useful to language learners.
+</p>
+
+<p>
+People can interact with your extension in a variety of ways.
+They might use a standard monitor, keyboard, and mouse,
+or they might use a screen magnifier and just a keyboard.
+Another possibility is a <em>screen reader</em>,
+an assistive application tool that interprets
+what's displayed onscreen
+for a blind or visually impaired user.
+A screen reader might speak out loud or produce Braille output.
+</p>
+
+<p>
+Although you can't predict what tools people will use,
+by following a few simple guidelines
+you can write an extension that is
+more likely to be accessible to more people.
+The guidelines on this page aren't going to
+make your extension accessible for absolutely everyone,
+but they're a good starting point.
+</p>
+
+
+<h2 id="controls">Use accessible UI controls</h2>
+
+<p>
+First, use UI controls that support accessibility.
+The easiest way to get an accessible control is to use a
+standard HTML control.
+If you need to build a custom control,
+keep in mind that it's much easier
+to make the control accessible from the beginning
+than to go back and add accessibility support later.
+</p>
+
+<h3 id="htmlcontrols">Standard controls</h3>
+
+<p>
+Try to use standard HTML UI controls whenever possible.
+Standard HTML controls (shown in the following figure)
+are keyboard accessible, scale easily,
+and are generally understood by screen readers.
+</p>
+
+<img src="{{static}}/images/a11y/standard-html-controls.png"
+ width="550" height="350"
+ alt="Screenshots and code for button, checkbox, radio, text, select/option, and link">
+
+
+<h3 id="aria">ARIA in custom controls</h3>
+
+<p>
+ARIA is a specification for making UI controls accessible to screen readers
+by means of a standard set of DOM attributes.
+These attributes provide clues to the screen reader
+about the function and current state of controls on a web page.
+ARIA is a
+<a href=" http://www.w3.org/WAI/intro/aria">work in progress at the W3C</a>.
+</p>
+
+<p>
+Adding ARIA support to custom controls in your extension
+involves modifying DOM elements to add attributes
+Google Chrome uses
+to raise events during user interaction.
+Screen readers respond to these events
+and describe the function of the control.
+The DOM attributes specified by ARIA are classified into
+<em>roles</em>, <em>states</em>, and <em>properties</em>.
+</p>
+
+<p>
+The ARIA attribute <em>role</em>
+is an indication of the control type
+and describes the way the control should behave.
+It is expressed with the DOM attribute <code>role</code>,
+with a value set to one of the pre-defined ARIA role strings.
+Because ARIA roles are static,
+the role attribute should not change its value.
+</p>
+
+<p>
+The <a href="http://www.w3.org/WAI/PF/aria/roles">ARIA Role Specification</a>
+holds detailed information on how to pick the correct role.
+For example, if your extension includes a toolbar,
+set the <code>role</code> attribute of the toolbar's DOM element as follows:
+</p>
+
+<pre>
+&lt;div role="toolbar"&gt;
+</pre>
+
+<p>
+ARIA attributes are also used to describe
+the current state and properties of controls of a particular role.
+A <em>state</em> is dynamic and should be updated during user interaction.
+For example, a control with the role "checkbox"
+could be in the states "checked" or "unchecked".
+A <em>property</em> is not generally dynamic,
+but is similar to a state
+in that it expresses specific information about a control.
+For more information on ARIA states and properties,
+refer to the
+<a href="http://www.w3.org/TR/wai-aria/states_and_properties">W3C States and Properties specification</a>.
+</p>
+
+
+<p class="note">
+<b>Note:</b>
+You don't have to use
+all of the states and properties available for a particular role.
+</p>
+
+<p>
+Here's an example of adding
+the ARIA property <code>aria-activedescendant</code>
+to the example toolbar control:
+</p>
+
+<pre>
+&lt;div role="toolbar" tabindex="0" aria-activedescendant="button1"&gt;
+</pre>
+
+<p>
+The
+<a href="http://www.w3.org/WAI/PF/aria/states_and_properties#aria-activedescendant"><code>aria-activedescendant</code></a>
+property specifies which child of the toolbar receives focus
+when the toolbar receives focus.
+In this example, the toolbar's first button
+(which has the <code>id</code> "button1")
+is the child that gets focus.
+The code <code>tabindex="0"</code>
+specifies that the toolbar
+receives focus in document order.
+</p>
+
+<p>
+Here's the complete specification for the example toolbar:
+</p>
+
+<pre>
+&lt;div role="toolbar" tabindex="0" aria-activedescendant="button1"&gt;
+  &lt;img src="buttoncut.png" role="button" alt="cut" id="button1"&gt;
+  &lt;img src="buttoncopy.png" role="button" alt="copy" id="button2"&gt;
+  &lt;img src="buttonpaste.png" role="button" alt="paste" id="button3"&gt;
+&lt;/div&gt;
+</pre>
+
+<p>
+Once ARIA roles, states, and properties are added to the DOM of a control,
+Google Chrome raises the appropriate events to the screen reader.
+Because ARIA support is still a work in progress,
+Google Chrome might not raise an event for every ARIA property,
+and screen readers might not recognize all of the events being raised.
+You can find more information on ARIA support in Google Chrome in the
+<a href="http://www.chromium.org/developers/design-documents/accessibility#TOC-WAI-ARIA-Support">Chromium Accessibility Design Document</a>.
+</p>
+
+<p>
+For a quick tutorial on adding ARIA controls to custom controls, see
+<a href="http://www.w3.org/2010/Talks/www2010-dsr-diy-aria/">Dave Raggett's presentation from WWW2010</a>.
+
+<h3 id="focus">Focus in custom controls</h3>
+
+<p>
+Make sure that operation and navigation controls of your extension
+can receive keyboard focus.
+Operation controls might include
+buttons, trees, and list boxes.
+Navigation controls might include tabs and menu bars.
+</p>
+
+<p>
+By default, the only elements in the HTML DOM
+that can receive keyboard focus
+are anchors, buttons, and form controls.
+However, setting the HTML attribute <code>tabIndex</code> to <code>0</code>
+places DOM elements in the default tab sequence,
+enabling them to receive keyboard focus.
+For example:
+</p>
+
+<pre>
+<em>element</em>.tabIndex = 0
+</pre>
+
+<p>
+Setting <code>tabIndex = -1</code> removes the element from the tab sequence
+but still allows the element to receive keyboard focus programmatically.
+Here's an example of setting keyboard focus:
+</p>
+
+<pre>
+<em>element</em>.focus();
+</pre>
+
+<p>
+Ensuring that your custom UI controls include keyboard support
+is important not only for users who don't use the mouse
+but also because screen readers use keyboard focus
+to determine which control to describe.
+</p>
+
+<h2 id="keyboard"> Support keyboard access </h2>
+
+<p>
+People should be able to use your extension
+even if they can't or don't want to use a mouse.
+</p>
+
+<h3 id="navigation"> Navigation </h3>
+
+<p>
+Check that the user can navigate between
+the different parts of your extension
+without using the mouse.
+Also check that any popups on page actions or browser actions
+are keyboard navigable. 
+</p>
+
+<p id="builtin">
+On Windows, you can use <b>Shift+Alt+T</b>
+to switch the keyboard focus to the toolbar,
+which lets you navigate to the icons of page actions and browser actions.
+The help topic
+<a href="http://www.google.com/support/chrome/bin/static.py?hl=en&page=guide.cs&guide=25799&from=25799&rd=1">Keyboard and mouse shortcuts</a>
+lists all of Google Chrome's keyboard shortcuts;
+details about toolbar navigation
+are in the section <b>Google Chrome feature shortcuts</b>.
+</p>
+
+<p class="note">
+<b>Note:</b>
+The Windows version of Google Chrome 6 was the first
+to support keyboard navigation to the toolbar.
+Support is also planned for Linux.
+On Mac OS X,
+access to the toolbar is provided through VoiceOver,
+Apple's screenreader.
+</p>
+
+<p>
+Make sure that it's easy to see
+which part of the interface has keyboard focus.
+Usually a focus outline moves around the interface,
+but if you’re using CSS heavily this outline might be suppressed 
+or the contrast might be reduced.
+Two examples of focus outline follow.
+</p>
+
+<img src="{{static}}/images/a11y/focus-outline-2.png"
+  width="200" height="75"
+  alt="A focus outline on a Search button">
+<br />
+<img src="{{static}}/images/a11y/focus-outline.png"
+  width="400" height="40"
+  alt="A focus outline on one of a series of links">
+
+
+<h3 id="shortcuts"> Shortcuts </h3>
+
+<p>
+Although the most common keyboard navigation strategy involves
+using the Tab key to move focus through the extension interface,
+that's not always the easiest or most efficient way
+to use the interface.
+You can make keyboard navigation easier
+by providing explicit keyboard shortcuts.
+</p>
+
+<p>
+To implement shortcuts,
+connect keyboard event listeners to your controls.
+A good reference is the DHTML Style Guide Working Group’s
+<a href="http://dev.aol.com/dhtml_style_guide">guidelines for keyboard shortcuts</a>.
+</p>
+
+<p>
+A good way to ensure discoverability of keyboard shortcuts
+is to list them somewhere.
+Your extension’s
+<a href="options.html">Options page</a>
+might be a good place to do this.
+</p>
+
+<p>
+For the example toolbar,
+a simple JavaScript keyboard handler could look like the following.
+Note how the ARIA property <code>aria-activedescendant</code>
+is updated in response to user input
+to reflect the current active toolbar button.
+</p>
+
+<pre>
+&lt;head&gt;
+&lt;script&gt;		
+ function optionKeyEvent(event) {
+  var tb = event.target;
+  var buttonid; 
+ 
+  ENTER_KEYCODE = 13;
+  RIGHT_KEYCODE = 39;
+  LEFT_KEYCODE = 37;
+  // Partial sample code for processing arrow keys.
+  if (event.type == "keydown") {
+    // Implement circular keyboard navigation within the toolbar buttons
+    if (event.keyCode == ENTER_KEYCODE) {
+      ExecuteButtonAction(getCurrentButtonID());
+      <em>// getCurrentButtonID defined elsewhere </em>
+    } else if (event.keyCode == event.RIGHT_KEYCODE) {
+      // Change the active toolbar button to the one to the right (circular). 
+      var buttonid = getNextButtonID();
+      <em>// getNextButtonID defined elsewhere </em>
+      tb.setAttribute("aria-activedescendant", buttonid); 
+    } else if (event.keyCode == event.LEFT_KEYCODE) {
+      // Change the active toolbar button to the one to the left (circular). 
+      var buttonid = getPrevButtonID();
+      <em>// getPrevButtonID defined elsewhere </em>
+      tb.setAttribute("aria-activedescendant", buttonid); 
+    } else {
+      return true;
+    }
+    return false;
+  }
+}  
+&lt;/script&gt;		
+
+&lt;div role="toolbar" tabindex="0" aria-activedescendant="button1" id="tb1" 
+     onkeydown="return optionKeyEvent(event);"
+     onkeypress="return optionKeyEvent(event);"&gt;
+  &lt;img src="buttoncut" role="button" alt="cut" id="button1"&gt;      
+  &lt;img src="buttoncopy" role="button" alt="copy" id="button1"&gt;     
+  &lt;img src="buttonpaste" role="button" alt="paste" id="button1"&gt;     
+&lt;/div&gt;
+</pre>
+
+
+<h2 id="more"> Provide accessible content </h2>
+
+
+<p>
+The remaining guidelines might be familiar
+because they reflect good practices for all web content,
+not just extensions.
+</p>
+
+<h3 id="text">Text</h3>
+
+<p>
+Evaluate your use of text in your extension.
+Many people might find it helpful
+if you provide a way to increase the text size within your extension.
+If you are using keyboard shortcuts,
+make sure that they don't interfere with
+the zoom shortcuts built into Google Chrome.
+</p>
+
+<p>
+As an indicator of the flexibility of your UI,
+apply the <a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-scale">200% test</a>.
+If you increase the text size or page zoom 200%,
+is your extension still usable?
+</p>
+
+<p>
+Also, avoid baking text into images:
+users cannot modify the size of text displayed as an image,
+and screenreaders cannot interpret images.
+Consider using a web font instead,
+such as one of the fonts collected in the
+<a href="http://code.google.com/apis/webfonts/">Google Font API</a>.
+Text styled in a web font is searchable,
+scales to different sizes,
+and is accessible to people using screen readers.
+</p>
+
+<h3 id="colors">Colors</h3>
+
+<p>
+Check that there is sufficient contrast between
+background color and foreground/text color in your extension.
+<a href="http://snook.ca/technical/colour_contrast/colour.html">This contrast checking tool</a>
+checks whether your background and foreground colors
+provide appropriate contrast.
+If you’re developing in a Windows environment,
+you can also enable High Contrast Mode
+to check the contrast of your extension.
+When evaluating contrast,
+verify that every part of your extension that relies on
+color or graphics to convey information is clearly visible.
+For specific images, you can use a tool such as the
+<a href="http://www.vischeck.com/vischeck/">Vischeck simulation tool</a>
+to see what an image looks like in various forms of color deficiency.
+</p>
+
+<p>
+You might consider offering different color themes,
+or giving the user the ability to customize the color scheme
+for better contrast. 
+</p>
+
+<h3 id="sound">Sound</h3>
+
+<p>
+If your extension relies upon sound or video to convey information,
+ensure that captions or a transcript are available.
+See the
+<a href="http://www.dcmp.org/ciy/">Described and Captioned Media Program guidelines</a>
+for more information on captions. 
+</p>
+
+<h3 id="images">Images</h3>
+
+<p>
+Provide informative alt text for your images.
+For example:
+</p>
+
+<pre>
+&lt;img src="img.jpg" alt="The logo for the extension"&gt;
+</pre>
+
+<p>
+Use the alt text to state the purpose of the image
+rather than as a literal description of the contents of an image.
+Spacer images or purely decorative images
+should have blank ("") alt text
+or be removed from the HTML entirely and placed in the CSS.
+</p>
+
+<p>
+If you must use text in an image,
+include the image text in the alt text.
+A good resource to refer to is the
+<a href="http://www.webaim.org/techniques/alttext/">WebAIM article on appropriate alt text</a>.
+
+<h2 id="examples">Examples</h2>
+
+<p>
+For an example that implements keyboard navigation and ARIA properties, see
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/news_a11y/">examples/extensions/news_a11y</a>
+(compare it to
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/news/">examples/extensions/news</a>).
+For more examples and for help in viewing the source code,
+see <a href="samples.html">Samples</a>.
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/about_apps.html b/chrome/common/extensions/docs/templates/articles/about_apps.html
new file mode 100644
index 0000000..cb83f78
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/about_apps.html
@@ -0,0 +1,118 @@
+<meta name="doc-family" content="apps">
+<h1>What Are Packaged Apps?</h1>
+
+
+<p>
+Packaged apps deliver an experience as capable as a native app,
+but as safe as a web page.
+Just like web apps,
+packaged apps are written in HTML5, JavaScript, and CSS.
+But packaged apps look and behave like native apps,
+and they have native-like capabilities
+that are much more powerful than those available to web apps.
+</p>
+
+<p>
+<iframe title="YouTube video player" width="610" height="380" src="http://www.youtube.com/embed/lBUGTVIJVfM" frameborder="0" allowfullscreen></iframe>
+</p>
+
+<p>
+Packaged apps have access to Chrome APIs and services not available to
+traditional web sites. You can build powerful apps that interact with network
+and hardware devices, media tools, and much more. Here's a short list of
+examples:
+</p>
+
+<ul>
+	<li>Shells (VMWare, Citrix, SSH, RDP or VNC clients)</li>
+	<li>Music/video streaming</li>
+	<li>Photo/video/music editing</li>
+</ul>
+
+<p>
+Watch the <a href="http://www.youtube.com/watch?v=j8oFAr1YR-0">Chrome Apps
+Google I/O presentation</a> for an in-depth introduction.
+</p>
+
+<h2 id="look">How they look</h2>
+
+<p>
+When a user opens a packaged app,
+their focus is specifically on the tasks
+relating to the app.
+Packaged apps have no traditional chrome:
+the omnibox (address bar), tab strip,
+and other browser interface elements no longer appear.
+Like native apps, they don’t live within the browser.
+When launched, packaged apps can open in windows
+that look like this (and you can style
+your windows in all different ways):
+</p>
+<br>
+
+<img src="{{static}}/images/editor.png"
+     width="770"
+     height="586"
+     alt="Text editor packaged app in a standalone window">
+
+<h2 id="behave">How they behave</h2>
+
+<p>
+Packaged app pages always load locally.
+This allows apps to be less dependent on the network.
+Once a user installs an app, they have full control over the app's lifecycle.
+Apps open and close quickly,
+and the system can shut apps down at any time to improve performance.
+Users can fully uninstall apps.
+</p>
+
+<p>
+Without any effort on your part, your apps will launch offline.
+But you will need to put some effort into making sure user data is stored locally while offline
+and then synced back up to your data server once online
+(see <a href="offline_apps.html">Offline First</a>).
+</p>
+
+<h2 id="develop">How to develop them</h2>
+
+<p>
+Packaged apps are modified web apps.
+You use the same code, frameworks, and tools of the web platform to write your apps.
+Some browser features have been removed, other web APIs have been disabled
+or changed to improve security and programming practices.
+</p>
+
+<p>
+New features have been added to help you build more native-like apps.
+The app container and programming models control how packaged apps look and behave.
+These models aim to provide users with a more native experience.
+Powerful APIs have been added so your apps can have native-like capabilities,
+and a serious security model is enforced to make sure these APIs are not abused.</p>
+
+<p>
+To learn more about how to develop packaged apps:
+</p>
+
+<ul>
+	<li>
+		<a href="app_architecture.html">Understanding the Architecture</a>
+		introduces the app container, programming, and security models.
+	</li>
+	<li>
+		<a href="app_lifecycle.html">The Fundamentals</a>
+		shows how to use this architecture and how to build
+		for offline, manage data, and embed external content.
+	</li>
+	<li>
+		<a href="app_network.html">Advanced Technologies</a>
+		shows how to use the powerful network and hardware APIs,
+		and how to connect your app to other apps using web intents.
+	</li>
+	<li>
+		<a href="app_deprecated.html">Disabled Features</a>
+		describes the web features that have been disabled
+		and what to use in their place, where relevant.
+	</li>
+</ul>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/activeTab.html b/chrome/common/extensions/docs/templates/articles/activeTab.html
new file mode 100644
index 0000000..fc07160
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/activeTab.html
@@ -0,0 +1,108 @@
+<style>
+  #active-tab-images {
+    margin-top: 1em;
+  }
+  #active-tab-images tr {
+    vertical-align: top;
+  }
+  #active-tab-images .spacing {
+    width: 1em;
+  }
+  #active-tab-before {
+    width: 334px;
+  }
+  #active-tab-after {
+    width: 265px;
+  }
+</style>
+
+<h1>The activeTab permission</h1>
+
+<p>
+The <code>activeTab</code> permission gives an extension temporary access to the currently active tab when the user <em>invokes</em> the extension - for example by clicking its <a href="browserAction.html">browser action</a>. Access to the tab lasts until the tab is navigated or closed.
+</p>
+
+<p>
+The main benefit of the <code>activeTab</code> permission is that it displays <em>no warning message</em> during installation:
+</p>
+
+<table id="active-tab-images" class="simple">
+  <tr>
+    <th>Without activeTab</th>
+    <th class="spacing"></th>
+    <th>With activeTab</th>
+  </tr>
+  <tr>
+    <td><img id="active-tab-before" src="{{static}}/images/active-tab-before.png"></td>
+    <td class="spacing"></td>
+    <td><img id="active-tab-after" src="{{static}}/images/active-tab-after.png"></td>
+  </tr>
+</table>
+
+<h2 id="example">Example</h2>
+
+<h3 id="manifest.json">manifest.json</h3>
+
+<p>
+<pre>
+{
+  "manifest_version": 2,
+  "name": "Page Redder",
+  "version": 1,
+  <b>"permissions": ["activeTab"]</b>,
+  "browser_action": {},
+  "background": {
+    "scripts": ["background.js"],
+    "persistent": false
+  }
+}
+</pre>
+</p>
+
+<h3 id="background.js">background.js</h3>
+
+<p>
+<pre>
+chrome.browserAction.onClicked.addListener(function(tab) {
+  // No tabs or host permissions needed!
+  chrome.tabs.executeScript(tab.id, {code:"document.body.bgColor='red'"});
+});
+</pre>
+</p>
+
+<h2 id="motivation">Motivation</h2>
+
+<p>
+Consider a web clipping extension that has a <a href="browserAction.html">browser action</a> and <a href="contextMenus.html">context menu item</a>. This extension may only really need to access tabs when its browser action is clicked, or when its context menu item is executed.
+</p>
+
+<p>
+Without <code>activeTab</code>, this extension would need to request full, persistent access to every web site, just so that it could do its work if it happened to be called upon by the user. This is a lot of power to entrust to such a simple extension. And if the extension is ever compromised, the attacker gets access to everything the extension had.
+</p>
+
+<p>
+In contrast, an extension with the <code>activeTab</code> permission only obtains access to a tab in response to an explicit user gesture. If the extension is compromised the attacker would need to wait for the user to invoke the extension before obtaining access. And that access only lasts until the tab is navigated or closed.
+</p>
+
+<h2 id="what-activeTab-allows">What activeTab allows</h2>
+
+<p>
+While the <code>activeTab</code> permission is enabled for a tab, an extension can:
+<ul>
+  <li>Call <code><a href="tabs.html#method-executeScript">executeScript()</a></code> or <code><a href="tabs.html#method-insertCSS">insertCSS()</a></code> on that tab.
+  <li>Get the URL, title, and favicon for that tab via an API that returns a <code><a href="tabs.html#type-Tab">Tab</a></code> object (essentially, <code>activeTab</code> grants the <code><a href="tabs.html#manifest">tabs</a></code> permission temporarily).
+</ul>
+</p>
+
+<h2 id="invoking-activeTab">Invoking activeTab</h2>
+
+<p>
+The following user gestures enable <code>activeTab</code>:
+<ul>
+  <li>Executing a <a href="browserAction.html">browser action</a>
+  <li>Executing a <a href="pageAction.html">page action</a>
+  <li>Executing a <a href="contextMenus.html">context menu item</a>
+  <li>Executing a keyboard shortcut from the <a href="commands.html">commands API</a>
+  <li>Accepting a suggestion from the <a href="omnibox.html">omnibox API</a>
+</ul>
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/angular_framework.html b/chrome/common/extensions/docs/templates/articles/angular_framework.html
new file mode 100644
index 0000000..46495f7
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/angular_framework.html
@@ -0,0 +1,771 @@
+<meta name="doc-family" content="apps">
+<h1>Build Apps with AngularJS</h1>
+<!--Article written by Eric Bidelman-->
+<p>
+This guide gets you started building packaged apps
+with the <a href="http://angularjs.org/">AngularJS</a> MVC framework.
+To illustrate Angular in action,
+we'll be referencing an actual app built using the framework,
+the Google Drive Uploader.
+The <a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/gdocs">source code</a>
+is available on GitHub.
+</p>
+
+<h2 id="first">About the app</h2>
+
+<img src="{{static}}/images/uploader.png"
+     width="296"
+     height="347"
+     style="float: right; padding-left: 5px"
+     alt="Google Drive Uploader">
+
+<p>
+The Google Drive Uploader allows users to quickly view and interact
+with files stored in their Google Drive account
+as well as upload new files using the
+<a href="http://www.html5rocks.com/en/tutorials/dnd/basics/">HTML Drag and Drop APIs</a>.
+It's a great example of building an app which talks
+to one of <a href="https://developers.google.com/apis-explorer/#p/">Google's APIs</a>;
+in this case, the Google Drive API.
+</p>
+
+<p class="note">
+<strong>Note: </strong>
+You can also build apps which talk to 3rd party APIs/services
+that are OAuth2-enabled.
+See <a href="http://developer.chrome.com/trunk/apps/app_identity.html#non">non-Google Account authentication</a>.
+</p>
+
+<p>
+The Uploader uses OAuth2 to access the user's data. The
+<a href="http://developer.chrome.com/trunk/apps/experimental.identity.html">chrome.experimental.identity API</a>
+handles fetching an OAuth token for the logged-in user,
+so the hard work is done for us!
+Once we have a long-lived access token,
+the apps uses the
+<a href="https://developers.google.com/drive/get-started">Google Drive API</a>
+to access the user's data.
+</p>
+
+<p>
+Key features this app uses:
+</p>
+
+<ul>
+    <li>Angular JS's autodetection for
+        <a href="http://developer.chrome.com/trunk/apps/app_csp.html">CSP</a></li>
+    <li>Render a list of files fetched from the
+        <a href="https://developers.google.com/drive/get-started">Google Drive API</a></li>
+    <li><a href="http://www.html5rocks.com/en/tutorials/file/filesystem/">HTML5 Filesystem API</a>
+        to store file icons offline</li>
+    <li><a href="http://www.html5rocks.com/en/tutorials/dnd/basics/">HTML5 Drag and Drop</a>
+        for importing/uploading new files from the desktop</li>
+    <li>XHR2 to load images, cross-domain</li>
+    <li><a href="http://developer.chrome.com/trunk/apps/app_identity.html">chrome.experimental.identity API</a>
+        for OAuth authorization</li>
+    <li>Chromeless frames to define the app's own navbar look and feel</li>
+</ul>
+
+<h2 id="second">Creating the manifest</h2>
+
+<p>
+All packaged apps require a <code>manifest.json</code> file
+which contains the information Chrome needs to launch the app.
+The manifest contains relevant metadata and
+lists any special permissions the app needs to run.
+</p>
+
+<p>
+A stripped down version of the Uploader's manifest looks like this:
+</p>
+
+<pre>
+{
+  "name": "Google Drive Uploader",
+  "version": "0.0.1",
+  "manifest_version": 2,
+  "oauth2": {
+    "client_id": "665859454684.apps.googleusercontent.com",
+    "scopes": [
+      "https://docs.google.com/feeds/",
+      "https://docs.googleusercontent.com/",
+      "https://spreadsheets.google.com/feeds/",
+      "https://www.googleapis.com/auth/drive"
+    ]
+  },
+ ...
+  "permissions": [
+    "experimental",
+    "https://docs.google.com/feeds/",
+    "https://docs.googleusercontent.com/",
+    "https://spreadsheets.google.com/feeds/",
+    "https://ssl.gstatic.com/",
+    "https://www.googleapis.com/"
+  ]
+}
+</pre>
+
+<p>
+The most important parts of this manifest are the "oauth2" and "permissions" sections.
+</p>
+
+<p>
+The "oauth2" section defines the required parameters by OAuth2 to do its magic.
+To create a "client_id", follow the instructions in
+<a href="http://developer.chrome.com/apps/app_identity.html#client_id">Get your client id</a>.
+The "scopes" list the authorization scopes
+that the OAuth token will be valid for (for example, the APIs the app wants to access).
+</p>
+
+<p class="note">
+<strong>Note: </strong>
+The Uploader actually uses the
+<a href="https://developers.google.com/google-apps/documents-list/">Documents List API v3</a>
+to access Google Drive content,
+hence needing the first several scopes to access all the different types of files.
+However, when using the Google Drive API, you only need
+<a href="https://www.googleapis.com/auth/drive">"https://www.googleapis.com/auth/drive"</a>.
+</p>
+
+<p>
+The "permissions" section includes the "experimental" bit
+(necessary because the current chrome.identity API is still experimental)
+and additional URLs that the app will access via XHR2.
+The URL prefixes are required in order for Chrome
+to know which cross-domain requests to allow.
+</p>
+
+<h2 id="three">Creating the event page</h2>
+
+<p>
+All packaged apps require a background script/page
+to launch the app and respond to system events.
+</p>
+
+<p>
+In its
+<a href="https://github.com/GoogleChrome/chrome-app-samples/blob/master/gdocs/js/background.js">background.js</a> 
+script,
+Drive Uploader opens a 500x600px window to the main page.
+It also specifies a minimum height and width for the window
+so the content doesn't become too crunched: 
+</p>
+
+<pre>
+chrome.app.runtime.onLaunched.addListener(function(launchData) {
+  chrome.app.window.create('../main.html', {
+    width: 500,
+    height: 600,
+    minWidth: 500,
+    minHeight: 600,
+    frame: 'none'
+  });
+});
+</pre>
+
+<p>
+The window is created as a chromeless window (frame: 'none').
+By default, windows render with the OS's default close/expand/minimize bar:
+</p>
+
+<img src="{{static}}/images/noframe.png"
+     width="508"
+     height="75"
+     alt="Google Drive Uploader with no frame">
+
+<p>
+The Uploader uses <code>frame: 'none'</code> to render the window as a "blank slate"
+and creates a custom close button in <code>main.html</code>:
+</p>
+
+<img src="{{static}}/images/customframe.png"
+     width="504"
+     height="50"
+     alt="Google Drive Uploader with custom frame">
+
+<p>
+The entire navigational area is wrapped in a &lt;nav> (see next section).
+To declutter the app a bit,
+the custom close button is hidden until the user interacts with this the area:
+</p>
+
+<pre>
+&lt;style>
+nav:hover #close-button {
+  opacity: 1;
+}
+
+#close-button {
+  float: right;
+  padding: 0 5px 2px 5px;
+  font-weight: bold;
+  opacity: 0;
+  -webkit-transition: all 0.3s ease-in-out;
+}
+&lt;/style>
+
+&lt;button class="btn" id="close-button" title="Close">x&lt;/button>
+</pre>
+
+<p>
+In
+<a href="https://github.com/GoogleChrome/chrome-app-samples/blob/master/gdocs/js/app.js">app.js</a>,
+this button is hooked up to <code>window.close()</code>.
+</p>
+
+<h2 id="four">Designing the app the Angular way</h2>
+
+<p>
+Angular is an MVC framework, so we need to define the app in such a way that a 
+model, view, and controller logically fall out of it. Luckily, this is trivial when using Angular.
+</p>
+
+<p>
+The View is the easiest, so let's start there.
+</p>
+
+<h3 id="view">Creating the view</h3>
+
+<p>
+<a href="https://github.com/GoogleChrome/chrome-app-samples/blob/master/gdocs/main.html">main.html</a>
+is the "V" in MVC; where we define HTML templates to render data into.
+In Angular, templates are simple blocks of HTML with some special sauce.
+</p>
+
+<p>
+Ultimately we want to display the user's list of files.
+For that, a simple &lt;ul> list makes sense.
+The Angular bits are highlighted in bold: 
+</p>
+
+<pre>
+&lt;ul>
+  &lt;li <strong>data-ng-repeat="doc in docs"</strong>>
+    &lt;img data-ng-src=<strong>"&#123;{doc.icon}&#125;"</strong>> &lt;a href=<strong>"&#123;{doc.alternateLink}&#125;"</strong>><strong>&#123;{doc.title}&#125;</strong>&lt;/a>
+<strong>&#123;{doc.size}&#125;</strong>
+    &lt;span class="date"><strong>&#123;{doc.updatedDate}&#125;</strong>&lt;/span>
+  &lt;/li>
+&lt;/ul>
+</pre>
+
+<p>
+This reads exactly as it looks:
+stamp out an &lt;li> for every doc in our data model "docs".
+Each item contains a file icon, link to open the file on the web,
+and last updatedDate.
+</p>
+
+<p class="note">
+<strong>Note: </strong>
+To make the template valid HTML,
+we're using <code>data-*</code> attributes for Angular's
+<a href="http://docs.angularjs.org/api/ng.directive:ngRepeat">nRepeat</a> iterator,
+but you don't have to.
+You could easily write the repeater as <code>&lt;li ng-repeat="doc in docs"></code>.
+</p>
+
+<p>
+Next, we need to tell Angular which controller will oversee this template's rendering.
+For that, we use the
+<a href="http://docs.angularjs.org/api/ng.directive:ngController">ngController</a>
+directive to tell the <code>DocsController</code> to have reign over the template <body>:
+</p>
+
+<pre>
+&lt;body <strong>data-ng-controller="DocsController"</strong>>
+&lt;section id="main">
+  &lt;ul>
+    &lt;li data-ng-repeat="doc in docs">
+      &lt;img data-ng-src="&#123;{doc.icon}&#125;"> &lt;a href="&#123;{doc.alternateLink}&#125;">&#123;{doc.title}&#125;&lt;/a> &#123;{doc.size}&#125;
+      &lt;span class="date">&#123;{doc.updatedDate}&#125;&lt;/span>
+    &lt;/li>
+  &lt;/ul>
+&lt;/section>
+&lt;/body>
+</pre>
+
+<p>
+Keep in mind,
+what you don't see here is us hooking up event listeners or properties for data binding.
+Angular is doing that heavy lifting for us!
+</p>
+
+<p>
+The last step is to make Angular light up our templates.
+The typical way to do that is include the
+<a href="http://docs.angularjs.org/api/ng.directive:ngApp">ngApp</a>
+directive all the way up on &lt;html>:
+</p>
+
+<pre>
+&lt;html <strong>data-ng-app="gDriveApp"</strong>>
+</pre>
+
+<p>
+You could also scope the app down
+to a smaller portion of the page if you wanted to.
+We only have one controller in this app,
+but if we were to add more later,
+putting <a href="http://docs.angularjs.org/api/ng.directive:ngApp">ngApp</a>
+on the topmost element makes the entire page Angular-ready.
+</p>
+
+<p>
+The final product for <code>main.html</code> looks something like this:
+</p>
+
+<pre>
+&lt;html <strong>data-ng-app="gDriveApp"</strong>>
+&lt;head>
+  …
+  <!-- crbug.com/120693: so we don't need target="_blank" on every anchor. -->
+  &lt;base target="_blank">
+&lt;/head>
+&lt;body <strong>data-ng-controller="DocsController"</strong>>
+&lt;section id="main">
+  &lt;nav>
+    &lt;h2>Google Drive Uploader&lt;/h2>
+    &lt;button class="btn" <strong>data-ng-click="fetchDocs()"</strong>>Refresh&lt;/button>
+    &lt;button class="btn" id="close-button" title="Close">&lt;/button>
+  &lt;/nav>
+  &lt;ul>
+    &lt;li <strong>data-ng-repeat="doc in docs"</strong>>
+      &lt;img data-ng-src=<strong>"&#123;{doc.icon}&#125;"</strong>> &lt;a href=<strong>"&#123;{doc.alternateLink}&#125;"</strong>><strong>&#123;{doc.title}&#125;</strong>&lt;/a>  <strong>&#123;{doc.size}&#125;</strong>
+      &lt;span class="date"><strong>&#123;{doc.updatedDate}&#125;</strong>&lt;/span>
+    &lt;/li>
+  &lt;/ul>
+&lt;/section>
+</pre>
+
+<h3 id="csp">A word on Content Security Policy</h3>
+
+<p>
+Unlike many other JS MVC frameworks,
+Angular v1.1.0+ requires no tweaks to work within a strict
+<a href="http://developer.chrome.com/trunk/apps/app_csp.html">CSP</a>.
+It just works, out of the box!
+</p>
+
+<p>
+However, if you're using an older version
+of Angular between v1.0.1 and v1.1.0,
+you'll need tell Angular to run in a "content security mode".
+This is done by including the
+<a href="http://docs.angularjs.org/api/ng.directive:ngCsp">ngCsp</a>
+directive alongside <a href="http://docs.angularjs.org/api/ng.directive:ngApp">ngApp</a>:
+</p>
+
+<pre>
+&lt;html data-ng-app data-ng-csp>
+</pre>
+
+<h3 id="authorization">Handling authorization</h3>
+
+<p>
+The data model isn't generated by the app itself.
+Instead, it's populated from an external API (the Google Drive API).
+Thus, there's a bit of work necessary in order to populate the app's data.
+</p>
+
+<p>
+Before we can make an API request,
+we need to fetch an OAuth token for the user's Google Account.
+For that, we've created a method to wrap the call
+to <code>chrome.experimental.identity.getAuthToken()</code> and
+store the <code>accessToken</code>,
+which we can reuse for future calls to the Drive API.
+</p>
+
+<pre>
+GDocs.prototype.auth = function(opt_callback) {
+  try {
+    <strong>chrome.experimental.identity.getAuthToken({interactive: false}, function(token) {</strong>
+      if (token) {
+        this.accessToken = token;
+        opt_callback && opt_callback();
+      }
+    }.bind(this));
+  } catch(e) {
+    console.log(e);
+  }
+};
+</pre>
+
+<p class="note">
+<strong>Note: </strong>
+Passing the optional callback gives us the flexibility
+of knowing when the OAuth token is ready.
+</p>
+
+<p class="note">
+<strong>Note: </strong>
+To simplify things a bit,
+we've created a library,
+<a href="https://github.com/GoogleChrome/chrome-app-samples/blob/master/gdocs/js/gdocs.js">gdocs.js</a>
+to handle API tasks.
+</p>
+
+<p>
+Once we have the token,
+it's time to make requests against the Drive API and populate the model.
+</p>
+
+<h3 id="skeleton">Skeleton controller</h3>
+
+<p>
+The "model" for the Uploader is a simple array (called docs)
+of objects that will get rendered as those &lt;li>s in the template:
+</p>
+
+<pre>
+var gDriveApp = angular.module('gDriveApp', []);
+
+gDriveApp.factory('gdocs', function() {
+  var gdocs = new GDocs();
+  return gdocs;
+});
+
+function DocsController($scope, $http, gdocs) {
+  $scope.docs = [];
+
+  $scope.fetchDocs = function() {
+     ...
+  };
+
+  // Invoke on ctor call. Fetch docs after we have the oauth token.
+  gdocs.auth(function() {
+    $scope.fetchDocs();
+  });
+
+}
+</pre>
+
+<p>
+Notice that <code>gdocs.auth()</code> is called
+as part of the DocsController constructor.
+When Angular's internals create the controller,
+we're insured to have a fresh OAuth token waiting for the user.
+</p>
+
+<h2 id="five">Fetching data</h2>
+
+<p>
+Template laid out.
+Controller scaffolded.
+OAuth token in hand.
+Now what?
+</p>
+
+<p>
+It's time to define the main controller method,
+<code>fetchDocs()</code>.
+It's the workhorse of the controller,
+responsible for requesting the user's files and
+filing the docs array with data from API responses.
+</p>
+
+<pre>
+$scope.fetchDocs = function() {
+  $scope.docs = []; // First, clear out any old results
+
+  // Response handler that doesn't cache file icons.
+  var successCallback = function(resp, status, headers, config) {
+    var docs = [];
+    var totalEntries = resp.feed.entry.length;
+
+    resp.feed.entry.forEach(function(entry, i) {
+      var doc = {
+        title: entry.title.$t,
+        updatedDate: Util.formatDate(entry.updated.$t),
+        updatedDateFull: entry.updated.$t,
+        icon: gdocs.getLink(entry.link,
+                            'http://schemas.google.com/docs/2007#icon').href,
+        alternateLink: gdocs.getLink(entry.link, 'alternate').href,
+        size: entry.docs$size ? '( ' + entry.docs$size.$t + ' bytes)' : null
+      };
+
+      $scope.docs.push(doc);
+
+      // Only sort when last entry is seen.
+      if (totalEntries - 1 == i) {
+        $scope.docs.sort(Util.sortByDate);
+      }
+    });
+  };
+
+  var config = {
+    params: {'alt': 'json'},
+    headers: {
+      'Authorization': 'Bearer ' + gdocs.accessToken,
+      'GData-Version': '3.0'
+    }
+  };
+
+  $http.get(gdocs.DOCLIST_FEED, config).success(successCallback);
+};
+</pre>
+
+<p>
+<code>fetchDocs()</code> uses Angular's <code>$http</code> service
+to retrieve the main feed over XHR.
+The oauth access token is included
+in the <code>Authorization</code> header
+along with other custom headers and parameters.
+</p>
+
+<p>
+The <code>successCallback</code> processes the API response and
+creates a new doc object for each entry in the feed. 
+</p>
+
+<p>
+If you run <code>fetchDocs()</code> right now,
+everything works and the list of files shows up:
+</p>
+
+<img src="{{static}}/images/listoffiles.png"
+     width="580"
+     height="680"
+     alt="Fetched list of files in Google Drive Uploader">
+
+<p>
+Woot!
+</p>
+
+<p>
+Wait,...we're missing those neat file icons.
+What gives?
+A quick check of the console shows a bunch of CSP-related errors:
+</p>
+
+<img src="{{static}}/images/csperrors.png"
+     width="947"
+     height="84"
+     alt="CSP errors in developer console">
+
+<p>
+The reason is that we're trying
+to set the icons <code>img.src</code> to external URLs.
+This violates CSP.
+For example:
+<code>https://ssl.gstatic.com/docs/doclist/images/icon_10_document_list.png</code>.
+To fix this,
+we need to pull in these remote assets locally to the app.
+</p>
+
+<h3 id="import">Importing remote image assets</h3>
+
+<p>
+For CSP to stop yelling at us,
+we use XHR2 to "import" the file icons as Blobs,
+then set the <code>img.src</code>
+to a <code>blob: URL</code> created by the app.
+</p>
+
+<p>
+Here's the updated <code>successCallback</code>
+with the added XHR code:
+</p>
+
+<pre>
+var successCallback = function(resp, status, headers, config) {
+  var docs = [];
+  var totalEntries = resp.feed.entry.length;
+
+  resp.feed.entry.forEach(function(entry, i) {
+    var doc = {
+      ...
+    };
+
+    <strong>$http.get(doc.icon, {responseType: 'blob'}).success(function(blob) {
+      console.log('Fetched icon via XHR');
+
+      blob.name = doc.iconFilename; // Add icon filename to blob.
+
+      writeFile(blob); // Write is async, but that's ok.
+
+      doc.icon = window.URL.createObjectURL(blob);
+
+      $scope.docs.push(doc);
+
+      // Only sort when last entry is seen.
+      if (totalEntries - 1 == i) {
+        $scope.docs.sort(Util.sortByDate);
+      }
+    });</strong>
+  });
+};
+</pre>
+
+<p>
+Now that CSP is happy with us again,
+we get nice file icons:
+</p>
+
+<img src="{{static}}/images/fileicons.png"
+     width="580"
+     height="680"
+     alt="Google Drive Uploader with file icons">
+
+<h2 id="six">Going offline: caching external resources</h2>
+
+<p>
+The obvious optimization that needs to be made:
+not make 100s of XHR requests for each file icon
+on every call to <code>fetchDocs()</code>.
+Verify this in the Developer Tools console
+by pressing the "Refresh" button several times.
+Every time, n images are fetched:
+</p>
+
+<img src="{{static}}/images/fetchedicon.png"
+     width="118"
+     height="19"
+     alt="Console log 65: Fetched icon via XHR">
+
+<p>
+Let's modify <code>successCallback</code>
+to add a caching layer.
+The additions are highlighted in bold:
+</p>
+
+<pre>
+$scope.fetchDocs = function() {
+  ...
+
+  // Response handler that caches file icons in the filesystem API.
+  var successCallbackWithFsCaching = function(resp, status, headers, config) {
+    var docs = [];
+    var totalEntries = resp.feed.entry.length;
+
+    resp.feed.entry.forEach(function(entry, i) {
+      var doc = {
+        ...
+      };
+
+      <strong>// 'https://ssl.gstatic.com/doc_icon_128.png' -> 'doc_icon_128.png'
+      doc.iconFilename = doc.icon.substring(doc.icon.lastIndexOf('/') + 1);</strong>
+
+      // If file exists, it we'll get back a FileEntry for the filesystem URL.
+      // Otherwise, the error callback will fire and we need to XHR it in and
+      // write it to the FS.
+      <strong>var fsURL = fs.root.toURL() + FOLDERNAME + '/' + doc.iconFilename;
+      window.webkitResolveLocalFileSystemURL(fsURL, function(entry) {
+        doc.icon = entry.toURL(); // should be === to fsURL, but whatevs.</strong>
+        
+        $scope.docs.push(doc); // add doc to model.
+
+        // Only want to sort and call $apply() when we have all entries.
+        if (totalEntries - 1 == i) {
+          $scope.docs.sort(Util.sortByDate);
+          $scope.$apply(function($scope) {}); // Inform angular that we made changes.
+        }
+
+      <strong>}, function(e) {
+        // Error: file doesn't exist yet. XHR it in and write it to the FS.
+        
+        $http.get(doc.icon, {responseType: 'blob'}).success(function(blob) {
+          console.log('Fetched icon via XHR');
+
+          blob.name = doc.iconFilename; // Add icon filename to blob.
+
+          writeFile(blob); // Write is async, but that's ok.
+
+          doc.icon = window.URL.createObjectURL(blob);
+
+          $scope.docs.push(doc);
+
+          // Only sort when last entry is seen.
+          if (totalEntries - 1 == i) {
+            $scope.docs.sort(Util.sortByDate);
+          }
+        });
+
+      });</strong>
+    });
+  };
+
+  var config = {
+    ...
+  };
+
+  $http.get(gdocs.DOCLIST_FEED, config).success(successCallbackWithFsCaching);
+};
+</pre>
+
+<p>
+Notice that in the <code>webkitResolveLocalFileSystemURL()</code> callback
+we're calling <code>$scope.$apply()</code>
+when  the last entry is seen.
+Normally calling <code>$apply()</code> isn't necessary.
+Angular detects changes to data models automagically.
+However in our case,
+we have an addition layer of asynchronous callback
+that Angular isn't aware of.
+We must explicitly tell Angular when our model has been updated.
+</p>
+
+<p>
+On first run,
+the icons won't be in the HTML5 Filesystem and the calls to
+<code>window.webkitResolveLocalFileSystemURL()</code> will result
+in its error callback being invoked.
+For that case,
+we can reuse the technique from before and fetch the images.
+The only difference this time is that
+each blob is written to the filesystem (see
+<a href="https://github.com/GoogleChrome/chrome-app-samples/blob/master/gdocs/js/app.js#L25">writeBlob()</a>).
+The console verifies this behavior:
+</p>
+
+<img src="{{static}}/images/writecompleted.png"
+     width="804"
+     height="42"
+     alt="Console log 100: Write completed">
+
+<p>
+Upon next run (or press of the "Refresh" button),
+the URL passed to <code>webkitResolveLocalFileSystemURL()</code> exists
+because the file has been previously cached.
+The app sets the <code>doc.icon</code>
+to the file's <code>filesystem: URL</code> and
+avoids making the costly XHR for the icon.
+</p>
+
+<h2 id="seven">Drag and drop uploading</h2>
+
+<p>
+An uploader app is false advertising
+if it can't upload files!
+</p>
+
+<p>
+<a href="https://github.com/GoogleChrome/chrome-app-samples/blob/master/gdocs/js/app.js#L177">app.js</a>
+handles this feature by implementing a small library
+around HTML5 Drag and Drop called <code>DnDFileController</code>.
+It gives the ability to drag in files from the desktop
+and have them uploaded to Google Drive.
+</p>
+
+<p>
+Simply adding this to the gdocs service does the job:
+</p>
+
+<pre>
+gDriveApp.factory('gdocs', function() {
+  var gdocs = new GDocs();
+  
+  var dnd = new DnDFileController('body', function(files) {
+    var $scope = angular.element(this).scope();
+    Util.toArray(files).forEach(function(file, i) {
+      gdocs.upload(file, function() {
+        $scope.fetchDocs();
+      });
+    });
+  });
+
+  return gdocs;
+});
+</pre>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/api_index.html b/chrome/common/extensions/docs/templates/articles/api_index.html
new file mode 100644
index 0000000..3b1f7b9
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/api_index.html
@@ -0,0 +1,59 @@
+<h1 class="page_title">chrome.* APIs</h1>
+{{?is_apps}}
+<p class="doc-family apps">
+Chrome provides many special-purpose APIs like
+<code>chrome.runtime</code> and <code>chrome.alarms</code>
+to packaged apps.
+</p>
+{{:is_apps}}
+<p class="doc-family extensions">
+Chrome provides APIs such as
+<code>chrome.bookmarks</code> and <code>chrome.tab</code>
+so that extensions can interact with the browser.
+</p>
+{{/is_apps}}
+
+<h2 id="supported">Supported APIs</h2>
+
+<p>
+Here are the supported chrome.* APIs:
+</p>
+
+<ul>
+  {{?is_apps}}
+  {{#api_list.apps.chrome}}
+  <li><a href="{{name}}.html">{{name}}</a></li>
+  {{/}}
+  {{:is_apps}}
+  {{#api_list.extensions.chrome}}
+  <li><a href="{{name}}.html">{{name}}</a></li>
+  {{/}}
+  {{/is_apps}}
+</ul>
+
+<h2 id="experimental">Experimental APIs</h2>
+
+<p>
+Chrome also has
+<a href="experimental.html">experimental APIs</a>,
+some of which will become supported APIs
+in future releases of Chrome.
+</p>
+
+<h2 id="conventions">API conventions</h2>
+
+<p>
+Unless the doc says otherwise,
+methods in the chrome.* APIs are <b>asynchronous</b>:
+they return immediately,
+without waiting for the operation to finish.
+If you need to know the outcome of an operation,
+then you pass a callback function into the method.
+For more information, watch this video:
+</p>
+
+{{^is_apps}}
+<p class="doc-family extensions">
+<iframe title="YouTube video player" width="640" height="390" src="http://www.youtube.com/embed/bmxr75CV36A?rel=0" frameborder="0" allowfullscreen></iframe>
+</p>
+{{/is_apps}}
diff --git a/chrome/common/extensions/docs/templates/articles/api_other.html b/chrome/common/extensions/docs/templates/articles/api_other.html
new file mode 100644
index 0000000..7fc08ee
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/api_other.html
@@ -0,0 +1,87 @@
+<h1>Other APIs</h1>
+
+<p>
+In addition to the
+<a href="api_index.html">chrome.* APIs</a>,
+extensions can use all the APIs
+that the browser provides
+to web pages and apps.
+If the browser doesn't support an API you want to use,
+you can bundle additional API libraries into your extension.
+</p>
+
+<p>Here's a sampling of the APIs that extensions can use:</p>
+
+<dl>
+<dt><strong> Standard JavaScript APIs </strong></dt>
+<dd> These are the same core JavaScript and
+  <a href="https://developer.mozilla.org/en/Gecko_DOM_Reference">Document Object Model</a>
+  (DOM) APIs
+  that you can use in ordinary web apps.
+  
+<!-- Use onclick in your toolbar div to add click behavior.
+E.g. window.open(someUrl). --></dd>
+<dt><strong> XMLHttpRequest </strong></dt>
+<dd>
+  Use <a href="xhr.html">XMLHttpRequest</a>
+  to request data from one or more servers.
+  The <a href="manifest.html#permissions">permissions</a> field
+  of the manifest specifies
+  which hosts the extension can send requests to.
+  </dd>
+<dt> <strong>HTML5 and other emerging APIs</strong></dt>
+<dd> Google Chrome supports HTML5 features,
+     along with other emerging APIs.
+     Here are some of the APIs you can use:
+  <ul>
+  <li> audio
+    (<a href="http://www.html5rocks.com/tutorials/audio/quick/">tutorial</a>) </li>
+  <li> application cache
+    (<a href="http://www.html5rocks.com/tutorials/appcache/beginner/">tutorial</a>) </li>
+  <li> canvas
+    (<a href="http://www.html5rocks.com/en/tutorials/#canvas">articles</a>) </li>
+  <li> geolocation
+    (<a href="http://www.html5rocks.com/tutorials/geolocation/trip_meter/">tutorial</a>) </li>
+  <li> local storage
+    (<a href="http://www.html5rocks.com/en/tutorials/offline/storage/">tutorial</a>) </li>
+  <li> notifications
+    (<a href="http://www.html5rocks.com/tutorials/notifications/quick/">tutorial</a>) </li>
+  <li> video
+    (<a href="http://www.html5rocks.com/en/tutorials/video/basics/">tutorial</a>) </li>
+  <li> web database
+    (<a href="http://www.html5rocks.com/tutorials/webdatabase/todo/">tutorial</a>) </li>
+  </ul>
+  <p>
+  See <a href="http://www.html5rocks.com">html5rocks.com</a>
+  for HTML5 information, tutorials, an interactive playground,
+  and links to other resources.
+  </p>
+</dd>
+
+<dt><strong> WebKit APIs </strong></dt>
+<dd>
+  Because Google Chrome is built upon WebKit,
+  your extensions can use WebKit APIs.
+  Especially useful are the experimental CSS features
+  such as filters, animations, and transformations.
+  Here's an example of using WebKit styles
+  to make the UI spin:
+  <pre>&lt;style>
+  div:hover {
+    -webkit-transform: rotate(360deg);
+    -webkit-transition: all 1s ease-out;
+  }
+&lt;/style>
+</pre>
+  
+</dd>
+<dt><strong> V8 APIs</strong>, such as<strong> JSON </strong></dt>
+<dd> Because JSON is in V8, you don't need to include a JSON library to use JSON functions. </dd>
+<dt><strong>APIs in bundled libraries</strong></dt>
+<dd> If you want to use a library that the browser doesn't provide
+(for example, jQuery),
+you can bundle that library's JavaScript files with your extension.
+Bundled libraries work in extensions
+just as they do in other web pages.
+</dd>
+</dl>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/app_architecture.html b/chrome/common/extensions/docs/templates/articles/app_architecture.html
new file mode 100644
index 0000000..17ca249
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/app_architecture.html
@@ -0,0 +1,173 @@
+<meta name="doc-family" content="apps">
+<h1>Understand the Architecture</h1>
+
+
+<p>
+Packaged apps integrate closely with a user’s operating system.
+They are designed to be run outside of a browser tab,
+to run robustly in offline and poor connectivity scenarios and
+to have far more powerful capabilities than are available
+in a typical web browsing environment.
+The app container, programming, and security models
+support these packaged app requirements.
+</p>
+
+<h2 id="container">App container model</h2>
+
+<p>
+The app container describes the visual appearance
+and loading behavior of packaged apps.
+Packaged apps look different than traditional web apps
+because the app container does not show any traditional web page UI controls;
+it simply contains a blank rectangular area.
+This allows an app to blend with “native” apps on the system,
+and it prevents the user from “messing” with the app logic
+by manually changing the URL.
+</p>
+
+<p>
+Packaged apps are loaded differently than web apps.
+Both load the same type of content:
+HTML documents with CSS and JavaScript;
+however, a packaged app is loaded in the app container,
+not in the browser tab.
+Also, the app container must load the main document
+of the packaged app from a local source.
+This forces all packaged apps to be at least minimally functional
+when offline and it provides a place
+to enforce stricter security measures.
+</p>
+
+<img src="{{static}}/images/container.png"
+     width="671"
+     height="172"
+     alt="how app container model works">
+
+
+<h2 id="programming">Programming model</h2>
+
+<p>
+The programming model describes the lifecycle
+and window behavior of packaged apps.
+Similar to native apps,
+the goal of this programming model is to give users
+and their systems full control over the app lifecycle.
+The packaged app lifecycle should be independent
+of browser window behavior or a network connection.
+</p>
+
+<p>
+The “event page” manages the packaged app lifecycle
+by responding to user gestures and system events.
+This page is invisible, only exists in the background,
+and can be closed automatically by the system runtime.
+It controls how windows open and close and
+when the app is started or terminated.
+There can only be one “event page” for a packaged app.
+<p>
+
+<p>
+<iframe title="YouTube video player" width="610" height="380" src="http://www.youtube.com/embed/yr1jgREbH8U" frameborder="0" allowfullscreen></iframe>
+</p>
+
+<h3 id="lifecycle">App lifecycle at a glance</h3>
+
+<p>
+For detailed instructions on how to use the programming model,
+see <a href="app_lifecycle.html">Manage App Lifecycle</a>.
+Here's a brief summary of the packaged app lifecyle
+to get you started:
+</p>
+
+<br>
+
+<table class="simple">
+  <tr>
+    <th scope="col"> Stage </th>
+    <th scope="col"> Summary </th>
+  </tr>
+  <tr>
+    <td>Installation</td>
+    <td>User chooses to install the app and explicitly accepts the
+    	<a href="manifest.html#permissions">permissions</a>.
+    </td>
+  </tr>
+  <tr>
+    <td>Startup</td>
+    <td>The event page is loaded,
+      the 'launch' event fires,
+      and app pages open in windows.
+      You 
+      <a href="app_lifecycle.html#eventpage">create the windows</a>
+      that your app requires,
+      how they look, and how they communicate
+      with the event page and with other windows.
+    </td>
+  </tr>
+  <tr>
+    <td>Termination</td>
+    <td>User can terminate apps at any time
+      and app can be quickly restored to previous state.
+      <a href="app_lifecycle.html#H3-7">Stashing data</a>
+    	protects against data loss.</td>
+  </tr>
+  <tr>
+    <td>Update</td>
+    <td>Apps can be updated at any time;
+      however, the code that a packaged app is running
+      cannot change during a startup/termination cycle.</td>
+  </tr>
+  <tr>
+    <td>Uninstallation</td>
+    <td>User can actively uninstall apps.
+    	When uninstalled, no executing code or
+    	private data is left behind.</td>
+  </tr>
+</table>
+
+<h2 id="security">Security model</h2>
+
+<p>
+The packaged apps security model protects users
+by ensuring their information is managed
+in a safe and secure manner.
+<a href="app_csp.html">Comply with CSP</a>
+includes detailed information on how to comply with content security policy.
+This policy blocks dangerous scripting
+reducing cross-site scripting bugs
+and protecting users against man-in-the-middle attacks.
+</p>
+
+<p>
+Loading the packaged app main page locally provides a place
+to enforce stricter security than the web.
+Like Chrome extensions,
+users must explicitly agree to trust the packaged app on install;
+they grant the app permission to access and use their data.
+Each API that your app uses will have its own permission.
+The packaged apps security model also provides the ability
+to set up privilege separation on a per window basis.
+This allows you to minimize the code in your app
+that has access to dangerous APIs,
+while still getting to use them.
+</p>
+
+<p>
+Packaged apps reuse Chrome extension process isolation,
+and take this a step further by isolating storage and external content.
+Each app has its own private storage area
+and can’t access the storage of another app
+or personal data (such as cookies) for websites that you use in your browser.
+All external processes are isolated from the app.
+Since iframes run in the same process as the surrounding page,
+they can only be used to load other app pages.
+You can use the <code>object</code> tag to
+<a href="app_external.html">embed external content</a>;
+this content runs in a separate process from the app.
+</p>
+
+<p>
+<iframe title="YouTube video player" width="610" height="380" src="http://www.youtube.com/embed/EDtiWN42lHs" frameborder="0" allowfullscreen></iframe>
+</p>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
diff --git a/chrome/common/extensions/docs/templates/articles/app_csp.html b/chrome/common/extensions/docs/templates/articles/app_csp.html
new file mode 100644
index 0000000..ed754ac
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/app_csp.html
@@ -0,0 +1,126 @@
+<h1>Comply with CSP</h1>
+
+
+<p>
+If you're unfamiliar with Content Security Policy (CSP),
+<a href="http://www.html5rocks.com/en/tutorials/security/content-security-policy/">An Introduction to Content Security Policy</a>
+is a good starting point.
+It covers the broader web platform view of CSP;
+packaged apps CSP isn't as flexible.
+You should read the
+<a href="http://code.google.com/chrome/extensions/contentSecurityPolicy.html">Chrome extension Content Security Policy</a>
+as it's the foundation for the packaged app CSP.
+For brevity's sake,
+we don't repeat the same information here.
+</p>
+
+<p>
+CSP is a policy to mitigate against cross-site scripting issues,
+and we all know that cross-scripting is bad.
+We aren’t going to try and convince you
+that CSP is a warm-and-fuzzy new policy.
+There's work involved;
+you'll need to learn how to do fundamental tasks differently.
+</p>
+
+<p>
+The purpose of this doc is to tell you
+exactly what the CSP policy is for packaged apps,
+what you need to do to comply with it,
+and how you can still do those fundamental tasks
+in a way that is CSP&ndash;compliant.
+</p>
+
+<h2 id="what">What is the CSP for packaged apps?</h2>
+
+<p>The content security policy for packaged apps restricts
+you from doing the following:</p>
+
+<ul>
+	<li>You can’t use inline scripting in your packaged app pages.
+		The restriction bans both &lt;script> blocks and
+		event handlers (&lt;button onclick=”...”>).</li>
+	<li>You can’t reference any external resources in any of your app files
+		(except for video and audio resources).
+		You can’t embed external resources in an iframe.</li>
+	<li>You can’t use string-to-JavaScript methods like
+		<code>eval()</code> and <code>function()</code>.</li>
+</ul>
+
+<p>This is implemented via the following policy value:</p>
+
+<pre>
+default-src 'self';
+connect-src *;
+style-src 'self' data: chrome-extension-resource: 'unsafe-inline';
+img-src 'self' data: chrome-extension-resource:;
+frame-src 'self' data: chrome-extension-resource:;
+font-src 'self' data: chrome-extension-resource:;
+media-src *;
+</pre>
+
+<p>
+Your packaged app can only refer to scripts and objects
+within your app, with the exception of media files
+(apps can refer to video and audio outside the package).
+Chrome extensions will let you relax the default Content Security Policy;
+packaged apps won’t.
+</p>
+
+<h2 id="how">How to comply with CSP</h2>
+
+<p>
+All JavaScript and all resources should be local
+(everything gets packaged in your packaged app).
+</p>
+
+<h2 id="but">"But then how do I..."</h2>
+
+<p>
+It's very possible that you are using templating libraries
+and many of these won’t work with CSP.
+You may also want to access external resources in your app
+(external images, content from websites).
+</p>
+
+<h3 id="templating">Use templating libraries</h3>
+
+<p>
+Use a library that offers precompiled templates
+and you’re all set.
+You can still use a library that doesn’t offer precompilation,
+but it will require some work on your part and there are restrictions.
+</p>
+
+<p>
+You will need to use sandboxing to isolate any content
+that you want to do ‘eval’ things to.
+Sandboxing lifts CSP on the content that you specify.
+If you want to use the very powerful Chrome APIs in your packaged app,
+your sandboxed content can't directly interact with these APIs
+(see <a href="app_external.html#sandboxing">Sandbox local content</a>).
+</p>
+
+<h3 id="remote_resources">Access remote resources</h3>
+
+<p>
+You can fetch remote resources via <code>XMLHttpRequest</code>
+and serve them via <code>blob:</code>, <code>data:</code>,
+or <code>filesystem:</code> URLs
+(see <a href="app_external.html#external">Referencing external resources</a>).
+</p>
+
+<p>
+Video and audio can be loaded from remote services
+because they have good fallback behavior when offline or under spotty connectivity.
+</p>
+
+<h3 id="embed_content">Embed web content</h3>
+
+<p>
+Instead of using an iframe,
+you can call out to an external URL using an object tag
+(see <a href="app_external.html#objecttag">Embed external web pages</a>).
+</p>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
diff --git a/chrome/common/extensions/docs/templates/articles/app_deprecated.html b/chrome/common/extensions/docs/templates/articles/app_deprecated.html
new file mode 100644
index 0000000..850c3ff
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/app_deprecated.html
@@ -0,0 +1,99 @@
+<h1>Disabled Web Features</h1>
+<!--  -->
+
+<p>
+Though packaged apps use the web platform,
+some web features have been disabled
+or else are used in a different way.
+Mainly this is to avoid security issues and
+to improve programming practices.
+Below is a summary of the disabled features
+of the web platform
+and potential work-arounds:
+</p>
+
+<table class="simple">
+  <tr>
+    <th scope="col"> Disabled </th>
+    <th scope="col"> Work-around </th>
+  </tr>
+  <tr>
+    <td><code>alert</code></td>
+    <td>Use a custom lightbox/popup.</td>
+  </tr>
+  <tr>
+    <td>Browser chrome APIs</td>
+    <td>N/A.</td>
+  </tr>
+  <tr>
+    <td><code>confirm</code></td>
+    <td>Use a custom lightbox/popup.</td>
+  </tr>
+  <tr>
+    <td><code>document.cookie</code></td>
+    <td>Packaged app pages are not rendered on the server, so there is no need to use these.</td>
+  </tr>
+  <tr>
+    <td><code>document.close</code></td>
+    <td>N/A.</td>
+  </tr>
+  <tr>
+    <td><code>document.open</code></td>
+    <td>N/A.</td>
+  </tr>
+  <tr>
+    <td><code>document.write</code></td>
+    <td>Use document.createElement.</td>
+  </tr>
+  <tr>
+    <td>External resources</td>
+    <td>Use the <code>object</code> tag for iframes
+      See <a href="app_external.html">Embed Content</a>.
+      Video and audio are allowed to have non-local URLs.</td>
+  </tr>
+  <tr>
+    <td>Flash</td>
+    <td>Use HTML5 Platform.</td>
+  </tr>
+  <tr>
+    <td>Form submission</td>
+    <td>Use JavaScript to process form content
+      (listen for submit event, process data locally first
+      before sending to server).</td>
+  </tr>
+  <tr>
+    <td>javascript: urls</td>
+    <td>You cannot use bookmarklets for inline javascript on anchors.
+      Use the traditional click handler instead.</td>
+  </tr>
+  <tr>
+    <td>localStorage</td>
+    <td>Use IndexedDB or the Storage API (which also syncs to the cloud).</td>
+  </tr>
+  <tr>
+    <td>Navigation</td>
+    <td>Links open up with the system web browser.
+      <code>window.history</code> and <code>window.location</code>
+      are disabled.</td>
+  </tr>
+  <tr>
+    <td>Non-sandboxed plugins</td>
+    <td>N/A.</td>
+  </tr>
+  <tr>
+    <td><code>showModalDialog</code></td>
+    <td>Use a custom lightbox/popup.</td>
+  </tr>
+  <tr>
+    <td>Synchronous <code>XMLHttpRequest</code></td>
+    <td>Use async-only <code>XMLHttpRequest</code>:
+      <a href="http://updates.html5rocks.com/2012/01/Getting-Rid-of-Synchronous-XHRs">Getting Rid of Synchrounous XXRs</a>.</td>
+  </tr>
+  <tr>
+    <td>webSql</td>
+    <td>Use IndexedDB or
+      <a href="app_storage.html">Filesystem API</a>.</td>
+  </tr>
+</table>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
diff --git a/chrome/common/extensions/docs/templates/articles/app_external.html b/chrome/common/extensions/docs/templates/articles/app_external.html
new file mode 100644
index 0000000..db080c6
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/app_external.html
@@ -0,0 +1,281 @@
+<h1>Embed Content</h1>
+
+
+<p>
+The <a href="app_architecture.html#security">packaged apps security model</a> disallows
+external content in iframes and
+the use of inline scripting and <code>eval()</code>.
+You can override these restrictions,
+but your external content must be isolated from the app.
+</p>
+
+<p>
+Isolated content cannot directly
+access the app's data or any of the APIs.
+Use cross-origin XMLHttpRequests
+and post-messaging to communicate between the event page and sandboxed content
+and indirectly access the APIs.
+</p>
+
+<p class="note">
+<b>API Sample: </b>
+Want to play with the code?
+Check out the
+<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/sandbox">sandbox</a> sample.
+</p>
+
+<h2 id="external">Referencing external resources</h2>
+
+<p>
+The <a href="app_csp.html">Content Security Policy</a> used by apps disallows
+the use of many kinds of remote URLs, so you can't directly reference external
+images, stylesheets, or fonts from an app page. Instead, you can use use
+cross-origin XMLHttpRequests to fetch these resources,
+and then serve them via <code>blob:</code> URLs.
+</p>
+
+<h3 id="manifest">Manifest requirement</h3>
+
+<p>
+To be able to do cross-origin XMLHttpRequests, you'll need to add a permission
+for the remote URL's host:
+</p>
+
+<pre>
+"permissions": [
+    "...",
+    "https://supersweetdomainbutnotcspfriendly.com/"
+  ]
+</pre>
+
+<h3 id="cross-origin">Cross-origin XMLHttpRequest</h3>
+
+<p>
+Fetch the remote URL into the app and serve its contents as a <code>blob:</code>
+URL:
+</p>
+
+<pre>
+var xhr = new XMLHttpRequest();
+xhr.open('GET', 'https://supersweetdomainbutnotcspfriendly.com/image.png', true);
+xhr.responseType = 'blob';
+xhr.onload = function(e) {
+  var img = document.createElement('img');
+  img.src = window.webkitURL.createObjectURL(this.response);
+  document.body.appendChild(img);
+};
+
+xhr.send();
+</pre>
+
+<p>You may want to <a href="offline_apps.html#saving-locally">save</a>
+these resources locally, so that they are available offline.</p>
+
+<h2 id="webview">Embed external web pages</h2>
+
+<p class="note">
+<b>API Sample: </b>
+Want to play with the code? Check out the
+<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/browser">browser</a>
+sample.
+</p>
+
+<p>
+The <code>webview</code> tag allows you to embed external web content in your
+app, for example, a web page. It replaces iframes that point to remote URLs,
+which are disabled inside packaged apps. Unlike iframes, the
+<code>webview</code> tag runs in a separate process. This means that an exploit
+inside of it will still be isolated and won't be able to gain elevated
+privileges. Further, since its storage (cookies, etc.) is isolated from the app,
+there is no way for the web content to access any of the app's data.
+</p>
+
+<h3 id="webview_element">Add webview element</h3>
+
+<p>
+Your <code>webview</code> element must include the URL to the source content
+and specify its dimensions.
+</p>
+
+<pre>&lt;webview src="http://news.google.com/" width="640" height="480">&lt;/webview></pre>
+
+<h3 id="properties">Update properties</h3>
+
+<p>
+To dynamically change the <code>src</code>, <code>width</code> and
+<code>height</code> properties of a <code>webview</code> tag, you can either
+set those properties directly on the JavaScript object, or use the
+<code>setAttribute</code> DOM function.
+</p>
+
+<pre>
+document.querySelector('#mywebview').src =
+    'http://blog.chromium.org/';
+// or
+document.querySelector('#mywebview').setAttribute(
+    'src', 'http://blog.chromium.org/');
+</pre>
+
+<h2 id="sandboxing">Sandbox local content</h2>
+
+<p>
+Sandboxing allows specified pages
+to be served in a sandboxed, unique origin.
+These pages are then exempt from their Content Security Policy.
+Sandboxed pages can use iframes, inline scripting,
+and <code>eval()</code>.
+Check out the manifest field description for
+<a href="manifest.html#sandbox">sandbox</a>.
+</p>
+
+<p>
+It's a trade-off though:
+sandboxed pages can't use the chrome.* APIs.
+If you need to do things like <code>eval()</code>,
+go this route to be exempt from CSP,
+but you won't be able to use the cool new stuff.
+</p>
+
+<h3 id="inline_scripts">Use inline scripts in sandbox</h3>
+
+<p>
+Here's a sample sandboxed page
+which uses an inline script and <code>eval()</code>:
+</p>
+
+<pre>
+&lt;html>
+  &lt;body>
+    &lt;h1>Woot&lt;/h1>
+    &lt;script>
+      document.write('I am an inline script.&lt;br>');
+      eval('document.write(\'I am an eval-ed inline script.\');');
+    &lt;/script>
+  &lt;/body>
+&lt;/html>
+</pre>
+
+<h3 id="include_sandbox">Include sandbox in manifest</h3>
+
+<p>
+You need to include the <code>sandbox</code> field in the manifest
+and list the app pages to be served in a sandbox:
+</p>
+
+<pre>
+"sandbox": {
+  "pages": ["sandboxed.html"]
+}
+</pre>
+
+<h3 id="opening_sandbox">Opening a sandboxed page in a window</h3>
+
+<p>
+Just like any other app pages,
+you can create a window that the sandboxed page opens in.
+Here's a sample that creates two windows,
+one for the main app window that isn't sandboxed,
+and one for the sandboxed page:
+</p>
+
+<pre>
+chrome.app.runtime.onLaunched.addListener(function() {
+  chrome.app.window.create('window.html', {
+    'width': 400,
+    'height': 400,
+    'left': 0,
+    'top': 0
+  });
+
+  chrome.app.window.create('sandboxed.html', {
+    'width': 400,
+    'height': 400,
+    'left': 400,
+    'top': 0
+  });
+});
+</pre>
+
+<h3 id="embedding_sandbox">Embedding a sandboxed page in an app page</h3>
+
+<p>Sandboxed pages can also be embedded within another app page
+  using an <code>iframe</code>:</p>
+
+<pre>
+&lt;!DOCTYPE html>
+&lt;html>
+&lt;head>
+&lt;/head>
+  &lt;body>
+    &lt;p>I am normal app window.&lt;/p>
+
+    &lt;iframe src="sandboxed.html" width="300" height="200">&lt;/iframe>
+  &lt;/body>
+&lt;/html>
+</pre>
+
+
+<h2 id="postMessage">Sending messages to sandboxed pages</h2>
+
+<p>
+There are two parts to sending a message:
+you need to post a message from the sender page/window,
+and listen for messages on the receiving page/window.
+</p>
+
+<h3 id="post_message">Post message</h3>
+
+<p>
+You can use <code>postMessage</code> to communicate
+between your app and sandboxed content.
+Here's a sample background script
+that posts a message to the sandboxed page it
+opens:
+</p>
+
+<pre>
+var myWin = null;
+
+chrome.app.runtime.onLaunched.addListener(function() {
+  chrome.app.window.create('sandboxed.html', {
+    'width': 400,
+    'height': 400
+  }, function(win) {
+       myWin = win;
+       myWin.postMessage('Just wanted to say hey.', '*');
+     });
+});
+</pre>
+
+<p>
+Generally speaking on the web,
+you want to specify the exact origin
+from where the message is sent.
+Packaged apps have no access
+to the unique origin of sandboxed content,
+so you can only whitelist all origins
+as acceptable origins ('*').
+On the receiving end,
+you generally want to check the origin;
+but since packaged apps content is contained,
+it isn't necessary.
+To find out more,
+see <a href="https://developer.mozilla.org/en/DOM/window.postMessage">window.postMessage</a>.
+</p>
+
+<h3 id="listen_message">Listen for message</h3>
+
+<p>
+Here's a sample message receiver
+that gets added to your sandboxed page:
+</p>
+
+<pre>
+var messageHandler = function(e) {
+  console.log('Background script says hello.', e.data);
+};
+
+window.addEventListener('message', messageHandler);
+</pre>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
diff --git a/chrome/common/extensions/docs/templates/articles/app_frameworks.html b/chrome/common/extensions/docs/templates/articles/app_frameworks.html
new file mode 100644
index 0000000..42765be
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/app_frameworks.html
@@ -0,0 +1,329 @@
+<h1>MVC Architecture</h1>
+
+
+<p>
+As modern browsers become more powerful with rich features,
+building full-blown web applications in JavaScript is not only feasible,
+but increasingly popular.
+Based on
+<a href="http://httparchive.org/trends.php?s=intersection&minlabel=Jan+20+2011&maxlabel=Jan+15+2012">trends</a>
+on <a href="http://httparchive.org/">HTTP Archive</a>,
+deployed JavaScript code size has grown 45% over the course of the year.
+</p>
+
+<img src="{{static}}/images/jstransferrequests.png"
+     width="568"
+     height="292"
+     alt="JS transfer size and JS requests">
+
+<p>
+With JavaScript's popularity climbing,
+our client-side applications are much more complex than before.
+Application development requires collaboration from multiple developers.
+Writing <strong>maintainable</strong> and
+<strong>reusable</strong> code is crucial in the new web app era.
+The Chrome packaged app, with its rich client-side features, is no exception. 
+</p>
+
+<p>
+Design patterns are important to write maintainable and reusable code.
+A pattern is a reusable solution that can be applied to commonly occurring problems in software design &mdash;
+in our case &mdash; writing Chrome packaged apps.
+We recommend that developers decouple the app
+into a series of independent components following the MVC pattern.
+</p>
+
+<p>
+In the last few years,
+a series of JavaScript MVC frameworks have been developed,
+such as <a href="http://backbonejs.org/">backbone.js</a>, <a href="http://emberjs.com/">ember.js</a>, <a href="http://angularjs.org/">AngularJS</a>, <a href="http://sencha.com/">Sencha</a>, <a href="http://www.kendoui.com/">Kendo UI</a>, and more.
+While they all have their unique advantages, each one of them follows some form of MVC pattern
+with the goal of encouraging developers to write more structured JavaScript code.
+</p>
+
+<h2 id="mvc">MVC pattern overview</h2>
+
+<p>
+MVC offers architectural benefits over standard JavaScript &mdash;
+it helps you write better organized, and therefore more maintainable code.
+This pattern has been used and extensively tested
+over multiple languages and generations of programmers.
+</p>
+
+<p>
+MVC is composed of three components:
+</p>
+
+<img src="{{static}}/images/mvc.png"
+     width="466"
+     height="303"
+     alt="model-view-controller">
+
+<h3 id="model">Model</h3>
+
+<p>
+Model is where the application’s data objects are stored.
+The model doesn’t know anything about views and controllers.
+When a model changes, typically it will notify its observers that a change has occurred.
+</p>
+
+<p>
+To understand this further, let’s use the Todo list app, a simple, one page web app that tracks your task list.
+</p>
+
+<br>
+
+<img src="{{static}}/images/todos.png"
+     width="444"
+     height="366"
+     alt="model-view-controller">
+
+<p>
+The model here represents attributes associated
+with each todo item such as description and status.
+When a new todo item is created,
+it is stored in an instance of the model.
+</p>
+
+<h3 id="view">View</h3>
+
+<p>
+View is what's presented to the users and how users interact with the app.
+The view is made with HTML, CSS, JavaScript and often templates.
+This part of your Chrome packaged app has access to the DOM.
+</p>
+
+<p>
+For example, in the above todo list web app,
+you can create a view that nicely presents the list of todo items to your users.
+Users can also enter a new todo item through some input format;
+however, the view doesn’t know how to update the model because that’s the controller’s job.
+</p>
+
+<h3 id="controller">Controller</h3>
+
+<p>
+The controller is the decision maker and the glue between the model and view.
+The controller updates the view when the model changes.
+It also adds event listeners to the view and
+updates the model when the user manipulates the view.
+</p>
+
+<p>
+In the todo list web app,
+when the user checks an item as completed,
+the click is forwarded to the controller.
+The controller modifies the model to mark item as completed.
+If the data needs to be persistent, it also makes an async save to the server.
+In rich client-side web app development such as Chrome packaged apps,
+keeping the data persistent in local storage is also crucial.
+In this case, the controller also handles saving the data
+to the client-side storage such as <a href="app_storage.html">FileSystem API</a>.
+</p>
+
+<p>
+There are a few variations of the MVC design pattern
+such as MVP (Model&ndash;View&ndash;Presenter)
+and MVVP(Model&ndash;View&ndash;ViewModel).
+Even with the so called MVC design pattern itself,
+there is some variation between the traditional MVC pattern
+vs the modern interpretation in various programming languages.
+For example, some MVC&ndash;based frameworks will have
+the view observe the changes in the models
+while others will let the controller handle the view update.
+This article is not focused on the comparison of various implementations
+but rather on the separation&ndash;of&ndash;concerns and
+it's importance in writing modern web apps.
+</p>
+
+<p>
+If you are interested in learning more,
+we recommend <a href="https://plus.google.com/u/0/115133653231679625609/posts">Addy Osmani's</a> online book: <a href="http://addyosmani.com/resources/essentialjsdesignpatterns/book/">Learning JavaScript Design Patterns</a>.
+</p>
+
+<p>
+To summarize, the MVC pattern brings modularity
+to application developers and it enables:
+</p>
+
+<ul>
+	<li>Reusable and extendable code.</li>
+	<li>Separation of view logic from business logic.</li>
+	<li>Allow simultaneous work between developers who are responsible
+		for different components (such as UI layer and core logic).</li>
+	<li>Easier to maintain.</li>
+</ul>
+
+<h2 id="mvcpersistence">MVC persistence patterns</h2>
+
+<p>
+There are many different ways of implementing persistence
+with an MVC framework, each with different trade&ndash;offs.
+When writing Chrome packaged apps,
+choose the frameworks with MVC and persistence patterns
+that feel natural to you and fit you application needs.
+</p>
+
+<h3 id="model_persistence">Model does its own persistence - ActiveRecord pattern</h3>
+
+<p>
+Popular in both server&ndash;side frameworks like Ruby on Rails,
+and client-side frameworks like
+<a href="http://backbonejs.org">Backbone.js</a> and
+<a href="http://emberjs.com/">ember.js</a>,
+the ActiveRecord pattern places the responsibility
+for persistence on the model itself
+and is typically implemented via JSON API.
+</p>
+
+<p>
+A slightly different take from
+having a model handle the persistence
+is to introduce a separate concept of Store and Adapter API.
+Store, Model and
+Adapter (in some frameworks it is called Proxy)
+work hand by hand.
+Store is the repository that holds the loaded models,
+and it also provides functions such as creating,
+querying and filtering the model instances contained within it.
+</p>
+
+<p>
+An adapter, or a proxy, receives the requests from a store and
+translates them into appropriate actions to take
+against your persistent data layer
+(such as JSON API).
+This is interesting in the modern web app design
+because you often interact with more than one persistent data layer
+such as a remote server and browser’s local storage.
+Chrome package apps provides both
+<a href="storage.html">Chrome Storage API</a> and
+<a href="fileSystem.html">HTML 5 fileSystem API</a> for client side storage.
+</p>
+
+<p>Pros:</p>
+
+<ul>
+	<li>Simple to use and understand.</li>
+</ul>
+
+<p>
+Cons:
+</p>
+
+<ul>
+	<li>Hard to test since the persistence layer is ‘baked’ into the object hierarchy.</li>
+	<li>Having different objects use different persistent stores is difficult
+		(for example, FileSystem APIs vs indexedDB vs server&ndash;side).</li>
+	<li>Reusing Model in other applications may create conflicts,
+		such as sharing a single Customer class between two different views,
+		each view wanting to save to different places.</li>
+</ul>
+
+<h3 id="controller_persistence">Controller does persistence</h3>
+
+<p>
+In this pattern, the controller holds a reference
+to both the model and a datastore
+and is responsible for keeping the model persisted.
+The controller responds to lifecycle events like Load, Save, Delete,
+and issues commands to the datastore to fetch or update the model. 
+</p>
+
+<p>
+Pros:
+</p>
+
+<ul>
+	<li>Easier to test, controller can be passed a mock datastore to write tests against.</li>
+	<li>The same model can be reused with multiple datastores just by constructing controllers with different datastores.</li>
+</ul>
+
+<p>
+Cons:
+</p>
+
+<ul>
+	<li>Code can be more complex to maintain.</li>
+</ul>
+
+<h3 id="app_controller">AppController does persistence</h3>
+
+<p>
+In some patterns, there is a supervising controller responsible
+for navigating between one MVC and another.
+The AppController decides, for example,
+that a ‘Back’ button moves the client from an editing screen
+(which contains MVC widgets/formats),
+to a settings screen.
+</p>
+
+<p>
+In the AppController pattern,
+the AppController responds to events
+and changes the app’s current screen by issuing a call
+to the datastore to load any models needed and
+constructing all of the matching views and controllers for that screen.
+</p>
+
+<p>
+Pros:
+</p>
+
+<ul>
+	<li>Moves persistence layer even higher up the stack where it can be easily changed.</li>
+	<li>Doesn’t pollute lower level controllers like a DatePickerController with the need to know about persistence.</li>
+	<li>Aligns nicely with an ‘Intent’ model.
+		Each AppController corresponds to an intent, like “ChoosePhoto”, or “SendMessage”.
+		The Intent contains a reference to the model data needed (Customer#123) and
+		the type of CRUD operation (load, save, delete, and so on).</li>
+</ul>
+
+<p>
+Cons:
+</p>
+
+<ul>
+	<li>Each ‘Page/Screen’ of the app now requires a lot of boilerplate to write or update: Model, View, Controller, AppController.</li>
+</ul>
+
+<h3 id="recommended">Recommended MVC frameworks</h3>
+
+<p>
+MVC is crucial to designing Chrome packaged apps.
+We recommend the following <a href="app_csp.html">CSP&ndash;Compliant</a> MVC frameworks
+for writing secure and scalable Chrome packaged apps:
+</p>
+
+<ul>
+  <li><a href="http://angularjs.org/">AngularJS</a>
+  (<a href="https://github.com/GoogleChrome/textdrive-app">Text Drive Reference App</a> and <a href="angular_framework.html">Build Apps with AngularJS tutorial</a>)</li>
+  <li><a href="http://www.kendoui.com/">Kendo UI</a>
+  (<a href="https://github.com/GoogleChrome/kendo-photo-booth-app">Photo Booth Reference App</a>)</li>
+  <li><a href="http://www.sencha.com/">Sencha</a>
+  (<a href="https://github.com/GoogleChrome/sencha-video-player-app">Video Player Reference App</a> and <a href="sencha_framework.html">Build Apps with Sencha Ext JS tutorial</a>)</li>
+</ul>
+
+<h2 id="resources">Useful resources</h2>
+
+<h3 id="online">Online</h3>
+
+<ul>
+	<li><a href="http://www.html5rocks.com/">HTML5Rocks.com</a></li>
+	<li><a href="http://addyosmani.com/resources/essentialjsdesignpatterns/book/">Learning JavaScript Design Patterns</a>
+		(by Addy Osmani)</li>
+	<li><a href="http://addyosmani.github.com/todomvc/">TodoMVC</a></li>
+</ul>
+
+<h3 id="books">Books</h3>
+
+<ul>
+	<li><a href="http://www.amazon.com/JavaScript-Web-Applications-Alex-MacCaw/dp/144930351X">JavaScript Web Applications</a>
+		(By Alex MacCaw)</li>
+	<li><a href="http://www.amazon.com/JavaScript-Patterns-Stoyan-Stefanov/dp/0596806752/ref=pd_sim_b_2">JavaScript Patterns</a>
+		(By Stoyan Stefonov)</li>
+	<li><a href="http://www.amazon.com/Maintainable-JavaScript-Nicholas-C-Zakas/dp/1449327680">Maintainable JavaScript</a>
+		(By Nicolas Z. Zakas)</li>
+</ul>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
diff --git a/chrome/common/extensions/docs/templates/articles/app_hardware.html b/chrome/common/extensions/docs/templates/articles/app_hardware.html
new file mode 100644
index 0000000..c9fc8b0
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/app_hardware.html
@@ -0,0 +1,696 @@
+<h1>Accessing Hardware Devices</h1>
+
+
+<p>
+This doc shows you how packaged apps can connect to USB devices
+and read from and write to a user's serial ports.
+See also the reference docs for the
+<a href="experimental.usb.html">USB API</a>
+and the
+<a href="serial.html">Serial API</a>.
+The <a href="experimental.bluetooth.html">Bluetooth API</a> has just landed and
+we'll write more on this soon.
+We've included a link to an early sample below.
+</p>
+
+<p class="note">
+<b>API Samples: </b>
+Want to play with the code?
+Check out the
+<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/serial">serial</a>,
+<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/servo">servo</a>,
+<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/usb">usb</a>,
+and <a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/bluetooth-demo">bluetooth-demo</a> samples.
+</p>
+
+<h2 id="usb">Accessing USB devices</h2>
+
+<p>
+You can use the USB API to send messages to any connected device.
+</p>
+
+<h3 id="manifest">Manifest requirement</h3>
+
+<p>
+You must add the "usb" permission
+to the manifest file:
+</p>
+
+<pre>
+"permissions": [
+  "app.window",
+  "experimental",
+  "usb"
+]
+</pre>
+
+<h3 id="finding_device">Finding a device</h3>
+
+<p>
+Every device in a USB bus is identified
+by its vendor and product IDs.
+To find a device,
+use the <code>findDevice()</code> method
+which has four parameters:
+</p>
+
+<pre>
+chrome.experimental.usb.findDevice(vendorId, productId, eventCallbacks, onDeviceFoundCallback)
+</pre>
+
+<br>
+
+<table class="simple">
+  <tr>
+    <th scope="col"> Parameter (type) </th>
+    <th scope="col"> Description </th>
+  </tr>
+  <tr>
+    <td>vendorId (long)</td>
+    <td>The vendor ID for your USB device (in Linux, use lsusb to find it).</td>
+  </tr>
+  <tr>
+    <td>productId (long)</td>
+    <td>The product ID for your USB device (in Linux, use lsusb to find it).</td>
+  </tr>
+  <tr>
+    <td>eventCallbacks(object)</td>
+    <td>An object with a single key, "onEvent",
+      to be called whenever an event occurs on the corresponding device.
+      This will be the primary method of receiving information from the device.
+      As the host-initiated USB protocol is complex, read on to learn more.
+    </td>
+  </tr>
+  <tr>
+    <td>onDeviceFoundCallback()</td>
+    <td>Called when the device is found.
+      The callback's parameter is an object
+      with three properties: <code>handle</code>,
+      <code>vendorId</code>,
+      <code>productId</code>;
+      or <code>NULL</code>,
+      if no device is found.
+      Save this object
+      as it's required to send messages to the device.</td>
+  </tr>
+</table>
+
+<p>
+Example:
+</p>
+
+<pre>
+var onDeviceFound = function(device) {
+  deviceObj=device;
+  if (device) {
+     console.log(“Device found: ”+device.handle);
+  } else {
+     console.log(“Device could not be found”);
+  }
+};
+
+var onUsbEvent = function(event) {
+     console.log(“USB event!”);
+};
+
+chrome.experimental.usb.findDevice(vendorId, productId, {"onEvent": onUsbEvent}, onDeviceFound);
+</pre>
+
+<h3 id="usb_transfers">USB transfers and receiving data from a device</h3>
+
+<p>
+USB protocol defines four types of transfers:
+control, bulk, isochronous and interrupt.
+Theoretically they can all occur in both directions:<br>
+device-to-host and host-to-device.
+Host-to-device is initiated by your code and is described in the next sections.
+</p>
+
+<p>
+Device-to-host messages are handled by Chrome and delivered
+to the <code>onEvent()</code> callback
+defined in the <code>findDevice()</code> method.
+For each message from the device,
+the <code>onEvent</code> callback will receive 
+an event object with the following properties:
+</p>
+
+<br>
+
+<table class="simple">
+  <tr>
+    <th scope="col"> Property </th>
+    <th scope="col"> Description </th>
+  </tr>
+  <tr>
+    <td>type (string)</td>
+    <td>Currently always contains the string "transferResult".</td>
+  </tr>
+  <tr>
+    <td>resultCode (integer)</td>
+    <td>0 is success; other values indicate failure.</td>
+  </tr>
+  <tr>
+    <td>data (integer array)</td>
+    <td>Contains the data sent by the device.
+    </td>
+  </tr>
+</table>
+
+<p>
+Example:
+</p>
+
+<pre>
+var onUsbEvent = function(event) {
+    if (event &amp;&amp; event.resultCode===0 &amp;&amp; event.data) {
+     console.log(“got ”+event.data.length+" bytes");
+    }
+};
+
+chrome.experimental.usb.findDevice( vendorId, productId, {"onEvent": onUsbEvent}, onDeviceFound);
+</pre>
+
+<h3 id="control_transfers">Sending data - control transfers</h3>
+
+<p>
+Control transfers are generally used to send configuration
+or command parameters to a USB device.
+The method is simple and receives three parameters:
+</p>
+
+<pre>
+chrome.experimental.usb.controlTransfer(deviceObj, transferInfo, transferCallback)
+</pre>
+
+<br>
+
+<table class="simple">
+  <tr>
+    <th scope="col"> Parameter (types)</th>
+    <th scope="col"> Description </th>
+  </tr>
+  <tr>
+    <td>deviceObj</td>
+    <td>Object received from <code>findDevice()</code> callback.</td>
+  </tr>
+  <tr>
+    <td>transferInfo</td>
+    <td>Parameter object with values from the table below.
+      Check your USB device protocol specification
+      to know which values are supported.</td>
+  </tr>
+  <tr>
+    <td>transferCallback()</td>
+    <td>Invoked when the transfer has completed.
+      Please note this only indicates that
+      the transfer has been processed.
+      The device's response, if any, will always be sent through
+      the <code>onEvent()</code> callback set on <code>findDevice()</code>.
+    </td>
+  </tr>
+</table>
+
+<p>
+Values for <code>transferInfo</code> object:
+</p>
+
+<table class="simple">
+  <tr>
+    <th scope="col"> Value </th>
+    <th scope="col"> Description </th>
+  </tr>
+  <tr>
+    <td>requestType (string)</td>
+    <td>"vendor", "standard", "class" or "reserved".</td>
+  </tr>
+  <tr>
+    <td>recipient (string)</td>
+    <td>"device", "interface", "endpoint" or "other".</td>
+  </tr>
+  <tr>
+    <td>direction (string)</td>
+    <td>in" or "out".
+      IN direction is used to notify the device
+      that it should send information to the host.
+      All communication in a USB bus is host-initiated,
+      so use an 'in' transfer to allow a device
+      to send information back.</td>
+  </tr>
+  <tr>
+    <td>request (integer)</td>
+    <td>Defined by your device's protocol.</td>
+  </tr>
+  <tr>
+    <td>value (integer)</td>
+    <td>Defined by your device's protocol.</td>
+  </tr>
+  <tr>
+    <td>index (integer)</td>
+    <td>Defined by your device's protocol.</td>
+  </tr>
+  <tr>
+    <td>length (integer)</td>
+    <td>Only used when direction is "in".
+      Notifies the device that this is the amount
+      of data the host is expecting in response.</td>
+  </tr>
+  <tr>
+    <td>data (integer array)</td>
+    <td>Defined by your device's protocol,
+      required when direction is "out".
+      <b>WARNING: </b>in the near future,
+      this parameter will likely change
+      to <code>ArrayBuffer</code>.</td>
+  </tr>
+</table>
+
+<p>
+Example:
+</p>
+
+<pre>
+var transferInfo = {
+   "requestType": "vendor",
+   "recipient": "device",
+   "direction": "out",
+   "request":  0x31,
+   "value": 120,
+   "index": 0,
+   "data": [4, 8, 15, 16, 23, 42]
+ };
+chrome.experimental.usb.controlTransfer(deviceObj, transferInfo, optionalCallback);
+</pre>
+
+<h3 id="isochronous_transfers">Sending data - isochronous transfers</h3>
+
+<p>
+Isochronous transfers are commonly used for streams of data.
+Video and sound, for example, are good candidates for this transfer type.
+To send an isochronous transfer, use:
+</p>
+
+<pre>
+chrome.experimental.usb.isochronousTransfer(deviceObj, isochronousTransferInfo, transferCallback)
+</pre>
+
+<br>
+
+<table class="simple">
+  <tr>
+    <th scope="col"> Parameter </th>
+    <th scope="col"> Description </th>
+  </tr>
+  <tr>
+    <td>deviceObj</td>
+    <td>Object sent on <code>findDevice()</code> callback.</td>
+  </tr>
+  <tr>
+    <td>isochronousTransferInfo</td>
+    <td>Parameter object with the values in the table below.</td>
+  </tr>
+  <tr>
+    <td>transferCallback()</td>
+    <td>Invoked when the transfer has completed.
+      Notice that this callback doesn't represent any response from the device.
+      It's just to notify your code that the asynchronous transfer request
+      has been processed and you can go ahead.
+      The device's response, if any, will always be sent through
+      the <code>onEvent()</code> callback set on <code>findDevice()</code>.
+    </td>
+  </tr>
+</table>
+
+<p>
+Values for <code>isochronousTransferInfo</code> object:
+</p>
+
+<table class="simple">
+  <tr>
+    <th scope="col"> Value </th>
+    <th scope="col"> Description </th>
+  </tr>
+  <tr>
+    <td>transferInfo (object)</td>
+    <td>A parameter object with the following parameters:<br>
+      <b>direction (string): </b>"in" or "out".<br>
+      <b>endpoint (integer): </b>defined by your device's protocol.<br>
+      <b>length (integer): </b>only used when direction is "in".
+      Notifies the device that this is the amount
+      of data the host is expecting in response<br>
+      <b>data (integer array): </b>defined by your device's protocol;
+      only used when direction is "out".
+    </td>
+  </tr>
+  <tr>
+    <td>packets (integer)</td>
+    <td>Total number of packets expected in this transfer.</td>
+  </tr>
+  <tr>
+    <td>packetLength (integer)</td>
+    <td>Expected length of each packet in this transfer.</td>
+  </tr>
+</table>
+
+<p>
+Example:
+</p>
+
+<pre>
+var transferInfo = {
+   "direction": "in",
+   "endpoint": 1,
+   "length": 2560
+ };
+var isoTransferInfo = {
+  "transferInfo": transferInfo,
+  "packets": 20,
+  "packetLength": 128
+};
+chrome.experimental.usb.isochronousTransfer(deviceObj, isoTransferInfo, optionalCallback);
+</pre>
+
+<h3 id="bulk_transfers">Sending data - bulk transfers</h3>
+
+<p>
+Bulk transfer is a USB transfer type commonly used
+to transfer a large amount of data reliably.
+The method has three parameters:
+</p>
+
+<pre>
+chrome.experimental.usb.bulkTransfer(deviceObj, transferInfo, transferCallback)
+</pre>
+
+<br>
+
+<table class="simple">
+  <tr>
+    <th scope="col"> Parameter </th>
+    <th scope="col"> Description </th>
+  </tr>
+  <tr>
+    <td>deviceObj</td>
+    <td>Object sent on <code>findDevice()</code> callback.</td>
+  </tr>
+  <tr>
+    <td>transferInfo</td>
+    <td>Parameter object with the values in the table below.</td>
+  </tr>
+  <tr>
+    <td>transferCallback()</td>
+    <td>Invoked when the transfer has completed.
+      Notice that this callback doesn't represent any response from the device.
+      It's just to notify your code that the asynchronous transfer request
+      has been processed and you can go ahead.
+      The device's response, if any, will always be sent through
+      the <code>onEvent()</code> callback set on <code>findDevice()</code>.
+    </td>
+  </tr>
+</table>
+
+<p>
+Values for <code>transferInfo</code> object:
+</p>
+
+<table class="simple">
+  <tr>
+    <th scope="col"> Value </th>
+    <th scope="col"> Description </th>
+  </tr>
+  <tr>
+    <td>direction (string)</td>
+    <td>"in" or "out".</td>
+  </tr>
+  <tr>
+    <td>endpoint (integer)</td>
+    <td>Defined by your device's protocol.</td>
+  </tr>
+  <tr>
+    <td>length (integer)</td>
+    <td>Only used when direction is "in".
+      Notifies the device that this is the amount
+      of data the host is expecting in response.</td>
+  </tr>
+  <tr>
+    <td>data (integer array)</td>
+    <td>Defined by your device's protocol;
+      only used when direction is "out".</td>
+  </tr>
+</table>
+
+<p>
+Example:
+</p>
+
+<pre>
+var transferInfo = {
+   "direction": "out",
+   "endpoint": 1,
+   "data": [4, 8, 15, 16, 23, 42]
+ };
+</pre>
+
+<h3 id="interrupt_transfers">Sending data - interrupt transfers</h3>
+
+<p>
+Interrupt transfers are used to send important notifications.
+Since all USB communication is initiated by the host,
+host code usually polls the device periodically,
+sending interrupt IN transfers that will make the device send data back
+if there is anything on the interrupt queue.
+The method has three parameters:
+</p>
+
+<pre>
+chrome.experimental.usb.interruptTransfer(deviceObj, transferInfo, transferCallback)
+</pre>
+
+<br>
+
+<table class="simple">
+  <tr>
+    <th scope="col"> Parameter </th>
+    <th scope="col"> Description </th>
+  </tr>
+  <tr>
+    <td>deviceObj</td>
+    <td>Object sent on <code>findDevice()</code> callback.</td>
+  </tr>
+  <tr>
+    <td>transferInfo</td>
+    <td>Parameter object with the values in the table below.</td>
+  </tr>
+  <tr>
+    <td>transferCallback()</td>
+    <td>Invoked when the transfer has completed.
+      Notice that this callback doesn't represent any response from the device.
+      It's just to notify your code that the asynchronous transfer request
+      has been processed and you can go ahead.
+      The device's response, if any, will always be sent through
+      the <code>onEvent()</code> callback set on <code>findDevice()</code>.
+    </td>
+  </tr>
+</table>
+
+<p>
+Values for <code>transferInfo</code> object:
+</p>
+
+<table class="simple">
+  <tr>
+    <th scope="col"> Value </th>
+    <th scope="col"> Description </th>
+  </tr>
+  <tr>
+    <td>direction (string)</td>
+    <td>"in" or "out".</td>
+  </tr>
+  <tr>
+    <td>endpoint (integer)</td>
+    <td>Defined by your device's protocol.</td>
+  </tr>
+  <tr>
+    <td>length (integer)</td>
+    <td>Only used when direction is "in".
+      Notifies the device that this is the amount
+      of data the host is expecting in response.</td>
+  </tr>
+  <tr>
+    <td>data (integer array)</td>
+    <td>Defined by your device's protocol;
+      only used when direction is "out".</td>
+  </tr>
+
+<p>
+Example:
+</p>
+
+<pre>
+var transferInfo = {
+   "direction": "in", 
+   "endpoint": 1,
+   "length": 2
+ };
+chrome.experimental.usb.interruptTransfer(deviceObj, transferInfo, optionalCallback);
+</pre>
+
+<h3 id="caveats">Caveats</h3>
+
+<p>
+On Linux,
+you must have specific permission to access USB devices.
+Create a file: <code>/etc/udev/rules.d/50-yourdevicename.rules</code>.
+Add the following content:
+</p>
+
+<pre>
+SUBSYSTEM=="usb", ATTR{idVendor}=="yourdevicevendor", MODE="0664", GROUP="plugdev"
+</pre>
+
+<p>
+On MacOSX,
+devices which expose a HID profile cannot be managed
+using this low level API due to system restrictions.
+Currently there is no workaround.
+</p>
+
+<h2 id="serial">Accessing serial devices</h2>
+
+<p>
+You can use the serial API to read
+and write from a serial device.
+</p>
+
+<h3 id="requirement">Manifest requirement</h3>
+
+<p>
+The "serial" permission is not yet required;
+"experimental" is enough for now:
+</p>
+
+<pre>
+"permissions": ["experimental"]
+</pre>
+
+<h3 id="listing">Listing available serial ports</h3>
+
+<p>
+To get a list of available serial ports,
+use the <code>getPorts()</code> method:
+</p>
+
+<pre>
+var onGetPorts = function(ports) {
+  for (var i=0; i&lt;ports.length; i++) {
+    console.log(ports[i]);
+  }
+}
+chrome.experimental.serial.getPorts(onGetPorts);
+</pre>
+
+<h3 id="opening">Opening a serial device</h3>
+
+<p>
+Here's how to open a serial device:
+</p>
+
+<pre>
+var onOpen = function(connectionInfo) {
+   // The serial device has been opened. Save its id to use later.
+  var conId = connectionInfo.connectionId;
+  // Do whatever you need to do with the opened device.
+}
+// Open the serial device /dev/ttyS01
+chrome.experimental.serial.open("/dev/ttyS01", onOpen);
+</pre>
+
+<h3 id="closing">Closing a serial device</h3>
+
+<p>
+Here's how to close a serial device:
+</p>
+
+<pre>
+var onClose = function(result) {
+ console.log(“Serial port closed”);
+}
+chrome.experimental.serial.close(conId, onClose);
+</pre>
+
+<h3 id="reading">Reading from a serial device</h3>
+
+<p>
+The serial API reads from the serial port and
+delivers the read bytes as an ArrayBuffer.
+There is no guarantee that all the available bytes will be read in one chunk
+(they are currently read one byte per time, but this might change in the future).
+The following procedure can accumulate read bytes until a new line is read,
+and then call a listener with the <code>ArrayBuffer</code> bytes converted to a String:
+</p>
+
+<pre>
+var dataRead='';
+
+var onCharRead=function(readInfo) {
+    if (!connectionInfo) {
+      return;
+    }
+    if (readInfo &amp;&amp; readInfo.bytesRead>0 &amp;&amp; readInfo.data) {
+      var str=ab2str(readInfo.data);
+      if (str[str.length-1]==='\n') {
+        dataRead+=str.substring(0, str.length-1);
+        onLineRead(dataRead);
+        dataRead="";
+      } else {
+        dataRead+=str;
+      }
+    }
+    chrome.experimental.serial.read(connectionInfo.connectionId, onCharRead);
+  }
+
+/* Convert an ArrayBuffer to a String, using UTF-8 as the encoding scheme.
+   This is consistent with how Arduino sends characters by default */
+  var ab2str=function(buf) {
+    return String.fromCharCode.apply(null, new Uint8Array(buf));
+  };
+</pre>
+
+<h3 id="writing">Writing to a serial device</h3>
+
+<p>
+The writing routine is simpler than the reading,
+since the writing can occur all at once.
+The only catch is that if your data protocol is String based,
+you have to convert your output string to an <code>ArrayBuffer</code>
+to compy with write's method signature.
+See the code below:
+</p>
+
+<pre>
+var writeSerial=function(str) {
+  chrome.experimental.serial.write(connectionInfo.connectionId, str2ab(str), onWrite);
+}
+var str2ab=function(str) {
+  var buf=new ArrayBuffer(str.length);
+  var bufView=new Uint8Array(buf);
+  for (var i=0; i&lt;str.length; i++) {
+    bufView[i]=str.charCodeAt(i);
+  }
+  return buf;
+}
+</pre>
+
+<h3 id="flushing">Flushing a serial device buffer</h3>
+
+<p>
+You can flush your serial device buffer by issuing the flush command on the API:
+</p>
+
+<pre>
+var flushSerial=function(str) {
+  chrome.experimental.serial.flush(connectionInfo.connectionId, onFlush);
+}
+</pre>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
diff --git a/chrome/common/extensions/docs/templates/articles/app_identity.html b/chrome/common/extensions/docs/templates/articles/app_identity.html
new file mode 100644
index 0000000..7fb9ba0
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/app_identity.html
@@ -0,0 +1,249 @@
+<h1>Identify User</h1>
+
+
+<p>
+Web authentication protocols utilize HTTP features,
+but packaged apps run inside the app container;
+they don’t load over HTTP and can’t perform redirects or set cookies.
+</p>
+
+<p>
+Use the <a href="experimental.identity.html">Chrome Identity API</a>
+to authenticate users:
+the <code>getAuthToken</code> for users logged into their Google Account and
+the <code>launchWebAuthFlow</code> for users logged into a non-Google account.
+If your app uses its own server to authenticate users, you will need to use the latter.
+</p>
+
+<p class="note">
+<b>API Samples: </b>
+Want to play with the code?
+Check out the
+<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/identity">identity</a> sample.
+</p>
+
+<h2 id="how">How it works</h2>
+
+<p>
+Apps that use Google accounts
+need to specify the OAuth2 client ID
+and scopes in their manifest.
+When users install the apps,
+the OAuth2 permissions are displayed along with the Chrome permissions.
+Once a user accepts the permissions,
+the apps can get the access token
+using <code>getAuthToken</code>.
+</p>
+
+<p>
+Apps that want to perform authentication
+with any provider must call <code>launchAuthFlow</code>.
+This method uses a browser pop-up to show the provider pages
+and captures redirects to the specific URL patterns.
+The redirect URLs are passed to the app
+and the app extracts the token from the URL.
+</p>
+
+<h2 id="google">Google account authentication</h2>
+
+<p>
+Here are the five steps you need to complete:
+</p>
+
+<ol>
+	<li>Add permissions to your manifest and upload your app.</li>
+	<li>Copy key in the installed <code>manifest.json</code> to your source manifest.</li>
+	<li>Get your client ID.</li>
+	<li>Update your manifest to include the client ID and scopes.</li>
+	<li>Get the authentication token.</li>
+</ol>
+
+<h3 id="add_permissions">Add permissions and upload app</h3>
+
+<p>
+The identity API is still experimental.
+You need to make sure the experimental
+and identity permissions are in your manifest.
+You can then upload your app to the apps and extensions management page
+(see <a href="publish_app.html">Publish</a>).
+</p>
+
+<pre>
+"permissions": [
+  "experimental",
+  "identity"
+ ]
+</pre>
+
+<h3 id="copy_key">Copy key to your manifest</h3>
+
+<p>
+You need to copy the key in the installed
+<code>manifest.json</code> to your source manifest.
+This ensures that the key isn't overridden anytime your reload your app
+or share the app with other users.
+It's not the most graceful task, but here's how it goes:
+</p>
+
+<ol>
+	<li>Go to your
+		<a href="http://www.chromium.org/user-experience/user-data-directory">user data directory</a>.
+		Example on MacOs: <code>~/Library/Application\ Support/Google/Chrome/Default/Extensions</code></li>
+	<li>List the installed apps and extensions and match your app ID on the apps and extensions management page
+		to the same ID here.</li>
+	<li>Go to the installed app directory (this will be a version within the app ID).
+		Open the installed <code>manifest.json</code>
+		(pico is a quick way to open the file).</li>
+	<li>Copy the "key" in the installed <code>manifest.json</code> and paste it into your app's source manifest file.</li>
+</ol>
+
+<h3 id="client_id">Get your client ID</h3>
+
+<p>
+Setting up the client ID is currently not available externally
+via <a href="https://devconsole-canary.corp.google.com/apis/">Google APIs Console</a>.
+So to setup the OAuth2 client ID,
+email <a href="mailto:chrome-apps-auth-requests@google.com">chrome-apps-auth-request@google.com</a>
+with your stable app ID and
+we will reply appropriately with your OAuth2 client ID.
+</p>
+
+<h3 id="update_manifest">Update your manifest</h3>
+
+<p>
+You need to update your manifest to include
+the client ID and scopes.
+Here's the sample "oauth2" for the
+<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/gdocs">gdocs sample</a>:
+</p>
+
+<pre>
+"oauth2": {
+    "client_id": "665859454684.apps.googleusercontent.com",
+    "scopes": [
+      "https://docs.google.com/feeds/",
+      "https://docs.googleusercontent.com/",
+      "https://spreadsheets.google.com/feeds/",
+      "https://www.googleapis.com/auth/drive.file"
+    ]
+  }
+</pre>
+
+<h3 id="token">Get the token</h3>
+
+<p>
+You are now ready to get the auth token:
+</p>
+
+<pre>
+chrome.experimental.identity.getAuthToken(function(token) { })
+</pre>
+
+<h2 id="non">Non-Google account authentication</h2>
+
+<p>
+Here are the three steps you need to complete:
+</p>
+
+<ol>
+	<li>Register with the provider.</li>
+	<li>Add permissions for provider resources that your app will access.</li>
+	<li>Get the authentication token.</li>
+</ol>
+
+<h3 id="register_provider">Register with the provider</h3>
+
+<p>
+You need to register an OAuth2 client ID with the provider
+and configure the client ID as a website.
+For the  redirect URI to be entered during registration,
+use the URL of the form:
+<code>https://<extension-id>.chromiumapp.org/<anything-here></code>
+</p>
+
+<p>
+For example, if you app ID is abcdefghijklmnopqrstuvwxyzabcdef and
+you want provider_cb to be the path,
+to distinguish it with redirect URIs from other providers,
+you should use:
+<code>https://abcdefghijklmnopqrstuvwxyzabcdef.chromiumapp.org/provider_cb</code>
+</p>
+
+<h3 id="permissions_provider">Add permissions for provider</h3>
+
+<p>
+To make cross-original XHRs to Google API endpoints,
+you need to whitelist those patterns in the permissions:
+</p>
+
+<pre>
+"permissions": [
+  ...
+  "https://docs.google.com/feeds/",
+  "https://docs.googleusercontent.com/",
+  “https://www.website-of-provider-with-user-photos.com/photos/”
+]
+</pre>
+
+<h3 id="token2">Get the token</h3>
+
+<p>
+To get the token:
+</p>
+
+<pre>
+chrome.experimental.identity.launchWebAuthFlow(
+  {‘url’: ‘&lt;url-to-do-auth>’, ‘interactive’: true},
+  function(redirect_url) { // Extract token from redirect_url });
+</pre>
+
+<p>
+The &lt;url-to-do-auth> is whatever the URL is to do auth to the provider from a website.
+For example, let us say that you are performing OAuth2 flow with a provider
+and have registered your app with client id 123456789012345 and
+you want access to user’s photos on the provider’s website:
+<code>https://www.website-of-provider-with-user-photos.com/dialog/oauth?client_id=123456789012345&amp;<br>redirect_uri=https://abcdefghijklmnopqrstuvwxyzabcdef.chromiumapp.org/provider_cb&amp;response_type=token&amp;scope=user_photos</code>
+</p>
+
+<p>
+The provider will perform authentication and if appropriate,
+will show login and/or approval UI to the user.
+It will then redirect to
+<code>https://abcdefghijklmnopqrstuvwxyzabcdef.chromiumapp.org/provider_cb#authToken=&lt;auth-token></code>
+</p>
+
+<p>
+Chrome will capture that and invoke the callback
+of the app with the full redirect URL.
+The app should extract the token out of the URL.
+</p>
+
+<h3 id="interactive">Interactive versus silent mode</h3>
+
+<p>
+When calling <code>launchWebAuthFlow</code>,
+you can pass a flag (‘interactive’: true in the example above)
+indicating whether you want the API to be called
+in interactive mode or not (aka silent mode).
+If you invoke the API in interactive mode,
+the user is shown UI, if necessary,
+to get the token (signin UI and/or approval UI;
+or for that matter any provider specific UI).
+</p>
+
+<p>
+If you invoke the API in silent mode,
+the API will only return a token if the provider is able
+to provide a token without showing any UI.
+This is useful in cases when an app is doing the flow at app startup, for example,
+or in general in cases where there is no user gesture involved.
+</p>
+
+<p>
+The best practice we suggest is to use silent mode
+when there is no user gesture involved and use interactive mode
+if there is a user gesture (for example, the user clicked the Sign In button in your app).
+Note that we do not enforce gesture requirement.
+</p>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
diff --git a/chrome/common/extensions/docs/templates/articles/app_intents.html b/chrome/common/extensions/docs/templates/articles/app_intents.html
new file mode 100644
index 0000000..f17208d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/app_intents.html
@@ -0,0 +1,224 @@
+<h1>Connect Apps with Web Intents</h1>
+
+
+<p>
+<a href="http://webintents.org/">Web Intents</a>
+allow your application to quickly communicate
+with other applications on the user's system and inside their browser.
+Your application can register to handle specific user actions
+such as editing images via the <code>manifest.json</code>;
+your application can also invoke actions to be handled by other applications.
+</p>
+
+<p>Pacakged apps use Web Intents as their primary mechanism for inter-app 
+communication.</p>
+
+<p class="note">
+<b>API Samples: </b>
+Want to play with the code?
+Check out the
+<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/webintents">webintents</a> sample.
+</p>
+
+<h2 id="register">Register your app to handle an action</h2>
+
+<p>
+You must supply the intent in the manifest:
+</p>
+
+<pre>
+"intents":{
+ "http://webintents.org/edit" : [{
+   "title" : "Best Image editing app",
+   "type" : ["image/*"]
+ }]
+}
+</pre>
+
+<p>
+Unlike extensions and hosted apps, packaged applications do not 
+need a "href" attribute in the manifest declaration, this is 
+because packaged apps have a single entry point for 
+launch - the <code>onLaunched</code> event.  
+</p>
+
+<h2 id="content">Handling content types</h2>
+
+<p>
+Your application can be the user's preferred choice for handling a file type.
+For example, your application could handle viewing images or viewing pdfs.
+You must supply the intent in the manifest
+and use the "http://webintents.org/view" action:
+</p>
+<p>To be able declare your application's ability to view RSS and ATOM 
+feeds, you would add the following to your manifest.
+</p>
+<pre>
+"intents": {
+ "http://webintents.org/view" : [{
+   "title" : "RSS Feed Reader",
+   "type" : ["application/atom+xml", "application/rss+xml"]
+ }]
+}
+</pre>
+
+<p>
+Your application will receive intent payload through the <code>onLaunched</code> event.
+</p>
+<pre>
+chrome.app.runtime.onLaunched(function(intent) {
+  // App Launched
+  if(intent.action == "http://webinents.org/view" &amp;&amp;
+     intent.type == "application/atom+xml") {
+
+    // obtain the ATOM feed data.
+    var data = intent.data;
+  }
+});
+</pre>
+
+
+<h2 id="launching">Launching an app with a file</h2>
+<p>
+If your app handles the <code>view</code> intent,
+it is possible to launch it from the command line with a file as a parameter.
+</p>
+<pre>
+chrome.exe --app-id [app_id] [path_to_file]
+</pre>
+<p>
+This will implicity launch your application with an intent payload populated
+with the action set to "http://webintents.org/view", the type set to the 
+mime-type of the file and the data as a <code>FileEntry</code> object.
+</p>
+<pre>
+chrome.app.runtime.onLaunched(function(intent) {
+  // App Launched
+  var data = intent.data;
+});
+</pre>
+
+<h2 id="launching">Manipulating the file</h2>
+<p>
+  When your application is launched with a file as the parameter
+  on the command-line,
+  the <code>intent.data</code> property is a <code>FileEntry</code>.
+  This is really cool because now you have a direct reference back to the physical
+  file on the disk,
+  and you can write data back to it.
+</p>
+
+<pre>
+chrome.app.runtime.onLaunched(function(intent) {
+  // App Launched
+  var data = intent.data;
+  if(data instanceof FileEntry) {
+    data.createWriter(function(writer) {
+      writer.onwriteend = function(e) {
+        console.log('Write completed.');
+      };
+
+      writer.onerror = function(e) {
+        console.log('Write failed: ' + e.toString());
+      };
+
+      // Create a new Blob and write it to log.txt.
+      var blob = new Blob(['Lorem Ipsum'], {type: 'text/plain'});
+      writer.write(blob);
+    });
+  }
+});
+</pre>
+
+<h2 id="return">Returning data to calling application</h2>
+<p>
+Lots of applications want to cooperate
+with the app that invoked them.
+It's easy to send data back to the calling client
+using <code>intent.postResult</code>:
+</p>
+
+<pre>
+chrome.app.runtime.onLaunched(function(intent) {
+  // App Launched
+  console.log(intent.action);
+  console.log(intent.type);
+  var data = intent.data;
+  // Do something with the data;
+
+  intent.postResult(newData);
+});
+</pre>
+
+<h2 id="localize">Localizing your app title</h2>
+
+<p>
+If your application or extension is localized
+as per the guidelines in
+<a href="i18n.html">Internationalization (i18n)</a>,
+you can localize the title of your intent in the picker
+using the exact same infrastructure:
+</p>
+
+<pre>
+"intents": {
+ "http://webintents.org/edit" : [{
+   "title" : "__MSG_intent_title__",
+   "type" : ["image/*"],
+   "disposition" : "inline"
+ }]
+}
+</pre>
+
+<h2 id="invoke">Invoking an action</h2>
+<p>
+If your application needs to be able
+to use the functionality of another application,
+it can simply ask the browser for it.
+To ask for an application that supports image editing,
+it's as simple as:
+</p>
+
+<pre>
+var intent = new WebKitIntent("http://webintents.org/edit", "image/png", "dataUri://");
+
+window.navigator.webkitStartActivity(intent, function(data) {
+// The data from the remote application is returned here.
+});
+</pre>
+
+<h2 id="errors">Handling Errors and Exceptions</h2>
+<p>
+   If your service application needs to signal to the client application
+   that an unrecoverable error has occurred,
+   then your application will need
+   to call <code>postError</code> on the intent object.
+   This will signal to the client’s onError callback
+   that something has gone wrong.
+</p>
+
+<h3 id="client">Client</h3>
+
+<pre>
+var intent = new WebKitIntent("http://webintents.org/edit", "image/png", "dataUri://");
+
+var onSuccess = function(data) {};
+var onError = function() {};
+
+window.navigator.webkitStartActivity(intent, onSuccess, onError);
+</pre>
+
+<h3 id="service">Service</h3>
+<pre>
+chrome.app.runtime.onLaunched(function(intent) {
+  // App Launched
+  console.log(intent.action);
+  console.log(intent.type);
+  var data = intent.data;
+  // Do something with the data;
+
+  intent.postResult(newData);
+});
+</pre>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
diff --git a/chrome/common/extensions/docs/templates/articles/app_known_issues.html b/chrome/common/extensions/docs/templates/articles/app_known_issues.html
new file mode 100644
index 0000000..1bcc59b
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/app_known_issues.html
@@ -0,0 +1,33 @@
+<h1>Known Issues</h1>
+
+<p>Chrome's support for packaged apps is under active development and rapidly
+evolving, so there are still a few rough edges. Here's a list of known issues
+in the current <a href="https://tools.google.com/dlpage/chromesxs/">Chrome
+Canary</a>:</p>
+{{+partials.known_issues}}
+<p>
+If one of the above describes an issue you've run into, and you wish to be
+updated when it's resolved, click the star icon on the page header. Do not
+respond to the bug to say "me too" or ask "when will this be fixed?"; such
+updates can cause hundreds of emails to be sent. Add a comment only if you have
+information (such as a better test case or a suggested fix) that is likely
+to be helpful.
+</p>
+
+<h2 id="cant_find_it">Can't find it in the list?</h2>
+
+<p>
+  If none of the issues listed above describe what you're running into, you
+  may wish to <a href="http://code.google.com/p/chromium/issues/list?can=2&q=Feature%3DApps+Type%3DBug">search</a>
+  the bug tracker, in case it has been reported but not marked as being known
+  yet.
+</p>
+
+<p>
+  If you still can't find something that matches the problem you're having, you
+  can <a href="https://code.google.com/p/chromium/issues/entry?template=Defect report from user&labels=Feature-Apps,Type-Bug,Area-Internals,Pri-2">report
+  the bug</a> on the Chromium bug tracker. Please include as many details as
+  possible in your bug report, including Chrome version number, operating
+  system, and any other relevant configuration details. Bug reports that attach
+  a small app that exhibits the problem are especially appreciated.
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/app_lifecycle.html b/chrome/common/extensions/docs/templates/articles/app_lifecycle.html
new file mode 100644
index 0000000..9c1112a
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/app_lifecycle.html
@@ -0,0 +1,230 @@
+<meta name="doc-family" content="apps">
+<h1>Manage App Lifecycle</h1>
+
+
+<p>
+The app runtime and event page are responsible
+for managing the app lifecycle.
+The app runtime manages app installation,
+controls the event page,
+and can shutdown the app at anytime.
+The event page listens out for events from the app runtime
+and manages what gets launched and how.
+</p>
+
+<h2 id="lifecycle">How the lifecycle works</h2>
+
+<p>
+The app runtime loads the event page
+from a user's desktop and
+the <code>onLaunch()</code> event is fired.
+This event tells the event page what windows
+to launch and their dimensions.
+The lifecycle diagram here isn't the nicest to look at,
+but it's practical (and we will make it nicer soon).
+</p>
+
+<img src="{{static}}/images/applifecycle.png"
+     width="444"
+     height="329"
+     alt="how app lifecycle works">
+
+<p>
+When the event page has no executing JavaScript,
+no pending callbacks, and no open windows,
+the runtime unloads the event page and closes the app.
+Before unloading the event page,
+the <code>onSuspend()</code> event is fired.
+This gives the event page opportunity
+to do simple clean-up tasks
+before the app is closed.
+</p>
+
+<h2 id="eventpage">Create event page and windows</h2>
+
+<p>
+All apps must have an event page.
+This page contains the top-level logic of the application
+with none of its own UI and is responsible
+for creating the windows for all other app pages.
+</p>
+
+<h3 id="create_event_page">Create event page</h3>
+
+<p>
+To create the event page,
+include the "background" field in the app manifest
+and include the <code>background.js</code> in the scripts array.
+Any library scripts used by the event page need to be added
+to the "background" field first:
+</p>
+
+<pre>
+"background": {
+  "scripts": [
+    "foo.js",
+    "background.js"
+  ]
+}
+</pre>
+
+<p>
+Your event page must include the <code>onLaunched()</code> function.
+This function is called
+when your application is launched in any way:
+</p>
+
+<pre>
+chrome.app.runtime.onLaunched.addListener(function() { 
+  // Tell your app what to launch and how.
+});
+</pre>
+
+<h3 id="create_windows">Create windows</h3>
+
+<p>
+An event page may create one or more windows at its discretion.
+By default, these windows are created with a script connection
+to the event page and are directly scriptable by the event page.
+</p>
+
+<p>
+Windows can either be shells or panels.
+Shell windows have no browser chrome.
+Panel windows are the same as shell windows,
+except they have different size and position restrictions,
+for example, a chat panel.
+</p>
+
+<p>Here's a sample <code>background.js</code>
+  with a 'shell' window:</p>
+
+<pre>
+chrome.app.runtime.onLaunched.addListener(function() {
+  chrome.app.window.create('main.html', {
+    width: 800,
+    height: 600,
+    minWidth: 800,
+    minHeight: 600,
+    left: 100,
+    top: 100,
+    type: 'shell'
+  });
+});
+</pre>
+
+<p>Here's a sample <code>background.js</code>
+  with a 'panel' window:
+</p>
+
+<pre>
+chrome.app.runtime.onLaunched.addListener(function() {
+  chrome.app.window.create('index.html', {
+    width: 400,
+    height: 200,
+    type: 'panel'
+  });
+});
+</pre>
+
+<h3 id="launch_data">Including Launch Data</h3>
+
+<p>
+Depending on how your app is launched,
+you may need to include launch data
+in your event page.
+By default, there is no launch data
+when the app is started by the app launcher.
+For apps that provide intents,
+you need to include the
+<code>launchData.intent</code> parameter.
+</p>
+
+<p>
+Web intents can be launched by other apps invoking their intent, 
+or by the runtime when apps are launched to view or edit files,
+for example from the operating system file explorer.
+To find out how to launch an app with web intents,
+see <a href="app_intents.html#launching">Launching an App with a File</a>.
+</p>
+
+<h2 id="runtime">Listening for app runtime events</h2>
+
+<p>
+The app runtime controls the app installs, updates, and uninstalls.
+You don't need to do anything to set up the app runtime,
+but your event page can listen out for the <code>onInstalled()</code> event
+to store local settings and the
+<code>onSuspend()</code> event to do simple clean-up tasks
+before the event page is unloaded.
+</p>
+
+<h3 id="local_settings">Storing local settings</h3>
+
+<p>
+<code>chrome.runtime.onInstalled()</code>
+is called when your app has first been installed,
+or when it has been updated.
+Any time this function is called,
+the <code>onInstalled</code> event is fired.
+The event page can listen for this event and use the
+<a href="storage.html">Storage API</a>
+to store and update local settings
+(see also <a href="app_storage.html#options">Storage options</a>).
+</p>
+
+<pre>
+chrome.runtime.onInstalled.addListener(function() { 
+  chrome.storage.local.set(object items, function callback);
+});
+</pre>
+
+<h3 id="preventing_loss">Preventing data loss</h3>
+
+<p>
+Users can uninstall your app at any time.
+When uninstalled,
+no executing code or private data is left behind.
+This can lead to data loss
+since the users may be uninstalling an app
+that has locally edited, unsynchronized data.
+You should stash data to prevent data loss.
+</p>
+
+<p>
+At a minimum,
+you should store user settings
+so that if users reinstall your app,
+their information is still available for reuse.
+Using the Storage API
+(<a href="storage.html#property-sync">chrome.storage.sync</a>),
+user data can be automatically synced
+with Chrome sync.
+</p>
+
+<h3 id="clean-up">Clean-up before app closes</h3>
+
+<p>
+The app runtime sends the <code>onSuspend()</code>
+event to the event page before unloading it.
+Your event page can listen out for this event and
+do clean-up tasks before the app closes.
+</p>
+
+<p>
+Once this event is fired,
+the app runtime starts the process of closing the app:
+all events stop firing and
+JavaScript execution is halted.
+Any asynchronous operations started
+while handling this event are not guaranteed to complete.
+Keep the clean-up tasks synchronous and simple.
+</p>
+
+<pre>
+chrome.runtime.onSuspend.addListener(function() { 
+  // Do some simple clean-up tasks.
+});
+</pre>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
diff --git a/chrome/common/extensions/docs/templates/articles/app_network.html b/chrome/common/extensions/docs/templates/articles/app_network.html
new file mode 100644
index 0000000..8daf9d5
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/app_network.html
@@ -0,0 +1,178 @@
+<h1>Network Communications</h1>
+
+<p>
+Packaged apps can act as a network client
+for TCP and UDP connections.
+This doc shows you how to use TCP and UDP
+to send and receive data over the network.
+For more information,
+see the 
+<a href="socket.html">Sockets API</a>.
+</p>
+
+<p class="note">
+<b>API Samples: </b>
+Want to play with the code?
+Check out the
+<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/telnet">telnet</a>
+and <a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/udp">udp</a> samples.
+</p>
+
+<h2 id="manifest">Manifest requirements</h2>
+
+<p>
+For packaged apps that use TCP or UDP,
+add the "socket" permission to the manifest
+and specify the IP end point permission rules.
+For example:
+</p>
+
+<pre>
+"permissions": [
+    {"socket": [
+      "rule1",
+      "rule2",
+      ...
+    ]}
+  ]
+</pre>
+
+<p>
+The syntax of socket permission rules follows these patterns:
+</p>
+
+<pre>
+&lt;socket-permission-rule>
+     := &lt;op> | &lt;op> ':' &lt;host> | &lt;op> ':' ':' &lt;port> |
+        &lt;op> ':' &lt;host> ':' &lt;port>
+ &lt;op> := 'tcp-connect' | 'tcp-listen' | 'udp-bind' | 'udp-send-to'
+ &lt;host> := '*' | '*.' &lt;anychar except '/' and '*'>+
+ &lt;port> := '*' | &lt;port number between 1 and 65535>)
+</pre>
+
+<p>
+Examples of socket permission rules:
+</p>
+
+<ul>
+  <li>"tcp-connect:*:23" &ndash; connecting on port 23 of any hosts</li>
+  <li>"tcp-connect:www.example.com:23" &ndash; connecting port 23 of <em>www.example.com</em></li>
+  <li>"tcp-connect" &ndash; connecting any ports of any hosts</li>
+  <li>"udp-send-to::99" &ndash; sending UDP packet to port 99 of any hosts</li>
+  <li>"udp-bind::8899" &ndash; binding local port 8899 to receive UDP package</li>
+  <li>"tcp-listen::8080" &ndash; TCP listening on local port 8080</li>
+</ul>
+
+<h2 id="tcp">Using TCP</h2>
+
+<p>
+Packaged apps can make connections to any service that supports TCP.
+</p>
+
+<h3 id="connecting">Connecting to a socket</h3>
+
+<p>
+Here's a sample showing how to connect to a socket:
+</p>
+
+<pre>
+chrome.socket.create('tcp', {}, function(createInfo) {
+  socket.connect(createInfo.socketId, IP, PORT, onConnectedCallback);
+});
+</pre>
+
+<p>
+Keep a handle to the socketId so that
+you can later read and write to this socket.
+</p>
+
+<pre>
+chrome.socket.write(socketId, arrayBuffer, onWriteCompleteCallback);
+</pre>
+
+<h3 id="reading">Reading to & writing from a socket</h3>
+
+<p>
+Reading and writing from a socket uses ArrayBuffer objects.
+</p>
+
+<pre>
+chrome.socket.read(socketId, null, function(readInfo) {
+  if (readInfo.resultCode > 0) {
+    // readInfo.data is an arrayBuffer.
+  }
+});
+</pre>
+
+<h3 id="disconnecting">Disconnecting from a socket</h3>
+
+<p>Here's how to disconnect:</p>
+
+<pre>chrome.socket.disconnect(socketId);</pre>
+
+<h2 id="udp">Using UDP</h2>
+
+<p>
+Packaged apps can make connections to any service that supports UDP.
+</p>
+
+<h3 id="sending">Sending data</h3>
+
+<p>
+Here's a sample showing how to send data
+over the network using UDP:
+</p>
+
+<pre>
+// Create the Socket
+chrome.socket.create('udp', '127.0.0.1', 1337, {},
+ function(socketInfo) {
+   // The socket is created, now we want to connect to the service
+   var socketId = socketInfo.socketId;
+   chrome.socket.connect(socketId, function(result) {
+     // We are now connected to the socket so send it some data
+     chrome.socket.write(socketId, arrayBuffer,
+       function(sendInfo) {
+         console.log("wrote " + sendInfo.bytesWritten);
+       }
+     );
+   });
+ }
+);
+</pre>
+
+<h3 id="receiving">Receiving data</h3>
+
+<p>
+This example is very similar to the 'Sending data' example
+with the addition of a special handler in the 'create' method.
+The parameter is an object with one value 'onEvent'
+that is a function reference to the method
+that will be called when data is available on the port.
+</p>
+
+<pre>
+// Handle the data response
+var handleDataEvent = function(d) {
+  var data = chrome.socket.read(d.socketId);
+  console.log(data);
+};
+
+// Create the Socket
+chrome.socket.create('udp', '127.0.0.1', 1337, { onEvent: handleDataEvent },
+ function(socketInfo) {
+   // The socket is created, now we want to connect to the service
+   var socketId = socketInfo.socketId;
+   chrome.socket.connect(socketId, function(result) {
+     // We are now connected to the socket so send it some data
+     chrome.socket.write(socketId, arrayBuffer,
+       function(sendInfo) {
+         console.log("wrote " + sendInfo.bytesWritten);
+       }
+     );
+   });
+ }
+);
+</pre>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
diff --git a/chrome/common/extensions/docs/templates/articles/app_storage.html b/chrome/common/extensions/docs/templates/articles/app_storage.html
new file mode 100644
index 0000000..77c65d7
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/app_storage.html
@@ -0,0 +1,229 @@
+<h1>Manage Data</h1>
+
+
+<p>
+Almost every aspect of app development involves some element
+of sending or receiving data.
+Starting with the basics,
+you should use an MVC framework to help you design and implement your app
+so that data is completely separate from the app's view on that data
+(see <a href="app_frameworks.html">MVC Architecture</a>).
+</p>
+
+<p>
+You also need to think about how data is handled when your app is offline
+(see <a href="offline_apps.html">Offline First</a>).
+This doc briefly introduces the storage options
+for sending, receiving, and saving data locally;
+the remainder of the doc shows you how
+to use Chrome's File System API
+(see also the <a href="fileSystem.html">fileSystem API</a>).
+</p>
+
+<p class="note">
+<b>API Samples: </b>
+Want to play with the code?
+Check out the
+<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/filesystem-access">filesystem-access</a>
+and <a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/storage">storage</a> samples.
+</p>
+
+<h2 id="options">Storage options</h2>
+
+<p>
+Packaged apps use many different mechanisms
+to send and receive data.
+For external data (resources, web pages),
+you need to be aware of the
+<a href="app_csp.html">Content Security Policy (CSP)</a>.
+Similar to Chrome Extensions,
+you can use
+<a href="app_external.html#external">cross-origin XMLHttpRequests</a>
+to communicate with remote servers.
+You can also isolate external pages,
+so that the rest of your app is secure
+(see <a href="app_external.html#objecttag">Embed external web pages</a>).
+With Web Intents,
+your app can share data with other apps
+(see <a href="app_intents.html">Connect Apps with Web Intents</a>).
+</p>
+
+<p>
+When saving data locally,
+you can use the <a href="storage.html">Chrome Storage API</a>
+to save small amounts of string data and
+IndexedDB to save structured data.
+With IndexedDB, you can persist JavaScript objects
+to an object store and use the store's indexes to query data
+(to learn more, see HTML5 Rock's
+<a href="http://www.html5rocks.com/tutorials/indexeddb/todo/">Simple Todo List Tutorial</a>).
+For all other types of data,
+like binary data,
+use the Filesystem API.
+</p>
+
+<p>
+Chrome's Filesystem API extends the
+<a href="http://www.html5rocks.com/tutorials/file/filesystem/">HTML5 FileSystem API</a>;
+apps can create, read, navigate,
+and write to a sandboxed section
+of the user's local file system.
+With Chrome's File System API,
+packaged apps can read and write files
+to user-selected locations.
+For example,
+a photo-sharing app can use the File System API
+to read and write any photos that a user selects.
+</p>
+
+<h2 id="manifest">Adding file system permission</h2>
+
+<p>
+To use Chrome's File System API,
+you need to add the "fileSystem" permission to the manifest,
+so that you can obtain permission from the user
+to store persistent data.
+
+<pre>
+"permissions": [
+  "...",
+  "fileSystem"
+]
+</pre>
+
+<h2 id="import">User-options for selecting files</h2>
+
+<p>
+Users expect to select files
+in the same way they always do.
+At a minimum,
+they expect a 'choose file' button
+and standard file-chooser.
+If your app makes heavy use of file-handing,
+you should also implement drag-and-drop
+(see below and also check out 
+<a href="http://www.html5rocks.com/tutorials/dnd/basics/">Native HTML5 Drag and Drop</a>).
+</p>
+
+<p>
+If your app connects with other apps using
+<a href="app_intents.html">Web Intents</a>
+you can set up data sharing with those apps.
+Users can view and/or write
+to their files using a connected app
+without having to do all sorts of extra steps
+to move files back and forth.
+</p>
+
+<h2 id="path">Obtaining the path of a fileEntry</h2>
+
+<p>
+To get the full path
+of the file the user selected,
+<code>fileEntry</code>,
+call <code>getDisplayPath()</code>:
+</p>
+
+<pre>
+function displayPath(fileEntry) {
+  chrome.fileSystem.getDisplayPath(fileEntry, function(path) {
+    console.log(path)
+  });
+}
+</pre>
+
+<h2 id="drag">Implementing drag-and-drop</h2>
+
+<p>
+If you need to implement drag-and-drop selection,
+the drag-and-drop file controller
+(<code>dnd.js</code>) in
+the <a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/filesystem-access">filesystem-access</a>
+sample is a good starting point.
+The controller creates a file entry
+from a <code>DataTransferItem</code>
+via drag-and-drop.
+In this example,
+the <code>fileEntry</code> is set
+to the first dropped item.
+</p>
+
+<pre>
+var dnd = new DnDFileController('body', function(data) {
+  var fileEntry = data.items[0].webkitGetAsEntry();
+  displayPath(fileEntry);
+});
+</pre>
+
+<h2 id="read">Reading a file</h2>
+
+<p>
+The following code opens the file (read-only) and
+reads it as text using a <code>FileReader</code> object.
+If the file doesn't exist, an error is thrown.
+</p>
+
+<pre>
+var chosenFileEntry = null;
+
+chooseFileButton.addEventListener('click', function(e) {
+  chrome.fileSystem.chooseEntry({type: 'openFile'}, function(readOnlyEntry) {
+ 
+    readOnlyEntry.file(function(file) {
+      var reader = new FileReader();
+
+      reader.onerror = errorHandler;
+      reader.onloadend = function(e) {
+        console.log(e.target.result);
+      };
+
+      reader.readAsText(file);
+    });
+	});
+});
+</pre>
+
+<h2 id="write">Writing a file</h2>
+
+<p>
+The two common use-cases
+for writing a file are "Save" and "Save as".
+The following code creates a
+<code>writableEntry</code>
+from the read-only <code>chosenFileEntry</code> and
+writes the selected file to it.
+</p>
+
+<pre>
+ chrome.fileSystem.getWritableEntry(chosenFileEntry, function(writableFileEntry) {
+    writableFileEntry.createWriter(function(writer) {
+      writer.onerror = errorHandler;
+      writer.onwriteend = callback;
+
+    chosenFileEntry.file(function(file) {
+      writer.write(file);
+    });   
+  }, errorHandler);
+});
+</pre>
+
+<p>
+The following code creates a new file
+with "Save as" functionality and
+writes the new blob to the file
+using the <code>writer.write()</code> method.
+</p>
+
+<pre>
+chrome.fileSystem.chooseEntry({type: 'saveFile'}, function(writableFileEntry) {
+    writableFileEntry.createWriter(function(writer) {
+      writer.onerror = errorHandler;
+      writer.onwriteend = function(e) {
+        console.log('write complete');
+      };
+      writer.write(new Blob(['1234567890'], {type: 'text/plain'}));  
+    }, errorHandler);
+});
+</pre>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/apps.html b/chrome/common/extensions/docs/templates/articles/apps.html
new file mode 100644
index 0000000..d87b9eb
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/apps.html
@@ -0,0 +1,177 @@
+<h1>Packaged Apps</h1>
+
+
+<p class="warning">
+<b>Warning: </b>
+All content in this doc refers to the legacy version of packaged apps.
+Your legacy packaged apps will still work,
+but you won't have access to any of the new APIs.
+Check out the new version of
+<a href="/{{branchInfo.current}}/apps/about_apps.html">packaged apps</a>;
+otherwise, you're missing out!
+</p>
+
+<p>
+This page talks about packaged apps&mdash;how
+you implement them,
+and how they're different from
+extensions and ordinary web apps.
+</p>
+
+<h2 id="overview">Overview</h2>
+
+<p>
+A packaged app is a web app
+that's bundled into a <code>.crx</code> file
+and can use Chrome extension features.
+You build a packaged app just like you build an extension,
+except that a packaged app can't include a
+<a href="browserAction.html">browser action</a> or
+<a href="pageAction.html">page action</a>.
+Instead, a packaged app includes at least one HTML file
+within its <code>.crx</code> file
+that provides the app's user interface.
+</p>
+
+<p>
+Packaged apps are a type of
+<a href="http://code.google.com/chrome/apps/">installable web app</a>&mdash;a
+web app that can be installed in Chrome.
+The other type of installable web app is a
+<a href="http://code.google.com/chrome/apps/docs/developers_guide.html">hosted app</a>,
+which is an ordinary web app with a bit of additional metadata.
+</p>
+
+<p>
+If you're developing a web app for the Chrome Web Store,
+you might want to use a packaged app
+instead of a hosted app if any of the following are true:
+</p>
+
+<ul>
+  <li>
+    You don't want to run a service to host your app.
+  </li>
+  <li>
+    You want to build an app that works really well offline.
+  </li>
+  <li>
+    You want tighter integration with Chrome,
+    using the extension APIs.
+  </li>
+</ul>
+
+<p>
+To learn more about
+the differences between web apps and websites,
+extensions and packaged apps, and packaged apps and hosted apps,
+read these:
+</p>
+
+<ul>
+  <li> <a href="http://code.google.com/chrome/webstore/docs/choosing.html">Choosing an App Type</a> </li>
+  <li> <a href="http://code.google.com/chrome/apps/articles/thinking_in_web_apps.html">Thinking in Web Apps</a> </li>
+  <li> <a href="http://code.google.com/chrome/webstore/articles/apps_vs_extensions.html">Extensions, Packaged Apps, and Hosted Apps in the Chrome Web Store</a> </li>
+</ul>
+
+
+<h2 id="manifest"> The manifest </h2>
+
+<p>
+A packaged app's manifest can have any field
+that's available to extensions,
+except for "browser_action" and "page_action".
+In addition, a packaged app's manifest <b>must</b>
+have an "app" field.
+Here is a typical manifest for a packaged app:
+</p>
+
+<pre>
+{
+  "name": "My Awesome Racing Game",
+  "description": "Enter a world where a Vanagon can beat a Maserati",
+  "version": "1",
+  <b>"app": {
+    "launch": {
+      "local_path": "main.html"
+    }
+  },</b>
+  "icons": {
+    "16": "icon_16.png",
+    "128": "icon_128.png"
+  }
+}
+</pre>
+
+<p>
+The "app" field has one subfield, "launch",
+which specifies the <em>launch page</em> for the app&mdash;the
+page (HTML file bundled into the <code>.crx</code> file)
+that the browser goes to when the user clicks the app's icon
+in the New Tab page.
+The "launch" field can contain the following:
+</p>
+
+<dl>
+  <dt>local_path:</dt>
+    <dd><em>Required.</em>
+    Specifies the launch page
+    as a relative path referring to a file
+    in the <code>.crx</code> package.
+    </dd>
+  <dt>container:</dt>
+    <dd> The value "panel" makes the app appear
+    in an app panel.
+    By default, or when you specify "tab",
+    the app appears in a tab.
+
+    <!-- PENDING: In the overview
+    (or somewhere else before here)
+    we should show and define both app panels and tabs.
+    We should link to that place from here. -->
+    </dd>
+  <dt>height:</dt>
+    <dd>
+    If the container is set to "panel",
+    this integer specifies the height
+    of the panel in pixels.
+    For example, you might specify
+    <code>"height":400</code>.
+    Note that you don't use quotation marks in the value.
+    This field specifies the height of the area
+    to display contents in;
+    window decorations add a few more pixels to the total height.
+    If the container isn't a panel, this field is ignored.
+    </dd>
+  <dt>width:</dt>
+    <dd>
+    Similar to "height",
+    but specifies the width of the panel.
+    </dd>
+  </dd>
+</dl>
+
+<p>
+Packaged apps usually provide a 16x16 icon
+to be used as the favicon for
+tabs that contain app's pages.
+They also should provide a 128x128 icon,
+but not a 48x48 icon.
+See the manifest documentation for the
+<a href="manifest.html#icons">"icons" field</a>
+for more information.
+</p>
+
+<p>
+For further details on what a packaged app's manifest can contain, see the
+<a href="manifest.html">manifest documentation</a>.
+</p>
+
+<h2 id="next">What next?</h2>
+
+<p>
+Read the <a href="overview.html">Overview</a> to learn
+basic concepts about extensions.
+</p>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
diff --git a/chrome/common/extensions/docs/templates/articles/autoupdate.html b/chrome/common/extensions/docs/templates/articles/autoupdate.html
new file mode 100644
index 0000000..2027386
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/autoupdate.html
@@ -0,0 +1,155 @@
+<h1>Autoupdating</h1>
+
+
+<p>We want extensions and apps to be autoupdated for some of the same reasons as Google Chrome itself: to incorporate bug and security fixes, add new features or performance enhancements, and improve user interfaces.</p>
+
+<p>If you publish using the <a href="https://chrome.google.com/webstore/developer/dashboard">Chrome Developer Dashboard</a>, you can <em>ignore this page</em>. You can use the dashboard to release updated versions to users, as well as to the Chrome Web Store.</p>
+
+<p>If you want to host somewhere other than the store, keep reading.
+You should also read <a href="hosting.html">Hosting</a> and
+<a href="packaging.html">Packaging</a>.</p>
+
+
+<h2 id="overview">Overview</h2>
+<ul><li>A manifest may contain an "update_url" field, pointing to a location for doing update checks.</li>
+<li>The content returned by an update check is an <em>update manifest</em> XML document listing the latest version of an extension.</li></ul>
+
+<p>Every few hours, the browser checks whether any installed extensions or apps have an update URL. For each one, it makes a request to that URL looking for an update manifest XML file. If the update manifest mentions a version that is more recent than what's installed, the browser downloads and installs the new version. As with manual updates, the new <code>.crx</code> file must be signed with the same private key as the currently installed version.</p>
+
+
+<h2 id="update_url">Update URL</h2>
+<p>If you're hosting your own extension or app, you need to add the "update_url" field to your <a href="manifest.html"><code>manifest.json</code></a> file,
+like this:</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"update_url": "http://myhost.com/mytestextension/updates.xml"</b>,
+  ...
+}
+</pre>
+
+<h2 id="update_manifest">Update manifest</h2>
+<p>The update manifest returned by the server should be an XML document that looks like this (highlights indicate parts you should modify):</p>
+
+<pre>
+&lt;?xml version='1.0' encoding='UTF-8'?&gt;
+&lt;gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'&gt;
+  &lt;app appid='<b>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</b>'&gt;
+   &nbsp;&lt;updatecheck&nbsp;codebase='<b>http://myhost.com/mytestextension/mte_v2.crx</b>'&nbsp;version='<b>2.0</b>' /&gt;
+  &lt;/app&gt;
+&lt;/gupdate&gt;
+</pre>
+
+<p>This XML format is borrowed from that used by Omaha, Google's update infrastructure. See <a href="http://code.google.com/p/omaha/">http://code.google.com/p/omaha/</a> for more details.
+The extensions system uses the following attributes
+for the <strong>&lt;app></strong>
+and <strong>&lt;updatecheck></strong> elements of the update manifest:
+</p>
+
+<p><b>appid</b><br>
+The extension or app ID, generated based on a hash of the public key,
+as described in <a href="packaging.html">Packaging</a>. You can find the
+ID of an extension or packaged app by going to the Extensions page (<b>chrome://extensions</b>).
+</p>
+{{?is_apps}}
+<p>
+Hosted apps, however, are not listed on the Extensions page.  You can find the ID of any
+app using the following steps:
+</p>
+{{/is_apps}}
+
+{{?is_apps}}
+<ul>
+  <li> Open the app. You can do this by clicking its icon on the New Tab page.</li>
+  <li> Open the JavaScript console.  You can do this by clicking the wrench icon
+       and choosing <b>Tools &gt; JavaScript Console</b>.</li>
+  <li> Enter the following expression into the JavaScript console: <code>chrome.app.getDetails().id</code>
+      <p>The console shows the app's ID as a quoted string.</p>
+  </li>
+</ul>
+{{/is_apps}}
+
+<p><b>codebase</b><br>
+A URL to the <code>.crx</code> file.</p>
+
+<p><b>version</b><br>
+Used by the client to determine whether it should download the <code>.crx</code> file specified by <code>codebase</code>. It should match the value of "version" in the <code>.crx</code> file's <code>manifest.json</code> file.</p>
+<p>The update manifest XML file may contain information about multiple extensions by including multiple &lt;app&gt; elements.</p>
+
+
+<h2 id="testing">Testing</h2>
+<p>The default update check frequency is several hours,
+but you can force an update using the Extensions page's
+<b>Update extensions now</b> button.
+</p>
+
+<p>
+Another option is to use the --extensions-update-frequency command-line flag to set a more frequent interval in seconds. For example, to make checks run every 45 seconds, run Google Chrome like this:</p>
+<pre>
+chrome.exe <b>--extensions-update-frequency=45</b></pre>
+
+<p>Note that this affects checks for all installed extensions and apps, so consider the bandwidth and server load implications of this. You may want to temporarily uninstall all but the one you are testing with, and should not run with this option turned on during normal browser usage.</p>
+
+
+<h2 id="request_parameters">Advanced usage: request parameters</h2>
+<p>The basic autoupdate mechanism is designed to make the server-side work as easy as just dropping a static XML file onto any plain web server such as Apache, and updating that XML file as you release new versions of your extensions.</p>
+<p>More advanced developers may wish to take advantage of the fact that we add on parameters to the request for the update manifest to indicate the extension ID and version. Then they can use the same update URL for all of their extensions, pointing to a URL running dynamic server-side code instead of a static XML file.</p>
+<p>The format of the request parameters is:</p>
+<p><code>&nbsp;&nbsp;?x=<em>&lt;extension_data&gt;</em></code></p>
+<p>Where <code><em>&lt;extension_data&gt;</em></code> is a URL-encoded string of the format:</p>
+<p><code>&nbsp;&nbsp;<em>id=&lt;id&gt;</em>&amp;v=<em>&lt;version&gt;</em></code></p>
+
+<p>For example, say you have two extensions,
+both of which point to the same update URL
+(<code>http://test.com/extension_updates.php</code>):
+</p>
+
+<ul>
+<li> Extension 1
+  <ul>
+    <li> ID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" </li>
+    <li> Version: "1.1"</li>
+  </ul>
+<li> Extension 2
+  <ul>
+    <li> ID: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" </li>
+    <li> Version: "0.4"</li>
+  </ul>
+</ul>
+
+
+<p>The request to update each individual extension would be:</p>
+
+<ul>
+  <li> <code>http://test.com/extension_updates.php?x=id%3Daaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%26v%3D1.1</code> </li>
+  <li> <code>http://test.com/extension_updates.php?x=id%3Dbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb%26v%3D0.4</code> </li>
+</ul>
+
+<p>
+Multiple extensions can be listed in a single request for each unique update URL.
+For the above example, if a user has both of the extensions installed,
+then the two requests are merged into a single request:</p>
+<p><code>http://test.com/extension_updates.php?x=id%3Daaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%26v%3D1.1&amp;x=id%3Dbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb%26v%3D0.4</code></p>
+
+<p>If the number of installed extensions using the same update URL is large enough that a GET request URL is too long (over 2000 characters or so), the update check issues additional GET requests as necessary.</p>
+
+<p class="note">
+<b>Note:</b>
+In the future, instead of issuing multiple GET requests,
+a single POST request might be issued
+with the request parameters in the POST body.
+</p>
+
+<h2 id="minimum_browser_version">Advanced usage: minimum browser version</h2>
+<p>As we add more APIs to the extensions system, it's possible you will want to release an updated version of an extension or app that will work only with newer versions of the browser. While Google Chrome itself is autoupdated, it can take a few days before the majority of the user base has updated to any given new release. To ensure that a given update will apply only to Google Chrome versions at or higher than a specific version, you add the "prodversionmin" attribute to the &lt;app&gt; element in your update manifest. For example:</p>
+
+<pre>&lt;?xml version='1.0' encoding='UTF-8'?&gt;
+&lt;gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'&gt;
+&nbsp;&nbsp;&lt;app appid='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'&gt;
+&nbsp;&nbsp; &nbsp;&lt;updatecheck&nbsp;codebase='http://myhost.com/mytestextension/mte_v2.crx'&nbsp;version='2.0' <b>prodversionmin='3.0.193.0'</b>/&gt;
+&nbsp;&nbsp;&lt;/app&gt;
+&lt;/gupdate&gt;
+</pre>
+
+<p>This would ensure that users would autoupdate to version 2 only if they are running Google Chrome 3.0.193.0 or greater.</p>
diff --git a/chrome/common/extensions/docs/templates/articles/background_pages.html b/chrome/common/extensions/docs/templates/articles/background_pages.html
new file mode 100644
index 0000000..701d4d1
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/background_pages.html
@@ -0,0 +1,160 @@
+<h1>Background Pages</h1>
+
+
+<p id="eventPageWarning" class="warning">
+  <em>Caution:</em> Consider using event pages instead.
+  <a href="event_pages.html">Learn more</a>.
+</p>
+
+<p>
+A common need for extensions is to have
+a single long-running script to manage some task or state.
+Background pages to the rescue.
+</p>
+
+<p>
+As the <a href="overview.html#arch">architecture overview</a> explains,
+the background page is an HTML page that runs in the extension process.
+It exists for the lifetime of your extension,
+and only one instance of it at a time is active.
+</p>
+
+<p>
+In a typical extension with a background page,
+the UI &mdash;
+for example, the browser action or page action
+and any options page &mdash;
+is implemented by dumb views.
+When the view needs some state,
+it requests the state from the background page.
+When the background page notices a state change,
+the background page tells the views to update.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+
+<p>
+Register your background page in the
+<a href="manifest.html">extension manifest</a>.
+In the common case, a background page
+does not require any HTML markup.
+These kind of background pages can be
+implemented using JavaScript files alone,
+like this:
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"background": {
+    "scripts": ["background.js"]
+  }</b>,
+  ...
+}</pre>
+
+<p>
+A background page will be generated
+by the extension system
+that includes each of the files listed
+in the <code>scripts</code> property.
+</p>
+
+<p>
+If you need to specify HTML
+in your background page, you can
+do that using the <code>page</code>
+property instead:
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"background": {
+    "page": "background.html"
+  }</b>,
+  ...
+}</pre>
+
+<p>
+If you need the browser to start up early&mdash;so
+you can display notifications, for example&mdash;then
+you might also want to specify the
+<a href="manifest.html#permissions">"background" permission</a>.
+</p>
+
+
+<h2 id="details">Details</h2>
+
+<p>
+You can communicate between your various pages using direct script calls,
+similar to how frames can communicate.
+The <a href="extension.html#method-getViews"><code>chrome.extension.getViews()</code></a> method
+returns a list of window objects
+for every active page belonging to your extension,
+and the
+<a href="extension.html#method-getBackgroundPage"><code>chrome.extension.getBackgroundPage()</code></a> method
+returns the background page.
+</p>
+
+<h2 id="example">Example</h2>
+
+<p>
+The following code snippet demonstrates
+how the background page
+can interact with other pages in the extension.
+It also shows how you can use
+the background page to handle events
+such as user clicks.
+</p>
+
+<p>
+The extension in this example
+has a background page
+and multiple pages created
+(with
+<a href="tabs.html#method-create"><code>chrome.tabs.create()</code></a>)
+from a file named <code>image.html</code>.
+
+</p>
+
+<pre>
+<em>//In background.js:</em>
+// React when a browser action's icon is clicked.
+chrome.browserAction.onClicked.addListener(function(tab) {
+  var viewTabUrl = chrome.extension.getURL('image.html');
+  var imageUrl = <em>/* an image's URL */</em>;
+
+  // Look through all the pages in this extension to find one we can use.
+  var views = chrome.extension.getViews();
+  for (var i = 0; i < views.length; i++) {
+    var view = views[i];
+
+    // If this view has the right URL and hasn't been used yet...
+    if (view.location.href == viewTabUrl && !view.imageAlreadySet) {
+
+      // ...call one of its functions and set a property.
+      view.setImageUrl(imageUrl);
+      view.imageAlreadySet = true;
+      break; // we're done
+    }
+  }
+});
+
+<em>//In image.html:</em>
+&lt;html>
+  &lt;script>
+    function setImageUrl(url) {
+      document.getElementById('target').src = url;
+    }
+  &lt;/script>
+
+  &lt;body>
+    &lt;p>
+    Image here:
+    &lt;/p>
+
+    &lt;img id="target" src="white.png" width="640" height="480">
+
+  &lt;/body>
+&lt;/html>
+</pre>
diff --git a/chrome/common/extensions/docs/templates/articles/contentSecurityPolicy.html b/chrome/common/extensions/docs/templates/articles/contentSecurityPolicy.html
new file mode 100644
index 0000000..f6f7f29
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/contentSecurityPolicy.html
@@ -0,0 +1,340 @@
+<h1>Content Security Policy (CSP)</h1>
+
+
+<p>
+  In order to mitigate a large class of potental cross-site scripting issues,
+  Chrome's extension system has incorporated the general concept of
+  <a href="http://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html">
+    <strong>Content Security Policy (CSP)</strong>
+  </a>. This introduces some fairly strict policies that will make extensions
+  more secure by default, and provides you with the ability to create and
+  enforce rules governing the types of content that can be loaded and executed
+  by your extensions and applications.
+</p>
+
+<p>
+  In general, CSP works as a black/whitelisting mechanism for resources loaded
+  or executed by your extensions. Defining a reasonable policy for your
+  extension enables you to carefully consider the resources that your extension
+  requires, and to ask the browser to ensure that those are the only resources
+  your extension has access to. These policies provide security over and above
+  the <a href="manifest.html#permissions">host permissions</a> your extension
+  requests; they're an additional layer of protection, not a replacement.
+</p>
+
+<p>
+  On the web, such a policy is defined via an HTTP header or <code>meta</code>
+  element. Inside Chrome's extension system, neither is an appropriate
+  mechanism. Instead, an extension's policy is defined via the extension's
+  <a href="manifest.html"><code>manifest.json</code></a> file as follows:
+</p>
+
+<pre>{
+  ...,
+  "content_security_policy": "[POLICY STRING GOES HERE]"
+  ...
+}</pre>
+
+<p class="note">
+  For full details regarding CSP's syntax, please take a look at
+  <a href="http://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html#syntax">
+    the Content Security Policy specification
+  </a>, and the <a href="http://www.html5rocks.com/en/tutorials/security/content-security-policy/">
+    "An Introduction to Content Security Policy"
+  </a> article on HTML5Rocks.
+</p>
+
+<h2 id="restrictions">Default Policy Restrictions</h2>
+
+<p>
+  Packages that do not define a <a href="manifestVersion.html">
+    <code>manifest_version</code>
+  </a> have no default content security policy. Those that select
+  <code>manifest_version</code></a> 2, have a default content security policy
+  of:
+</p>
+
+<pre>script-src 'self'; object-src 'self'</pre>
+
+<p>
+  This policy adds security by limiting extensions and applications in three 
+  ways:
+</p>
+
+<h3 id="JSEval">Eval and related functions are disabled</h3>
+
+<p>Code like the following does not work:</p>
+
+<pre>
+alert(eval("foo.bar.baz"));
+window.setTimeout("alert('hi')", 10);
+window.setInteral("alert('hi')", 10);
+new Function("return foo.bar.baz");
+</pre>
+
+<p>Evaluating strings of JavaScript like this is a common XSS attack vector.
+Instead, you should write code like:
+
+<pre>
+alert(foo && foo.bar && foo.bar.baz);
+window.setTimeout(function() { alert('hi'); }, 10);
+window.setInterval(function() { alert('hi'); }, 10);
+function() { return foo && foo.bar && foo.bar.baz };
+</pre>
+
+<h3 id="JSExecution">Inline JavaScript will not be executed</h3>
+
+<p>
+  Inline JavaScript will not be executed. This restriction bans both inline
+ <code>&lt;script&gt;</code> blocks <strong>and</strong> inline event handlers
+ (e.g. <code>&lt;button onclick="..."&gt;</code>).
+</p>
+
+<p>
+  The first restriction wipes out a huge class of cross-site scripting attacks
+  by making it impossible for you to accidentally execute script provided by a
+  malicious third-party. It does, however, require you to write your code with a
+  clean separation between content and behavior (which you should of course do
+  anyway, right?). An example might make this clearer. You might try to write a
+  <a href="browserAction.html#popups">Browser Action's popup</a> as a single
+  <code>popup.html</code> containing:
+</p>
+
+<pre>&lt;!doctype html&gt;
+&lt;html&gt;
+  &lt;head&gt;
+    &lt;title&gt;My Awesome Popup!&lt;/title&gt;
+    &lt;script&gt;
+      function awesome() {
+        // do something awesome!
+      }
+
+      function totallyAwesome() {
+        // do something TOTALLY awesome!
+      }
+
+      function clickHandler(element) {
+        setTimeout(<strong>"awesome(); totallyAwesome()"</strong>, 1000);
+      }
+
+      function main() {
+        // Initialization work goes here.
+      }
+    &lt;/script&gt;
+  &lt;/head&gt;
+  &lt;body onload="main();"&gt;
+    &lt;button <strong>onclick="clickHandler(this)"</strong>&gt;
+      Click for awesomeness!
+    &lt;/button&gt;
+  &lt;/body&gt;
+&lt;/html&gt;</pre>
+
+<p>
+  Three things will need to change in order to make this work the way you expect
+  it to:
+</p>
+
+<ul>
+  <li>
+    The <code>clickHandler</code> definition needs to move into an external
+    JavaScript file (<code>popup.js</code> would be a good target).
+  </li>
+  <li>
+    <p>The inline event handler definitions must be rewritten in terms of
+    <code>addEventListener</code> and extracted into <code>popup.js</code>.</p>
+    <p>If you're currently kicking off your program's execution via code like
+    <code>&lt;body onload="main();"&gt;</code>, consider replacing it by hooking
+    into the document's <code>DOMContentLoaded</code> event, or the window's
+    <code>load</code> event, depending on your needs. Below we'll use the
+    former, as it generally triggers more quickly.</p>
+  </li>
+  <li>
+    The <code>setTimeout</code> call will need to be rewritten to avoid
+    converting the string <code>"awesome(); totallyAwesome()"</code> into
+    JavaScript for execution.
+  </li>
+</ul>
+
+<p>
+  Those changes might look something like the following:
+</p>
+
+<pre>popup.js:
+=========
+
+function awesome() {
+  // Do something awesome!
+}
+
+function totallyAwesome() {
+  // do something TOTALLY awesome!
+}
+
+<strong>function awesomeTask() {
+  awesome();
+  totallyAwesome();
+}</strong>
+
+function clickHandler(e) {
+  setTimeout(<strong>awesomeTask</strong>, 1000);
+}
+
+function main() {
+  // Initialization work goes here.
+}
+
+// Add event listeners once the DOM has fully loaded by listening for the
+// `DOMContentLoaded` event on the document, and adding your listeners to
+// specific elements when it triggers.
+<strong>document.addEventListener('DOMContentLoaded', function () {</strong>
+  document.querySelector('button').addEventListener('click', clickHandler);
+  main();
+});
+</pre>
+<pre>popup.html:
+===========
+
+&lt;!doctype html&gt;
+&lt;html&gt;
+  &lt;head&gt;
+    &lt;title&gt;My Awesome Popup!&lt;/title&gt;
+    &lt;script <strong>src="popup.js"</strong>&gt;&lt;/script&gt;
+  &lt;/head&gt;
+  &lt;body&gt;
+    &lt;button&gt;Click for awesomeness!&lt;/button&gt;
+  &lt;/body&gt;
+&lt;/html&gt;</pre>
+
+<p>
+
+
+<h3 id="resourceLoading">Only local script and and object resources are loaded</h3>
+
+<p>
+  Script and object resources can only be loaded from the extension's
+  package, not from the web at large. This ensures that your extension only
+  executes the code you've specifically approved, preventing an active network
+  attacker from maliciously redirecting your request for a resource.
+</p>
+
+<p>
+  Instead of writing code that depends on jQuery (or any other library) loading
+  from an external CDN, consider including the specific version of jQuery in
+  your extension package. That is, instead of:
+</p>
+
+<pre>&lt;!doctype html&gt;
+&lt;html&gt;
+  &lt;head&gt;
+    &lt;title&gt;My Awesome Popup!&lt;/title&gt;
+    &lt;script src="<strong>http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js</strong>"&gt;&lt;/script&gt;
+  &lt;/head&gt;
+  &lt;body&gt;
+    &lt;button&gt;Click for awesomeness!&lt;/button&gt;
+  &lt;/body&gt;
+&lt;/html&gt;</pre>
+
+<p>
+  Download the file, include it in your package, and write:
+<p>
+
+<pre>&lt;!doctype html&gt;
+&lt;html&gt;
+  &lt;head&gt;
+    &lt;title&gt;My Awesome Popup!&lt;/title&gt;
+    &lt;script src="<strong>jquery.min.js</strong>"&gt;&lt;/script&gt;
+  &lt;/head&gt;
+  &lt;body&gt;
+    &lt;button&gt;Click for awesomeness!&lt;/button&gt;
+  &lt;/body&gt;
+&lt;/html&gt;</pre>
+
+<h2 id="relaxing">Relaxing the default policy</h2>
+
+<h3 id="relaxing-inline-script">Inline Script</h3>
+
+<p>
+  There is no mechanism for relaxing the restriction against executing inline
+  JavaScript. In particular, setting a script policy that includes
+  <code>'unsafe-inline'</code> will have no effect.
+</p>
+
+<h3 id="relaxing-remote-script">Remote Script</h3>
+
+<p>
+  If you have a need for some external JavaScript or object
+  resources, you can relax the policy to a limited extent by whitelisting
+  secure origins from which scripts should be accepted. We want to ensure that
+  executable resources loaded with an extension's elevated permissions are
+  exactly the resources you expect, and haven't been replaced by an active
+  network attacker. As <a
+  href="http://en.wikipedia.org/wiki/Man-in-the-middle_attack">man-in-the-middle
+  attacks</a> are both trivial and undetectable over HTTP, those origins will
+  not be accepted. Currently, we allow whitelisting origins with the following
+  schemes: <code>HTTPS</code>, <code>chrome-extension</code>, and
+  <code>chrome-extension-resource</code>.
+</p>
+
+<p>
+  To ease development, we're also allowing the whitelisting of resources loaded
+  over HTTP from servers on your local machine. You may whitelist script and
+  object sources on any port of either <code>http://127.0.0.1</code> or
+  <code>http://localhost</code>.
+</p>
+
+<p class="note">
+  The restriction against resources loaded over HTTP applies only to those
+  resources which are directly executed. You're still free, for example, to
+  make XMLHTTPRequest connections to any origin you like; the default policy
+  doesn't restrict <code>connect-src</code> or any of the other CSP directives
+  in any way.
+</p>
+
+<p>
+  A relaxed policy definition which allows script resources to be loaded from
+  <code>example.com</code> over HTTPS might look like:
+</p>
+
+<pre>"content_security_policy": "script-src 'self' https://example.com; object-src 'self'"</pre>
+
+<p class="note">
+  Note that both <code>script-src</code> and <code>object-src</code> are defined
+  by the policy. Chrome will not accept a policy that doesn't limit each of
+  these values to (at least) <code>'self'</code>.
+</p>
+
+<p>
+  Making use of Google Analytics is the canonical example for this sort of
+  policy definition. It's common enough that we've provided an Analytics
+  boilerplate of sorts in the <a href="samples.html#analytics">Event Tracking
+  with Google Analytics</a> sample extension, and a
+<a href="tut_analytics.html">brief tutorial</a> that goes into more detail.
+</p>
+
+<h3 id="relaxing-eval">Evaluated JavaScript</h3>
+
+<p>
+  The policy against <code>eval()</code> and its relatives like
+  <code>setTimeout(String)</code>, <code>setInterval(String)</code>, and
+  <code>new Function(String)</code> can be relaxed by adding
+  <code>'unsafe-eval'</code> to your policy:
+</p>
+
+<pre>"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"</pre>
+
+<p>
+  However, we strongly recommend against doing this. These functions are
+  notorious XSS attack vectors.
+</p>
+
+<h2 id="tightening">Tightening the default policy</h2>
+
+<p>
+  You may, of course, tighten this policy to whatever extent your extension
+  allows in order to increase security at the expense of convenience. To specify
+  that your extension can only load resources of <em>any</em> type (images, etc)
+  from its own package, for example, a policy of <code>default-src 'self'</code>
+  would be appropriate. The <a href="samples.html#mappy">Mappy</a> sample
+  extension is a good example of an extension that's been locked down above and
+  beyond the defaults.
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/content_scripts.html b/chrome/common/extensions/docs/templates/articles/content_scripts.html
new file mode 100644
index 0000000..5773e60
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/content_scripts.html
@@ -0,0 +1,461 @@
+<h1>Content Scripts</h1>
+
+
+<p>
+Content scripts are JavaScript files that run in the context of web pages.
+By using the standard
+<a href="http://www.w3.org/TR/DOM-Level-2-HTML/">Document
+Object Model</a> (DOM),
+they can read details of the web pages the browser visits,
+or make changes to them.
+</p>
+
+<p>
+Here are some examples of what content scripts can do:
+</p>
+
+<ul>
+  <li>Find unlinked URLs in web pages and convert them into hyperlinks
+  <li>Increase the font size to make text more legible
+  <li>Find and process <a href="http://microformats.org/">microformat</a> data in the DOM
+</ul>
+
+<p>
+However, content scripts have some limitations.
+They <b>cannot</b>:
+</p>
+
+<ul>
+  <li>
+    Use chrome.* APIs
+    (except for parts of
+    <a href="extension.html"><code>chrome.extension</code></a>)
+  </li>
+  <li>
+    Use variables or functions defined by their extension's pages
+  </li>
+  <li>
+    Use variables or functions defined by web pages or by other content scripts
+  </li>
+</ul>
+
+<p>
+These limitations aren't as bad as they sound.
+Content scripts can <em>indirectly</em> use the chrome.* APIs,
+get access to extension data,
+and request extension actions
+by exchanging <a href="messaging.html">messages</a>
+with their parent extension.
+Content scripts can also
+make <a href="xhr.html">cross-site XMLHttpRequests</a>
+to the same sites as their parent extensions,
+and they can
+<a href="#host-page-communication">communicate with web pages</a>
+using the shared DOM.
+For more insight into what content scripts can and can't do,
+learn about the
+<a href="#execution-environment">execution environment</a>.
+</p>
+
+<h2 id="registration">Manifest</h2>
+
+<p>If your content script's code should always be injected,
+register it in the
+<a href="manifest.html">extension manifest</a>
+using the <code>content_scripts</code> field,
+as in the following example.
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"content_scripts": [
+    {
+      "matches": ["http://www.google.com/*"],
+      "css": ["mystyles.css"],
+      "js": ["jquery.js", "myscript.js"]
+    }
+  ]</b>,
+  ...
+}</pre>
+
+<p>
+If you want to inject the code only sometimes,
+use the
+<a href="manifest.html#permissions"><code>permissions</code></a> field instead,
+as described in <a href="#pi">Programmatic injection</a>.
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "tabs", "http://www.google.com/*"
+  ]</b>,
+  ...
+}</pre>
+
+<p>
+Using the <code>content_scripts</code> field,
+an extension can insert multiple content scripts into a page;
+each of these content scripts can have multiple JavaScript and CSS files.
+Each item in the <code>content_scripts</code> array
+can have the following properties:</p>
+
+<table class="simple">
+  <tr>
+    <th>Name</th>
+    <th>Type</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td><code>matches</code></td>
+    <td>array of strings</td>
+    <td><em>Required.</em>
+    Specifies which pages this content script will be injected into.
+    See <a href="match_patterns.html">Match Patterns</a>
+    for more details on the syntax of these strings
+    and <a href="#match-patterns-globs">Match patterns and globs</a>
+    for information on how to exclude URLs.</td>
+  </tr>
+  <tr>
+    <td><code>exclude_matches</code></td>
+    <td>array of strings</td>
+    <td><em>Optional.</em>
+    Excludes pages that this content script would otherwise be
+    injected into.
+    See <a href="match_patterns.html">Match Patterns</a>
+    for more details on the syntax of these strings
+    and <a href="#match-patterns-globs">Match patterns and globs</a>
+    for information on how to exclude URLs.</td>
+  </tr>
+  <tr>
+    <td><code>css<code></td>
+    <td>array of strings</td>
+    <td><em>Optional.</em>
+    The list of CSS files to be injected into matching pages. These are injected in the order they appear in this array, before any DOM is constructed or displayed for the page.</td>
+  </tr>
+  <tr>
+    <td><code>js<code></td>
+    <td><nobr>array of strings</nobr></td>
+    <td><em>Optional.</em>
+    The list of JavaScript files to be injected into matching pages. These are injected in the order they appear in this array.</td>
+  </tr>
+  <tr id="run_at">
+    <td><code>run_at<code></td>
+    <td>string</td>
+    <td><em>Optional.</em>
+    Controls when the files in <code>js</code> are injected. Can be "document_start", "document_end", or "document_idle". Defaults to "document_idle".
+
+    <br><br>
+
+    In the case of "document_start", the files are injected after any files from <code>css</code>, but before any other DOM is constructed or any other script is run.
+
+    <br><br>
+
+    In the case of "document_end", the files are injected immediately after the DOM is complete, but before subresources like images and frames have loaded.
+
+    <br><br>
+
+    In the case of "document_idle", the browser chooses a time to inject scripts between "document_end" and immediately after the <code><a href="http://www.whatwg.org/specs/web-apps/current-work/#handler-onload">window.onload</a></code> event fires. The exact moment of injection depends on how complex the document is and how long it is taking to load, and is optimized for page load speed.
+
+    <br><br>
+
+    <b>Note:</b> With "document_idle", content scripts may not necessarily receive the <code>window.onload</code> event, because they may run after it has
+    already fired. In most cases, listening for the <code>onload</code> event is unnecessary for content scripts running at "document_idle" because they are guaranteed to run after the DOM is complete. If your script definitely needs to run after <code>window.onload</code>, you can check if <code>onload</code> has already fired by using the <code><a href="http://www.whatwg.org/specs/web-apps/current-work/#dom-document-readystate">document.readyState</a></code> property.</td>
+  </tr>
+  <tr>
+    <td><code>all_frames<code></td>
+    <td>boolean</td>
+    <td><em>Optional.</em>
+    Controls whether the content script runs in all frames of the matching page, or only the top frame.
+    <br><br>
+    Defaults to <code>false</code>, meaning that only the top frame is matched.</td>
+  </tr>
+  <tr>
+    <td><code>include_globs</code></td>
+    <td>array of string</td>
+    <td><em>Optional.</em>
+    Applied after <code>matches</code> to include only those URLs that also match this glob. Intended to emulate the <a href="http://wiki.greasespot.net/Metadata_Block#.40include"><code>@include</code></a> Greasemonkey keyword.
+    See <a href="#match-patterns-globs">Match patterns and globs</a> below for more details.</td>
+  </tr>
+  <tr>
+    <td><code>exclude_globs</code></td>
+    <td>array of string</td>
+    <td><em>Optional.</em>
+    Applied after <code>matches</code> to exclude URLs that match this glob.
+    Intended to emulate the <a href="http://wiki.greasespot.net/Metadata_Block#.40include"><code>@exclude</code></a> Greasemonkey keyword.
+    See <a href="#match-patterns-globs">Match patterns and globs</a> below for more details.</td>
+  </tr>
+</table>
+
+<h3 id="match-patterns-globs">Match patterns and globs</h3>
+
+<p>
+The content script will be injected into a page if its URL matches any <code>matches</code> pattern and any <code>include_globs</code> pattern, as long as the URL doesn't also match an <code>exclude_matches</code> or <code>exclude_globs</code> pattern.
+
+Because the <code>matches</code> property is required, <code>exclude_matches</code>, <code>include_globs</code>, and <code>exclude_globs</code> can only be used to limit which pages will be affected.
+</p>
+
+<p>
+For example, assume <code>matches</code> is <code>["http://*.nytimes.com/*"]</code>:
+</p>
+<ul>
+<li>If <code>exclude_matches</code> is <code>["*://*/*business*"]</code>, then the content script would be injected into "http://www.nytimes.com/health" but not into "http://www.nytimes.com/business".</li>
+<li>If <code>include_globs</code> is <code>["*nytimes.com/???s/*"]</code>, then the content script would be injected into "http:/www.nytimes.com/arts/index.html" and "http://www.nytimes.com/jobs/index.html" but not into "http://www.nytimes.com/sports/index.html".</li>
+<li>If <code>exclude_globs</code> is <code>["*science*"]</code>, then the content script would be injected into "http://www.nytimes.com" but not into "http://science.nytimes.com" or "http://www.nytimes.com/science".</li>
+</ul>
+<p>
+
+<p>
+Glob properties follow a different, more flexible syntax than <a href="match_patterns.html">match patterns</a>.  Acceptable glob strings are URLs that may contain "wildcard" asterisks and question marks. The asterisk (*) matches any string of any length (including the empty string); the question mark (?) matches any single character.
+</p>
+
+<p>
+For example, the glob "http://???.example.com/foo/*" matches any of the following:
+</p>
+<ul>
+  <li>"http://www.example.com/foo/bar"</li>
+  <li>"http://the.example.com/foo/"</li>
+</ul>
+<p>
+However, it does <em>not</em> match the following:
+</p>
+<ul>
+  <li>"http://my.example.com/foo/bar"</li>
+  <li>"http://example.com/foo/"</li>
+  <li>"http://www.example.com/foo"</li>
+</ul>
+
+<h2 id="pi">Programmatic injection</h2>
+
+<p>
+Inserting code into a page programmatically is useful
+when your JavaScript or CSS code
+shouldn't be injected into every single page
+that matches the pattern &mdash;
+for example, if you want a script to run
+only when the user clicks a browser action's icon.
+</p>
+
+<p>
+To insert code into a page,
+your extension must have
+<a href="xhr.html#requesting-permission">cross-origin permissions</a>
+for the page.
+It also must be able to use the <code>chrome.tabs</code> module.
+You can get both kinds of permission
+using the manifest file's
+<a href="manifest.html#permissions">permissions</a> field.
+</p>
+
+<p>
+Once you have permissions set up,
+you can inject JavaScript into a page by calling
+<a href="tabs.html#method-executeScript"><code>executeScript()</code></a>.
+To inject CSS, use
+<a href="tabs.html#method-insertCSS"><code>insertCSS()</code></a>.
+</p>
+
+<p>
+The following code
+(from the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/browserAction/make_page_red/">make_page_red</a> example)
+reacts to a user click
+by inserting JavaScript into the current tab's page
+and executing the script.
+</p>
+
+<pre>
+<em>/* in background.html */</em>
+chrome.browserAction.onClicked.addListener(function(tab) {
+  chrome.tabs.executeScript(null,
+                           {code:"document.body.bgColor='red'"});
+});
+
+<em>/* in manifest.json */</em>
+"permissions": [
+  "tabs", "http://*/*"
+],
+</pre>
+
+<p>
+When the browser is displaying an HTTP page
+and the user clicks this extension's browser action,
+the extension sets the page's <code>bgcolor</code> property to 'red'.
+The result,
+unless the page has CSS that sets the background color,
+is that the page turns red.
+</p>
+
+<p>
+Usually, instead of inserting code directly (as in the previous sample),
+you put the code in a file.
+You inject the file's contents like this:
+</p>
+
+<pre>chrome.tabs.executeScript(null, {file: "content_script.js"});</pre>
+
+
+<h2 id="execution-environment">Execution environment</h2>
+
+<p>Content scripts execute in a special environment called an <em>isolated world</em>. They have access to the DOM of the page they are injected into, but not to any JavaScript variables or functions created by the page. It looks to each content script as if there is no other JavaScript executing on the page it is running on. The same is true in reverse: JavaScript running on the page cannot call any functions or access any variables defined by content scripts.
+
+<p>For example, consider this simple page:
+
+<pre>hello.html
+==========
+&lt;html&gt;
+  &lt;button id="mybutton"&gt;click me&lt;/button&gt;
+  &lt;script&gt;
+    var greeting = "hello, ";
+    var button = document.getElementById("mybutton");
+    button.person_name = "Bob";
+    button.addEventListener("click", function() {
+      alert(greeting + button.person_name + ".");
+    }, false);
+  &lt;/script&gt;
+&lt;/html&gt;</pre>
+
+<p>Now, suppose this content script was injected into hello.html:
+
+<pre>contentscript.js
+================
+var greeting = "hola, ";
+var button = document.getElementById("mybutton");
+button.person_name = "Roberto";
+button.addEventListener("click", function() {
+  alert(greeting + button.person_name + ".");
+}, false);
+</pre>
+
+<p>Now, if the button is pressed, you will see both greetings.
+
+<p>Isolated worlds allow each content script to make changes to its JavaScript environment without worrying about conflicting with the page or with other content scripts. For example, a content script could include JQuery v1 and the page could include JQuery v2, and they wouldn't conflict with each other.
+
+<p>Another important benefit of isolated worlds is that they completely separate the JavaScript on the page from the JavaScript in extensions. This allows us to offer extra functionality to content scripts that should not be accessible from web pages without worrying about web pages accessing it.
+
+
+<h2 id="host-page-communication">Communication with the embedding page</h2>
+
+<p>Although the execution environments of content scripts and the pages that host them are isolated from each other, they share access to the page's DOM. If the page wishes to communicate with the content script (or with the extension via the content script), it must do so through the shared DOM.</p>
+<p>An example can be accomplished using window.postMessage (or window.webkitPostMessage for Transferable objects):</p>
+<pre>contentscript.js
+================
+var port = chrome.extension.connect();
+
+window.addEventListener("message", function(event) {
+    // We only accept messages from ourselves
+    if (event.source != window)
+      return;
+
+    if (event.data.type &amp;&amp; (event.data.type == "FROM_PAGE")) {
+      console.log("Content script received: " + event.data.text);
+      port.postMessage(event.data.text);
+    }
+}, false);</pre>
+<pre>http://foo.com/example.html
+===========================
+document.getElementById("theButton").addEventListener("click", function() {
+    window.postMessage({ type: "FROM_PAGE", text: "Hello from the webpage!" }, "*");
+}, false);</pre>
+<p>In the above example, example.html (which is not a part of the extension) posts messages to itself, which are intercepted and inspected by the content script, and then posted to the extension process. In this way, the page establishes a line of communication to the extension process. The reverse is possible through similar means.</p>
+
+<h2 id="security-considerations">Security considerations</h2>
+
+<p>When writing a content script, you should be aware of two security issues.
+First, be careful not to introduce security vulnerabilities into the web site
+your content script is injected into.  For example, if your content script
+receives content from another web site (for example, by making an <a
+href="messaging.html">XMLHttpRequest</a>),
+be careful to filter that content for <a
+href="http://en.wikipedia.org/wiki/Cross-site_scripting">cross-site
+scripting</a> attacks before injecting the content into the current page.
+For example, prefer to inject content via innerText rather than innerHTML.
+Be especially careful when retrieving HTTP content on an HTTPS page because
+the HTTP content might have been corrupted by a network <a
+href="http://en.wikipedia.org/wiki/Man-in-the-middle_attack">"man-in-the-middle"</a>
+if the user is on a hostile network.</p>
+
+<p>Second, although running your content script in an isolated world provides
+some protection from the web page, a malicious web page might still be able
+to attack your content script if you use content from the web page
+indiscriminately.  For example, the following patterns are dangerous:
+<pre>contentscript.js
+================
+var data = document.getElementById("json-data")
+// WARNING! Might be evaluating an evil script!
+var parsed = eval("(" + data + ")")
+
+contentscript.js
+================
+var elmt_id = ...
+// WARNING! elmt_id might be "); ... evil script ... //"!
+window.setTimeout("animate(" + elmt_id + ")", 200);
+</pre>
+<p>Instead, prefer safer APIs that do not run scripts:</p>
+<pre>contentscript.js
+================
+var data = document.getElementById("json-data")
+// JSON.parse does not evaluate the attacker's scripts.
+var parsed = JSON.parse(data)
+
+contentscript.js
+================
+var elmt_id = ...
+// The closure form of setTimeout does not evaluate scripts.
+window.setTimeout(function() {
+  animate(elmt_id);
+}, 200);
+</pre>
+
+<h2 id="extension-files">Referring to extension files</h2>
+
+<p>
+Get the URL of an extension's file using
+<code>chrome.extension.getURL()</code>.
+You can use the result
+just like you would any other URL,
+as the following code shows.
+</p>
+
+
+<pre>
+<em>//Code for displaying &lt;extensionDir>/images/myimage.png:</em>
+var imgURL = <b>chrome.extension.getURL("images/myimage.png")</b>;
+document.getElementById("someImage").src = imgURL;
+</pre>
+
+<h2 id="examples"> Examples </h2>
+
+<p>
+You can find many
+<a href="samples.html#script">examples that use content scripts</a>.
+A simple example of communication via messages is in the
+<a href="samples.html#51a83d2ba3a32e3ff1bdb624d4e18ccec4c4038e">timer sample</a>.
+See <a href="samples.html#ede3c47b7757245be42ec33fd5ca63df4b490066">make_page_red</a> and
+<a href="samples.html#028eb5364924344029bcbe1d527f132fc72b34e5">email_this_page</a>
+for examples of programmatic injection.
+</p>
+
+
+<h2 id="videos"> Videos </h2>
+
+<p>
+The following videos discuss concepts that are important for content scripts.
+The first video describes content scripts and isolated worlds.
+</p>
+
+<p>
+<iframe title="YouTube video player" width="640" height="390" src="http://www.youtube.com/embed/laLudeUmXHM?rel=0" frameborder="0" allowfullscreen></iframe>
+</p>
+
+<p>
+The next video describes message passing,
+featuring an example of a content script
+sending a request to its parent extension.
+</p>
+
+<p>
+<iframe title="YouTube video player" width="640" height="390" src="http://www.youtube.com/embed/B4M_a7xejYI?rel=0" frameborder="0" allowfullscreen></iframe>
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/crx.html b/chrome/common/extensions/docs/templates/articles/crx.html
new file mode 100644
index 0000000..34eeb0d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/crx.html
@@ -0,0 +1,147 @@
+<h1>CRX Package Format</h1>
+
+
+<p>
+CRX files are ZIP files with a special header and the <code>.crx</code> file
+extension.
+</p>
+
+<h2 id="package_header">Package header</h2>
+
+<p>
+The header contains the author's public key and the extension's signature. 
+The signature is generated from the ZIP file using SHA-1 with the
+author's private key. The header requires a little-endian byte ordering with 
+4-byte alignment. The following table describes the fields of
+the <code>.crx</code> header in order:
+</p>
+
+<table class="simple">
+  <tr>
+    <th>Field</th><th>Type</th><th>Length</th><th>Value</th><th>Description</th>
+  </tr>
+  <tr>
+    <td><em>magic number</em></td><td>char[]</td><td>32 bits</td><td>Cr24</td>
+    <td>
+      Chrome requires this constant at the beginning of every <code>.crx</code>
+      package.
+    </td>
+  </tr>
+  <tr>
+    <td><em>version</em></td><td>unsigned&nbsp;int</td><td>32 bits</td><td>2</td>
+    <td>The version of the <code>*.crx</code> file format used (currently 2).</td> 
+  </tr>
+  <tr>
+    <td><em>public key length</em></td><td>unsigned&nbsp;int</td><td>32 bits</td>
+    <td><i>pubkey.length</i></td>
+    <td>
+      The length of the RSA public key in <em>bytes</em>.
+    </td>
+  </tr>
+  <tr>
+    <td><em>signature length</em></td><td>unsigned&nbsp;int</td><td>32 bits</td>
+    <td><i>sig.length</i></td>
+    <td>
+      The length of the signature in <em>bytes</em>.
+    </td>
+  </tr>
+  <tr>
+    <td><em>public key</em></td><td>byte[]</td><td><i>pubkey.length</i></i></td>
+    <td><i>pubkey.contents</i></td>
+    <td>
+      The contents of the author's RSA public key, formatted as an X509 
+      SubjectPublicKeyInfo block.
+    </td>
+  </tr>
+  <tr>
+    <td><em>signature</em></td><td>byte[]</td><td><i>sig.length</i></td>
+    <td><i>sig.contents</i></td>
+    <td>
+      The signature of the ZIP content using the author's private key. The
+      signature is created using the RSA algorithm with the SHA-1 hash function.
+    </td>
+  </tr>
+</table>
+
+<h2 id="extensions_contents">Extension contents</h2>
+
+<p>
+The extension's ZIP file is appended to the <code>*.crx</code> package after the
+header. This should be the same ZIP file that the signature in the header
+was generated from.
+</p>
+
+<h2 id="example">Example</h2>
+
+<p>
+The following is an example hex dump from the beginning of a <code>.crx</code> 
+file.
+</p>
+
+<pre>
+43 72 32 34   # "Cr24" -- the magic number
+02 00 00 00   # 2 -- the crx format version number
+A2 00 00 00   # 162 -- length of public key in bytes
+80 00 00 00   # 128 -- length of signature in bytes
+...........   # the contents of the public key
+...........   # the contents of the signature
+...........   # the contents of the zip file
+
+</pre>
+
+<h2 id="scripts">Packaging scripts</h2>
+<p>
+Members of the community have written the following scripts to package 
+<code>.crx</code> files.
+</p>
+
+<h3 id="ruby">Ruby</h3>
+<blockquote>
+<a href="http://github.com/Constellation/crxmake">github: crxmake</a>
+</blockquote>
+
+<h3 id="bash">Bash</h3>
+<pre>
+#!/bin/bash -e
+#
+# Purpose: Pack a Chromium extension directory into crx format
+
+if test $# -ne 2; then
+  echo "Usage: crxmake.sh &lt;extension dir&gt; &lt;pem path&gt;"
+  exit 1
+fi
+
+dir=$1
+key=$2
+name=$(basename "$dir")
+crx="$name.crx"
+pub="$name.pub"
+sig="$name.sig"
+zip="$name.zip"
+trap 'rm -f "$pub" "$sig" "$zip"' EXIT
+
+# zip up the crx dir
+cwd=$(pwd -P)
+(cd "$dir" && zip -qr -9 -X "$cwd/$zip" .)
+
+# signature
+openssl sha1 -sha1 -binary -sign "$key" < "$zip" > "$sig"
+
+# public key
+openssl rsa -pubout -outform DER < "$key" > "$pub" 2>/dev/null
+
+byte_swap () {
+  # Take "abcdefgh" and return it as "ghefcdab"
+  echo "${1:6:2}${1:4:2}${1:2:2}${1:0:2}"
+}
+
+crmagic_hex="4372 3234" # Cr24
+version_hex="0200 0000" # 2
+pub_len_hex=$(byte_swap $(printf '%08x\n' $(ls -l "$pub" | awk '{print $5}')))
+sig_len_hex=$(byte_swap $(printf '%08x\n' $(ls -l "$sig" | awk '{print $5}')))
+(
+  echo "$crmagic_hex $version_hex $pub_len_hex $sig_len_hex" | xxd -r -p
+  cat "$pub" "$sig" "$zip"
+) > "$crx"
+echo "Wrote $crx"
+</pre>
diff --git a/chrome/common/extensions/docs/templates/articles/declare_permissions.html b/chrome/common/extensions/docs/templates/articles/declare_permissions.html
new file mode 100644
index 0000000..92c3ef9
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/declare_permissions.html
@@ -0,0 +1,306 @@
+<h1>Declare Permissions</h1>
+
+<p>
+To use most chrome.* APIs,
+your extension or app must declare its intent in the
+"permissions" field of the
+<a href="manifest.html">manifest</a>.
+Each permission can be either one of a list of known strings
+(such as "geolocation")
+or a <a href="match_patterns.html">match pattern</a>
+that gives access to one or more hosts.
+Permissions help to limit damage
+if your extension or app is compromised by malware.
+Some permissions are also displayed to users before installation,
+as detailed in
+<a href="permission_warnings.html">Permission Warnings</a>.
+</p>
+
+<p>
+If an API requires you to declare a permission in the manifest,
+then its documentation tells you how to do so.
+For example,
+the <a href="storage.html">Storage</a> page
+shows you how to
+declare the "storage" permission.
+</p>
+
+<p>
+Here's an example of the permissions part of a manifest file:
+</p>
+
+{{^is_apps}}
+<pre>
+"permissions": [
+  "tabs",
+  "bookmarks",
+  "http://www.blogger.com/",
+  "http://*.google.com/",
+  "unlimitedStorage"
+],
+</pre>
+{{/is_apps}}
+
+{{?is_apps}}
+<pre>
+"permissions": [
+    "serial",
+    "storage",
+    "videoCapture"
+],
+</pre>
+{{/is_apps}}
+
+<p>
+The following table lists the currently available permissions:
+</p>
+
+{{?is_apps}}
+<p class="note">
+<strong>Note:</strong>
+Hosted apps can use the
+"background", "clipboardRead", "clipboardWrite", "geolocation", "notifications",
+and "unlimitedStorage" permissions, but not any other permissions listed in this
+table.
+</p>
+{{/is_apps}}
+
+<table>
+<tr>
+  <th> Permission </th> <th> Description </th>
+</tr>
+<tr>
+  <td> <em>match pattern</em> </td>
+  <td> Specifies a <em>host permission</em>.
+       Required if the extension or app wants to interact
+       with the code running on pages.
+       Many capabilities, such as
+       <a href="xhr.html">cross-origin XMLHttpRequests</a>,
+       <a href="content_scripts.html#pi">programmatically injected
+       content scripts</a>, and
+       <a href="cookies.html">the extension's cookies API</a>
+       require host permissions. For details on the syntax, see
+       <a href="match_patterns.html">Match Patterns</a>.
+  </td>
+</tr>
+{{^is_apps}}
+<tr>
+  <td> "activeTab" </td>
+  <td> Requests that the extension be granted permissions according to the
+       <a href="activeTab.html">activeTab</a> specification.
+  </td>
+</tr>
+{{/is_apps}}
+<tr id="bg">
+  <td> "background" </td>
+  <td> <p>
+       Makes Chrome start up early and and shut down late,
+       so that apps and extensions can have a longer life.
+       </p>
+
+       <p>
+       When any installed hosted app, packaged app, or extension
+       has "background" permission, Chrome runs (invisibly)
+       as soon as the user logs into their computer&mdash;before
+       the user launches Chrome.
+       The "background" permission also makes Chrome continue running
+       (even after its last window is closed)
+       until the user explicitly quits Chrome.
+       </p>
+
+       <p class="note">
+       <b>Note:</b>
+       Disabled apps and extensions
+       are treated as if they aren't installed.
+       </p>
+
+       <p>
+       You typically use the "background" permission with a
+       <a href="background_pages.html">background page</a>,
+       <a href="event_pages.html">event page</a>
+       or (for hosted apps) a
+       <a href="http://code.google.com/chrome/apps/docs/background.html">background window</a>.
+       </p>
+       </td>
+</tr>
+{{^is_apps}}
+<tr>
+  <td> "bookmarks" </td>
+  <td> Required if the extension uses the
+       <a href="bookmarks.html">chrome.bookmarks</a> module. </td>
+</tr>
+<tr>
+  <td> "chrome://favicon/" </td>
+  <td> Required if the extension uses the
+       "chrome://favicon/<em>url</em>" mechanism
+       to display the favicon of a page.
+       For example, to display the favicon of http://www.google.com/,
+       you declare the "chrome://favicon/" permission
+       and use HTML code like this:
+       <pre>&lt;img src="chrome://favicon/http://www.google.com/"></pre>
+       </td>
+</tr>
+{{/is_apps}}
+<tr>
+  <td> "clipboardRead" </td>
+  <td> Required if the extension or app uses
+       <code>document.execCommand('paste')</code>. </td>
+</tr>
+<tr>
+  <td> "clipboardWrite" </td>
+  <td> Indicates the extension or app uses
+       <code>document.execCommand('copy')</code> or
+       <code>document.execCommand('cut')</code>. This permission is <b>required
+       for hosted apps</b>; it's recommended for extensions and packaged apps.
+       </td>
+</tr>
+{{^is_apps}}
+<tr>
+  <td> "contentSettings" </td>
+  <td> Required if the extension uses the
+       <a href="contentSettings.html">chrome.contentSettings</a> module. </td>
+</tr>
+{{/is_apps}}
+<tr>
+  <td> "contextMenus" </td>
+  <td> Required if the extension or app uses the
+       <a href="contextMenus.html">chrome.contextMenus</a> module. </td>
+</tr>
+{{^is_apps}}
+<tr>
+  <td> "cookies" </td>
+  <td> Required if the extension uses the
+       <a href="cookies.html">chrome.cookies</a> module. </td>
+</tr>
+{{/is_apps}}
+<tr>
+  <td> "experimental" </td>
+  <td> Required if the extension or app uses any
+       <a href="http://code.google.com/chrome/extensions/dev/experimental.html">chrome.experimental.* APIs</a>.</td>
+</tr>
+{{^is_apps}}
+<tr>
+  <td id="fileBrowserHandler"> "fileBrowserHandler" </td>
+  <td> Required if the extension uses the
+       <a href="fileBrowserHandler.html">fileBrowserhandler</a> module. </td>
+</tr>
+{{/is_apps}}
+{{?is_apps}}
+<tr>
+  <td id="fileSystem"> "fileSystem": ["write"] </td>
+  <td> Required if the app uses the
+       <a href="fileSystem.html">fileSystem API</a> to write files.</td>
+</tr>
+{{/is_apps}}
+<tr>
+  <td id="geolocation"> "geolocation" </td>
+  <td> Allows the extension or app to use the proposed HTML5
+       <a href="http://dev.w3.org/geo/api/spec-source.html">geolocation API</a>
+       without prompting the user for permission. </td>
+</tr>
+{{^is_apps}}
+<tr>
+  <td> "history" </td>
+  <td> Required if the extension uses the
+       <a href="history.html">chrome.history</a> module. </td>
+</tr>
+{{/is_apps}}
+<tr>
+  <td> "idle" </td>
+  <td> Required if the extension or app uses the
+       <a href="idle.html">chrome.idle</a> module. </td>
+</tr>
+{{^is_apps}}
+<tr>
+  <td> "management" </td>
+  <td> Required if the extension uses the
+       <a href="management.html">chrome.management</a> module. </td>
+</tr>
+{{/is_apps}}
+<tr>
+  <td> "notifications" </td>
+  <td> Allows the extension or app to use the proposed HTML5
+       <a href="http://www.chromium.org/developers/design-documents/desktop-notifications/api-specification">notification API</a>
+       without calling permission methods
+       (such as <code>checkPermission()</code>).
+       For more information see
+       <a href="notifications.html">Desktop Notifications</a>.</td>
+</tr>
+{{^is_apps}}
+<tr>
+  <td> "privacy" </td>
+  <td> Required if the extension uses the
+       <a href="privacy.html">chrome.privacy</a> module. </td>
+</tr>
+<tr>
+  <td> "proxy" </td>
+  <td> Required if the extension uses the
+       <a href="proxy.html">chrome.proxy</a> module. </td>
+</tr>
+{{/is_apps}}
+<tr>
+  <td> "storage" </td>
+  <td> Required if the extension or app uses the
+       <a href="storage.html">chrome.storage</a> module. </td>
+</tr>
+{{^is_apps}}
+<tr>
+  <td> "tabs" </td>
+  <td> Required if the extension uses the
+       <a href="tabs.html">chrome.tabs</a> or
+       <a href="windows.html">chrome.windows</a> module. </td>
+</tr>
+<tr>
+  <td> "topSites" </td>
+  <td> Required if the extension uses the
+       <a href="topSites.html">chrome.topSites</a> module. </td>
+</tr>
+{{/is_apps}}
+<tr>
+  <td> "tts" </td>
+  <td> Required if the extension or app uses the
+       <a href="tts.html">chrome.tts</a> module. </td>
+</tr>
+{{^is_apps}}
+<tr>
+  <td> "ttsEngine" </td>
+  <td> Required if the extension uses the
+       <a href="ttsEngine.html">chrome.ttsEngine</a> module. </td>
+</tr>
+{{/is_apps}}
+<tr>
+  <td> "unlimitedStorage"</td>
+  <td> Provides an unlimited quota for storing HTML5 client-side data,
+       such as databases and local storage files.
+       Without this permission, the extension or app is limited to
+       5 MB of local storage.
+
+      <p class="note">
+      <b>Note:</b>
+      This permission applies only to Web SQL Database and application cache
+      (see issue <a href="http://crbug.com/58985">58985</a>).
+      Also, it doesn't currently work with wildcard subdomains such as
+      <code>http://*.example.com</code>.
+      </p>
+  </td>
+<tr>
+{{^is_apps}}
+<tr>
+  <td> "webNavigation" </td>
+  <td> Required if the extension uses the
+       <a href="webNavigation.html">chrome.webNavigation</a> module. </td>
+</tr>
+<tr>
+  <td> "webRequest" </td>
+  <td> Required if the extension uses the
+       <a href="webRequest.html">chrome.webRequest</a> module. </td>
+</tr>
+<tr>
+  <td> "webRequestBlocking" </td>
+  <td> Required if the extension uses the
+       <a href="webRequest.html">chrome.webRequest</a> module in a blocking
+       fashion. </td>
+</tr>
+{{/is_apps}}
+</tr>
+</table>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/develop_apps.html b/chrome/common/extensions/docs/templates/articles/develop_apps.html
new file mode 100644
index 0000000..44541d9
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/develop_apps.html
@@ -0,0 +1,49 @@
+<h1>Before You Start</h1>
+
+
+<p>
+This documentation tells you how to write packaged apps.
+All developers, however, should know that the new APIs
+for packaged apps are being released as a developer preview.
+This means that they are evolving daily,
+and anything you read now might be different in the near future.
+Please keep up to date with the API reference and documentation.
+If you hit any stumbling blocks,
+feedback is welcome at
+<a href="http://groups.google.com/a/chromium.org/group/chromium-apps">#chromium-apps</a>
+</p>
+
+<p class="caution">
+<b>Note:</b>
+If you've written packaged apps before,
+your <a href="http://developer.chrome.com/trunk/extensions/apps.html">legacy packaged apps</a>
+will still work the way they always have,
+but they won't have access to the new APIs.
+</p>
+
+<h2 id="start">Where to start</h2>
+
+<p>
+The <a href="about_apps.html">Getting Started</a> guide is a great place to start.
+It's fast reading; shouldn't take more than 10 minutes to read all three docs.
+After the Getting Started guide,
+decide what's most relevant to you.
+The <a href="app_lifecycle.html">Fundamentals</a> guide covers
+the details of the app and data lifecycle,
+or learn more about good app design
+by reading <a href="app_frameworks.html">MVC Architecture</a>.
+We've also got lots of sample code in our repository
+that is linked to directly from the documentation.
+</p>
+
+<p>
+If you're familiar with the Chrome extension docs,
+then the Reference docs should seem familiar.
+Packaged apps and extensions share a common platform.
+They can access many of the same APIs,
+they have the same manifest and permissions format.
+Many of the reference docs are shared;
+we've filtered accessibility to docs that aren't shared.
+</p>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/devguide.html b/chrome/common/extensions/docs/templates/articles/devguide.html
new file mode 100644
index 0000000..ffcc7af
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/devguide.html
@@ -0,0 +1,143 @@
+<h1>Developer's Guide</h1>
+
+<p>
+These pages assume you've completed
+the <a href="getstarted.html">Getting Started</a> tutorial
+and <a href="overview.html">Overview</a>.
+</p>
+
+<table class="simple">
+  <tr>
+    <td colspan="2"><h4>Changing the Google Chrome chrome</h4></td>
+  </tr>
+  <tr>
+    <td colspan="2"> &nbsp;</td>
+  </tr>
+  <tr>
+    <td> <a href="browserAction.html">Browser&nbsp;Actions</a> </td>
+    <td> Add icons to the toolbar <em>(extensions only)</em> </td>
+  </tr>
+  <tr>
+    <td> <a href="notifications.html">Desktop&nbsp;Notifications</a> </td>
+    <td> Notify users of important events </td>
+  </tr>
+  <tr>
+    <td> <a href="omnibox.html">Omnibox</a> </td>
+    <td> Add a keyword to the address bar </td>
+  </tr>
+  <tr>
+    <td> <a href="options.html">Options&nbsp;Pages</a> </td>
+    <td> Let users customize your extension </td>
+  </tr>
+  <tr>
+    <td> <a href="override.html">Override&nbsp;Pages</a> </td>
+    <td> Implement your own version of standard browser pages
+         such as the New Tab page</td>
+  </tr>
+  <tr>
+    <td> <a href="pageAction.html">Page&nbsp;Actions</a> </td>
+    <td> Add temporary icons inside the address bar <em>(extensions only)</em> </td>
+  </tr>
+  <tr>
+    <td> <a href="themes.html">Themes</a> </td>
+    <td> Change the overall appearance of the browser </td>
+  </tr>
+
+  <tr>
+    <td colspan="2"><h4>Interacting with Google Chrome in other ways</h4></td>
+  </tr>
+  <tr>
+    <td colspan="2"> &nbsp;</td>
+  </tr>
+  <tr>
+    <td> <a href="bookmarks.html">Bookmarks</a> </td>
+    <td> Create, organize, and otherwise manipulate the user's bookmarks </td>
+  </tr>
+  <tr>
+    <td> <a href="cookies.html">Cookies</a> </td>
+    <td> Explore and modify the browser's cookie system </td>
+  </tr>
+  <tr>
+    <td> <a href="devtools.html">Developer&nbsp;Tools</a> </td>
+    <td> Add features to Chrome Developer Tools </td>
+  </tr>
+  <tr>
+    <td> <a href="events.html">Events</a> </td>
+    <td> Detect when something interesting happens </td>
+  </tr>
+  <tr>
+    <td> <a href="history.html">History</a> </td>
+    <td> Interact with the browser's record of visited pages </td>
+  </tr>
+  <tr>
+    <td> <a href="tabs.html">Tabs</a> </td>
+    <td> Create, modify, and rearrange tabs in the browser </td>
+  </tr>
+  <tr>
+    <td> <a href="windows.html">Windows</a> </td>
+    <td> Create, modify, and rearrange windows in the browser </td>
+  </tr>
+
+  <tr>
+    <td colspan="2"><h4>Implementing the innards of your extension</h4></td>
+  </tr>
+  <tr>
+    <td colspan="2"> &nbsp;</td>
+  </tr>
+  <tr>
+    <td> <a href="a11y.html">Accessibility (a11y)</a> </td>
+    <td> Make your extension accessible to people with disabilities </td>
+  </tr>
+  <tr>
+    <td> <a href="event_pages.html">Event Pages</a> </td>
+    <td> Put all the common code for your extension in a single place </td>
+  </tr>
+  <tr>
+    <td> <a href="content_scripts.html">Content&nbsp;Scripts</a> </td>
+    <td> Run JavaScript code in the context of web pages </td>
+  </tr>
+  <tr>
+    <td> <a href="xhr.html">Cross-Origin&nbsp;XHR</a> </td>
+    <td> Use XMLHttpRequest to send and receive data from remote servers </td>
+  </tr>
+  <tr>
+    <td> <a href="i18n.html">Internationalization</a> </td>
+    <td> Deal with language and locale </td>
+  </tr>
+  <tr>
+    <td> <a href="messaging.html">Message&nbsp;Passing</a> </td>
+    <td> Communicate from a content script to its parent extension,
+         or vice versa</td>
+  </tr>
+  <tr>
+    <td> <a href="permissions.html">Optional Permissions</a> </td>
+    <td> Modify your extension's permissions </td>
+  </tr>
+  <tr>
+    <td> <a href="npapi.html">NPAPI&nbsp;Plugins</a> </td>
+    <td> Load native binary code </td>
+  </tr>
+
+  <tr>
+    <td colspan="2"><h4>Finishing and distributing your extension</h4></td>
+  </tr>
+  <tr>
+    <td colspan="2"> &nbsp;</td>
+  </tr>
+  <tr>
+    <td> <a href="autoupdate.html">Autoupdating</a> </td>
+    <td> Update extensions automatically </td>
+  </tr>
+  <tr>
+    <td> <a href="hosting.html">Hosting</a> </td>
+    <td> Host extensions on Google servers or your own </td>
+  </tr>
+  <tr>
+    <td> <a href="external_extensions.html">Other Deployment Options</a> </td>
+    <td> Distribute extensions on your network or with other software </td>
+  </tr>
+  <tr>
+    <td> <a href="packaging.html">Packaging</a> </td>
+    <td> Create a <code>.crx</code> file so you can distribute your extension </td>
+  </tr>
+</table>
diff --git a/chrome/common/extensions/docs/templates/articles/devtools.html b/chrome/common/extensions/docs/templates/articles/devtools.html
new file mode 100644
index 0000000..7654cc5
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/devtools.html
@@ -0,0 +1,74 @@
+<h1>chrome.devtools.* APIs</h1>
+
+<p>
+The following API modules provide support for extending
+Chrome Developer Tools:
+</p>
+
+<a name="api-list"></a>
+<ul>
+  <li>
+  <a href="devtools.inspectedWindow.html">devtools.inspectedWindow</a></li><li>
+  <a href="devtools.network.html">devtools.network</a></li><li>
+  <a href="devtools.panels.html">devtools.panels</a></li>
+</ul>
+
+<h2 id="using">How to use DevTools APIs</h2>
+
+<ol>
+  <li>
+    Specify the "devtools_page" field in your extension's manifest:
+<pre>{
+  "name": ...
+  "version": "1.0",
+  "minimum_chrome_version": "10.0",
+  <b>"devtools_page": "devtools.html"</b>,
+  ...
+}
+</pre>
+  </li>
+  <li>
+    An instance of the devtools_page specified in your extension's manifest
+    will be created for every Developer Tools window opened. The page may add
+    other extension pages as panels and sidebars to the Developer Tools window
+    using <a href="devtools.panels.html">devtools.panels</a>
+    API.
+  </li>
+  <li>
+    The chrome.devtools.* API modules are available only to the
+    pages loaded within the Developer Tools window. Content scripts and other
+    extension pages do not have these APIs. Thus, the APIs are available only
+    through the lifetime of the Developer Tools window.
+  </li>
+  <li>The APIs available to extension pages within the Developer Tools
+    window include all <a href="#api-list">devtools modules
+    listed above</a> and <a href="extension.html">chrome.extension</a> API.
+    Other extension APIs are not available to the Developer Tools pages, but
+    you may invoke them by sending a request to the background page of your
+    extension, similarly to how it's done in the
+    <a href="overview.html#contentScripts">content scripts</a>.
+  </li><li>
+    There are also some Developer Tools APIs that are still experimental.
+    Please refer to <a href="experimental.html">chrome.experimental.* APIs</a>
+    for the list of experimental APIs and guidelines on how to use them.
+  </li>
+  <li>
+    <a href="http://groups.google.com/group/google-chrome-developer-tools/topics">Give us feedback!</a>
+    Your comments and suggestions help us improve the APIs.
+  </li>
+</ol>
+
+<h2 id="other">More information</h2>
+
+<p>
+For information on the standard APIs that extensions can use, see
+<a href="api_index.html">chrome.* APIs</a> and
+<a href="api_other.html">Other APIs</a>.
+</p>
+
+<h2 id="examples">Examples</h2>
+
+<p>
+You can find examples that use Developer Tools APIs in
+<a href="samples.html#devtools">Samples</a>.
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/docs.html b/chrome/common/extensions/docs/templates/articles/docs.html
new file mode 100644
index 0000000..75afbc8
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/docs.html
@@ -0,0 +1,135 @@
+<h1>Hello There!</h1>
+
+<p>
+This documentation tells you how to write extensions
+for the
+<a href="http://www.google.com/chrome">Google Chrome browser</a>.
+</p>
+
+<h2 id="readme"> Where to start </h2>
+
+<p>
+Before you code,
+read these:
+</p>
+
+<dl>
+  <dt> <a href="getstarted.html">Getting Started (Hello, World!)</a> </dt>
+  <dd> Build a simple "Hello, World" extension in about 5 minutes </dd>
+
+  <dt> <a href="overview.html">Overview</a> </dt>
+  <dd> Learn about the fundamental design points of the extension system </dd>
+</dl>
+
+<p>
+Also check out these:
+</p>
+
+<ul>
+  <li>
+    <a href="devguide.html">Developer's Guide</a>
+  </li>
+  <li>
+    <a href="samples.html">Samples</a>
+  </li>
+  <li>
+    <a href="http://stackoverflow.com/questions/tagged/google-chrome-extension">Stack Overflow [google-chrome-extension] tag</a>
+  </li>
+  <li>
+    <a href="http://groups.google.com/a/chromium.org/group/chromium-extensions">Group: chromium-extensions</a>
+  </li>
+  <li>
+    <a href="http://chrome.google.com/webstore">Chrome Web Store</a>
+  </li>
+  <li>
+    <a href="webstore.html">Chrome Web Store
+      developer documentation</a>
+  </li>
+</ul>
+
+<h2 id="versions"> Doc versions </h2>
+<p>
+In general, you should view these pages at
+<b>http://developer.chrome.com/extensions/<em>&lt;filename></em></b>
+(for example,
+<a href="/extensions/overview.html">http://developer.chrome.com/extensions/overview.html</a>).
+However, if you need to see the very latest doc
+or you're using a different version of Google Chrome
+than most of the world is
+(perhaps the <a href="http://dev.chromium.org/getting-involved/dev-channel">Dev channel</a>),
+you might want to use a different URL
+(for example,
+<a href="/dev/extensions/overview.html">.../<b>dev/</b>extensions/overview.html</a>).
+The following table lists the doc locations and explains how they differ.
+</p>
+
+<p>
+<table class="simple">
+  <tr>
+    <th> URL </th>   <th> Version </th>
+  </tr>
+  <tr>
+    <td>
+      <a href="/extensions/overview.html">.../extensions/...</a>
+    </td>
+    <td>
+      The version you should probably be using.
+      This documents the most stable version of the extension API.
+    </td>
+  </tr>
+  <tr>
+    <td>
+      <a href="/beta/extensions/overview.html">.../<b>beta/</b>extensions/...</a>
+    </td>
+    <td>
+      <p>
+      Documentation for the Beta channel version of Google Chrome.
+      </p>
+
+      <p>
+      <strong>Note:</strong>
+      APIs on the Beta channel are subject to change.
+      </p>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      <a href="/dev/extensions/overview.html">.../<b>dev/</b>extensions/...</a>
+    </td>
+    <td>
+      <p>
+      Documentation for the Dev channel version of Google Chrome.
+      This version might also have bug fixes and feature information
+      that are relevant to the current doc
+      but haven't been integrated into it yet.
+      </p>
+
+      <p>
+      <strong>Note:</strong>
+      APIs on the Dev channel are subject to change.
+      </p>
+    </td>
+  </tr>
+  <tr>
+    <td>
+      <a href="/trunk/extensions/overview.html">.../<b>trunk/</b>extensions/...</a>
+    </td>
+    <td>
+      The very latest documentation.
+      Look here if you're using
+      <a href="http://tools.google.com/dlpage/chromesxs">Canary</a>
+      or a tip-of-tree version of
+      <a href="http://dev.chromium.org">Chromium</a>
+      or if you're curious about features to come.
+      This version might also have bug fixes and feature information
+      that are relevant to the current doc
+      but haven't been integrated into it yet.
+
+      <p>
+      <strong>Note:</strong>
+      The trunk version of the doc is preliminary and might have errors.
+      </p>
+    </td>
+  </tr>
+</table>
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/event_pages.html b/chrome/common/extensions/docs/templates/articles/event_pages.html
new file mode 100644
index 0000000..3760ded
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/event_pages.html
@@ -0,0 +1,177 @@
+<h1>Event Pages</h1>
+
+
+<p>
+Event pages are very similar to
+<a href="background_pages.html">background pages</a>,
+with one important difference:
+event pages are loaded only when they are needed.
+When the event page is not actively doing something,
+it is unloaded, freeing memory and other system resources.
+</p>
+
+<p>
+Event pages are available in the stable channel as of Chrome 22, and the
+performance advantages are significant, especially on low-power devices. Please
+prefer them to persistent background pages whenever possible for new development
+and begin <a href="#transition">migrating existing background pages</a> to this
+new model.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+
+<p>
+Register your event page in the
+<a href="manifest.html">extension manifest</a>:
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"background": {
+    "scripts": ["eventPage.js"],
+    "persistent": false
+  }</b>,
+  ...
+}</pre>
+
+<p>
+Notice that without the "persistent" key, you have
+a regular background page. Persistence is what differentiates
+an event page from a background page.
+</p>
+
+<h2 id="lifetime">Lifetime</h2>
+
+<p>
+The event page is loaded when it is "needed", and unloaded
+when it goes idle again. Here are some examples of things
+that will cause the event page to load:
+</p>
+<ul>
+<li>The extension is first installed or is updated to a new version
+(in order to <a href="#registration">register for events</a>).
+<li>The event page was listening for an event, and the event is dispatched.
+<li>A content script or other extension
+<a href="messaging.html">sends a message.</a>
+<li>Another view in the extension (for example, a popup) calls
+<code><a href="runtime.html#method-getBackgroundPage">chrome.runtime.getBackgroundPage()</a></code>.
+</ul>
+
+<p>
+Once it has been loaded, the event page will stay running
+as long as it is active (for example, calling an extension
+API or issuing a network request). Additionally, the
+event page will not unload until all visible views (for example,
+popup windows) are closed and all message ports are closed.
+</p>
+
+<p>
+You can observe the lifetime of your event page by clicking
+on "View Background Pages" in Chrome's Wrench menu, or by
+opening Chrome's task manager. You can see when your event
+page loads and unloads by observing when an entry for your
+extension appears in the list of processes.
+</p>
+
+<p>
+Once the event page has been idle a short time
+(a few seconds), the
+<code><a href="runtime.html#event-onSuspend">chrome.runtime.onSuspend</a></code>
+event is dispatched. The event page has a few more seconds to handle this
+event before it is forcibly unloaded. Note that once the event is dispatched,
+new activity will not keep the event page open.
+</p>
+
+<h2 id="registration">Event registration</h2>
+
+<p>
+Chrome keeps track of events that an extension has added listeners
+for. When it dispatches such an event, the extension's event page is
+loaded. Conversely, if the extension removes all of its listeners for
+an event by calling <code>removeListener</code>, Chrome will no longer
+load its event page for that event.
+</p>
+
+<p>
+Because the listeners themselves only exist in the context of the
+event page, you must use <code>addListener</code> each time the event
+page loads; only doing so at
+<code><a href="runtime.html#event-onInstalled">chrome.runtime.onInstalled</a></code>
+by itself is insufficient.
+</p>
+
+<p>
+For an example of event registration in action, you can view the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/gmail/">Google Mail
+Checker</a> extension.
+</p>
+
+<h2 id="transition">Convert background page to event page</h2>
+<p>
+Follow this checklist to convert your extension's
+(persistent) background page to an event page.
+
+<ol>
+  <li>Add <code>"persistent": false</code> to your manifest as shown above.
+
+  <li>Register to receive any events your extension is interested in
+  each time the event page is loaded. The event page will be loaded once
+  for each new version of your extension. After that it will only be
+  loaded to deliver events you have registered for.
+
+  <li>If you need to do some initialization when your extension is
+  installed or upgraded, listen to the
+  <code><a href="runtime.html#event-onInstalled">chrome.runtime.onInstalled</a></code>
+  event. If you need to do some initialization each time the browser starts,
+  listen to the
+  <code><a href="runtime.html#event-onBrowserStartup">chrome.runtime.onBrowserStartup</a></code>
+  event; however, you should (in almost all cases) prefer
+  the <code>chrome.runtime.onInstalled</code> event.
+
+  <li>If you need to keep runtime state in memory throughout a browser
+  session, use the <a href="storage.html">storage API</a> or
+  IndexedDB. Since the event page does not stay loaded for long, you
+  can no longer rely on global variables for runtime state.
+
+  <li>Use <a href="events.html#filtered">event filters</a> to restrict
+  your event notifications to the cases you care about. For example, if
+  you listen to the <code><a href="tabs.html#event-onUpdated">tabs.onUpdated</a></code>
+  event, try using the
+  <code><a href="webNavigation.html#event-onCompleted">webNavigation.onComplete</a></code>
+  event with filters instead (the tabs API does not support filters).
+  That way, your event page will only be loaded for events that
+  interest you.
+
+  <li>Listen to the
+  <code><a href="runtime.html#event-onSuspend">chrome.runtime.onSuspend</a></code>
+  event if you need to do last second cleanup before your event page
+  is shut down. However, we recommend persisting periodically instead.
+  That way if your extension crashes without receiving
+  <code>onSuspend</code>, no data will typically be lost.
+
+  <li>If your extension uses <code>window.setTimeout()</code> or
+  <code>window.setInterval()</code>, switch to using the
+  <a href="alarms.html">alarms API</a> instead. DOM-based timers won't
+  be honored if the event page shuts down.
+
+  <li>If your extension uses,
+  <code><a href="extension.html#method-getBackgroundPage">chrome.extension.getBackgroundPage()</a></code>,
+  switch to
+  <code><a href="runtime.html#method-getBackgroundPage">chrome.runtime.getBackgroundPage()</a></code>
+  instead. The newer method is asynchronous so that it can start the event
+  page if necessary before returning it.
+
+  <li>If you're using <a href="messaging.html">message passing</a>, be sure
+  to close unused message ports. The event page will not shut down until all
+  message ports are closed.
+
+  <li>If you're using the <a href="contextMenus.html">context menus</a> API,
+  pass a string <code>id</code> parameter to
+  <code><a href="contextMenus.html#method-create">chrome.contextMenus.create</a></code>,
+  and use the
+  <code><a href="contextMenus.html#event-onClicked">chrome.contextMenus.onClicked</a></code>
+  callback instead of an <code>onclick</code> parameter to
+  <code><a href="contextMenus.html#method-create">chrome.contextMenus.create</a></code>.
+</ol>
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/experimental.html b/chrome/common/extensions/docs/templates/articles/experimental.html
new file mode 100644
index 0000000..1abf6fa
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental.html
@@ -0,0 +1,140 @@
+<h1 class="page_title">chrome.experimental.* APIs</h1>
+{{^is_apps}}
+<div class="doc-family extensions">
+<p>
+Before you start, <strong>choose the right version of this page.</strong>
+You should read either:
+</p>
+
+<ul>
+  <li> <a href="http://code.google.com/chrome/extensions/trunk/experimental.html">Most recent experimental APIs</a>
+  <li> <a href="http://code.google.com/chrome/extensions/dev/experimental.html">Dev channel experimental APIs</a></li>
+</ul>
+
+<p>
+For information about browser versions such as
+Canary (bleeding edge) and the Dev channel, see
+<a href="http://www.chromium.org/getting-involved/dev-channel">Chrome Release Channels</a>.
+For details about channel-specific docs,
+see <a href="docs.html#versions">Doc versions</a>.
+</p>
+</div>
+{{/is_apps}}
+
+<h2 id="overview">
+List of APIs
+</h2>
+
+{{^is_apps}}
+<p class="doc-family extensions">
+We'd like your <a href="http://groups.google.com/a/chromium.org/group/chromium-extensions/topics">feedback</a>
+on the following experimental APIs:
+</p>
+{{/is_apps}}
+
+<ul>
+  {{?is_apps}}
+  {{#api_list.apps.experimental}}
+  <li><a href="{{name}}.html">{{name}}</a></li>
+  {{/}}
+  {{:is_apps}}
+  {{#api_list.extensions.experimental}}
+  <li><a href="{{name}}.html">{{name}}</a></li>
+  {{/}}
+  {{/is_apps}}
+</ul>
+
+{{^is_apps}}
+<p class="doc-family extensions">
+Pay special attention to the following APIs,
+which we expect to finalize soon:
+<b>devtools</b>,
+<b>infobars</b>,
+<b>permissions</b>,
+For examples of using the experimental APIs, see
+<a href="samples.html#experimental">Samples</a>.
+</p>
+{{/is_apps}}
+
+<p class="warning">
+<b>Caution:</b>
+Don't depend on these experimental APIs.
+They might disappear,
+and they <em>will</em> change.
+Also, the Chrome Web Store doesn't allow you to
+upload items that use experimental APIs.
+</p>
+
+
+<h2 id="using">How to use experimental APIs</h2>
+
+<ol>
+  {{^is_apps}}
+  <li class="doc-family extensions">
+    Make sure you're using either
+    <a href="http://tools.google.com/dlpage/chromesxs">Canary</a>
+    (which you can use at the same time as other Chrome channels) or the
+<a href="http://www.chromium.org/getting-involved/dev-channel">Dev channel</a>.
+    Although the experimental APIs might work in other versions,
+    we need your feedback on the latest incarnation of the APIs,
+    which you can find in Canary and on the Dev channel.
+  </li>
+  <li class="doc-family extensions">
+    Using either the
+    <a href="http://code.google.com/chrome/extensions/trunk/experimental.html">most recent API documentation</a> (if you're using Canary) or the
+    <a href="http://code.google.com/chrome/extensions/dev/experimental.html">API documentation for the Dev channel</a>,
+    write the code for your extension.
+  </li>
+  {{/is_apps}}
+  <li>
+    Specify the "experimental"
+    <a href="manifest.html#permissions">permission</a>
+    in your manifest, like this:
+<pre>
+"permissions": [
+  <b>"experimental"</b>,
+  ...
+],
+</pre>
+  </li>
+  <li>
+    Enable the experimental API in your browser.
+    You can do this in either of two ways:
+    <ul>
+      <li> Go to <b>chrome://flags</b>,
+        find "Experimental Extension APIs",
+        click its "Enable" link,
+        and restart Chrome.
+        From now on,
+        unless you return to that page and disable experimental APIs,
+        you'll be able to run extensions and apps that use experimental APIs.
+      </li>
+      <li> Specify the <b>--enable-experimental-extension-apis</b> flag
+        each time you launch the browser.
+        On Windows, you can do this by modifying
+        the properties of the shortcut that you use to launch Google Chrome.
+        For example:
+
+<pre>
+<em>path_to_chrome.exe</em> <b>--enable-experimental-extension-apis</b></pre>
+      </li>
+    </ul>
+  </li>
+
+  {{^is_apps}}
+  <li class="doc-family extension">
+    <a href="http://groups.google.com/a/chromium.org/group/chromium-extensions/topics">Give us feedback!</a>
+    Your comments and suggestions help us
+    improve the APIs and decide
+    which ones should move from experimental to supported.
+  </li>
+  {{/is_apps}}
+</ol>
+
+<h2 id="other">More APIs</h2>
+
+<p>
+For information on the standard APIs, see
+<a href="api_index.html">chrome.* APIs</a> and
+<a href="api_other.html">Other APIs</a>.
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_browsingData.html b/chrome/common/extensions/docs/templates/articles/experimental_browsingData.html
new file mode 100644
index 0000000..26bd199
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_browsingData.html
@@ -0,0 +1,10 @@
+<h1>experimental.browsingData</h1>
+
+<p>
+The <code>BrowsingData</code> API is no longer experimental;
+it's supported! You can read all about it at its new home:
+</p>
+
+<blockquote>
+<a href="browsingData.html">chrome.browsingData</a>
+</blockquote>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_contentSettings.html b/chrome/common/extensions/docs/templates/articles/experimental_contentSettings.html
new file mode 100644
index 0000000..e89889b
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_contentSettings.html
@@ -0,0 +1,11 @@
+<h1>experimental.chrome.contentSettings</h1>
+
+<p>
+The <code>contentSettings</code> API is no longer experimental;
+it's supported!
+You can read all about it at its new home:
+</p>
+
+<blockquote>
+<a href="contentSettings.html">chrome.contentSettings</a>
+</blockquote>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_contextMenus.html b/chrome/common/extensions/docs/templates/articles/experimental_contextMenus.html
new file mode 100644
index 0000000..cc4f5c6
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_contextMenus.html
@@ -0,0 +1,11 @@
+<h1>experimental.chrome.contextMenus</h1>
+
+<p>
+The <code>contextMenus</code> API is no longer experimental;
+it's supported!
+You can read all about it at its new home:
+</p>
+
+<blockquote>
+<a href="contextMenus.html">chrome.contextMenus</a>
+</blockquote>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_cookies.html b/chrome/common/extensions/docs/templates/articles/experimental_cookies.html
new file mode 100644
index 0000000..3e060bd
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_cookies.html
@@ -0,0 +1,11 @@
+<h1>experimental.chrome.cookies</h1>
+
+<p>
+The <code>cookies</code> API is no longer experimental;
+it's supported!
+You can read all about it at its new home:
+</p>
+
+<blockquote>
+<a href="cookies.html">chrome.cookies</a>
+</blockquote>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_debugger.html b/chrome/common/extensions/docs/templates/articles/experimental_debugger.html
new file mode 100644
index 0000000..8a06e06
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_debugger.html
@@ -0,0 +1,10 @@
+<h1>experimental.debugger</h1>
+
+<p>
+The <code>Debugger</code> API is no longer experimental;
+it's supported! You can read all about it at its new home:
+</p>
+
+<blockquote>
+<a href="debugger.html">chrome.debugger</a>
+</blockquote>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_devtools.html b/chrome/common/extensions/docs/templates/articles/experimental_devtools.html
new file mode 100644
index 0000000..5f6cf1f
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_devtools.html
@@ -0,0 +1,8 @@
+<h1>chrome.experimental.devtools.* APIs</h1>
+
+<p>
+Some of the Developer Tools APIs are no longer experimental. Please see <a
+href="devtools.html">chrome.devtools.* APIs</a> for the guidelines on how to use
+these. For the list of Developer Tools APIs that are still experimental,
+please refer to <a href="experimental.html">chrome.experimental.* APIs</a>.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_devtools_inspectedWindow.html b/chrome/common/extensions/docs/templates/articles/experimental_devtools_inspectedWindow.html
new file mode 100644
index 0000000..b6ee688
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_devtools_inspectedWindow.html
@@ -0,0 +1,10 @@
+<h1>experimental.devtools.inspectedWindow</h1>
+
+<p>
+The <code>devtools.inspectedWindow</code> API is no longer experimental;
+it's supported! You can read all about it at its new home:
+</p>
+
+<blockquote>
+<a href="devtools.inspectedWindow.html">chrome.devtools.inspectedWindow</a>
+</blockquote>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_devtools_network.html b/chrome/common/extensions/docs/templates/articles/experimental_devtools_network.html
new file mode 100644
index 0000000..b8ff5f9
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_devtools_network.html
@@ -0,0 +1,10 @@
+<h1>experimental.devtools.network</h1>
+
+<p>
+The <code>devtools.network</code> API is no longer experimental;
+it's supported! You can read all about it at its new home:
+</p>
+
+<blockquote>
+<a href="devtools.network.html">chrome.devtools.network</a>
+</blockquote>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_devtools_panels.html b/chrome/common/extensions/docs/templates/articles/experimental_devtools_panels.html
new file mode 100644
index 0000000..38fbe1e
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_devtools_panels.html
@@ -0,0 +1,10 @@
+<h1>experimental.devtools.panels</h1>
+
+<p>
+The <code>devtools.panels</code> API is no longer experimental;
+it's supported! You can read all about it at its new home:
+</p>
+
+<blockquote>
+<a href="devtools.panels.html">chrome.devtools.panels</a>
+</blockquote>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_devtools_resources.html b/chrome/common/extensions/docs/templates/articles/experimental_devtools_resources.html
new file mode 100644
index 0000000..b8ad01a
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_devtools_resources.html
@@ -0,0 +1,7 @@
+<h1>chrome.experimental.devtools.resources
+API</h1>
+<p>
+The <code>experimental.devtools.resources</code> module is deprecated, use
+<a href="experimental.devtools.network.html"
+><code>experimental.devtools.network</code></a> instead.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_history.html b/chrome/common/extensions/docs/templates/articles/experimental_history.html
new file mode 100644
index 0000000..5de0989
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_history.html
@@ -0,0 +1,11 @@
+<h1>experimental.chrome.history</h1>
+
+<p>
+The <code>history</code> API is no longer experimental;
+it's supported!
+You can read all about it at its new home:
+</p>
+
+<blockquote>
+<a href="history.html">chrome.history</a>
+</blockquote>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_inputUI.html b/chrome/common/extensions/docs/templates/articles/experimental_inputUI.html
new file mode 100644
index 0000000..c8cd81c
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_inputUI.html
@@ -0,0 +1,8 @@
+<h1>InputUI API</h1>
+
+
+<p id="classSummary">
+Use the <code>chrome.experimental.input.ui</code> module to implement
+input method user interface. This module is for Chrome OS only.
+<a href="experimental.html">chrome.experimental.* APIs</a> page.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_privacy.html b/chrome/common/extensions/docs/templates/articles/experimental_privacy.html
new file mode 100644
index 0000000..09b080b
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_privacy.html
@@ -0,0 +1,10 @@
+<h1>experimental.privacy</h1>
+
+<p>
+The <code>Privacy</code> API is no longer experimental;
+it's supported! You can read all about it at its new home:
+</p>
+
+<blockquote>
+<a href="privacy.html">chrome.privacy</a>
+</blockquote>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_storage.html b/chrome/common/extensions/docs/templates/articles/experimental_storage.html
new file mode 100644
index 0000000..f85aa4b
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_storage.html
@@ -0,0 +1,10 @@
+<h1>experimental.storage</h1>
+
+<p>
+The <code>Storage</code> API is no longer experimental;
+it's supported! You can read all about it at its new home:
+</p>
+
+<blockquote>
+<a href="storage.html">chrome.storage</a>
+</blockquote>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_webInspector.html b/chrome/common/extensions/docs/templates/articles/experimental_webInspector.html
new file mode 100644
index 0000000..0fe405f
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_webInspector.html
@@ -0,0 +1,9 @@
+<h1>experimental.webInspector.* APIs</h1>
+
+<p>
+<code>experimental.webInspector.*</code> APIs have been renamed to
+<a href="experimental.devtools.html"><code>chrome.experimental.devtools.*</code>
+</a>.
+The old namepsace is deprecated and will be removed from future versions of
+Chrome.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_webInspector_audits.html b/chrome/common/extensions/docs/templates/articles/experimental_webInspector_audits.html
new file mode 100644
index 0000000..85a42bc
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_webInspector_audits.html
@@ -0,0 +1,8 @@
+<h1>experimental.webInspector.audits
+API</h1>
+<p>The <code>experimental.webInspector.audits</code> API has been renamed to
+<a href="experimental.devtools.audits.html"><code
+>chrome.experimental.devtools.audits</code></a>.
+The old namepsace is deprecated and will be removed from future versions of
+Chrome.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_webInspector_panels.html b/chrome/common/extensions/docs/templates/articles/experimental_webInspector_panels.html
new file mode 100644
index 0000000..6f6ceb8
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_webInspector_panels.html
@@ -0,0 +1,8 @@
+<h1>experimental.webInspector.panels
+API</h1>
+<p>The <code>experimental.webInspector.panels</code> API has been renamed to
+<a href="experimental.devtools.panels.html"><code
+>chrome.experimental.devtools.panels</code></a>.
+The old namepsace is deprecated and will be removed from future versions of
+Chrome.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_webInspector_resources.html b/chrome/common/extensions/docs/templates/articles/experimental_webInspector_resources.html
new file mode 100644
index 0000000..b4be42d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_webInspector_resources.html
@@ -0,0 +1,8 @@
+<h1>experimental.webInspector.resources
+API</h1>
+<p>The <code>experimental.webInspector.resources</code> API has been renamed to
+<a href="experimental.devtools.resources.html"><code
+>chrome.experimental.devtools.resources</code></a>.
+The old namepsace is deprecated and will be removed from future versions of
+Chrome.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/experimental_webRequest.html b/chrome/common/extensions/docs/templates/articles/experimental_webRequest.html
new file mode 100644
index 0000000..be95f8b
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/experimental_webRequest.html
@@ -0,0 +1,15 @@
+<h1>experimental.webRequest</h1>
+
+<p>
+The <code>WebRequest</code> API is no longer experimental;
+it's supported! You can read all about it at its new home:
+</p>
+
+<blockquote>
+<a href="webRequest.html">chrome.webRequest</a>
+</blockquote>
+
+<p>
+What you see below are early outcomes of the plan to implement a declarative
+version of the WebRequest API. Please ignore this until we give notice.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/external_extensions.html b/chrome/common/extensions/docs/templates/articles/external_extensions.html
new file mode 100644
index 0000000..c25bf74
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/external_extensions.html
@@ -0,0 +1,314 @@
+<h1>Other Deployment Options</h1>
+
+
+<p>
+Usually, users install their own extensions.
+But sometimes you might want an extension
+to be installed automatically.
+Here are two typical cases:
+</p>
+
+<ul>
+  <li>
+    An extension is associated with some other software,
+    and the extension should be installed
+    whenever the user installs that other software.
+    The extension could also be uninstalled
+    when the user removes that other software.
+  </li>
+  <li>
+    A network admin wants to install the same extensions
+    throughout the company.
+  </li>
+</ul>
+
+<p>
+An extension that's installed automatically is known as an
+<em>external extension</em>.
+Google Chrome supports two ways of
+installing external extensions:
+</p>
+
+<ul>
+  <li> Using a preferences JSON file </li>
+  <li> Using the Windows registry (Windows only) </li>
+</ul>
+
+<p>
+Both ways support installing an extension from a <code>.crx</code> extension
+file on the user's computer.  The preferences JSON file also supports installing
+an extension hosted at an
+<a href="autoupdate.html#H2-1">update URL</a>.
+See <a href="hosting.html">hosting</a> for details on hosting an extension.
+</p>
+
+<h2 id="prereqs">Before you begin</h2>
+
+<p>
+First, package a
+<a href="packaging.html"><code>.crx</code> file</a>
+and make sure that it installs successfully.
+</p>
+<p>
+If you wish to install from an
+ <a href="autoupdate.html#H2-1">update URL</a>, ensure that the extension
+is properly <a href="hosting.html">hosted</a>.
+</p>
+
+<p>
+Then, before you edit the preferences file or the registry,
+make a note of the following:
+</p>
+
+<ul>
+ <li> The intended <b>location</b> of the extension's <code>.crx</code> file,
+        or the update URL from which it is served </li>
+ <li> The extension's <b>version</b>
+   (from the manifest file or the <b>chrome://extensions</b> page) </li>
+ <li> The extension's <b>ID</b>
+   (from the <b>chrome://extensions</b> page
+   when you've loaded the packed extension) </li>
+</ul>
+
+<p>
+The following examples assume the version is <code>1.0</code>
+and the ID is <code>aaaaaaaaaabbbbbbbbbbcccccccccc</code>.
+</p>
+
+<h2 id="preferences">Using a preferences file</h2>
+
+<p class="note">
+<b>Windows note:</b>
+Until <a href="http://crbug.com/41902">bug 41902</a> is fixed,
+you might want to use the <a href="#registry">Windows registry</a>
+instead of the preferences file.
+</p>
+
+<p class="note">
+<b>Note:</b>
+Previous versions of Google Chrome used an
+<code>external_extensions.json</code> file to specify which extensions to
+install. This file has been deprecated in favor of individual <code>.json</code>
+files, one per extension.
+</p>
+
+<ol>
+<li>If you are installing from a file, make the <code>.crx</code> extension
+file available to the machine you want to install the extension on.
+(Copy it to a local directory or to a network share for example,
+<code>\\server\share\extension.crx</code>
+or <code>/home/share/extension.crx</code>.)
+</li>
+
+<li>Create a file with the following name in one of the folders listed below:
+  <code>aaaaaaaaaabbbbbbbbbbcccccccccc.json</code> where the file name (without the extension)
+  corresponds to your extension's ID.
+  The location depends on the operating system.
+  <dl>
+  <dt> Windows: </dt>
+    <dd> <code><em>chrome_root</em>\Application\<em>chrome_version</em>\Extensions\</code>
+    <br />
+    Example: <code>c:\Users\Me\AppData\Local\Google\Chrome\Application\6.0.422.0\Extensions\</code>
+    </dd>
+  <dt> Mac OS X:</dt>
+    <dd>For a specific user: <code>~USERNAME/Library/Application Support/Google/Chrome/External Extensions/</code><br>
+        For all users: <code>/Library/Application Support/Google/Chrome/External Extensions/</code>
+    <p>The external extension file for all users is read only if every directory in the path is owned by the user <code>root</code>, has the group <code>admin</code> or <code>wheel</code>, and is not world writable.  The path must also be free of symbolic links.  These restrictions prevent an unprivileged user from causing extensions to be installed for all users.  See <a href="#troubleshooting">troubleshooting</a> for details.</p>
+    <p class="note">
+    <b>Note:</b> The above path for all users was added in Chrome 16.  Prior versions used a different path:<br/>
+     <code>/Applications/Google Chrome.app/Contents/Extensions/</code>
+     This path was deprecated in version 17.  Support was removed in version 20.  Use one of the paths above instead.</p>
+  </dd>
+
+  <dt> Linux: </dt>
+    <dd> <code>/opt/google/chrome/extensions/</code> <br>
+    </dd>
+    <dd> <code>/usr/share/google-chrome/extensions/</code> <br>
+    <b>Note:</b> Use <code>chmod</code> if necessary
+    to make sure that the <code>aaaaaaaaaabbbbbbbbbbcccccccccc.json</code> files
+    are world-readable.
+    </dd>
+  </dl>
+</li>
+
+<li>If you are installing from a file, specify the extension's location and version with fields
+named "external_crx" and "external_version" in the file created above.
+<p>
+Example:
+<pre>
+  {
+    "external_crx": "/home/share/extension.crx",
+    "external_version": "1.0"
+  }
+</pre>
+</p>
+<p class="note">
+<b>Note:</b>
+You need to escape
+each <code>\</code> character in the location.
+For example,
+<code>\\server\share\extension.crx</code> would be
+<code>"\\\\server\\share\\extension.crx"</code>.
+</p>
+<p>
+<p>
+If you are installing from an update URL, specify the extension's update URL
+with field name "external_update_url".
+</p>
+Example:
+<pre>{
+  "external_update_url": "http://myhost.com/mytestextension/updates.xml"
+}</pre>
+<p>
+If you would like to install extension only for some browser locales,
+you can list supported locales in field name "supported_locale". Locale may
+specify parent locale like "en", in this case the extension will be
+installed for all English locales like "en-US", "en-GB", etc.
+If another browser locale is selected that is not supported by the extension,
+the external extensions will be uninstalled. If "supported_locales" list
+is missing, the extension will be installed for any locale.
+</p>
+Example:
+<pre>{
+  "external_update_url": "http://myhost.com/mytestextension/updates.xml",
+  "supported_locales": [ "en", "fr", "de" ]
+}</pre>
+</li>
+<li>Save the JSON file. </li>
+<li>Launch Google Chrome and go to <b>chrome://extensions</b>;
+you should see the extension listed. </li>
+</ol>
+
+<h3 id="troubleshooting">Troubleshooting Mac OS permissions problems</h3>
+
+<p>On Mac OS, the external extensions files for all users are only read if file system permissions prevent unprivileged users from changing it.  If you do not see external extensions installed when Chrome is launched, there may be a permissions problem with the external extensions preferences files.  To see if this is the problem, follow these steps:</p>
+
+<ol>
+  <li> Launch the Console program.  You can find it under /Applications/Utilities/Console. </li>
+  <li> If the leftmost icon in the Console says "Show Log List", click that icon.  A second column appears at the left. </li>
+  <li> Click "Console Messages" in the left pane. </li>
+  <li> Search for the string <b>Can not read external extensions</b>.  If there is a problem reading the external extensions files, you will see an error message.  Look for another error message directly above it, which should explain the issue.  For example, if you see the following error:
+     "Path /Library/Application Support/Google/Chrome is owned by the wrong group", you need to use <code>chgrp</code> or the Finder's Get Info dialog to change the directory's group owner to the Administrator group.</li>
+  <li> After fixing the issue, relaunch Chrome.  Test that the external extension is now installed.  It is possible that one permissions error keeps Chrome from detecting a second error.  If the external extension was not installed, repeat these steps until you do not see an error in the Console application.
+</ol>
+
+<h2 id="registry">Using the Windows registry</h2>
+
+<ol>
+<li>Make the <code>.crx</code> extension file available
+to the machine you want to install the extension on.
+(Copy it to a local directory or to a network share &mdash;
+for example, <code>\\server\share\extension.crx</code>.)
+</li>
+<li>Find or create the following key in the
+registry:
+<ul>
+  <li> 32-bit Windows: <code>HKEY_LOCAL_MACHINE\Software\Google\Chrome\Extensions</code> </li>
+  <li> 64-bit Windows: <code>HKEY_LOCAL_MACHINE\Software\Wow6432Node\Google\Chrome\Extensions</code> </li>
+</ul>
+</li>
+
+<li>Create a new key (folder)
+under the <b>Extensions</b> key with the
+same name as the ID of your extension
+(for example, <code>aaaaaaaaaabbbbbbbbbbcccccccccc</code>).
+</li>
+<li>Create two string values (<code>REG_SZ</code>) named "path" and "version",
+  and set them to the extension's location and version.
+  For example:
+<ul>
+  <li>path: <code>\\server\share\extension.crx</code> </li>
+  <li>version: <code>1.0</code> </li>
+</ul>
+</li>
+<li>Launch the browser and go to
+<b>chrome://extensions</b>; you should
+see the extension listed. </li>
+</ol>
+
+<h2 id="updating">Updating and uninstalling</h2>
+
+<p>Google Chrome scans the metadata entries
+in the preferences and registry
+each time the browser starts, and makes
+any necessary changes to the installed
+external extensions. </p>
+
+<p>To update your extension to a new version,
+update the file, and then update the version
+in the preferences or registry. </p>
+
+<p>To uninstall your extension
+(for example, if your software is uninstalled),
+remove your preference file (aaaaaaaaaabbbbbbbbbbcccccccccc.json)
+or the metadata from the registry. </p>
+
+<h2 id="faq">FAQ</h2>
+
+<p>
+This section answers common questions about external extensions.
+</p>
+
+<br>
+
+<p><b>Can I specify a URL as a path to the external extension?</b> </p>
+<p>Yes, if you use a <a href="#preferences">preferences JSON</a> file. The
+extension must be hosted as explained in <a href="hosting.html">hosting</a>.
+Use the "external_update_url" property to point to an
+<a href="autoupdate.html#H2-2">update manifest</a> that has the URL for your
+extension.</p>
+
+<br>
+
+<p><b>What are some common mistakes when installing with the preferences
+file?</b></p>
+<ul>
+  <li>
+    Not specifying the same id/version
+    as the one listed in the <code>.crx</code> </li>
+  <li>
+    The .json file (<code>aaaaaaaaaabbbbbbbbbbcccccccccc.json</code>) is in
+    the wrong location or the ID specified does not match the extension ID.
+  <li>
+    Syntax error in JSON file
+    (forgetting to separate entries with comma or
+    leaving a trailing comma somewhere) </li>
+  <li>
+    JSON file entry points to the wrong path
+    to the <code>.crx</code> (or path specified but no filename) </li>
+  <li>
+    Backslashes in UNC path not escaped
+    (for example, <code>"\\server\share\file"</code> is wrong;
+    it should be <code>"\\\\server\\share\\extension"</code>) </li>
+  <li>
+    Permissions problems on a network share </li>
+</ul>
+
+<br>
+
+<p><b>What are some common mistakes when installing with the registry?</b> </p>
+<ul>
+  <li>Not specifying the same id/version
+    as the one listed in the <code>.crx</code> </li>
+  <li>Key created in the wrong location in the registry </li>
+  <li>Registry entry points to the wrong path to the <code>.crx</code> file
+    (or path specified but no filename) </li>
+  <li>Permissions problems on a network share </li>
+</ul>
+
+<br>
+
+<p><b>What if the user uninstalls the extension?</b> </p>
+<p>If the user uninstalls the extension through the UI, it will no
+longer be installed or updated on each startup. In other words, the
+external extension is blacklisted. </p>
+
+<br>
+
+<p><b>How do I get off the blacklist?</b> </p>
+<p>If the user uninstalls your extension, you should respect that
+decision. However, if you (the developer) accidentally uninstalled
+your extension through the UI,
+you can remove the blacklist tag
+by installing the extension normally
+through the UI, and then uninstalling it. </p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/faq.html b/chrome/common/extensions/docs/templates/articles/faq.html
new file mode 100644
index 0000000..fb6ae71
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/faq.html
@@ -0,0 +1,477 @@
+<h1>Frequently Asked Questions</h1>
+
+
+<!--  -->
+
+<p>
+If you don't find an answer to your question here,
+try the
+<a href="http://code.google.com/chrome/webstore/faq.html">Chrome Web Store FAQ</a>, the
+<a href="http://stackoverflow.com/questions/tagged/google-chrome-extension">[google-chrome-extension] tag on Stack Overflow</a>, the
+<a href="http://groups.google.com/a/chromium.org/group/chromium-extensions">group</a>, or the
+<a href="http://www.google.com/support/chrome_webstore/">store help</a>.
+</p>
+
+<div id="faq-TOC">
+  <h4 id="general">General</h4>
+  <ul>
+    <li><a href="#faq-gen-01">What are Google Chrome Extensions?</a></li>
+    <li><a href="#faq-dev-01">How can I set up Chrome for extension development?</a></li>
+    <li><a href="#faq-gen-02">What technologies are used to write extensions for Chrome?</a></li>
+    <li><a href="#faq-gen-03">Are extensions fetched from the web every time the browser is loaded?</a></li>
+    <li><a href="#faq-dev-14">How do I determine which version of Chrome is deployed to which channel?</a></li>
+  </ul>
+  <h4 id="capabilities">Capabilities</h4>
+  <ul>
+    <li><a href="#faq-dev-02">Can extensions make cross-domain Ajax requests?</a></li>
+    <li><a href="#faq-dev-03">Can extensions use 3rd party web services?</a></li>
+    <li><a href="#faq-dev-07">Can extensions encode/decode JSON data?</a></li>
+    <li><a href="#faq-dev-08">Can extensions store data locally?</a></li>
+    <li><a href="#faq-dev-04">Can extensions use OAuth?</a></li>
+    <li><a href="#faq-dev-06">Can extensions load DLLs?</a></li>
+    <li><a href="#faq-dev-05">Can extensions create UI outside of the rendered web page?</a></li>
+    <li><a href="#faq-interact-chrome">Can extensions listen to clicks on Chrome tabs and navigation buttons?</a>
+    <li><a href="#faq-dev-11">Can two extensions communicate with each other?</a></li>
+    <li><a href="#faq-dev-13">Can extensions use Google Analytics?</a></li>
+    <li><a href="#faq-dev-15">Can extensions modify chrome:// URLs?</a></li>
+    <li><a href="#faq-open-popups">Can extensions open browser/page action popups without user interaction?</a></li>
+    <li><a href="#faq-persist-popups">Can extensions keep popups open after the user clicks away from them?</a></li>
+    <li><a href="#faq-lifecycle-events">Can extensions be notified when they are installed/uninstalled?</a></li>
+  </ul>
+  <h4 id="development">Development</h4>
+  <ul>
+    <li><a href="#faq-building-ui">How do I build a UI for my extension?</a>
+    <li><a href="#faq-dev-09">How much data can I store in localStorage?</a></li>
+    <li><a href="#faq-dev-10">How do I create an options menu for my application?</a></li>
+    <li><a href="#faq-dev-12">What debugging tools are available to extension developers?</a></li>
+    <li><a href="#faq-dev-16">Why do wildcard matches not work for top level domains (TLDs)?</a></li>
+    <li><a href="#faq-management">Why does the management API not fire events when my extension is installed/uninstalled?</a></li>
+    <li><a href="#faq-firstrun">How can an extension determine whether it is running for the first time?</a></li>
+  </ul>
+  <h4 id="features">Features and bugs</h4>
+  <ul>
+    <li><a href="#faq-fea-01">I think I've found a bug! How do I make sure it gets fixed?</a></li>
+    <li><a href="#faq-fea-02">I have a feature request! How can I report it?</a></li>
+  </ul>
+</div>
+
+<h2 id="general2">General</h2>
+
+<h3 id="faq-gen-01">What are Google Chrome Extensions?</h3>
+<p>
+  Google Chrome Extensions are applications that run inside the
+  Chrome browser and provide additional functionality, integration with third
+  party websites or services, and customized browsing experiences.
+</p>
+
+<h3 id="faq-dev-01">How can I set up Chrome for extension development?</h3>
+<p>
+  As long as you are using a version of Chrome that supports
+  extensions, you already have everything you need to start writing an
+  extension of your own.
+  You can start by turning on Developer mode.
+  </p>
+
+  <p>
+  Click the settings menu icon
+  <img src="{{static}}/images/hotdogmenu.png" height="29" width="29" alt=""
+    class="nomargin" />
+  and select <b>Extensions</b> from the <b>Tools</b> menu.
+  If there's a "+" next to "Developer mode",
+  click the "+" so it turns into a "-".
+  Now you can reload extensions,
+  load an unpacked directory of files as if it were a packaged extension,
+  and more. For a complete tutorial, see
+  <a href="http://code.google.com/chrome/extensions/getstarted.html">Getting Started</a>.
+</p>
+
+<h3 id="faq-gen-02">What technologies are used to write extensions for Chrome?</h3>
+<p>
+  Extensions are written using the same standard web
+  technologies that developers use to create websites. HTML is used as a
+  content markup language, CSS is used for styling, and JavaScript for
+  scripting. Because Chrome supports HTML5 and CSS3, developers can
+  use the latest open web technologies such as canvas and CSS animations in
+  their extensions. Extensions also have access to several
+  <a href="http://code.google.com/chrome/extensions/api_other.html">JavaScript APIs</a>
+  that help perform functions like JSON encoding and interacting with the
+  browser.
+</p>
+
+
+<h3 id="faq-gen-03">Are extensions fetched from the web every time the browser is loaded?</h3>
+<p>
+  Extensions are downloaded by the Chrome browser upon install, and
+  are subsequently run off of the local disk in order to speed up
+  performance. However, if a new version of the extension is pushed online,
+  it will be automatically downloaded in the background to any users who
+  have the extension installed. Extensions may also make requests for remote
+  content at any time, in order to interact with a web service or pull new
+  content from the web.
+</p>
+
+<h3 id="faq-dev-14">How do I determine which version of Chrome is deployed to which channel?</h3>
+<p>
+  To determine which version of Chrome is currently available on each
+  of the different platforms, visit
+  <a href="http://omahaproxy.appspot.com">omahaproxy.appspot.com</a>.  On that
+  site you will see data in a format similar to:
+</p>
+
+<pre>cf,dev,#.#.###.#,#.#.###.#,mm/dd/yy,mm/dd/yy,#####,#####,#####
+cf,beta,#.#.###.#,#.#.###.#,mm/dd/yy,mm/dd/yy,#####,#####,#####
+cf,stable,#.#.###.#,#.#.###.#,mm/dd/yy,mm/dd/yy,#####,#####,#####
+linux,dev,#.#.###.#,#.#.###.#,mm/dd/yy,mm/dd/yy,#####,#####,#####
+linux,beta,#.#.###.#,#.#.###.#,mm/dd/yy,mm/dd/yy,#####,#####,#####
+linux,stable,#.#.###.#,#.#.###.#,mm/dd/yy,mm/dd/yy,#####,#####,#####
+mac,dev,#.#.###.#,#.#.###.#,mm/dd/yy,mm/dd/yy,#####,#####,#####
+mac,beta,#.#.###.#,#.#.###.#,mm/dd/yy,mm/dd/yy,#####,#####,#####
+mac,stable,#.#.###.#,#.#.###.#,mm/dd/yy,mm/dd/yy,#####,#####,#####
+win,canary,#.#.###.#,#.#.###.#,mm/dd/yy,mm/dd/yy,#####,#####,#####
+win,dev,#.#.###.#,#.#.###.#,mm/dd/yy,mm/dd/yy,#####,#####,#####
+win,beta,#.#.###.#,#.#.###.#,mm/dd/yy,mm/dd/yy,#####,#####,#####
+win,stable,#.#.###.#,#.#.###.#,mm/dd/yy,mm/dd/yy,#####,#####,#####
+cros,dev,#.#.###.#,#.#.###.#,mm/dd/yy,mm/dd/yy,#####,#####,#####
+cros,beta,#.#.###.#,#.#.###.#,mm/dd/yy,mm/dd/yy,#####,#####,#####</pre>
+
+<p>
+  Each line represents information about a different platform and channel
+  combination. The
+  listed platforms are <code>cf</code> (Google Chrome Frame),
+  <code>linux</code>, <code>mac</code>, <code>win</code>, and
+  <code>cros</code> (Google Chrome OS).  The listed
+  channels are <code>canary</code>, <code>dev</code>, <code>beta</code>,
+  and <code>stable</code>.
+  The two four-part numbers after the channel represent the current and previous
+  versions of Chrome deployed to that platform-channel
+  combination.  The rest of the information is metadata about when the releases
+  were first pushed, as well as revision numbers associated with each build.
+</p>
+
+
+<h2 id="capabilities2">Capabilities</h2>
+
+<h3 id="faq-dev-02">Can extensions make cross-domain Ajax requests?</h3>
+<p>
+  Yes. Extensions can make cross-domain requests.  See
+  <a href="http://code.google.com/chrome/extensions/xhr.html">this page</a>
+  for more information.
+</p>
+
+<h3 id="faq-dev-03">Can extensions use 3rd party web services?</h3>
+<p>
+  Yes. Extensions are capable of making cross-domain Ajax
+  requests, so they can call remote APIs directly. APIs that provide data
+  in JSON format are particularly easy to use.
+</p>
+
+<h3 id="faq-dev-07">Can extensions encode/decode JSON data?</h3>
+<p>
+  Yes, because V8 (Chrome's JavaScript engine) supports
+  JSON.stringify and JSON.parse natively, you may use these functions in your
+  extensions
+  <a href="http://json.org/js.html">as described here</a> without including
+  any additional JSON libraries in your code.
+</p>
+
+<h3 id="faq-dev-08">Can extensions store data locally?</h3>
+<p>
+  Yes, extensions can use <a href="http://dev.w3.org/html5/webstorage/">localStorage</a>
+  to store string data permanently. Using Chrome's built-in JSON
+  functions, you can store complex data structures in localStorage.  For
+  extensions that need to execute SQL queries on their stored data,
+  Chrome implements
+  <a href="http://dev.w3.org/html5/webdatabase/">client side SQL databases</a>,
+  which may be used as well.
+</p>
+
+<h3 id="faq-dev-04">Can extensions use OAuth?</h3>
+<p>
+  Yes, there are extensions that use OAuth to access remote data
+  APIs. Most developers find it convenient to use a
+  <a href="http://unitedheroes.net/OAuthSimple/js/OAuthSimple.js">JavaScript OAuth library</a>
+  in order to simplify the process of signing OAuth requests.
+</p>
+
+<h3 id="faq-dev-06">Can extensions load DLLs?</h3>
+<p>
+  Yes, using the <a href="npapi.html">NPAPI interface</a>.
+  Because of the possibility for abuse, though, we will review your extension
+  before hosting it in the Chrome Web Store.
+</p>
+
+<h3 id="faq-dev-05">Can extensions create UI outside of the rendered web page?</h3>
+<p>
+  Yes, your extension may add buttons to the Chrome browser's user interface.
+  See <a href="browserAction.html">browser actions</a> and
+  <a href="pageAction.html">page actions</a> for more information.
+</p>
+<p>
+  An extension may also create popup notifications, which exist outside of the
+  browser window.  See the <a href="notifications.html">desktop
+    notifications</a> documentation for more details.
+</p>
+
+<h3 id="faq-interact-chrome">Can extensions listen to clicks on Chrome tabs and
+  navigation buttons?</h3>
+<p>
+  No.  Extensions are limited to listening to the events described in the <a
+    href="api_index.html">API documentation</a>.
+</p>
+
+<h3 id="faq-dev-11">Can two extensions communicate with each other?</h3>
+<p>
+  Yes, extensions may pass messages to other extensions. See the
+  <a href="messaging.html#external">message passing documentation</a>
+  for more information.
+</p>
+
+<h3 id="faq-dev-13">Can extensions use Google Analytics?</h3>
+<p>
+  Yes, since extensions are built just like websites, they can use
+  <a href="http://www.google.com/analytics/">Google Analytics</a> to track
+  usage.  However, we strongly advise you to modify the tracking code to pull
+  an HTTPS version of the Google Analytics library.  See
+  <a href="tut_analytics.html">this tutorial</a> for more information on doing
+  this.
+</p>
+
+<h3 id="faq-dev-15">Can extensions modify chrome:// URLs?</h3>
+<p>
+  No. The extensions APIs have been designed to minimize backwards
+  compatibility issues that can arise when new versions of the browser are
+  pushed. Allowing content scripts on <code>chrome://</code>
+  URLs would mean that developers would begin to rely on the DOM, CSS, and
+  JavaScript of these pages to stay the same.  In the best case, these pages
+  could not be updated as quickly as they are being updated right now.
+  In the worst case, it could mean that an update to one
+  of these pages could cause an extension to break, causing key parts of the
+  browser to stop working for users of that extension.
+</p>
+
+<p>
+  The reason that <a href="override.html">replacing the content</a>
+  hosted at these URLs entirely is
+  allowed is because it forces an extension developer to implement all of the
+  functionality they want without depending on the browser's internal implementation
+  to stay the same.
+</p>
+
+<h3 id="faq-open-popups">Can extensions open browser/page action popups without
+  user interaction?</h3>
+<p>
+  No, popups can only be opened if the user clicks on the corresponding page or
+  browser action.  An extension cannot open its popup programatically.
+</p>
+
+<h3 id="faq-persist-popups">Can extensions keep popups open after the user
+  clicks away from them?</h3>
+<p>
+  No, popups automatically close when the user focuses on some portion of the
+  browser outside of the popup.  There is no way to keep the popup open after
+  the user has clicked away.
+</p>
+
+<h3 id="faq-lifecycle-events">Can extensions be notified when they are
+  installed/uninstalled?</h3>
+<p>
+  You can listen to the
+  <a href="runtime.html#event-onInstalled">runtime.onInstalled</a>
+  event to be notified when your extension is installed or updated, or when
+  Chrome itself is updated. There is no corresponding event for when your
+  extension is uninstalled.
+</p>
+
+
+<h2 id="development2">Development</h2>
+
+
+<h3 id="faq-building-ui">How do I build a UI for my extension?</h3>
+<p>
+  Extensions use HTML and CSS to define their user interfaces, so you can use
+  standard form controls to build your UI, or style the interface with CSS,
+  as you would a web page.  Additionally, extensions can add
+  <a href="#faq-dev-05">some limited UI elements to Chrome itself.</a>
+</p>
+
+<h3 id="faq-dev-09">How much data can I store in localStorage?</h3>
+<p>
+  Extensions can store up to 5MB of data in localStorage.
+</p>
+
+<h3 id="faq-dev-10">How do I create an options menu for my application?</h3>
+<p>
+  You can let users set options for your extension by creating an
+  <a href="http://code.google.com/chrome/extensions/trunk/options.html">options page</a>,
+  which is a simple HTML page that will be loaded when a user clicks the
+  "options" button for your extension. This page can read and write settings
+  to localStorage, or even send options to a web server so that they can be
+  persisted across browsers.
+</p>
+
+<h3 id="faq-dev-12">What debugging tools are available to extension developers?</h3>
+<p>
+  Chrome's built-in developer tools can be used to debug extensions
+  as well as web pages. See this
+  <a href="http://code.google.com/chrome/extensions/tut_debugging.html ">tutorial on debugging extensions</a>
+  for more information.
+</p>
+
+<h3 id="faq-dev-16">Why do wildcard matches not work for top level domains
+  (TLDs)?</h3>
+<p>
+  You cannot use wildcard match patterns like <code>http://google.*/*</code>
+  to match TLDs (like <code>http://google.es</code> and
+  <code>http://google.fr</code>) due to the
+  complexity of actually restricting such a match to only the desired domains.
+</p>
+<p>
+  For the example of <code>http://google.*/*</code>, the Google domains would
+  be matched, but so would <code>http://google.someotherdomain.com</code>.
+  Additionally, many sites do not own all of the TLDs for their
+  domain.  For an example, assume you want to use
+  <code>http://example.*/*</code> to match <code>http://example.com</code> and
+  <code>http://example.es</code>, but <code>http://example.net</code> is a
+  hostile site.  If your extension has a bug, the hostile site could potentially
+  attack your extension in order to get access to your extension's increased
+  privileges.
+</p>
+<p>
+  You should explicitly enumerate the TLDs that you wish to run
+  your extension on.
+</p>
+
+<h3 id="faq-management">Why does the management API not fire events when my
+  extension is installed/uninstalled?</h3>
+<p>
+  The <a href="management.html">management API</a> was intended to help create
+  new tab page replacement extensions.  It was not intended to fire
+  install/uninstall events for the current extension.
+</p>
+
+<h3 id="faq-firstrun">How can an extension determine whether it is running for
+  the first time?</h3>
+<p>
+  You can listen to the
+  <a href="runtime.html#event-onInstalled">runtime.onInstalled</a>
+  event. See <a href="#faq-lifecycle-events">this FAQ entry</a>.
+</p>
+
+<h2 id="features2">Features and bugs</h2>
+
+
+<h3 id="faq-fea-01">I think I've found a bug! How do I make sure it gets
+  fixed?</h3>
+<p>
+  While developing an extension, you may find behavior that does not
+  match the extensions documentation and may be the result of a bug in
+  Chrome.  The best thing to do is to make sure an appropriate issue
+  report is filed, and the Chromium team has enough information to reproduce
+  the behavior.
+</p>
+
+<p>The steps you should follow to ensure this are:</p>
+
+<ol>
+  <li>
+    Come up with a <em>minimal</em> test extension that demonstrates the issue
+    you wish to report.  This extension should have as little code as possible
+    to demonstrate the bug&mdash;generally this should be 100 lines of
+    code or less.  Many times, developers find that they cannot reproduce their
+    issues this way, which is a good indicator that the bug is in their own
+    code.
+  </li>
+  <li>
+    Search the issue tracker at
+    <a href="http://www.crbug.com">http://www.crbug.com</a> to see whether
+    someone has reported a similar issue.  Most issues related to
+    extensions are filed under <strong>Feature=Extensions</strong>, so to
+    look for an extension bug related to the
+    chrome.tabs.executeScript function (for example), search for
+    "<code>Feature=Extensions Type=Bug chrome.tabs.executeScript</code>",
+    which will give you
+    <a href="http://code.google.com/p/chromium/issues/list?can=2&q=Feature%3DExtensions+Type%3DBug+chrome.tabs.executeScript&colspec=ID+Stars+Pri+Area+Feature+Type+Status+Summary+Modified+Owner+Mstone+OS&x=mstone&y=area&cells=tiles">
+    this list of results</a>.
+  </li>
+  <li>
+    If you find a bug that describes your issue, click the star icon to be
+    notified when the bug receives an update.  <em>Do not respond to the
+    bug to say "me too" or ask "when will this be fixed?"</em>; such updates
+    can cause hundreds of emails to be sent.  Add a comment only if you have
+    information (such as a better test case or a suggested fix) that is likely
+    to be helpful.
+  </li>
+  <li>
+    If you found no appropriate bug to star, file a new issue report at
+    <a href="http://new.crbug.com">http://new.crbug.com</a>.  Be as explicit
+    as possible when filling out this form: choose a descriptive title,
+    explain the steps to reproduce the bug, and describe the expected and
+    actual behavior.  Attach your test example to the report and add
+    screenshots if appropriate.  The easier your report makes it for others
+    to reproduce your issue, the greater chance that your bug will be fixed
+    promptly.
+  </li>
+  <li>
+    Wait for the bug to be updated.  Most new bugs are triaged within a week,
+    although it can sometimes take longer for an update.  <em>Do not reply
+    to the bug to ask when the issue will be fixed.</em>  If your bug has not
+    been modified after two weeks, please post a message to the
+    <a href="http://groups.google.com/a/chromium.org/group/chromium-extensions/topics">
+    discussion group</a> with a link back to your bug.
+  </li>
+  <li>
+    If you originally reported your bug on the discussion group and were
+    directed to this FAQ entry, reply to your original thread with a link
+    to the bug you starred or reported.  This will make it easier for others
+    experiencing the same issue to find the correct bug.
+  </li>
+</ol>
+
+<h3 id="faq-fea-02">I have a feature request! How can I report it?</h3>
+
+<p>If you identify a feature (especially if it's related to an experimental
+  API) that could be added to improve the extension development experience,
+  make sure an appropriate request is filed in the issue tracker.</p>
+
+<p>The steps you should follow to ensure this are:</p>
+
+<ol>
+  <li>
+    Search the issue tracker at
+    <a href="http://www.crbug.com">http://www.crbug.com</a> to see whether
+    someone has requested a similar feature.  Most requests related to
+    extensions are filed under <strong>Feature=Extensions</strong>, so to
+    look for an extension feature request related to keyboard shortcuts
+    (for example), search
+    for "<code>Feature=Extensions Type=Feature shortcuts</code>",
+    which will give you
+    <a href="http://code.google.com/p/chromium/issues/list?can=2&q=Feature%3DExtensions+Type%3DFeature+shortcuts&colspec=ID+Stars+Pri+Area+Feature+Type+Status+Summary+Modified+Owner+Mstone+OS&x=mstone&y=area&cells=tiles">
+    this list of results</a>.
+  </li>
+  <li>
+    If you find a ticket that matches your request, click the star icon to be
+    notified when the bug receives an update.  <em>Do not respond to the
+    bug to say "me too" or ask "when will this be implemented?"</em>; such
+    updates can cause hundreds of emails to be sent.
+  </li>
+  <li>
+    If you found no appropriate ticket to star, file a new request at
+    <a href="http://new.crbug.com">http://new.crbug.com</a>.  Be as detailed
+    as possible when filling out this form: choose a descriptive title
+    and explain exactly what feature you would like and how you plan to use it.
+  </li>
+  <li>
+    Wait for the ticket to be updated.  Most new requests are triaged within a
+    week, although it can sometimes take longer for an update.  <em>Do not reply
+    to the ticket to ask when the feature will be added.</em>  If your
+    ticket has not been modified after two weeks, please post a message to the
+    <a href="http://groups.google.com/a/chromium.org/group/chromium-extensions/topics">
+    discussion group</a> with a link back to your request.
+  </li>
+  <li>
+    If you originally reported your request on the discussion group and were
+    directed to this FAQ entry, reply to your original thread with a link
+    to the ticket you starred or opened.  This will make it easier for others
+    with the same request to find the correct ticket.
+  </li>
+</ol>
diff --git a/chrome/common/extensions/docs/templates/articles/first_app.html b/chrome/common/extensions/docs/templates/articles/first_app.html
new file mode 100644
index 0000000..e74a8f4
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/first_app.html
@@ -0,0 +1,166 @@
+<meta name="doc-family" content="apps">
+<h1>Create Your First App</h1>
+
+
+<p>
+This tutorial walks you through creating your first packaged app.
+Packaged apps are structured similarly to extensions
+so current developers will recognize the manifest and packaging methods.
+When you're done,
+you'll just need to produce a zip file of your code and assets
+in order to <a href="publish_app.html">publish</a> your app.
+</p>
+
+<p>
+A packaged app contains these components:
+</p>
+
+<ul>
+  <li>The <strong>manifest</strong> tells Chrome about your app, what it is,
+    how to launch it and the extra permissions that it requires.</li>
+  <li>The <strong>background script</strong> is used to create the event page
+    responsible for managing the app life cycle.</li>
+  <li>All code must be included in the package. This includes HTML, JS, CSS
+    and Native Client modules.</li>
+  <li>All <strong>icons</strong> and other assets must be included
+    in the package as well.</li>
+</ul>
+
+<p class="note">
+<b>API Samples: </b>
+Want to play with the code?
+Check out the
+<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/hello-world">hello-world</a>
+sample.
+</p>
+
+<h2 id="one">Step 1: Create the manifest</h2>
+
+<p>
+First create your <code>manifest.json</code> file
+(<a href="manifest.html">Formats: Manifest Files</a>
+describes this manifest in detail):
+</p>
+
+<pre>
+{
+  "name": "Hello World!",
+  "description": "My first packaged app.",
+  "version": "0.1",
+  "app": {
+    "background": {
+      "scripts": ["background.js"]
+    }
+  },
+  "icons": { "16": "calculator-16.png", "128": "calculator-128.png" }
+}
+</pre>
+
+<p class="note">
+<b>Important:</b>
+Packaged apps <b>must</b> use
+<a href="manifestVersion.html">manifest version 2</a>.
+</p>
+
+<h2 id="two">Step 2: Create the background script</h2>
+
+<p>
+Next create a new file called <code>background.js</code>
+with the following content:
+</p>
+
+<pre>
+chrome.app.runtime.onLaunched.addListener(function() {
+  chrome.app.window.create('window.html', {
+    'width': 400,
+    'height': 500
+  });
+});
+</pre>
+
+<p>
+In the above sample code,
+the <a href="app_lifecycle.html#lifecycle">onLaunched event</a>
+will be fired when the user starts the app.
+It then immediately opens a window for the app of the specified width and height.
+Your background script may contain additional listeners,
+windows, post messages, and launch data,
+all of which are used by the event page to manage the app.
+</p>
+
+<h2 id="three">Step 3: Create a window page</h2>
+
+<p>
+Create your <code>window.html</code> file:
+</p>
+
+<pre>
+&lt;!DOCTYPE html>
+&lt;html>
+  &lt;head>
+  &lt;/head>
+  &lt;body>
+    &lt;div>Hello, world!&lt;/div>
+  &lt;/body>
+&lt;/html>
+</pre>
+
+<h2 id="four">Step 4: Create the icons</h2>
+
+<p>
+Copy these icons to your app folder:
+</p>
+
+<ul>
+  <li><a href="{{static}}/images/calculator-16.png">calculator-16.png</a></li>
+  <li><a href="{{static}}/images/calculator-128.png">calculator-128.png</a></li>
+</ul>
+
+<h2 id="five">Step 5: Launch your app</h2>
+
+<h3 id="enable">Enable flags</h3>
+
+<p>
+Many of the packaged apps APIs are still experimental,
+so you should enable experimental APIs
+so that you can try them out:
+</p>
+
+<ul>
+  <li>Go to <b>chrome://flags</b>.</li>
+  <li>Find "Experimental Extension APIs",
+    and click its "Enable" link.</li>
+  <li>Restart Chrome.</li>
+</ul>
+
+<h3 id="load">Load your app</h3>
+
+<p>
+To load your app,
+bring up the apps and extensions management page
+by clicking the settings icon
+<img src="{{static}}/images/hotdogmenu.png" width="29" height="29" alt=""
+        style="margin-top:0" />
+and choosing <b>Tools > Extensions</b>.
+</p>
+
+<p>
+Make sure the <b>Developer mode</b>
+checkbox has been selected.
+</p>
+
+<p>
+Click the <b>Load unpacked extension</b> button,
+navigate to your app's folder
+and click <b>OK</b>.
+</p>
+
+<h3 id="open">Open new tab and launch</h3>
+
+<p>
+Once you've loaded your app,
+open a New Tab page
+and click on your new app icon.
+</p>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
diff --git a/chrome/common/extensions/docs/templates/articles/getstarted.html b/chrome/common/extensions/docs/templates/articles/getstarted.html
new file mode 100644
index 0000000..8d077a3
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/getstarted.html
@@ -0,0 +1,282 @@
+<h1>Getting Started: Building a Chrome Extension</h1>
+
+<p>
+  Extensions allow you to add functionality to Chrome without diving deeply
+  into native code. You can create new extensions for Chrome with those core
+  technologies that you're already familiar with from web development: HTML,
+  CSS, and JavaScript. If you've ever built a web page, you should feel right at
+  home with extensions pretty quickly; we'll put that to the test right now by
+  walking through the construction of a simple extension that will give you
+  one-click access to pictures of kittens. Kittens!
+</p>
+
+<p>
+  We'll do so by implementing a UI element we call a
+  <a href="browserAction.html">browser action</a>, which allows us to place a
+  clickable icon right next to Chrome's Omnibox for easy access. Clicking that
+  icon will open a popup window filled with kittenish goodness, which will look
+  something like this:
+</p>
+
+<img src="{{static}}/images/gettingstarted-1.jpg"
+     width="600"
+     height="420"
+     alt="Chrome, with an extension's popup open and displaying many kittens.">
+
+<p>
+  If you'd like to follow along at home (and you should!), create a shiny new
+  directory on your computer, and pop open your favourite text editor. Let's get
+  going!
+</p>
+
+<h2 id="declaration">Something to Declare</h2>
+
+<p>
+  The very first thing we'll need to create is a <dfn>manifest file</dfn> named
+  <code>manifest.json</code>. The manifest is nothing more than a JSON-formatted
+  table of contents, containing properties like your extension's name and
+  description, its version number, and so on. At a high level, we'll use it to
+  declare to Chrome what the extension is going to do, and what permissions it
+  requires in order to do those things.
+</p>
+
+<p>
+  In order to display kittens, we'll want to tell Chrome that we'd like to
+  create a browser action, and that we'd like free-reign to access kittens from
+  a particular source on the net. A manifest file containing those instructions
+  looks like this:
+</p>
+
+<pre class="lang-js" data-lang="javascript" data-filename="manifest.json"><code>{
+  "manifest_version": 2,
+
+  "name": "One-click Kittens",
+  "description": "This extension demonstrates a browser action with kittens.",
+  "version": "1.0",
+
+  "permissions": [
+    "https://secure.flickr.com/"
+  ],
+  "browser_action": {
+    "default_icon": "icon.png",
+    "default_popup": "popup.html"
+  }
+}</code></pre>
+
+<p>
+  Go ahead and save that data to a file named <code>manifest.json</code> in the
+  directory you created, or
+  <a href="examples/tutorials/getstarted/manifest.json" download="manifest.json">
+    download a copy of <code>manifest.json</code> from our sample repository
+  </a>.
+</p>
+
+<h3 id="manifest">What does it mean?</h3>
+
+<p>
+  The attribute names are fairly self-descriptive, but let's walk through the
+  manifest line-by-line to make sure we're all on the same page.
+</p>
+
+<p>
+  The first line, which declares that we're using version 2 of the manifest file
+  format, is mandatory (version 1 is old, deprecated, and generally not
+  awesome).
+</p>
+
+<p>
+  The next block defines the extension's name, description, and version. These
+  will be used both inside of Chrome to show a user which extensions you have
+  installed, and also on the Chrome Web Store to display your extension to
+  potentially new users. The name should be short and snappy, and the
+  description no longer than a sentence or so (you'll have more room for a
+  detailed description later).
+</p>
+
+<p>
+  The final block first requests permission to work with data on
+  <code>https://secure.flickr.com/</code>, and declares that this extension
+  implements a browser action, assigning it a default icon and popup in the
+  process.
+</p>
+
+<h2 id="resources">Resources</h2>
+
+<p>
+  You probably noticed that <code>manifest.json</code> pointed at two resource
+  files when defining the browser action: <code>icon.png</code> and
+  <code>popup.html</code>. Both resources must exist inside the extension
+  package, so let's create them now:
+</p>
+
+<ul class="imaged">
+  <li>
+    <p>
+      <img src="{{static}}/images/gettingstarted-icon.png"
+           width="127"
+           height="127"
+           alt="The popup's icon will be displayed right next to the Omnibox.">
+      <code>icon.png</code> will be displayed next to the Omnibox, waiting for
+      user interaction. Download a copy of icon.png from our sample repository,
+      <a href="examples/tutorials/getstarted/icon.png" download="icon.png">
+        Download a copy of <code>icon.png</code> from our sample repository
+      </a>, and save it into the directory you're working in. You could also
+      create your own if you're so inclined; it's just a 19px-square PNG file.
+    </p>
+  </li>
+  <li>
+    <p>
+      <img src="{{static}}/images/gettingstarted-popup.jpg"
+           width="165"
+           height="200"
+           alt="The popup's HTML will be rendered directly below the icon when clicked.">
+      <code>popup.html</code> will be rendered inside the popup window that's
+      created in response to a user's click on the browser action. It's a
+      standard HTML file, just like you're used to from web development, giving
+      you more or less free reign over what the popup displays.
+      <a href="examples/tutorials/getstarted/popup.html" download="popup.html">
+        Download a copy of <code>popup.html</code> from our sample repository
+      </a>, and save it into
+      the directory you're working in.
+    </p>
+    <p>
+      <code>popup.html</code> requires an additional JavaScript file in order to
+      do the work of grabbing kitten images from the web and loading them into
+      the popup. To save you some effort, just
+      <a href="examples/tutorials/getstarted/popup.js" download="popup.js">
+        download a copy of <code>popup.js</code> from our sample repository
+      </a>, and save it into the directory you're working in.
+    </p>
+  </li>
+</ul>
+
+<p>
+  You should now have four files in your working directory:
+  <a href="examples/tutorials/getstarted/icon.png" download="icon.png"><code>icon.png</code></a>,
+  <a href="examples/tutorials/getstarted/manifest.json" download="manifest.json"><code>manifest.json</code></a>,
+  <a href="examples/tutorials/getstarted/popup.html" download="popup.html"><code>popup.html</code></a>,
+  <a href="examples/tutorials/getstarted/popup.js" download="popup.js"><code>popup.js</code></a>.
+  The next step is to load those files into Chrome.
+</p>
+
+<h2 id="unpacked">Load the extension</h2>
+
+<p>
+  Extensions that you download from the Chrome Web Store are packaged up as
+  <code>.crx</code> files, which is great for distribution, but not so great for
+  development. Recognizing this, Chrome gives you a quick way of loading up your
+  working directory for testing. Let's do that now.
+</p>
+
+<ol>
+  <li>
+    <p>
+      Visit <code>chrome://extensions</code> in your browser (or open up the
+      settings menu by clicking the icon to the far right of the Omnibox:
+      <img src="{{static}}/images/hotdogmenu.png"
+           height="29"
+           width="29"
+           alt="The menu's icon looks like a set of hot dogs. Seriously.">. and
+      select <strong>Extensions</strong> under the <strong>Tools</strong> menu
+      to get to the same place).
+    </p>
+  </li>
+  <li>
+    <p>
+      Ensure that the <strong>Developer Mode</strong> checkbox in the top
+      right-hand corner is checked.
+    </p>
+  </li>
+  <li>
+    <p>
+      Click <strong>Load unpacked extension&hellip;</strong> to pop up a
+      file-selection dialog.
+    </p>
+  </li>
+  <li>
+    <p>
+      Navigate to the directory in which your extension files live, and select
+      it.
+    </p>
+  </li>
+</ol>
+
+<p>
+  If the extension is valid, it'll be loaded up and active right away! If it's
+  invalid, an error message will be displayed at the top of the page. Correct
+  the error, and try again.
+</p>
+
+<h2 id="update-code">Fiddle with Code</h2>
+
+<p>
+  Now that you've got your first extension up and running, let's fiddle with
+  things so that you have an idea what your development process might look like.
+  As a trivial example, let's change the data source to search for pictures of
+  puppies instead of kittens.
+</p>
+
+<p>
+  Hop into <code>popup.js</code>, and edit line 11 from
+  <code>var QUERY = 'kittens';</code> to read
+  <code>var QUERY = 'puppies';</code>, and save your changes.
+</p>
+
+<p>
+  If you click on your extension's browser action again, you'll note that your
+  change hasn't yet had an effect. You'll need to let Chrome know that something
+  has happened, either explicitly by going back to the extension page
+  (<strong>chrome://extensions</strong>, or
+  <strong>Tools &gt; Extensions</strong> under the settings menu), and clicking
+  <strong>Reload</strong> under your extension, or by reloading the extensions
+  page itself (either via the reload button to the left of the Omnibox, or by
+  hitting F5 or Ctrl-R).
+</p>
+
+<p>
+  Once you've reloaded the extension, click the browser action icon again.
+  Puppies galore!
+</p>
+
+<h2 id="next-steps">What next?</h2>
+
+<p>
+  You now know about the manifest file's central role in bringing things
+  together, and you've mastered the basics of declaring a browser action, and
+  rendering some kittens (or puppies!) in response to a user's click. That's a
+  great start, and has hopefully gotten you interested enough to explore
+  further. There's a lot more out there to play around with.
+</p>
+
+<ul>
+  <li>
+    <p>
+      The <a href="overview.html">Chrome Extension Overview</a> backs up a bit,
+      and fills in a lot of detail about extensions' architecture in general,
+      and some specific concepts you'll want to be familiar with going forward.
+      It's the best next step on your journey towards extension mastery.
+    </p>
+  </li>
+  <li>
+    <p>
+      No one writes perfect code on the first try, which means that you'll need
+      to learn about the options available for debugging your creations. Our
+      <a href="tut_debugging.html">debugging tutorial</a> is perfect for that,
+      and is well worth carefully reading.
+    </p>
+  </li>
+  <li>
+    <p>
+      Chrome extensions have access to powerful APIs above and beyond what's
+      available on the open web: browser actions are just the tip of the
+      iceburg. Our <a href="api_index.html">chrome.* APIs documentation</a> will
+      walk you through each API in turn.
+    </p>
+  </li>
+  <li>
+    <p>
+      Finally, the <a href="devguide.html">developer's guide</a> has dozens of
+      additional links to pieces of documentation you might be interested in.
+    </p>
+  </li>
+</ul>
diff --git a/chrome/common/extensions/docs/templates/articles/hosting.html b/chrome/common/extensions/docs/templates/articles/hosting.html
new file mode 100644
index 0000000..091e93b
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/hosting.html
@@ -0,0 +1,84 @@
+<h1>Hosting</h1>
+
+<p>
+This page tells you how to host <code>.crx</code> files
+on your own server.
+If you distribute your extension, app, or theme solely through the
+<a href="http://chrome.google.com/webstore">Chrome Web Store</a>,
+you don't need this page.
+Instead, consult the
+<a href="http://www.google.com/support/chrome_webstore/">store help</a> and
+<a href="http://code.google.com/chrome/webstore/index.html">developer documentation</a>.
+</p>
+
+<p>
+By convention, extensions,
+installable web apps, and themes are served&mdash;whether
+by the Chrome Web Store or by a custom server&mdash;as
+<code>.crx</code> files.
+When you upload a ZIP file with the
+<a href="https://chrome.google.com/webstore/developer/dashboard">Chrome Developer Dashboard</a>,
+the dashboard creates the <code>.crx</code> file for you.
+</p>
+
+<p>
+If you aren't publishing using the dashboard,
+you need to create the <code>.crx</code> file yourself,
+as described in <a href="packaging.html">Packaging</a>.
+You can also specify
+<a href="autoupdate.html">autoupdate</a> information to ensure that
+your users will have the latest copy of the <code>.crx</code> file.
+</p>
+
+<p>
+A server that hosts <code>.crx</code> files
+must use appropriate HTTP headers,
+so that users can install the file
+by clicking a link to it.
+</p>
+
+<p>
+Google Chrome considers a file to be installable
+if <b>either</b> of the following is true:
+</p>
+
+<ul>
+  <li>
+    The file has the content type
+    <code>application/x-chrome-extension</code>
+  </li>
+  <li>
+    The file suffix is <code>.crx</code>
+    and <b>both</b> of the following are true:
+    <ul>
+      <li>
+        The file <b>is not</b> served with
+        the HTTP header <code>X-Content-Type-Options: nosniff</code>
+      </li>
+      <li>
+        The file <b>is</b> served
+        with one of the following content types:
+        <ul>
+          <li> empty string </li>
+          <li> "text/plain" </li>
+          <li> "application/octet-stream" </li>
+          <li> "unknown/unknown" </li>
+          <li> "application/unknown" </li>
+          <li> "*/*" </li>
+        </ul>
+      </li>
+    </ul>
+  </li>
+</ul>
+
+<p>
+The most common reason for failing to recognize an installable file
+is that the server sends the header
+<code>X-Content-Type-Options: nosniff</code>.
+The second most common reason
+is that the server sends an unknown content type&mdash;one
+that isn't in the previous list.
+To fix an HTTP header issue,
+either change the configuration of the server
+or try hosting the <code>.crx</code> file at another server.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/i18n-messages.html b/chrome/common/extensions/docs/templates/articles/i18n-messages.html
new file mode 100644
index 0000000..aad9927
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/i18n-messages.html
@@ -0,0 +1,330 @@
+<h1>Formats: Locale-Specific Messages</h1>
+
+
+<p>
+Each internationalized extension or app has at least one
+file named <code>messages.json</code>
+that provides locale-specific strings.
+This page describes the format of <code>messages.json</code> files.
+For information on how to internationalize and localize,
+see the <a href="i18n.html">Internationalization</a> page.
+</p>
+
+<h2 id="overview"> Field summary </h2>
+
+<p>
+The following code shows the supported fields for
+<code>messages.json</code>.
+Only the "<em>name</em>" and "message" fields are required.
+</p>
+
+<pre>
+{
+  "<a href="#name"><em>name</em></a>": {
+    "<a href="#message">message</a>": "<em>Message text, with optional placeholders.</em>",
+    "<a href="#description">description</a>": "<em>Translator-aimed description of the message.</em>",
+    "<a href="#placeholders">placeholders</a>": {
+      "<em>placeholder_name</em>": {
+        "content": "<em>A string to be placed within the message.</em>",
+        "example": "<em>Translator-aimed example of the placeholder string.</em>"
+      },
+      ...
+    }
+  },
+  ...
+}
+</pre>
+
+<h2 id="example"> Example </h2>
+
+<p>
+Here's a <code>messages.json</code> file
+that defines three messages
+named "prompt_for_name", "hello", and "bye":
+</p>
+
+<pre>
+{
+  "prompt_for_name": {
+    "message": "What's your name?",
+    "description": "Ask for the user's name"
+  },
+  "hello": {
+    "message": "Hello, $USER$",
+    "description": "Greet the user",
+    "placeholders": {
+      "user": {
+        "content": "$1",
+        "example": "Cira"
+      }
+    }
+  },
+  "bye": {
+    "message": "Goodbye, $USER$. Come back to $OUR_SITE$ soon!",
+    "description": "Say goodbye to the user",
+    "placeholders": {
+      "our_site": {
+        "content": "Example.com",
+      },
+      "user": {
+        "content": "$1",
+        "example": "Cira"
+      }
+    }
+  }
+}
+</pre>
+
+
+<h2 id="field_details">Field details</h2>
+
+<p>
+This section describes each field
+that can appear in a <code>messages.json</code> file.
+For details on how the messages file is used &mdash;
+for example, what happens when a locale doesn't define
+all the messages &mdash;
+see <a href="i18n.html">Internationalization</a>.
+</p>
+
+
+<h3 id="name">name</h3>
+
+<p>
+Actually, there's no field called "name".
+This field's name is the name of the message &mdash;
+the same <em>name</em> that you see in
+<code>__MSG_<em>name</em>__</code>
+or <code>getMessage("<em>name</em>")</code>.
+</p>
+
+<p>
+The name is a case-insensitive key
+that lets you retrieve the localized message text.
+The name can include the following characters:
+</p>
+
+<ul>
+  <li> A-Z </li>
+  <li> a-z </li>
+  <li> 0-9 </li>
+  <li> _ (underscore) </li>
+  <li> @ </li>
+</ul>
+
+<p class="note">
+<b>Note:</b>
+Don't define names that begin with "@@".
+Those names are reserved for
+<a href="i18n.html#overview-predefined">predefined messages</a>.
+</p>
+
+<p>
+Here are three examples of names,
+taken from the <a href="#example">Example</a> section:
+</p>
+
+<pre>
+"prompt_for_name": {
+  ...
+},
+"hello": {
+  ...
+},
+"bye": {
+  ...
+}
+</pre>
+
+<p>
+For more examples of using names, see the
+<a href="i18n.html">Internationalization</a> page.
+</p>
+
+
+<h3 id="message">message</h3>
+
+<p>
+The translated message,
+in the form of a string that can contain
+<a href="#placeholders">placeholders</a>.
+Use <code>$<em>placeholder_name</em>$</code>
+(case insensitive)
+to refer to a particular placeholder.
+For example, you can refer to a placeholder named "our_site" as
+<code>$our_site$</code>, <code>$OUR_SITE$</code>, or <code>$oUR_sITe$</code>.
+</p>
+
+<p>
+Here are three examples of messages,
+taken from the <a href="#example">Example</a> section:
+</p>
+
+<pre>
+"message": "What's your name?"
+...
+"message": "Hello, $USER$"
+...
+"message": "Goodbye, $USER$. Come back to $OUR_SITE$ soon!"
+</pre>
+
+<p>
+To put a dollar sign (<code>$</code>) into the string,
+use <code>$$</code>.
+For example, use the following code to specify the message
+<b>Amount (in $)</b>:
+
+<pre>
+"message": "Amount (in $$)"
+</pre>
+
+<p>
+Although placeholders such as <code>$USER$</code> are
+the preferred way of referring to <em>substitution strings</em>
+(strings specified using the <em>substitutions</em> parameter of
+<a href="i18n.html#method-getMessage"><code>getMessage()</code></a>)
+you can also refer to substitution strings directly
+within the message.
+For example, the following message
+refers to the first three substitution strings passed into
+<code>getMessage()</code>:
+</p>
+
+<pre>
+"message": "Params: $1, $2, $3"
+</pre>
+
+<p>
+Despite that example,
+we recommend that you stick to using placeholders
+instead of <code>$<em>n</em></code> strings
+within your messages.
+Think of placeholders as good variable names.
+A week after you write your code,
+you'll probably forget what <code>$1</code> refers to,
+but you'll know what your placeholders refer to.
+For more information on placeholders and substitution strings, see
+the <a href="#placeholders">placeholders</a> section.
+</p>
+
+<h3 id="description">description</h3>
+
+<p>
+<em>Optional.</em>
+A description of the message,
+intended to give context
+or details to help the translator
+make the best possible translation.
+</p>
+
+<p>
+Here are three examples of descriptions,
+taken from the <a href="#example">Example</a> section:
+</p>
+
+<pre>
+"description": "Ask for the user's name"
+...
+"description": "Greet the user"
+...
+"description": "Say goodbye to the user"
+</pre>
+
+<h3 id="placeholders">placeholders</h3>
+
+<p>
+<em>Optional.</em>
+Defines one or more substrings
+to be used within the message.
+Here are two reasons you might want to use a placeholder:
+</p>
+
+<ul>
+  <li>
+    To define the text
+    for a part of your message
+    that shouldn't be translated.
+    Examples: HTML code, trademarked names, formatting specifiers.
+  </li>
+  <li>
+    To refer to a substitution string passed into
+    <code>getMessage()</code>.
+    Example: <code>$1</code>.
+  </li>
+</ul>
+
+<p>
+Each placeholder has a name,
+a "content" item,
+and an optional "example" item.
+A placeholder's name is case-insensitive
+and can contain the same characters
+as a <a href="#name">message name</a>.
+</p>
+
+<p>
+The "content" item's value is a string
+that can refer to substitution strings, which are
+specified using the
+<a href="i18n.html#method-getMessage"><code>getMessage()</code></a> method's
+<em>substitutions</em> parameter.
+The value of a "content" item is typically something like
+"Example.com" or "$1".
+If you refer to
+a substitution string that doesn't exist,
+you get an empty string.
+The following table shows how
+<code>$<em>n</em></code> strings correspond to
+strings specified by the <em>substitutions</em> parameter.
+</p>
+
+<table class="simple">
+<tr>
+<th> <em>substitutions</em> parameter </th>
+<th> Value of $1</th>
+<th> Value of $2</th>
+<th> Value of $3</th>
+</tr>
+<tr>
+  <td> <code>userName</code> </td>
+  <td> value of <code>userName</code> </td>
+  <td> <code>""</code> </td>
+  <td> <code>""</code> </td>
+</tr>
+<tr>
+  <td> <code>["Cira", "Kathy"]</code> </td>
+  <td> <code>"Cira"</code> </td>
+  <td> <code>"Kathy"</code> </td>
+  <td> <code>""</code> </td>
+</tr>
+</table>
+
+<p>
+The "example" item
+(optional, but highly recommended)
+helps translators by showing how the content appears to the end user.
+For example, a placeholder
+for a dollar amount
+should have an example like <code>"$23.45"</code>.
+</p>
+
+<p>
+The following snippet,
+taken from the <a href="#example">Example</a> section,
+shows a "placeholders" item that contains two placeholders
+named "our_site" and "user".
+The "our_site" placeholder has no "example" item
+because its value is obvious from the "content" field.
+</p>
+
+<pre>
+"placeholders": {
+  "our_site": {
+    "content": "Example.com",
+  },
+  "user": {
+    "content": "$1",
+    "example": "Cira"
+  }
+}
+</pre>
diff --git a/chrome/common/extensions/docs/templates/articles/manifest.html b/chrome/common/extensions/docs/templates/articles/manifest.html
new file mode 100644
index 0000000..7228160
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/manifest.html
@@ -0,0 +1,656 @@
+<h1>Formats: Manifest Files</h1>
+
+
+<p>
+Every extension, installable web app, and theme has a
+<a href="http://www.json.org">JSON</a>-formatted manifest file,
+named <code>manifest.json</code>,
+that provides important information.
+</p>
+
+<h2 id="overview"> Field summary </h2>
+
+<p>
+The following code shows the supported manifest fields,
+with links to the page that discusses each field.
+The only fields that are always required
+are <b>name</b> and <b>version</b>.
+</p>
+
+<pre>
+{
+  <em>// Required</em>
+  "<a href="#name">name</a>": "<em>My Extension</em>",
+  "<a href="#version">version</a>": "<em>versionString</em>",
+  "<a href="#manifest_version">manifest_version</a>": 2,
+
+  <em>// Recommended</em>
+  "<a href="#description">description</a>": "<em>A plain text description</em>",
+  "<a href="#icons">icons</a>": { ... },
+  "<a href="#default_locale">default_locale</a>": "<em>en</em>",
+
+  <em>// Pick one (or none)</em>
+  "<a href="browserAction.html">browser_action</a>": {...},
+  "<a href="pageAction.html">page_action</a>": {...},
+  "<a href="themes.html">theme</a>": {...},
+  "<a href="#app">app</a>": {...},
+
+  <em>// Add any of these that you need</em>
+  "<a href="event_pages.html">background</a>": {"persistent": false, ...},
+  "<a href="background_pages.html">background</a>": {"persistent": true, ...},
+  "<a href="override.html">chrome_url_overrides</a>": {...},
+  "<a href="content_scripts.html">content_scripts</a>": [...],
+  "<a href="../extensions/contentSecurityPolicy.html">content_security_policy</a>": "<em>policyString</em>",
+  "<a href="fileBrowserHandler.html">file_browser_handlers</a>": [...],
+  "<a href="#homepage_url">homepage_url</a>": "http://<em>path/to/homepage</em>",
+  "<a href="#incognito">incognito</a>": "spanning" <em>or</em> "split",
+  "<a href="#intents">intents</a>": {...}
+  "<a href="#key">key</a>": "<em>publicKey</em>",
+  "<a href="#minimum_chrome_version">minimum_chrome_version</a>": "<em>versionString</em>",
+
+  "<a href="#nacl_modules">nacl_modules</a>": [...],
+  "<a href="#offline_enabled">offline_enabled</a>": true,
+  "<a href="omnibox.html">omnibox</a>": { "keyword": "<em>aString</em>" },
+  "<a href="options.html">options_page</a>": "<em>aFile</em>.html",
+  "<a href="declare_permissions.html">permissions</a>": [...],
+  "<a href="npapi.html">plugins</a>": [...],
+  "<a href="#requirements">requirements</a>": {...},
+  "<a href="autoupdate.html">update_url</a>": "http://<em>path/to/updateInfo</em>.xml",
+  "<a href="#web_accessible_resources">web_accessible_resources</a>": [...],
+  "<a href="#sandbox">sandbox</a>": [...]
+}
+</pre>
+
+
+<h2 id="field_details">Field details</h2>
+
+<p>
+This section covers fields that aren't described in another page.
+For a complete list of fields,
+with links to where they're described in detail,
+see the <a href="#overview">Field summary</a>.
+</p>
+
+
+<h3 id="app">app</h3>
+
+<p>
+Used by <a href="http://developer.chrome.com/trunk/apps/app_lifecycle.html#eventpage">packaged apps</a>
+to specify the app's background scripts.
+Also used by <a href="https://developers.google.com/chrome/apps/docs/developers_guide#live">hosted apps</a>
+to specify the URLs that the app uses.
+</p>
+
+<h3 id="default_locale">default_locale</h3>
+
+<p>
+Specifies the subdirectory of <code>_locales</code>
+that contains the default strings for this extension.
+This field is <b>required</b> in extensions
+that have a <code>_locales</code> directory;
+it <b>must be absent</b> in extensions
+that have no <code>_locales</code> directory.
+For details, see
+<a href="i18n.html">Internationalization</a>.
+</p>
+
+<h3 id="description">description</h3>
+
+<p>
+A plain text string
+(no HTML or other formatting;
+no more than 132 characters)
+that describes the extension.
+The description should be suitable for both
+the browser's extension management UI
+and the <a href="https://chrome.google.com/webstore">Chrome Web Store</a>.
+You can specify locale-specific strings for this field;
+see <a href="i18n.html">Internationalization</a> for details.
+</p>
+
+<h3 id="homepage_url">homepage_url</h3>
+
+<p>
+The URL of the homepage for this extension. The extensions management page (chrome://extensions)
+will contain a link to this URL.  This field is particularly useful if you
+<a href="hosting.html">host the extension on your own site</a>. If you distribute your
+extension using the <a href="https://chrome.google.com/webstore">Chrome Web Store</a>,
+the homepage URL defaults to the extension's own page.
+</p>
+
+<h3 id="icons">icons</h3>
+
+<p>
+One or more icons that represent the extension, app, or theme.
+You should always provide a 128x128 icon;
+it's used during installation and by the Chrome Web Store.
+Extensions should also provide a 48x48 icon,
+which is used in the extensions management page
+(chrome://extensions).
+You can also specify a 16x16 icon to be used as the favicon
+for an extension's pages.
+The 16x16 icon is also displayed in the experimental extension
+<a href="experimental.infobars.html">infobar</a>
+feature.
+</p>
+
+<p>
+Icons should generally be in PNG format,
+because PNG has the best support for transparency.
+They can, however, be in any format supported by WebKit,
+including BMP, GIF, ICO, and JPEG.
+Here's an example of specifying the icons:
+</p>
+
+<pre>
+"icons": { "16": "icon16.png",
+           "48": "icon48.png",
+          "128": "icon128.png" },
+</pre>
+
+<p class="note">
+<b>Important:</b>
+Use only the documented icon sizes.
+<br><br>
+You might notice that Chrome sometimes resizes these icons down to smaller
+sizes. For example, the install dialog might shrink the 128-pixel
+icon down to 69 pixels.
+<br><br>
+However, the details of
+Chrome's UI may change between versions, and these changes assume that
+developers are using the documented sizes. If you use other sizes,
+your icon may look bad in future versions of the browser.
+</p>
+
+<p>
+If you upload your extension, app, or theme using the
+<a href="https://chrome.google.com/webstore/developer/dashboard">Chrome Developer Dashboard</a>,
+you'll need to upload additional images,
+including at least one screenshot of your extension.
+For more information,
+see the
+<a href="http://code.google.com/chrome/webstore/">Chrome Web Store
+developer documentation</a>.
+</p>
+
+<h3 id="incognito">incognito</h3>
+
+<p>
+Either "spanning" or "split", to specify how this extension will
+behave if allowed to run in incognito mode.
+</p>
+
+<p>
+The default for extensions is "spanning", which means that the extension
+will run in a single shared process. Any events or messages from an incognito
+tab will be sent to the shared process, with an <em>incognito</em> flag
+indicating where it came from. Because incognito tabs cannot use this shared
+process, an extension using the "spanning" incognito mode will not be able to
+load pages from its extension package into the main frame of an incognito tab.
+</p>
+
+<p>
+The default for installable web apps is "split",
+which means that all app pages in
+an incognito window will run in their own incognito process. If the app or extension contains a background page, that will also run in the incognito process.
+This incognito process runs along side the regular process, but has a separate
+memory-only cookie store. Each process sees events and messages only from its
+own context (for example, the incognito process will see only incognito tab updates).
+The processes are unable to communicate with each other.
+</p>
+
+<p>
+As a rule of thumb, if your extension or app needs to load a tab in an incognito browser, use
+<em>split</em> incognito behavior. If your extension or app needs to be logged
+into a remote server or persist settings locally, use <em>spanning</em>
+incognito behavior.
+</p>
+
+<h3 id="intents">intents</h3>
+
+<p>
+A dictionary that specifies all intent handlers provided by this extension or app. Each key in the dictionary specifies an action verb that is handled by this extension. The following example specifies two handlers for the action verb "<a href="http://webintents.org/share">http://webintents.org/share</a>".
+</p>
+
+<pre>
+{
+  "name": "test",
+  "version": "1",
+  "intents": {
+    "http://webintents.org/share": [
+      {
+        "type": ["text/uri-list"],
+        "href": "/services/sharelink.html",
+        "title" : "Sample Link Sharing Intent",
+        "disposition" : "inline"
+      },
+      {
+        "type": ["image/*"],
+        "href": "/services/shareimage.html",
+        "title" : "Sample Image Sharing Intent",
+        "disposition" : "window"
+      }
+    ]
+  }
+}
+</pre>
+
+<p>
+The value of "type" is an array of mime types that is supported by this handler. The "href" indicates the URL of the page that handles the intent. For hosted apps, these URLs must be within the allowed set of URLs. For extensions, all URLs are inside the extension and considered relative to the extension root URL.
+</p>
+
+<p>
+The "title" is displayed in the intent picker UI when the user initiates the action specific to the handler.
+</p>
+
+<p>
+The "disposition" is either "inline" or "window". Intents with "window" disposition will open a new tab when invoked. Intents with "inline" disposition will be displayed inside the intent picker when invoked.
+</p>
+
+<p>
+For more information on intents, refer to the <a href="http://dvcs.w3.org/hg/web-intents/raw-file/tip/spec/Overview.html">Web Intents specification</a> and <a href="http://www.webintents.org">webintents.org</a>.
+</p>
+
+<h4 id="content_types">Handling content types via intents</h4>
+
+<p>
+Web Intents can be registered as content type viewers. To do that, the action verb must be <a href="http://webintents.org/view">"http://webintents.org/view"</a>, and the content type must be a white-listed MIME type.
+</p>
+
+<table>
+  <tr>
+    <th>Whitelisted MIME types</th>
+  </tr>
+  <tr><td>application/rss+xml</td></tr>
+  <tr><td>application/atom+xml</td></tr>
+</table>
+
+<h3 id="key">key</h3>
+
+<p>
+This value can be used to control
+the unique ID of an extension, app, or theme when
+it is loaded during development.
+</p>
+
+<p class="note">
+<b>Note:</b> You don't usually need to
+use this value. Instead, write your
+code so that the key value doesn't matter
+by using <a href="overview.html#relative-urls">relative paths</a>
+and <a href="extension.html#method-getURL">chrome.extension.getURL()</a>.
+</p>
+
+<p>
+To get a suitable key value, first
+install your extension from a <code>.crx</code> file
+(you may need to
+<a href="https://chrome.google.com/webstore/developer/dashboard">upload your extension</a>
+or <a href="packaging.html">package it manually</a>).
+Then, in your
+<a href="http://www.chromium.org/user-experience/user-data-directory">user
+data directory</a>, look in the file
+<code>Default/Extensions/<em>&lt;extensionId&gt;</em>/<em>&lt;versionString&gt;</em>/manifest.json</code>.
+You will see the key value filled in there.
+</p>
+
+<h3 id="minimum_chrome_version">minimum_chrome_version</h3>
+
+<p>
+The version of Chrome that your extension, app, or theme requires, if any.
+The format for this string is the same as for the
+<a href="#version">version</a> field.
+
+<h3 id="name">name</h3>
+
+<p>
+A short, plain text string
+(no more than 45 characters)
+that identifies the extension.
+The name is used in the install dialog,
+extension management UI,
+and the <a href="https://chrome.google.com/webstore">store</a>.
+You can specify locale-specific strings for this field;
+see <a href="i18n.html">Internationalization</a> for details.
+</p>
+
+<h3 id="nacl_modules">nacl_modules</h3>
+
+<p>
+One or more mappings from MIME types to the Native Client module
+that handles each type.
+For example, the bold code in the following snippet
+registers a Native Client module as the content handler
+for the OpenOffice spreadsheet MIME type.
+</p>
+
+<pre>
+{
+  "name": "Native Client OpenOffice Spreadsheet Viewer",
+  "version": "0.1",
+  "description": "Open OpenOffice spreadsheets, right in your browser.",
+  <b>"nacl_modules": [{
+    "path": "OpenOfficeViewer.nmf",
+    "mime_type": "application/vnd.oasis.opendocument.spreadsheet"
+  }]</b>
+}
+</pre>
+
+<p>
+The value of "path" is the location of a Native Client manifest
+(a <code>.nmf</code> file)
+within the extension directory.
+For more information on Native Client and <code>.nmf</code> files, see the
+<a href="http://code.google.com/chrome/nativeclient/docs/technical_overview.html">Native Client Technical Overview</a>.
+</p>
+
+<p>
+Each MIME type can be associated with only one <code>.nmf</code> file,
+but a single <code>.nmf</code> file might handle multiple MIME types.
+The following example shows an extension
+with two <code>.nmf</code> files
+that handle three MIME types.
+</p>
+
+<pre>
+{
+  "name": "Spreadsheet Viewer",
+  "version": "0.1",
+  "description": "Open OpenOffice and Excel spreadsheets, right in your browser.",
+  "nacl_modules": [{
+    "path": "OpenOfficeViewer.nmf",
+    "mime_type": "application/vnd.oasis.opendocument.spreadsheet"
+  },
+  {
+    "path": "OpenOfficeViewer.nmf",
+    "mime_type": "application/vnd.oasis.opendocument.spreadsheet-template"
+  },
+  {
+    "path": "ExcelViewer.nmf",
+    "mime_type": "application/excel"
+  }]
+}
+</pre>
+
+<p class="note">
+<strong>Note:</strong>
+You can use Native Client modules in extensions
+without specifying "nacl_modules".
+Use "nacl_modules" only if you want the browser
+to use your Native Client module
+to display a particular type of content.
+</p>
+
+<h3 id="offline_enabled">offline_enabled</h3>
+
+<p>
+Whether the app or extension is expected to work offline. When Chrome detects
+that it is offline, apps with this field set to true will be highlighted
+on the New Tab page.
+</p>
+
+<h3 id="requirements">requirements</h3>
+
+<p>
+Technologies required by the app or extension.
+Hosting sites such as the Chrome Web Store may use this list
+to dissuade users from installing apps or extensions
+that will not work on their computer.
+Supported requirements currently include "3D" and "plugins";
+additional requirements checks may be added in the future.
+</p>
+
+<p>
+The "3D" requirement denotes GPU hardware acceleration.
+The "webgl" requirement refers to the
+<a href="http://www.khronos.org/webgl/">WebGL API</a>.
+For more information on Chrome 3D graphics support,
+see the help article on
+<a href="http://www.google.com/support/chrome/bin/answer.py?answer=1220892">WebGL and 3D graphics</a>.
+You can list the 3D-related features your app requires,
+as demonstrated in the following example:
+</p>
+
+<pre>
+"requirements": {
+  "3D": {
+    "features": ["webgl"]
+  }
+}
+</pre>
+
+<p>
+The "plugins" requirement indicates
+if an app or extension requires NPAPI to run.
+This requirement is enabled by default
+when the manifest includes the
+<a href="http://developer.chrome.com/extensions/npapi.html">"plugins" field</a>. 
+For apps and extensions that still work when plugins aren't available,
+you can disable this requirement
+by setting NPAPI to false.
+You can also enable this requirement manually,
+by setting NPAPI to true,
+as shown in this example:
+</p>
+
+<pre>
+"requirements": {
+  "plugins": {
+    "npapi": true
+  }
+}
+</pre>
+
+
+<h3 id="version">version</h3>
+
+<p>
+One to four dot-separated integers
+identifying the version of this extension.
+A couple of rules apply to the integers:
+they must be between 0 and 65535, inclusive,
+and non-zero integers can't start with 0.
+For example, 99999 and 032 are both invalid.
+</p>
+
+<p>
+Here are some examples of valid versions:
+</p>
+
+<ul>
+  <li> <code>"version": "1"</code> </li>
+  <li> <code>"version": "1.0"</code> </li>
+  <li> <code>"version": "2.10.2"</code> </li>
+  <li> <code>"version": "3.1.2.4567"</code> </li>
+</ul>
+
+<p>
+The autoupdate system compares versions
+to determine whether an installed extension
+needs to be updated.
+If the published extension has a newer version string
+than the installed extension,
+then the extension is automatically updated.
+</p>
+
+<p>
+The comparison starts with the leftmost integers.
+If those integers are equal,
+the integers to the right are compared,
+and so on.
+For example, 1.2.0 is a newer version than 1.1.9.9999.
+</p>
+
+<p>
+A missing integer is equal to zero.
+For example, 1.1.9.9999 is newer than 1.1.
+</p>
+
+<p>
+For more information, see
+<a href="autoupdate.html">Autoupdating</a>.
+</p>
+
+
+
+<h3 id="manifest_version">manifest_version</h3>
+
+<p>
+One integer specifying the version of the manifest file format your package
+requires. As of Chrome 18, developers <em>should</em> specify <code>2</code>
+(without quotes) to use the format as described by this document:
+</p>
+
+<pre>"manifest_version": 2</pre>
+
+<p>
+Consider manifest version 1 <em>deprecated</em> as of Chrome 18. Version 2 is
+not yet <em>required</em>, but we will, at some point in the not-too-distant
+future, stop supporting packages using deprecated manifest versions. Extensions,
+applications, and themes that aren't ready to make the jump to the new manifest
+version in Chrome 18 can either explicitly specify version <code>1</code>, or
+leave the key off entirely.
+</p>
+
+<p>
+The changes between version 1 and version 2 of the manifest file format are
+described in detail in <a href="manifestVersion.html">the
+<code>manifest_version</code> documentation.</a>
+</p>
+
+<p class="caution">
+  Setting <code>manifest_version</code> 2 in Chrome 17 or lower is not
+  recommended. If your extension needs to work in older versions of Chrome,
+  stick with version 1 for the moment. We'll give you ample warning before
+  version 1 stops working.
+</p>
+
+<h3 id="web_accessible_resources">web_accessible_resources</h3>
+
+<p>
+An array of strings specifying the paths (relative to the package root) of
+packaged resources that are expected to be usable in the context of a web page.
+For example, an extension that injects a content script with the intention of
+building up some custom interface for <code>example.com</code> would whitelist
+any resources that interface requires (images, icons, stylesheets, scripts,
+etc.) as follows:
+</p>
+
+<pre>{
+  ...
+  "web_accessible_resources": [
+    "images/my-awesome-image1.png",
+    "images/my-amazing-icon1.png",
+    "style/double-rainbow.css",
+    "script/double-rainbow.js"
+  ],
+  ...
+}</pre>
+
+<p>
+These resources would then be available in a webpage via the URL
+<code>chrome-extension://[PACKAGE ID]/[PATH]</code>, which can be generated with
+the <a href="extension.html#method-getURL">
+  <code>chrome.extension.getURL</code>
+</a> method. Whitelisted resources are served with appropriate
+<a href="http://www.w3.org/TR/cors/">CORS</a> headers, so they're available via
+mechanisms like XHR.
+</p>
+
+<p>
+Injected content scripts themselves do not need to be whitelisted.
+</p>
+
+<p>
+Prior to manifest version 2 all resources within an extension could be accessed
+from any page on the web. This allowed a malicious website to
+<a href="http://en.wikipedia.org/wiki/Device_fingerprint">fingerprint</a> the
+extensions that a user has installed or exploit vulnerabilities (for example
+<a href="http://en.wikipedia.org/wiki/Cross-site_scripting">XSS bugs</a>)within
+installed extensions. Limiting availability to only resources which are
+explicitly intended to be web accessible serves to both minimize the available
+attack surface and protect the privacy of users.
+</p>
+
+<h4 id="availability">Default Availability</h4>
+
+<p>
+Resources inside of packages using <a href="#manifest_version"><code>manifest_version</code></a>
+2 or above are <strong>blocked by default</strong>, and must be whitelisted
+for use via this property.
+</p>
+
+<p>
+Resources inside of packages using <code>manifest_version</code> 1 are available
+by default, but <em>if</em> you do set this property, then it will be treated as
+a complete list of all whitelisted resources. Resources not listed will be
+blocked.
+</p>
+
+<h3 id="sandbox">sandbox</h3>
+
+<p>
+Defines an collection of app or extension pages that are to be served
+in a sandboxed unique origin, and optionally a Content Security Policy to use
+with them. Being in a sandbox has two implications:
+</p>
+
+<ol>
+<li>A sandboxed page will not have access to extension or app APIs, or
+direct access to non-sandboxed pages (it may communicate with them via
+<code>postMessage()</code>).</li>
+<li>
+  <p>A sandboxed page is not subject to the
+  <a href="../extensions/contentSecurityPolicy.html">Content Security Policy
+  (CSP)</a> used by the rest of the app or extension (it has its own separate
+  CSP value). This means that, for example, it can use inline script and
+  <code>eval</code>.</p>
+
+  <p>For example, here's how to specify that two extension pages are to be
+  served in a sandbox with a custom CSP:</p>
+
+  <pre>{
+  ...
+  "sandbox": {
+    "pages": [
+      "page1.html",
+      "directory/page2.html"
+    ]
+    <i>// content_security_policy is optional.</i>
+    "content_security_policy":
+        "sandbox allow-scripts; script-src https://www.google.com"
+  ],
+  ...
+}</pre>
+
+  <p>
+  If not specified, the default <code>content_security_policy</code> value is
+  <code>sandbox allow-scripts allow-forms</code>. You can specify your CSP
+  value to restrict the sandbox even further, but it must have the <code>sandbox</code>
+  directive and may not have the <code>allow-same-origin</code> token (see
+  <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html#attr-iframe-sandbox">the
+  HTML5 specification</a> for possible sandbox tokens).
+  </p>
+</li>
+</ol>
+
+<p>
+Note that you only need to list pages that you expected to be loaded in
+windows or frames. Resources used by sandboxed pages (e.g. stylesheets or
+JavaScript source files) do not need to appear in the
+<code>sandboxed_page</code> list, they will use the sandbox of the page
+that embeds them.
+</p>
+
+<p>
+<a href="sandboxingEval.html">"Using eval in Chrome Extensions. Safely."</a>
+goes into more detail about implementing a sandboxing workflow that enables use
+of libraries that would otherwise have issues executing under extension's
+<a href="../extensions/contentSecurityPolicy.html">default Content Security
+Policy</a>.
+</p>
+
+<p>
+Sandboxed page may only be specified when using
+<a href="#manifest_version"><code>manifest_version</code></a> 2 or above.
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/manifestVersion.html b/chrome/common/extensions/docs/templates/articles/manifestVersion.html
new file mode 100644
index 0000000..a39ebb3
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/manifestVersion.html
@@ -0,0 +1,241 @@
+<h1>Manifest Version</h1>
+
+<style type="text/css">
+#schedule h3 {
+  margin-bottom:0;
+}
+
+#schedule p {
+  margin-top:0;
+  margin-bottom:1em;
+}
+</style>
+
+<p>
+  Extensions, themes, and applications are simply bundles of resources, wrapped
+  up with a <a href="manifest.html"><code>manifest.json</code></a> file that
+  describes the package's contents. The format of this file is generally stable,
+  but occasionally breaking changes must be made to address important issues.
+  Developers should specify which version of the manifest specification their
+  package targets by setting a <code>manifest_version</code> key in their
+  manifests.
+</p>
+
+<h2 id="current-version">Current Version</h2>
+
+<p>
+  Developers should currently specify
+  <strong><code>'manifest_version': 2</code></strong>:
+</p>
+
+<pre>{
+  ...,
+  "manifest_version": 2,
+  ...
+}</pre>
+
+<p>
+  Manifest version 1 was <em>deprecated</em> in Chrome 18, and support will
+  be phased out according to the following schedule.
+</p>
+
+<h2 id="manifest-v1-support-schedule">Manifest version 1 support schedule</h2>
+
+<div id="schedule">
+<h3 id="manifest-v1-m21">Chrome 21</h3>
+<p><em>Beta: Early July 2012;
+  Stable: Mid-August 2012</em></p>
+<ul>
+  <li>The Web Store will block creation of new manifest version 1 items.
+  <li>The Web Store will allow updates to existing manifest version 1 items.
+</ul>
+
+<h3 id="manifest-v1-m23">Chrome 23</h3>
+<p><em>Beta: Late September 2012;
+  Stable: Early November 2012</em></p>
+<ul>
+  <li>The Web Store will block updates to manifest version 1 items.
+  <li>Chrome will stop packaging manifest version 1 items (or loading
+    them for development).
+</ul>
+
+<h3 id="manifest-v1-2012q1">First Quarter 2013</h3>
+<ul>
+  <li>The Web Store will remove manifest version 1 items from the wall,
+    search results, and category pages.
+  <li>Notice emails will be sent to all developers with manifest
+    version 1 items still in the store reminding them that these
+    items will be unpublished and providing update instructions.
+</ul>
+
+<h3 id="manifest-v1-2012q2">Second Quarter 2013</h3>
+<ul>
+  <li>The Web Store will unpublish all manifest version 1 items.
+  <li>Final notice emails will be sent to developers with manifest
+    version 1 items still in the Web Store.
+  <li>Chrome will continue to load and run installed manifest
+    version 1 items.
+</ul>
+
+<h3 id="manifest-v1-2012q3">Third Quarter 2013</h3>
+<ul>
+  <li>Chrome will stop loading or running manifest
+    version 1 items.
+</ul>
+</div>
+
+<h2 id="manifest-v1-changes">Changes between version 1 and 2</h2>
+
+<ul>
+  <li>
+    <p>
+      A content security policy is set to <code>`script-src 'self'
+      chrome-extension-resource:; object-src 'self'</code> by default. This has
+      a variety of impacts on developers, described at length in the
+      <a href="contentSecurityPolicy.html">
+      <code>content_security_policy</code></a> documentation.
+    </p>
+  </li>
+  <li>
+    <p>
+      A package's resources are no longer available by default to external
+      websites (as the <code>src</code> of an image, or a <code>script</code>
+      tag). If you want a website to be able to load a resource contained in
+      your package, you'll need to explicitly whitelist it via the
+      <a href="manifest.html#web_accessible_resources">
+        <code>web_accessible_resources</code>
+      </a> manifest attribute. This is particularly relevant for extensions that
+      build up an interface on a website via injected content scripts.
+    </p>
+  </li>
+  <li>
+    <p>
+      The <code>background_page</code> property has been replaced with a
+      <code>background</code> property that contains <em>either</em> a
+      <code>scripts</code> or <code>page</code> property. Details are available
+      in the <a href="event_pages.html">Event Pages</a> documentation.
+    </p>
+  </li>
+  <li>
+    <p>Browser action changes:</p>
+    <ul>
+      <li>
+        <p>
+          The <code>browser_actions</code> key in the manifest, and the
+          <code>chrome.browserActions</code> API are gone. Use the singular
+          <a href="browserAction.html">
+            <code>browser_action</code> and <code>chrome.browserAction</code>
+          </a> instead.
+        </p>
+      </li>
+      <li>
+        <p>
+          The <code>icons</code> property of <code>browser_action</code> has
+          been removed. Use <a href="browserAction.html#manifest">
+            the <code>default_icon</code> property
+          </a> or <a href="browserAction.html#method-setIcon">
+            <code>chrome.browserAction.setIcon</code>
+          </a> instead.
+        </p>
+      </li>
+      <li>
+        <p>
+          The <code>name</code> property of <code>browser_action</code> has been
+          removed. Use <a href="browserAction.html#manifest">
+            the <code>default_title</code> property
+          </a> or <a href="browserAction.html#method-setTitle">
+            <code>chrome.browserAction.setTitle</code>
+          </a> instead.
+        </p>
+      </li>
+      <li>
+        <p>
+          The <code>popup</code> property of <code>browser_action</code> has
+          been removed. Use <a href="browserAction.html#manifest">
+            the <code>default_popup</code> property
+          </a> or <a href="browserAction.html#method-setPopup">
+            <code>chrome.browserAction.setPopup</code>
+          </a> instead.
+        </p>
+      </li>
+      <li>
+        <p>
+          The <code>default_popup</code> property of <code>browser_action</code>
+          can no longer be specified as an object. It must be a string.
+        </p>
+      </li>
+    </ul>
+  </li>
+  <li>
+    <p>Page action changes:</p>
+    <ul>
+      <li>
+        <p>
+          The <code>page_actions</code> key in the manifest, and the
+          <code>chrome.pageActions</code> API are gone. Use the singular
+          <a href="pageAction.html">
+            <code>page_action</code> and <code>chrome.pageAction</code>
+          </a> instead.
+        </p>
+      </li>
+      <li>
+        <p>
+          The <code>icons</code> property of <code>page_action</code> has been
+          removed. Use <a href="pageAction.html#manifest">
+            the <code>default_icon</code> property
+          </a> or <a href="pageAction.html#method-setIcon">
+            <code>chrome.pageAction.setIcon</code>
+          </a> instead.
+        </p>
+      </li>
+      <li>
+        <p>
+          The <code>name</code> property of <code>page_action</code> has been
+          removed. Use <a href="pageAction.html#manifest">
+            the <code>default_title</code> property
+          </a> or <a href="pageAction.html#method-setTitle">
+            <code>chrome.pageAction.setTitle</code>
+          </a> instead.
+        </p>
+      </li>
+      <li>
+        <p>
+          The <code>popup</code> property of <code>page_action</code> has been
+          removed. Use <a href="pageAction.html#manifest">
+            the <code>default_popup</code> property
+          </a> or <a href="pageAction.html#method-setPopup">
+            <code>chrome.pageAction.setPopup</code>
+          </a> instead.
+        </p>
+      </li>
+      <li>
+        <p>
+          The <code>default_popup</code> property of <code>page_action</code>
+          can no longer be specified as an object. It must be a string.
+        </p>
+      </li>
+      <li>
+        <p>
+          The <code>chrome.self</code> API has been removed. Use
+          <a href="extension.html"><code>chrome.extension</code></a> instead.
+        </p>
+      </li>
+    </ul>
+  </li>
+  <li>
+    <p>
+      <code>chrome.extension.getTabContentses</code> (!!!) and
+      <code>chrome.extension.getExtensionTabs</code> are gone. Use
+      <a href="extension.html#method-getViews">
+        <code>chrome.extension.getViews({ "type": "tab" })</code>
+      </a> instead.
+    </p>
+  </li>
+  <li>
+    <p>
+      <code>Port.tab</code> is gone. Use
+      <a href="extension.html#type-Port"><code>Port.sender</code></a>
+      instead.
+    </p>
+  </li>
+</ul>
diff --git a/chrome/common/extensions/docs/templates/articles/match_patterns.html b/chrome/common/extensions/docs/templates/articles/match_patterns.html
new file mode 100644
index 0000000..204f878
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/match_patterns.html
@@ -0,0 +1,261 @@
+<h1>Match Patterns</h1>
+
+<p>
+<a href="content_scripts.html">Content scripts</a> operate on
+a set of URLs defined by match patterns.
+You can put one or more match patterns
+in the <code>"matches"</code> part of
+a content script's section of the manifest,
+as well as in the <code>"exclude_matches"</code> section.
+This page describes the match pattern syntax &mdash;
+the rules you need to follow when you specify
+which URLs your content script affects.
+</p>
+
+<p>
+A match pattern is essentially a URL
+that begins with a permitted scheme (<code>http</code>,
+<code>https</code>, <code>file</code>, <code>ftp</code>, or
+<code>chrome-extension</code>),
+and that can contain '<code>*</code>' characters.
+The special pattern
+<code>&lt;all_urls&gt;</code> matches any URL
+that starts with a permitted scheme.
+Each match pattern has 3 parts:</p>
+</p>
+
+<ul>
+  <li> <em>scheme</em> &mdash;
+    for example, <code>http</code> or <code>file</code>
+    or <code>*</code>
+    <p class="note">
+    <b>Note:</b>
+    Access to <code>file</code> URLs isn't automatic.
+    The user must visit the extensions management page
+    and opt in to <code>file</code> access for each extension that requests it.
+    </p>
+  </li>
+  <li> <em>host</em> &mdash;
+    for example, <code>www.google.com</code>
+    or <code>*.google.com</code>
+    or <code>*</code>;
+    if the scheme is <code>file</code>,
+    there is no <em>host</em> part
+  </li>
+  <li> <em>path</em> &mdash;
+    for example, <code>/*</code>, <code>/foo* </code>,
+    or <code>/foo/bar </code>
+  </li>
+</ul>
+
+<p>Here's the basic syntax:</p>
+
+<pre>
+<em>&lt;url-pattern&gt;</em> := <em>&lt;scheme&gt;</em>://<em>&lt;host&gt;</em><em>&lt;path&gt;</em>
+<em>&lt;scheme&gt;</em> := '*' | 'http' | 'https' | 'file' | 'ftp' | 'chrome-extension'
+<em>&lt;host&gt;</em> := '*' | '*.' <em>&lt;any char except '/' and '*'&gt;</em>+
+<em>&lt;path&gt;</em> := '/' <em>&lt;any chars&gt;</em>
+</pre>
+
+<p>
+The meaning of '<code>*</code>' depends on whether
+it's in the <em>scheme</em>, <em>host</em>, or <em>path</em> part.
+If the <em>scheme</em> is <code>*</code>,
+then it matches either <code>http</code> or <code>https</code>.
+If the <em>host</em> is just <code>*</code>,
+then it matches any host.
+If the <em>host</em> is <code>*.<em>hostname</em></code>,
+then it matches the specified host or any of its subdomains.
+In the <em>path</em> section,
+each '<code>*</code>' matches 0 or more characters.
+The following table shows some valid patterns.
+</p>
+
+<table class="simple">
+<tbody>
+<tr>
+  <th style="margin-left:0; padding-left:0">Pattern</th>
+  <th style="margin-left:0; padding-left:0">What it does</th>
+  <th style="margin-left:0; padding-left:0">Examples of matching URLs</th>
+</tr>
+
+<tr>
+  <td>
+    <code>http://*/*</code>
+  </td>
+
+  <td>Matches any URL that uses the <code>http</code> scheme</td>
+
+  <td>
+    http://www.google.com/<br>
+    http://example.org/foo/bar.html
+  </td>
+</tr>
+
+<tr>
+  <td>
+    <code>http://*/foo*</code>
+  </td>
+
+  <td>
+    Matches any URL that uses the <code>http</code> scheme, on any host,
+    as long as the path starts with <code>/foo</code>
+  </td>
+
+  <td>
+    http://example.com/foo/bar.html<br>
+    http://www.google.com/foo<b></b>
+  </td>
+</tr>
+
+<tr>
+  <td>
+    <code>https://*.google.com/foo*bar </code>
+  </td>
+
+  <td>
+    Matches any URL that uses the <code>https</code> scheme,
+    is on a google.com host
+    (such as www.google.com, docs.google.com, or google.com),
+    as long as the path starts with <code>/foo</code>
+    and ends with <code>bar</code>
+  </td>
+
+  <td>
+    http://www.google.com/foo/baz/bar<br>
+    http://docs.google.com/foobar
+  </td>
+</tr>
+
+<tr>
+  <td>
+    <code>http://example.org/foo/bar.html </code>
+  </td>
+
+  <td>Matches the specified URL</td>
+
+  <td>
+    http://example.org/foo/bar.html
+  </td>
+</tr>
+
+<tr>
+  <td>
+    <code>file:///foo*</code>
+  </td>
+
+  <td>Matches any local file whose path starts with <code>/foo</code>
+  </td>
+
+  <td>
+    file:///foo/bar.html<br>
+    file:///foo
+  </td>
+</tr>
+
+<tr>
+  <td>
+    <code>http://127.0.0.1/*</code>
+  </td>
+
+  <td>
+    Matches any URL that uses the <code>http</code> scheme
+    and is on the host 127.0.0.1
+  </td>
+  <td>
+    http://127.0.0.1/<br>
+    http://127.0.0.1/foo/bar.html
+  </td>
+</tr>
+
+<tr>
+  <td>
+    <code>*://mail.google.com/* </code>
+  </td>
+
+  <td>
+    Matches any URL that starts with
+    <code>http://mail.google.com</code> or
+    <code>https://mail.google.com</code>.
+  </td>
+
+  <td>
+    http://mail.google.com/foo/baz/bar<br>
+    https://mail.google.com/foobar
+  </td>
+</tr>
+
+<tr>
+  <td>
+    <code>chrome-extension://*/* </code>
+  </td>
+
+  <td>
+    Matches any URL pointing to an extension (the first <code>*</code>
+    represents a filter for extension IDs, the second for paths).
+  </td>
+
+  <td>
+    chrome-extension://askla...asdf/options.html
+  </td>
+</tr>
+
+<tr>
+  <td>
+    <code>&lt;all_urls&gt;</code>
+  </td>
+
+  <td>
+    Matches any URL that uses a permitted scheme.
+    (See the beginning of this section for the list of permitted
+    schemes.)
+  </td>
+  <td>
+    http://example.org/foo/bar.html<br>
+    file:///bar/baz.html
+  </td>
+</tr>
+</tbody>
+</table>
+
+<p>
+Here are some examples of <em>invalid</em> pattern matches:
+</p>
+
+<table class="simple">
+<tbody>
+<tr>
+  <th style="margin-left:0; padding-left:0">Bad pattern</th>
+  <th style="margin-left:0; padding-left:0">Why it's bad</th>
+</tr>
+
+<tr>
+  <td><code>http://www.google.com</code></td>
+  <td>No <em>path</em></td>
+</tr>
+
+<tr>
+  <td><code>http://*foo/bar</code></td>
+  <td>'*' in the <em>host</em> can be followed only by a '.' or '/'</td>
+</tr>
+
+<tr>
+  <td><code>http://foo.*.bar/baz&nbsp; </code></td>
+  <td>If '*' is in the <em>host</em>, it must be the first character</td>
+  </tr>
+
+<tr>
+  <td><code>http:/bar</code></td>
+  <td>Missing <em>scheme</em> separator ("/" should be "//")</td>
+</tr>
+
+<tr>
+  <td><code>foo://*</code></td>
+  <td>Invalid <em>scheme</em></td>
+</tr>
+</tbody>
+</table>
+
+<p>
+Some schemes are not supported in all contexts.
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/messaging.html b/chrome/common/extensions/docs/templates/articles/messaging.html
new file mode 100644
index 0000000..fa1f32d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/messaging.html
@@ -0,0 +1,275 @@
+<h1>Message Passing</h1>
+
+
+<p>
+Since content scripts run in the context of a web page and not the extension,
+they often need some way of communicating with the rest of the extension. For
+example, an RSS reader extension might use content scripts to detect the
+presence of an RSS feed on a page, then notify the background page in order to
+display a page action icon for that page.
+
+<p>
+Communication between extensions and their content scripts works by using
+message passing. Either side can listen for messages sent from the other end,
+and respond on the same channel. A message can contain any valid JSON object
+(null, boolean, number, string, array, or object). There is a simple API for
+<a href="#simple">one-time requests</a>
+and a more complex API that allows you to have
+<a href="#connect">long-lived connections</a>
+for exchanging multiple messages with a shared context. It is also possible to
+send a message to another extension if you know its ID, which is covered in
+the
+<a href="#external">cross-extension messages</a>
+section.
+
+
+<h2 id="simple">Simple one-time requests</h2>
+<p>
+If you only need to send a single message to another part of your extension
+(and optionally get a response back), you should use the simplified
+<a href="extension.html#method-sendMessage">chrome.extension.sendMessage()</a>
+or
+<a href="tabs.html#method-sendMessage">chrome.tabs.sendMessage()</a>
+methods. This lets you send a one-time JSON-serializable message from a
+content script to extension, or vice versa, respectively. An optional
+callback parameter allows you handle the response from the other side, if
+there is one.
+
+<p>
+Sending a request from a content script looks like this:
+<pre>
+contentscript.js
+================
+chrome.extension.sendMessage({greeting: "hello"}, function(response) {
+  console.log(response.farewell);
+});
+</pre>
+
+<p>
+Sending a request from the extension to a content script looks very similar,
+except that you need to specify which tab to send it to. This example
+demonstrates sending a message to the content script in the selected tab.
+<pre>
+background.html
+===============
+chrome.tabs.getSelected(null, function(tab) {
+  chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
+    console.log(response.farewell);
+  });
+});
+</pre>
+
+<p>
+On the receiving end, you need to set up an
+<a href="extension.html#event-onMessage">chrome.extension.onMessage</a>
+event listener to handle the message. This looks the same from a content
+script or extension page.
+<pre>
+chrome.extension.onMessage.addListener(
+  function(request, sender, sendResponse) {
+    console.log(sender.tab ?
+                "from a content script:" + sender.tab.url :
+                "from the extension");
+    if (request.greeting == "hello")
+      sendResponse({farewell: "goodbye"});
+  });
+</pre>
+
+<p class="note">
+<b>Note:</b> If multiple pages are listening for onMessage events, only the
+first to call sendResponse() for a particular event will succeed in sending the
+response. All other responses to that event will be ignored.
+</p>
+
+
+<h2 id="connect">Long-lived connections</h2>
+<p>
+Sometimes it's useful to have a conversation that lasts longer than a single
+request and response. In this case, you can open a long-lived channel from
+your content script to an extension page, or vice versa, using
+<a href="extension.html#method-connect">chrome.extension.connect()</a>
+or
+<a href="tabs.html#method-connect">chrome.tabs.connect()</a> respectively. The
+channel can optionally have a name, allowing you to distinguish between
+different types of connections.
+
+<p>
+One use case might be an automatic form fill extension. The content script
+could open a channel to the extension page for a particular login, and send a
+message to the extension for each input element on the page to request the
+form data to fill in. The shared connection allows the extension to keep
+shared state linking the several messages coming from the content script.
+
+<p>
+When establishing a connection, each end is given a
+<a href="extension.html#type-Port">Port</a>
+object which is used for sending and receiving messages through that
+connection.
+
+<p>
+Here is how you open a channel from a content script, and send and listen for
+messages:
+<pre>
+contentscript.js
+================
+var port = chrome.extension.connect({name: "knockknock"});
+port.postMessage({joke: "Knock knock"});
+port.onMessage.addListener(function(msg) {
+  if (msg.question == "Who's there?")
+    port.postMessage({answer: "Madame"});
+  else if (msg.question == "Madame who?")
+    port.postMessage({answer: "Madame... Bovary"});
+});
+</pre>
+
+<p>
+Sending a request from the extension to a content script looks very similar,
+except that you need to specify which tab to connect to. Simply replace the
+call to connect in the above example with
+<a href="tabs.html#method-connect">chrome.tabs.connect(tabId, {name:
+"knockknock"})</a>.
+
+<p>
+In order to handle incoming connections, you need to set up a
+<a href="extension.html#event-onConnect">chrome.extension.onConnect</a>
+event listener. This looks the same from a content script or an extension
+page. When another part of your extension calls "connect()", this event is
+fired, along with the
+<a href="extension.html#type-Port">Port</a>
+object you can use to send and receive messages through the connection. Here's
+what it looks like to respond to incoming connections:
+<pre>
+chrome.extension.onConnect.addListener(function(port) {
+  console.assert(port.name == "knockknock");
+  port.onMessage.addListener(function(msg) {
+    if (msg.joke == "Knock knock")
+      port.postMessage({question: "Who's there?"});
+    else if (msg.answer == "Madame")
+      port.postMessage({question: "Madame who?"});
+    else if (msg.answer == "Madame... Bovary")
+      port.postMessage({question: "I don't get it."});
+  });
+});
+</pre>
+
+<p>
+You may want to find out when a connection is closed, for example if you are
+maintaining separate state for each open port. For this you can listen to the
+<a href="extension.html#type-Port">Port.onDisconnect</a>
+event. This event is fired either when the other side of the channel manually
+calls
+<a href="extension.html#type-Port">Port.disconnect()</a>, or when the page
+containing the port is unloaded (for example if the tab is navigated).
+onDisconnect is guaranteed to be fired only once for any given port.
+
+
+<h2 id="external">Cross-extension messaging</h2>
+<p>
+In addition to sending messages between different components in your
+extension, you can use the messaging API to communicate with other extensions.
+This lets you expose a public API that other extensions can take advantage of.
+
+<p>
+Listening for incoming requests and connections is similar to the internal
+case, except you use the
+<a href="extension.html#event-onMessageExternal">chrome.extension.onMessageExternal</a>
+or
+<a href="extension.html#event-onConnectExternal">chrome.extension.onConnectExternal</a>
+methods. Here's an example of each:
+<pre>
+// For simple requests:
+chrome.extension.onMessageExternal.addListener(
+  function(request, sender, sendResponse) {
+    if (sender.id == blacklistedExtension)
+      return;  // don't allow this extension access
+    else if (request.getTargetData)
+      sendResponse({targetData: targetData});
+    else if (request.activateLasers) {
+      var success = activateLasers();
+      sendResponse({activateLasers: success});
+    }
+  });
+
+// For long-lived connections:
+chrome.extension.onConnectExternal.addListener(function(port) {
+  port.onMessage.addListener(function(msg) {
+    // See other examples for sample onMessage handlers.
+  });
+});
+</pre>
+
+<p>
+Likewise, sending a message to another extension is similar to sending one
+within your extension. The only difference is that you must pass the ID of the
+extension you want to communicate with. For example:
+<pre>
+// The ID of the extension we want to talk to.
+var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
+
+// Make a simple request:
+chrome.extension.sendMessage(laserExtensionId, {getTargetData: true},
+  function(response) {
+    if (targetInRange(response.targetData))
+      chrome.extension.sendMessage(laserExtensionId, {activateLasers: true});
+  });
+
+// Start a long-running conversation:
+var port = chrome.extension.connect(laserExtensionId);
+port.postMessage(...);
+</pre>
+
+<h2 id="security-considerations">Security considerations</h2>
+
+<p>
+When receiving a message from a content script or another extension, your
+background page should be careful not to fall victim to <a
+href="http://en.wikipedia.org/wiki/Cross-site_scripting">cross-site
+scripting</a>.  Specifically, avoid using dangerous APIs such as the
+below:
+</p>
+<pre>background.html
+===============
+chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
+  // WARNING! Might be evaluating an evil script!
+  var resp = eval("(" + response.farewell + ")");
+});
+
+background.html
+===============
+chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
+  // WARNING! Might be injecting a malicious script!
+  document.getElementById("resp").innerHTML = response.farewell;
+});
+</pre>
+<p>
+Instead, prefer safer APIs that do not run scripts:
+</p>
+<pre>background.html
+===============
+chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
+  // JSON.parse does not evaluate the attacker's scripts.
+  var resp = JSON.parse(response.farewell);
+});
+
+background.html
+===============
+chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
+  // innerText does not let the attacker inject HTML elements.
+  document.getElementById("resp").innerText = response.farewell;
+});
+</pre>
+
+<h2 id="examples">Examples</h2>
+
+<p>
+You can find simple examples of communication via messages in the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/messaging/">examples/api/messaging</a>
+directory.
+Also see the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/howto/contentscript_xhr">contentscript_xhr</a> example,
+in which a content script and its parent extension exchange messages,
+so that the parent extension can perform
+cross-site requests on behalf of the content script.
+For more examples and for help in viewing the source code, see
+<a href="samples.html">Samples</a>.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/notifications.html b/chrome/common/extensions/docs/templates/articles/notifications.html
new file mode 100644
index 0000000..20e74c2
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/notifications.html
@@ -0,0 +1,116 @@
+<h1>Desktop Notifications</h1>
+
+
+
+<p>
+Use desktop notifications to notify users that something
+important has happened.
+Notifications appear outside the browser window.
+As the following snapshots show,
+the details of how notifications look
+and where they're shown depend on the platform.
+</p>
+
+<img src="{{static}}/images/notification-windows.png"
+  width="28%" style="margin:2em 0.5em 1em; border:1px solid black;"
+  alt="Notifications on Microsoft Windows"/>
+<img src="{{static}}/images/notification-mac.png"
+  width="28%" style="margin:2em 0.5em 1em; border:1px solid black;"
+  alt="Notifications on Mac OS X"/>
+<img src="{{static}}/images/notification-linux.png"
+  width="28%" style="margin:2em 0.5em 1em; border:1px solid black;"
+  alt="Notifications on Ubuntu Linux"/>
+
+<p>
+You create the notification window
+using a bit of JavaScript and, optionally,
+an HTML page packaged inside your extension.
+</p>
+
+
+<h2 id="example">Example</h2>
+
+<p>First, declare the <code>notifications</code> permission in your manifest:</p>
+<pre>
+{
+  "name": "My extension",
+  "manifest_version": 2,
+  ...
+<b>  "permissions": [
+    "notifications"
+  ]</b>,
+  ...
+  // <strong>Note:</strong> Because of <a href="http://code.google.com/p/chromium/issues/detail?id=134315">bug 134315</a>, you must declare any images you
+  // want to use with createNotification() as a web accessible resource.
+<b>  "web_accessible_resources": [
+    "48.png"
+  ]</b>,
+}
+</pre>
+
+<p>Then, use <code>webkitNotifications</code> object to create notifications:</p>
+
+<pre>
+// <strong>Note:</strong> There's no need to call webkitNotifications.checkPermission().
+// Extensions that declare the <em>notifications</em> permission are always
+// allowed create notifications.
+
+// Create a simple text notification:
+var notification = webkitNotifications.createNotification(
+  '48.png',  // icon url - can be relative
+  'Hello!',  // notification title
+  'Lorem ipsum...'  // notification body text
+);
+
+// Or create an HTML notification:
+var notification = webkitNotifications.createHTMLNotification(
+  'notification.html'  // html url - can be relative
+);
+
+// Then show the notification.
+notification.show();
+</pre>
+
+<h2 id="reference">API Reference</h2>
+
+<p>See the <a href="http://dev.chromium.org/developers/design-documents/desktop-notifications/api-specification">Desktop Notifications Draft Specification</a>.</p>
+
+
+<h2 id="communication">Communicating with Other Views</h2>
+
+<p>
+You can communicate between a notification
+and other views in your extension using
+<a href="extension.html#method-getBackgroundPage">getBackgroundPage()</a> and
+<a href="extension.html#method-getViews">getViews()</a>. For example:
+</p>
+
+<pre>
+// Inside a notification...
+chrome.extension.getBackgroundPage().doThing();
+
+// From the background page...
+chrome.extension.getViews({type:"notification"}).forEach(function(win) {
+  win.doOtherThing();
+});
+</pre>
+
+
+<h2 id="examples">More Examples</h2>
+
+<p>
+You can find a simple example
+of using notifications in the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/notifications/">examples/api/notifications</a>
+directory.
+For other examples
+and for help in viewing the source code,
+see <a href="samples.html">Samples</a>.
+</p>
+
+<p>
+Also see html5rocks.com's
+<a href="http://www.html5rocks.com/tutorials/notifications/quick/">notifications tutorial</a>.
+Ignore the permission-related code;
+it's unnecessary if you declare the "notifications" permission.
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/npapi.html b/chrome/common/extensions/docs/templates/articles/npapi.html
new file mode 100644
index 0000000..a47d75e
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/npapi.html
@@ -0,0 +1,97 @@
+<h1>NPAPI Plugins</h1>
+
+<p>
+Leveraging HTML and JavaScript
+makes developing new extensions really easy,
+but what if you have existing legacy or proprietary code
+that you want to reuse in your extension?
+You can bundle an NPAPI plugin with your extension,
+allowing you to call into native binary code from JavaScript.
+</p>
+
+<h2 id="warning">Warning</h2>
+
+<p align="center"><b>NPAPI is a really big hammer that should only be used when no other approach will work.</b>
+
+<p>Code running in an NPAPI plugin has the full permissions of the current user and is not sandboxed or shielded from malicious input by Google Chrome in any way. You should be especially cautious when processing input from untrusted sources, such as when working with <a href="content_scripts.html#security-considerations">content scripts</a> or XMLHttpRequest.
+
+<p>Because of the additional security risks NPAPI poses to users, extensions that use it will require manual review before being accepted in the
+<a href="https://chrome.google.com/webstore">Chrome Web Store</a>.</p>
+
+<h2 id="details">Details</h2>
+
+<p>
+How to develop an NPAPI plugin is outside the scope of this document.
+See <a href="https://developer.mozilla.org/en/Plugins">Mozilla's
+NPAPI plugin reference</a> for information on how to do that.
+</p>
+
+<p>
+Once you have an NPAPI plugin,
+follow these steps to get your extension using it.
+</p>
+
+<ol>
+  <li>
+    Add a section to your extension's <code>manifest.json</code>
+    that describes where to find the plugin,
+    along with other properties about it:
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"plugins": [
+    { "path": "content_plugin.dll", "public": true },
+    { "path": "extension_plugin.dll" }
+  ]</b>,
+  ...
+}</pre>
+
+    <p>
+    The "path" property specifies the path to your plugin,
+    relative to the manifest file.
+    The "public" property specifies whether
+    your plugin can be accessed by regular web pages;
+    the default is false,
+    meaning only your extension can load the plugin.
+    </p>
+   </li>
+
+   <li>
+     Create an HTML file that loads your plugin by mime-type.
+     Assuming your mime-type is "application/x-my-extension":
+
+<pre>
+&lt;embed type="application/x-my-extension" id="pluginId"></embed>
+&lt;script>
+  var plugin = document.getElementById("pluginId");
+  var result = plugin.myPluginMethod();  // call a method in your plugin
+  console.log("my plugin returned: " + result);
+&lt;/script></pre>
+
+     <p>
+     This can be inside a background page
+     or any other HTML page used by your extension.
+     If your plugin is "public",
+     you can even use a content script to programmatically
+     insert your plugin into a web page.
+     </p>
+   </li>
+</ol>
+
+<h2 id="security-considerations">Security considerations</h2>
+
+<p>
+Including an NPAPI plugin in your extension is dangerous because plugins
+have unrestricted access to the local machine.  If your plugin contains
+a vulnerability, an attacker might be able to exploit that vulnerability
+to install malicious software on the user's machine.  Instead, avoid
+including an NPAPI plugin whenever possible.
+</p>
+
+<p>
+Marking your NPAPI plugin "public" increase the attack surface of your
+extension because the plugin is exposed directly to web content, making
+it easier for a malicious web site to manipulate your plugin.  Instead,
+avoid making your NPAPI plugin public whenever possible.
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/offline_apps.html b/chrome/common/extensions/docs/templates/articles/offline_apps.html
new file mode 100644
index 0000000..0879cd2
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/offline_apps.html
@@ -0,0 +1,313 @@
+<h1>Offline First</h1>
+
+
+<p>
+Because internet connections can be flakey or non-existent,
+you need to consider <em>offline first</em>:
+write your app as if it has no internet connection.
+Once your app works offline,
+add whatever network functionality you need
+for your app to do more when it’s online.
+Read on for tips on implementing your offline-enabled app.
+</p>
+
+<h2 id="overview"> Overview </h2>
+
+<p>
+Packaged apps get the following for free:
+</p>
+
+<ul>
+  <li> Your app’s files&mdash;all of its JavaScript,
+    CSS, and fonts, plus other resources it needs
+    (such as images)&mdash;are <b>already downloaded</b>. </li>
+  <li> Your app can <b>save and optionally sync</b>
+    small amounts of data using the
+    <a href="storage.html">Chrome Storage API</a>. </li>
+  <li> Your app can <b>detect changes in connectivity</b>
+    by listening for
+    <a href="https://developer.mozilla.org/en/Online_and_offline_events">online and offline events</a>. </li>
+</ul>
+
+<p>
+But those abilities aren't enough to guarantee that your app
+will work offline.
+Your offline-enabled app should follow these rules:
+</p>
+
+<dl>
+  <dt> Use local data whenever possible. </dt>
+  <dd> When using resources from the internet,
+    use <code>XMLHttpRequest</code> to get it,
+    and then save the data locally.
+    You can use the Chrome Storage API,
+    IndexedDB, or
+    Filesystem API to save data locally. </dd>
+
+  <dt> Separate your app’s UI from its data. </dt>
+  <dd>
+    Separating the UI and data not only
+    improves your app's design and
+    eases the task of enabling offline usage,
+    but also lets you provide other views of the user's data.
+    An MVC framework can help you keep the UI and data separate.
+  </dd>
+
+  <dt> Assume your app can be closed at any time. </dt>
+  <dd> Save application state
+    (both locally and remotely, when possible)
+    so that users can pick up
+    wherever they left off. </dd>
+
+  <dt> Test your app thoroughly. </dt>
+  <dd> Make sure your app works well in both
+    <a href="#testing">common and tricky scenarios</a>. </dd>
+</dl>
+
+
+<h2 id="possibilities"> Security restrictions </h2>
+
+<p>
+  Packaged apps are limited
+  in where they can place their resources:
+</p>
+
+<ul>
+  <li>
+    Because local data
+    is visible on the user's machine
+    and can't be securely encrypted,
+    <b>sensitive data must stay on the server</b>.
+    For example, don't store passwords or credit card numbers locally.
+    </li>
+  <li> All <b>JavaScript</b> that the app executes
+    must be in the app's package.
+    It <b>cannot</b> be inline.
+    <br />
+    <span class="comment">
+    {PENDING: Should "JavaScript" be "executable code"?
+    What about NaCl and Dart? Anything else -- shaders, e.g.?}
+    </span>
+    </li>
+  <li> All <b>CSS styles</b>, <b>images</b>, and <b>fonts</b>
+    can be initially located
+    either in the app's package
+    or at a remote URL.
+    If the resource is remote,
+    you can't specify it in your HTML.
+    Instead, get the data using <code>XMLHttpRequest</code>
+    (see <a href="app_external.html#external">Referencing external resources</a>).
+    Then either refer to the data with a blob URL
+    or (better yet) save and then load the data using the
+    <a href="app_storage.html">Filesystem API</a>.
+
+    <p class="note">
+      <b>Note:</b>
+      Styles can be inline or in separate <code>.css</code> files.
+    </p>
+
+    </li>
+</ul>
+
+<p>
+You can, however,
+load large media resources such as videos and sounds
+from external sites.
+One reason for this exception to the rule
+is that the &lt;video> and &lt;audio> elements
+have good fallback behavior when an app
+has limited or no connectivity.
+Another reason is that fetching and serving media
+with <code>XMLHttpRequest</code> and blob URLs
+currently does not allow
+streaming or partial buffering.
+</p>
+
+<p>
+To provide a sandboxed iframe,
+you can create an &lt;object> tag.
+Its contents can be remote,
+but it has no direct access to the Chrome app APIs
+(see <a href="app_external.html#objecttag">Embed external web pages</a>).
+</p>
+
+<p>
+Some of the restrictions on packaged apps are enforced by the 
+<a href="app_csp.html">Content Security Policy (CSP)</a>
+which is always the following and cannot be changed for packaged apps:
+</p>
+
+<pre>
+default-src 'self';
+connect-src *;
+style-src 'self' blob: data: filesystem: 'unsafe-inline';
+img-src 'self' blob: data: filesystem:;
+frame-src 'self' blob: data: filesystem:;
+font-src 'self' blob: data: filesystem:;
+media-src *;
+</pre>
+
+<h2 id="manifest"> Specifying offline_enabled </h2>
+
+<p>
+It is assumed that your app behaves well offline. If it doesn't, you should
+advertise that fact, so that its launch icon is dimmed when the user is offline.
+To do so, set <code>offline_enabled</code> to <code>false</code> in the
+<a href="manifest.html">app manifest file</a>:
+</p>
+
+<pre>
+{
+  "name": "My app",
+  ...
+  <b>"offline_enabled": false,</b>
+  ...
+}
+</pre>
+
+<p class="comment">
+{PENDING: should we link to <a href="https://chrome.google.com/webstore/category/collection/offline_enabled">Offline Apps collection</a>?
+show a screenshot of how offline apps are highlighted?
+anything else?}
+</p>
+
+<h2 id="saving-locally"> Saving data locally </h2>
+
+<p>
+The following table shows your options for saving data locally
+(see also <a href="app_storage.html">Manage Data</a>).
+</p>
+
+<table class="simple">
+<tr>
+  <th> API </th> <th> Best use </th> <th> Notes </th>
+</tr>
+<tr>
+  <td> Chrome Storage API </td>
+  <td> Small amounts of string data </td>
+  <td> Great for settings and state.
+       Easy to sync remotely (but you don't have to).
+       Not good for larger amounts of data,
+       due to quotas.
+       </td>
+</tr>
+<tr>
+  <td> IndexedDB API </td>
+  <td> Structured data </td>
+  <td> Enables fast searches on data.
+       Use with the
+       <a href="manifest.html#permissions">unlimitedStorage permission</a>. </td>
+</tr>
+<tr>
+  <td> Filesystem API </td>
+  <td> Anything else </td>
+  <td> Provides a sandboxed area where you can store files.
+       Use with the
+       <a href="manifest.html#permissions">unlimitedStorage permission</a>. </td>
+</tr>
+</table>
+
+<p class="note">
+<b>Note:</b>
+Packaged apps cannot use Web SQL Database or localStorage.
+The WebSQL specification has been deprecated for awhile now,
+and localStorage handles data synchronously
+(which means it can be slow).
+The Storage API handles data asynchronously.
+</p>
+
+
+<h2 id="saving-remotely"> Saving data remotely </h2>
+
+<p>
+In general, how you save data remotely is up to you,
+but some frameworks and APIs can help
+(see <a href="app_frameworks.html">MVC Architecture</a>).
+If you use the Chrome Storage API,
+then all syncable data
+is automatically synced
+whenever the app is online
+and the user is signed in to Chrome.
+If the user isn't signed in,
+they'll be prompted to sign in.
+However, note that the user's synced data
+is deleted if the user uninstalls your app.
+<span class="comment">
+{QUESTION: true?}
+</span>
+</p>
+
+<p>
+Consider saving users' data for at least
+30 days after your app is uninstalled,
+so that users will have a good experience
+if they reinstall your app.
+</p>
+
+
+<h2 id="mvc"> Separating UI from data </h2>
+
+<p>
+Using an MVC framework can help you design and implement your app
+so that the data is completely separate from the app's
+view on the data.
+See <a href="app_frameworks.html">MVC Architecture</a>
+for a list of MVC frameworks.
+</p>
+
+<p>
+If your app talks to a custom server,
+the server should give you data,
+not chunks of HTML.
+Think in terms of RESTful APIs.
+</p>
+
+<p>
+Once your data is separate from your app,
+it's much easier to provide alternate views of the data.
+For example,
+you might provide a website view of any public data.
+Not only can a website view
+be useful when your user is away from Chrome,
+but it can enable search engines to find the data.
+</p>
+
+
+<h2 id="testing"> Testing </h2>
+
+<p>
+Make sure your app works well under the following circumstances:
+</p>
+
+<ul>
+  <li>
+    The app is installed, and then immediately goes offline.
+    In other words, the first use of the app is offline.
+  </li>
+  <li>
+    The app is installed on one computer
+    and then synced to another.
+  </li>
+  <li>
+    The app is uninstalled and then immediately installed again.
+  </li>
+  <li>
+    The app is running on two computers at the same time,
+    with the same profile.
+    The app must behave reasonably
+    when one computer goes offline,
+    the user does a bunch of stuff on that computer,
+    and then the computer comes online again.
+  </li>
+  <li>
+    The app has intermittent connectivity,
+    switching often between online and offline.
+  </li>
+</ul>
+
+<p>
+Also make sure that the app saves <b>no sensitive user data</b>
+(such as passwords) on the user's machine.
+</p>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
diff --git a/chrome/common/extensions/docs/templates/articles/options.html b/chrome/common/extensions/docs/templates/articles/options.html
new file mode 100644
index 0000000..ddaecb4
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/options.html
@@ -0,0 +1,81 @@
+<h1>Options</h1>
+
+<p>To allow users to customize the behavior of your extension, you may wish to provide an options page. If you do, a link to it will be provided from the extensions management page at chrome://extensions. Clicking the Options link opens a new tab pointing at your options page.
+
+<h2 id="step_1">Step 1: Declare your options page in the manifest</h2>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"options_page": "options.html"</b>,
+  ...
+}</pre>
+
+
+<h2 id="step_2">Step 2: Write your options page</h2>
+
+Here is an example options page:
+
+<pre>// Save this script as `options.js`
+
+// Saves options to localStorage.
+function save_options() {
+  var select = document.getElementById("color");
+  var color = select.children[select.selectedIndex].value;
+  localStorage["favorite_color"] = color;
+
+  // Update status to let user know options were saved.
+  var status = document.getElementById("status");
+  status.innerHTML = "Options Saved.";
+  setTimeout(function() {
+    status.innerHTML = "";
+  }, 750);
+}
+
+// Restores select box state to saved value from localStorage.
+function restore_options() {
+  var favorite = localStorage["favorite_color"];
+  if (!favorite) {
+    return;
+  }
+  var select = document.getElementById("color");
+  for (var i = 0; i &lt; select.children.length; i++) {
+    var child = select.children[i];
+    if (child.value == favorite) {
+      child.selected = "true";
+      break;
+    }
+  }
+}
+document.addEventListener('DOMContentLoaded', restore_options);
+document.querySelector('#save').addEventListener('click', save_options);
+</pre>
+
+<pre>
+&lt;html>
+&lt;head>&lt;title>My Test Extension Options&lt;/title>&lt;/head>
+
+&lt;body>
+
+Favorite Color:
+&lt;select id="color">
+ &lt;option value="red">red&lt;/option>
+ &lt;option value="green">green&lt;/option>
+ &lt;option value="blue">blue&lt;/option>
+ &lt;option value="yellow">yellow&lt;/option>
+&lt;/select>
+
+&lt;br>
+&lt;div id="status">&lt;/div>
+&lt;button id="save">Save&lt;/button>
+&lt;/body>
+
+&lt;script src="options.js">&lt;/script>
+
+&lt;/html>
+</pre>
+
+<h2 id="important_notes">Important notes</h2>
+<ul>
+<li>We plan on providing some default css styles to encourage a consistent look across different extensions' options pages. You can star <a href="http://crbug.com/25317">crbug.com/25317</a> to be notified of updates.</li>
+</ul>
diff --git a/chrome/common/extensions/docs/templates/articles/override.html b/chrome/common/extensions/docs/templates/articles/override.html
new file mode 100644
index 0000000..3be1eeb
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/override.html
@@ -0,0 +1,186 @@
+<h1>Override Pages</h1>
+
+
+<style>
+#pics {
+  margin:2em 1em 1.5em;
+}
+
+#pics td {
+  text-align:center;
+  width:50%!important;
+  border:none;
+  padding:0 1em;
+  font-size:90%;
+}
+
+#pics img {
+  width:188;
+  height:246;
+  border:none;
+}
+</style>
+
+<p>
+Override pages are a way to substitute an HTML file from your extension
+for a page that Google Chrome normally provides.
+In addition to HTML,
+an override page usually has CSS and JavaScript code.
+</p>
+
+<p>
+An extension can replace any one of the following pages:
+<ul>
+  <li> <b>Bookmark Manager:</b>
+  The page that appears when the user chooses
+  the Bookmark Manager menu item
+  from the wrench menu or, on Mac,
+  the Bookmark Manager item from the Bookmarks menu.
+  You can also get to this page by entering the URL
+  <b>chrome://bookmarks</b>.
+  </li>
+
+  <li> <b>History:</b>
+  The page that appears when the user
+  chooses the History menu item
+  from the Tools (wrench) menu or, on Mac,
+  the Show Full History item from the History menu.
+  You can also get to this page by entering the URL
+  <b>chrome://history</b>.
+  </li>
+
+  <li> <b>New Tab:</b>
+  The page that appears when the user creates a new tab or window.
+  You can also get to this page by entering the URL
+  <b>chrome://newtab</b>.
+  </li>
+</ul>
+</p>
+
+<p class="note">
+<b>Note:</b>
+A single extension can override
+<b>only one page</b>.
+For example, an extension can't override both
+the Bookmark Manager and History pages.
+</p>
+
+<p>
+Incognito windows are treated specially.
+New Tab pages cannot be overridden in incognito windows.
+Other override pages work in incognito windows
+as long as the
+<a href="manifest.html#incognito">incognito</a>
+manifest property is set to "spanning"
+(which is the default value).
+See <a href="overview.html#incognito">Saving data and incognito mode</a>
+in the Overview for more details on how you should treat
+incognito windows.
+</p>
+
+<p>
+The following screenshots show the default New Tab page
+next to a custom New Tab page.
+</p>
+
+<table id="pics">
+  <tr>
+    <td> <b>The default New Tab page</b> </td>
+    <td> <b>An alternative New Tab page</b> </td>
+  </tr>
+  <tr>
+    <td>
+      <img src="{{static}}/images/ntp-default.png"
+        alt="default New Tab page"
+        width="200" height="173">
+    </td>
+    <td>
+      <img src="{{static}}/images/ntp-blank.png"
+        alt="a blank New Tab page"
+        width="200" height="173">
+    </td>
+  </tr>
+</table>
+
+<h2 id="manifest">Manifest</h2>
+
+<p>
+Register an override page in the
+<a href="manifest.html">extension manifest</a> like this:
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+
+<b>  "chrome_url_overrides" : {
+    "<em>pageToOverride</em>": "<em>myPage.html</em>"
+  }</b>,
+  ...
+}</pre>
+
+<p>
+For <code><em>pageToOverride</em></code>, substitute one of the following:
+</p>
+
+<ul>
+  <li> <code>bookmarks</code>
+  <li> <code>history</code>
+  <li> <code>newtab</code>
+</ul>
+
+
+<h2 id="tips">Tips</h2>
+
+<p>
+For an effective override page, follow these guidelines:
+</p>
+
+<ul>
+  <li>
+    <p>
+    <b>Make your page quick and small.</b> <br />
+    Users expect built-in browser pages to open instantly.
+    Avoid doing things that might take a long time.
+    For example, avoid synchronous fetches of network or database resources.
+    </p>
+  </li>
+  <li>
+    <p>
+    <b>Include a title in your page.</b> <br />
+    Otherwise people might see the URL of the page,
+    which could be confusing.
+    Here's an example of specifying the title:
+    <code>&lt;title>New&nbsp;Tab&lt;/title></code>
+    </p>
+  </li>
+  <li>
+    <p>
+    <b>Don't rely on the page having the keyboard focus.</b> <br />
+    The address bar always gets the focus first
+    when the user creates a new tab.
+    </p>
+  </li>
+  <li>
+    <p>
+    <b>Don't try to emulate the default New Tab page.</b> <br />
+    The APIs necessary to create
+    a slightly modified version of the default New Tab page &mdash;
+    with top pages,
+    recently closed pages,
+    tips,
+    a theme background image,
+    and so on &mdash;
+    don't exist yet.
+    Until they do,
+    you're better off trying to make something completely different.
+    </p>
+  </li>
+</ul>
+
+<h2 id="examples"> Examples </h2>
+
+<p>
+See the
+<a href="samples.html#chrome_url_overrides">override samples</a>.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/overview.html b/chrome/common/extensions/docs/templates/articles/overview.html
new file mode 100644
index 0000000..22b2f18
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/overview.html
@@ -0,0 +1,611 @@
+<h1>Overview</h1>
+
+
+<p>
+Once you've finished this page
+and the
+<a href="getstarted.html">Getting Started</a> tutorial,
+you'll be all set to start writing extensions.
+</p>
+
+<h2 id="what">The basics</h2>
+
+<p>
+An extension is a zipped bundle of files&mdash;HTML,
+CSS, JavaScript, images, and anything else you need&mdash;that
+adds functionality to the Google Chrome browser.
+Extensions are essentially web pages,
+and they can use all the
+<a href="api_other.html">APIs that the browser provides to web pages</a>,
+from XMLHttpRequest to JSON to HTML5.
+</p>
+
+<p>
+Extensions can interact with web pages or servers using
+<a href="content_scripts.html">content scripts</a> or
+<a href="xhr.html">cross-origin XMLHttpRequests</a>.
+Extensions can also interact programmatically
+with browser features such as
+<a href="bookmarks.html">bookmarks</a>
+and <a href="tabs.html">tabs</a>.
+</p>
+
+<h3 id="extension-ui">Extension UIs</h3>
+
+<p>
+Many extensions&mdash;but not packaged apps&mdash;add
+UI to Google Chrome in the form of
+<a href="browserAction.html">browser actions</a>
+or <a href="pageAction.html">page actions</a>.
+Each extension can have at most one browser action or page action.
+Choose a <b>browser action</b> when the extension is relevant to most pages.
+Choose a <b>page action</b> when the extension's icon
+should appear or disappear,
+depending on the page.
+</p>
+
+<table class="simple">
+<tr>
+  <td width="33%">
+    <img src="{{static}}/images/overview/browser-action.png"
+      width="147" height="100"
+      alt="screenshot" />
+  </td>
+  <td width="33%">
+    <img src="{{static}}/images/overview/page-action.png"
+      width="147" height="100"
+      alt="screenshot" />
+  </td>
+  <td>
+    <img src="{{static}}/images/overview/browser-action-with-popup.png"
+      width="147" height="100"
+      alt="screenshot" />
+  </td>
+</tr>
+
+<tr>
+  <td>
+    This <a href="samples.html#gmail">mail extension</a>
+    uses a <em>browser action</em>
+    (icon in the toolbar).
+  </td>
+  <td>
+    This <a href="samples.html#mappy">map extension</a>
+    uses a <em>page action</em>
+    (icon in the address bar)
+    and <em>content script</em>
+    (code injected into a web page).
+  </td>
+  <td>
+    This <a href="samples.html#news">news extension</a>
+    features a browser action that,
+    when clicked,
+    shows a <em>popup</em>.
+  </td>
+</tr>
+</table>
+
+<p>
+Extensions (and packaged apps) can also present a UI in other ways,
+such as adding to the Chrome context menu,
+providing an options page,
+or using a content script that changes how pages look.
+See the <a href="devguide.html">Developer's Guide</a>
+for a complete list of extension features,
+with links to implementation details
+for each one.
+</p>
+
+<h2 id="files">Files</h2>
+<p>
+Each extension has the following files:
+
+</p>
+
+<ul>
+  <li>A <b>manifest file</b></li>
+  <li>One or more <b>HTML files</b> (unless the extension is a theme)</li>
+  <li><em>Optional:</em> One or more <b>JavaScript files</b></li>
+  <li><em>Optional:</em> Any other files your extension needs&mdash;for
+  example, image files</li>
+</ul>
+
+<p>
+While you're working on your extension,
+you put all these files into a single folder.
+When you distribute your extension,
+the contents of the folder are packaged into a special ZIP file
+that has a <code>.crx</code> suffix.
+If you upload your extension using the
+<a href="https://chrome.google.com/webstore/developer/dashboard">Chrome Developer Dashboard</a>,
+the <code>.crx</code> file is created for you.
+For details on distributing extensions,
+see <a href="hosting.html">Hosting</a>.
+</p>
+
+
+<h3 id="relative-urls">Referring to files</h3>
+
+<p>
+You can put any file you like into an extension,
+but how do you use it?
+Usually,
+you can refer to the file using a relative URL,
+just as you would in an ordinary HTML page.
+Here's an example of referring to
+a file named <code>myimage.png</code>
+that's in a subfolder named <code>images</code>.
+</p>
+
+<pre>
+&lt;img <b>src="images/myimage.png"</b>>
+</pre>
+
+<p>
+As you might notice while you use the Google Chrome debugger,
+every file in an extension is also accessible by an absolute URL like this:
+</p>
+
+<blockquote>
+<b>chrome-extension://</b><em>&lt;extensionID></em><b>/</b><em>&lt;pathToFile></em>
+</blockquote>
+
+<p>
+In that URL, the <em>&lt;extensionID></em> is a unique identifier
+that the extension system generates for each extension.
+You can see the IDs for all your loaded extensions
+by going to the URL <b>chrome://extensions</b>.
+The <em>&lt;pathToFile></em> is the location of the file
+under the extension's top folder;
+it's the same as the relative URL.
+</p>
+
+<p>
+While you're working on an extension
+(before it's packaged),
+the extension ID can change.
+Specifically, the ID of an unpacked extension will change
+if you load the extension from a different directory;
+the ID will change again when you package the extension.
+If your extension's code
+needs to specify the full path to a file within the extension,
+you can use the <code>@@extension_id</code>
+<a href="i18n.html#overview-predefined">predefined message</a>
+to avoid hardcoding the ID during development.
+</p>
+
+<p>
+When you package an extension
+(typically, by uploading it with the dashboard),
+the extension gets a permanent ID,
+which remains the same even after you update the extension.
+Once the extension ID is permanent,
+you can change all occurrences of
+<code>@@extension_id</code> to use the real ID.
+</p>
+
+
+<h3 id="manifest">The manifest file</h3>
+
+<p>
+The manifest file, called <code>manifest.json</code>,
+gives information about the extension,
+such as the most important files
+and the capabilities that the extension might use.
+Here's a typical manifest file for a browser action
+that uses information from google.com:
+</p>
+
+<pre>
+{
+  "name": "My Extension",
+  "version": "2.1",
+  "description": "Gets information from Google.",
+  "icons": { "128": "icon_128.png" },
+  "background": {
+    "persistent": false,
+    "scripts": ["bg.js"]
+  },
+  "permissions": ["http://*.google.com/", "https://*.google.com/"],
+  "browser_action": {
+    "default_title": "",
+    "default_icon": "icon_19.png",
+    "default_popup": "popup.html"
+  }
+}</pre>
+
+<p>
+For details, see
+<a href="manifest.html">Manifest Files</a>.
+</p>
+
+<h2 id="arch">Architecture</h2>
+
+<p>
+Many extensions have a <em>background page</em>,
+an invisible page
+that holds the main logic of the extension.
+An extension can also contain other pages
+that present the extension's UI.
+If an extension needs to interact with web pages that the user loads
+(as opposed to pages that are included in the extension),
+then the extension must use a content script.
+</p>
+
+
+<h3 id="background_page">The background page</h3>
+
+<p>
+The following figure shows a browser
+that has at least two extensions installed:
+a browser action (yellow icon)
+and a page action (blue icon).
+Both the browser action and the page action
+have background pages.
+This figure shows the browser action's background page,
+which is defined by <code>background.html</code>
+and has JavaScript code that controls
+the behavior of the browser action in both windows.
+</p>
+
+<img src="{{static}}/images/overview/arch-1.gif"
+ width="232" height="168"
+ alt="Two windows and a box representing a background page (background.html). One window has a yellow icon; the other has both a yellow icon and a blue icon. The yellow icons are connected to the background page." />
+
+<p>
+There are two types of background pages:
+<a href="background_pages.html">persistent background pages</a>,
+and <a href="event_pages.html">event pages</a>. Persistent
+background pages, as the name suggests, are always open.
+Event pages are opened and closed as needed. Unless you absolutely
+need your background page to run all the time, prefer to use
+an event page.
+</p>
+
+<!-- PENDING: Perhaps show a picture of many background page processes.
+  This could build on a figure that shows the process architecture. -->
+
+<p>
+See <a href="event_pages.html">Event Pages</a>
+and <a href="background_pages.html">Background Pages</a>
+for more details.
+</p>
+
+<h3 id="pages">UI pages</h3>
+
+<p>
+Extensions can contain ordinary HTML pages that display the extension's UI.
+For example, a browser action can have a popup,
+which is implemented by an HTML file.
+Any extension can have an options page,
+which lets users customize how the extension works.
+Another type of special page is the override page.
+And finally, you can
+use <a href="tabs.html#method-create">chrome.tabs.create()</a>
+or <code>window.open()</code>
+to display any other HTML files that are in the extension.
+</p>
+
+<p>
+The HTML pages inside an extension
+have complete access to each other's DOMs,
+and they can invoke functions on each other.
+</p>
+
+<!-- PENDING: Change the following example and figure
+to use something that's not a popup?
+(It might lead people to think that popups need background pages.) -->
+
+<p>
+The following figure shows the architecture
+of a browser action's popup.
+The popup's contents are a web page
+defined by an HTML file
+(<code>popup.html</code>).
+This extension also happens to have a background page
+(<code>background.html</code>).
+The popup doesn't need to duplicate code
+that's in the background page
+because the popup can invoke functions on the background page.
+</p>
+
+<img src="{{static}}/images/overview/arch-2.gif"
+ width="256" height="168"
+ alt="A browser window containing a browser action that's displaying a popup. The popup's HTML file (popup.html) can communicate with the extension's background page (background.html)." />
+
+<p>
+See <a href="browserAction.html">Browser Actions</a>,
+<a href="options.html">Options</a>,
+<a href="override.html">Override Pages</a>,
+and the <a href="#pageComm">Communication between pages</a> section
+for more details.
+</p>
+
+
+<h3 id="contentScripts">Content scripts</h3>
+
+<p>
+If your extension needs to interact with web pages,
+then it needs a <em>content script</em>.
+A content script is some JavaScript
+that executes in the context of a page
+that's been loaded into the browser.
+Think of a content script as part of that loaded page,
+not as part of the extension it was packaged with
+(its <em>parent extension</em>).
+</p>
+
+<!-- [PENDING: Consider explaining that the reason content scripts are separated from the extension is due to chrome's multiprocess design.  Something like:
+
+Each extension runs in its own process.
+To have rich interaction with a web page, however,
+the extension must be able to
+run some code in the web page's process.
+Extensions accomplish this with content scripts.]
+-->
+
+<p>
+Content scripts can read details of the web pages the browser visits,
+and they can make changes to the pages.
+In the following figure,
+the content script
+can read and modify
+the DOM for the displayed web page.
+It cannot, however, modify the DOM of its parent extension's background page.
+</p>
+
+<img src="{{static}}/images/overview/arch-3.gif"
+ width="238" height="169"
+ alt="A browser window with a browser action (controlled by background.html) and a content script (controlled by contentscript.js)." />
+
+<p>
+Content scripts aren't completely cut off from their parent extensions.
+A content script can exchange messages with its parent extension,
+as the arrows in the following figure show.
+For example, a content script might send a message
+whenever it finds an RSS feed in a browser page.
+Or a background page might send a message
+asking a content script to change the appearance of its browser page.
+</p>
+
+<img src="{{static}}/images/overview/arch-cs.gif"
+ width="238" height="194"
+ alt="Like the previous figure, but showing more of the parent extension's files, as well as a communication path between the content script and the parent extension." />
+
+
+
+<p>
+For more information,
+see <a href="content_scripts.html">Content Scripts</a>.
+</p>
+
+
+<h2 id="apis"> Using the chrome.* APIs </h2>
+
+<p>
+In addition to having access to all the APIs that web pages and apps can use,
+extensions can also use Chrome-only APIs
+(often called <em>chrome.* APIs</em>)
+that allow tight integration with the browser.
+For example, any extension or web app can use the
+standard <code>window.open()</code> method to open a URL.
+But if you want to specify which window that URL should be displayed in,
+your extension can use the Chrome-only 
+<a href="tabs.html#method-create">chrome.tabs.create()</a>
+method instead.
+</p>
+
+<h3 id="sync"> Asynchronous vs. synchronous methods </h3>
+<p>
+Most methods in the chrome.* APIs are <b>asynchronous</b>:
+they return immediately, without waiting for the operation to finish.
+If you need to know the outcome of that operation,
+then you pass a callback function into the method.
+That callback is executed later (potentially <em>much</em> later),
+sometime after the method returns.
+Here's an example of the signature for an asynchronous method:
+</p>
+
+<p>
+<code>
+chrome.tabs.create(object <em>createProperties</em>, function <em>callback</em>)
+</code>
+</p>
+
+<p>
+Other chrome.* methods are <b>synchronous</b>.
+Synchronous methods never have a callback
+because they don't return until they've completed all their work.
+Often, synchronous methods have a return type.
+Consider the
+<a href="runtime.html#method-getURL">chrome.runtime.getURL()</a> method:
+</p>
+
+<p>
+<code>
+string chrome.runtime.getURL()
+</code>
+</p>
+
+<p>
+This method has no callback and a return type of <code>string</code>
+because it synchronously returns the URL
+and performs no other, asynchronous work.
+</p>
+
+
+<h3 id="sync-example"> Example: Using a callback </h3>
+
+<p>
+Say you want to navigate
+the user's currently selected tab to a new URL.
+To do this, you need to get the current tab's ID
+(using <a href="tabs.html#method-getSelected">chrome.tabs.getSelected()</a>)
+and then make that tab go to the new URL
+(using <a href="tabs.html#method-update">chrome.tabs.update()</a>).
+</p>
+
+<p>
+If <code>getSelected()</code> were synchronous,
+you might write code like this:
+</p>
+
+<pre>
+   <b>//THIS CODE DOESN'T WORK</b>
+<span class="linenumber">1: </span>var tab = chrome.tabs.getSelected(null); <b>//WRONG!!!</b>
+<span class="linenumber">2: </span>chrome.tabs.update(tab.id, {url:newUrl});
+<span class="linenumber">3: </span>someOtherFunction();
+</pre>
+
+<p>
+That approach fails
+because <code>getSelected()</code> is asynchronous.
+It returns without waiting for its work to complete,
+and it doesn't even return a value
+(although some asynchronous methods do).
+You can tell that <code>getSelected()</code> is asynchronous
+by the <em>callback</em> parameter in its signature:
+
+<p>
+<code>
+chrome.tabs.getSelected(integer <em>windowId</em>, function <em>callback</em>)
+</code>
+</p>
+
+<p>
+To fix the preceding code,
+you must use that callback parameter.
+The following code shows
+how to define a callback function
+that gets the results from <code>getSelected()</code>
+(as a parameter named <code>tab</code>)
+and calls <code>update()</code>.
+</p>
+
+<pre>
+   <b>//THIS CODE WORKS</b>
+<span class="linenumber">1: </span>chrome.tabs.getSelected(null, <b>function(tab) {</b>
+<span class="linenumber">2: </span>  chrome.tabs.update(tab.id, {url:newUrl});
+<span class="linenumber">3: </span><b>}</b>);
+<span class="linenumber">4: </span>someOtherFunction();
+</pre>
+
+<p>
+In this example, the lines are executed in the following order: 1, 4, 2.
+The callback function specified to <code>getSelected</code> is called
+(and line 2 executed)
+only after information about the currently selected tab is available,
+which is sometime after <code>getSelected()</code> returns.
+Although <code>update()</code> is asynchronous,
+this example doesn't use its callback parameter,
+since we don't do anything about the results of the update.
+</p>
+
+
+<h3 id="chrome-more"> More details </h3>
+
+<p>
+For more information, see the
+<a href="api_index.html">chrome.* API docs</a>
+and watch this video:
+</p>
+
+<p>
+<iframe title="YouTube video player" width="640" height="390" src="http://www.youtube.com/embed/bmxr75CV36A?rel=0" frameborder="0" allowfullscreen></iframe>
+</p>
+
+<h2 id="pageComm">Communication between pages </h2>
+
+<p>
+The HTML pages within an extension often need to communicate.
+
+Because all of an extension's pages
+execute in same process on the same thread,
+the pages can make direct function calls to each other.
+</p>
+
+<p>
+To find pages in the extension, use
+<a href="extension.html"><code>chrome.extension</code></a>
+methods such as
+<code>getViews()</code> and
+<code>getBackgroundPage()</code>.
+Once a page has a reference to other pages within the extension,
+the first page can invoke functions on the other pages,
+and it can manipulate their DOMs.
+</p>
+
+<h2 id="incognito"> Saving data and incognito mode </h2>
+
+<p>
+Extensions can save data using
+the HTML5 <a href="http://dev.w3.org/html5/webstorage/">web storage API</a>
+(such as <code>localStorage</code>)
+or by making server requests that result in saving data.
+Whenever you want to save something,
+first consider whether it's
+from a window that's in incognito mode.
+By default, extensions don't run in incognito windows.
+You need to consider what a user expects
+from your extension
+when the browser is incognito.
+</p>
+
+<p>
+<em>Incognito mode</em> promises that the window will leave no tracks.
+When dealing with data from incognito windows,
+do your best to honor this promise.
+For example, if your extension normally
+saves browsing history to the cloud,
+don't save history from incognito windows.
+On the other hand, you can store
+your extension's settings from any window,
+incognito or not.
+</p>
+
+<p class="note">
+<b>Rule of thumb:</b>
+If a piece of data might show where a user
+has been on the web or what the user has done,
+don't store it if it's from an incognito window.
+</p>
+
+<p>
+To detect whether a window is in incognito mode,
+check the <code>incognito</code> property of the relevant
+<a href="tabs.html#type-Tab">Tab</a> or
+<a href="windows.html#type-Window">Window</a> object.
+For example:
+</p>
+
+<pre>
+function saveTabData(tab, data) {
+  if (tab.incognito) {
+    chrome.runtime.getBackgroundPage(function(bgPage) {
+      bgPage[tab.url] = data;      // Persist data ONLY in memory
+    });
+  } else {
+    localStorage[tab.url] = data;  // OK to store data
+  }
+}
+</pre>
+
+
+<h2 id="now-what"> Now what? </h2>
+
+<p>
+Now that you've been introduced to extensions,
+you should be ready to write your own.
+Here are some ideas for where to go next:
+</p>
+
+<ul>
+  <li> <a href="getstarted.html">Tutorial: Getting Started</a> </li>
+  <li> <a href="tut_debugging.html">Tutorial: Debugging</a> </li>
+  <li> <a href="devguide.html">Developer's Guide</a> </li>
+  <li> <a href="http://dev.chromium.org/developers/design-documents/extensions/samples">Samples</a> </li>
+  <li> <a href="http://www.youtube.com/view_play_list?p=CA101D6A85FE9D4B">Videos</a>,
+    such as
+    <a href="http://www.youtube.com/watch?v=B4M_a7xejYI&feature=PlayList&p=CA101D6A85FE9D4B&index=6">Extension Message Passing</a>
+    </li>
+</ul>
diff --git a/chrome/common/extensions/docs/templates/articles/packaging.html b/chrome/common/extensions/docs/templates/articles/packaging.html
new file mode 100644
index 0000000..6a5dfb0
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/packaging.html
@@ -0,0 +1,190 @@
+<h1>Packaging</h1>
+
+
+<p>
+This page describes how to package your extension.
+As the <a href="overview.html">Overview</a> explains,
+extensions are packaged as signed ZIP files
+with the file extension "crx"&mdash;for example,
+<code>myextension.crx</code>.
+</p>
+
+<p>
+<b>Note:</b>
+You do not need to package your own extension.
+If you publish your extension using the
+<a href="https://chrome.google.com/webstore/developer/dashboard">Chrome Developer Dashboard</a>,
+then the only reason to create your own <code>.crx</code> file
+would be to distribute a non-public version&mdash;for example,
+to alpha testers.
+You can find information on publishing extensions and apps in the
+Chrome Web Store getting started tutorial, starting at
+<a href="http://code.google.com/chrome/webstore/docs/get_started_simple.html#step5">Step 5: Zip up your app</a>.
+</p>
+
+<p>
+When you package an extension,
+the extension is assigned a unique key pair.
+The extension's ID is based on a hash of the public key.
+The private key is used to sign each version of the extension
+and must be secured from public access.
+Be careful not to include your private key within
+your extensions!
+</p>
+
+
+<h2 id="creating">Creating a package</h2>
+
+<p>To package an extension:</p>
+<ol>
+  <li>
+    Bring up the Extensions management page
+    by going to this URL:
+    <blockquote>
+    <b>chrome://extensions</b>
+    </blockquote>
+  </li>
+
+  <li>
+    If <b>Developer mode</b> has a + by it,
+    click the +.
+  </li>
+
+  <li>
+    Click the <b>Pack extension</b> button.
+    A dialog appears.
+  </li>
+
+  <li>
+    In the <b>Extension root directory</b> field,
+    specify the path to the extension's folder&mdash;for example,
+    <code>c:\myext</code>.
+    (Ignore the other field;
+    you don't specify a private key file
+    the first time you package a particular extension.)
+  </li>
+
+  <li>
+    Click <b>OK</b>.
+    The packager creates two files:
+    a <code>.crx</code> file,
+    which is the actual extension that can be installed,
+    and a <code>.pem</code> file,
+    which contains the private key.
+  </li>
+</ol>
+
+
+<p>
+<b>Do not lose the private key!</b>
+Keep the <code>.pem</code> file secret and in a safe place.
+You'll need it later if you want to do any of the following:
+</p>
+<ul>
+<li><a href="#update">Update</a> the extension</li>
+<li><a href="#upload">Upload</a> the extension to the Chrome Web Store</li>
+</ul>
+
+<p>
+If the extension is successfully packaged, you'll see a dialog like this
+that tells you where to find
+the <code>.crx</code> and <code>.pem</code> files:</p>
+</p>
+
+<img src="{{static}}/images/package-success.gif"
+  width="554" height="208" />
+
+
+<h2 id="update">Updating a package</h2>
+
+<p>To create an updated version of your extension:</p>
+<ol>
+  <li>
+    Increase the version number in <code>manifest.json</code>.
+  </li>
+
+  <li>
+    Bring up the Extensions management page
+    by going to this URL: <b>chrome://extensions</b>
+  </li>
+
+  <li>
+    Click the <b>Pack extension</b> button.
+    A dialog appears.
+  </li>
+
+  <li>
+    In the <b>Extension root directory</b> field,
+    specify the path to the extension's folder&mdash;for example,
+    <code>c:\myext</code>.
+  </li>
+
+  <li>
+    In the <b>Private key file</b> field,
+    specify the location of the
+    already generated <code>.pem</code> file for this extension&mdash;for
+    example, <code>c:\myext.pem</code>.
+  </li>
+
+  <li>
+    Click <b>OK</b>.
+  </li>
+</ol>
+
+<p>If the updated extension is successfully packaged, you'll see a dialog like this:</p>
+
+<img src="{{static}}/images/update-success.gif"
+  width="298" height="160" />
+
+
+<h2 id="upload"> Uploading a previously packaged extension to the Chrome Web Store</h2>
+
+<p>
+You can use the Chrome Developer Dashboard
+to upload an extension that you've previously packaged yourself.
+However, unless you take special steps,
+the extension's ID in the Chrome Web Store
+will be different from its ID in the package you created.
+This different ID might be a problem if you've
+distributed your extension package,
+because it allows users to install multiple versions of your extension,
+each with its own local data.
+</p>
+
+<p>
+If you want to keep the extension ID the same,
+follow these steps:
+</p>
+
+<ol>
+  <li> Rename the private key that was generated
+    when you created the <code>.crx</code> file to <code>key.pem</code>. </li>
+  <li> Put <code>key.pem</code> in the top directory
+    of your extension. </li>
+  <li> Compress that directory into a ZIP file. </li>
+  <li> Upload the ZIP file using the
+    <a href="https://chrome.google.com/webstore/developer/dashboard">Chrome Developer Dashboard</a>. </li>
+</ol>
+
+
+<h2 id="packaging">Packaging at the command line</h2>
+
+<p>
+Another way to package extensions
+is by invoking <code>chrome.exe</code> at the command line.
+Use the <code>--pack-extension</code> flag
+to specify the location of the extension's folder.
+Use <code>--pack-extension-key</code>
+to specify the location of the extension's private key file.
+For example:
+</p>
+
+<pre>
+chrome.exe --pack-extension=c:\myext --pack-extension-key=c:\myext.pem
+</pre>
+
+<h2 id="format">Package format and scripts</h2>
+<p>
+For more information on the format, as well as pointers to scripts you can use
+to create <code>.crx</code> files, see <a href="crx.html">CRX Package Format</a>.
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/permission_warnings.html b/chrome/common/extensions/docs/templates/articles/permission_warnings.html
new file mode 100644
index 0000000..2197b10
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/permission_warnings.html
@@ -0,0 +1,448 @@
+<h1>Permission Warnings</h1>
+
+
+<!--
+NOTE: When this doc is updated, the online help should also be updated:
+http://www.google.com/support/chrome_webstore/bin/answer.py?hl=en&answer=186213
+
+We should periodically look at
+http://src.chromium.org/viewvc/chrome/trunk/src/chrome/app/generated_resources.grd?view=markup
+to make sure that we're covering all messages. Search for
+IDS_EXTENSION_PROMPT_WARNING
+(e.g. IDS_EXTENSION_PROMPT_WARNING_BROWSING_HISTORY).
+-->
+
+<p>
+To use most chrome.* APIs and extension capabilities,
+your extension must declare its intent in the
+<a href="manifest.html">manifest</a>,
+often in the "permissions" field.
+Some of these declarations
+result in a warning when
+a user installs your extension.
+</p>
+
+<p>
+When you autoupdate your extension,
+the user might see another warning
+if the extension requests new permissions.
+These new permissions might be new APIs that your extension uses,
+or they might be new websites
+that your extension needs access to.
+</p>
+
+
+<h2 id="examples"> Examples of permission warnings </h2>
+
+<p>
+Here's a typical dialog
+that a user might see when installing an extension:
+</p>
+
+<img src="{{static}}/images/perms-hw1.png"
+  width="410" height="193"
+  alt="Permission warning: 'It can: Access your data on api.flickr.com'"
+  />
+
+<p>
+The warning about access to data on api.flickr.com
+is caused by the following lines
+in the extension's manifest:
+</p>
+
+<pre>
+"permissions": [
+  <b>"http://api.flickr.com/"</b>
+],
+</pre>
+
+<p class="note">
+<b>Note:</b>
+You don't see permission warnings when
+you load an unpacked extension.
+You get permission warnings only when you install an extension
+from a <code>.crx</code> file.
+</p>
+
+<p>
+If you add a permission to the extension when you autoupdate it,
+the user might see a new permission warning.
+For example,
+assume you add a new site and the "tabs" permission
+to the previous example:
+</p>
+
+<pre>
+"permissions": [
+  "http://api.flickr.com/",
+  <b>"http://*.flickr.com/",
+  "tabs"</b>
+],
+</pre>
+
+<p>
+When the extension autoupdates,
+the increased permissions
+cause the extension to be disabled
+until the user re-enables it.
+Here's the warning the user sees:
+</p>
+
+<img src="{{static}}/images/perms-hw2-disabled.png"
+  width="814" height="30"
+  alt="Warning text: 'The newest version of the extension Hello World requires more permissions, so it has been disabled. [Re-enable].'"
+  />
+
+<p>
+Clicking the Re-enable button
+brings up the following warning:
+</p>
+
+<img src="{{static}}/images/perms-hw2.png"
+  width="412" height="220"
+  alt="Permission warning: 'It can: Access your data on api.flickr.com and flickr.com; Read and modify your browsing history'"
+  />
+
+
+<h2 id="warnings"> Warnings and their triggers </h2>
+
+<p>
+It can be surprising when adding a permission such as "tabs"
+results in the seemingly unrelated warning
+that the extension can access your browsing activity.
+The reason for the warning is that
+although the <code>chrome.tabs</code> API
+might be used only to open new tabs,
+it can also be used to see the URL that's associated
+with every newly opened tab
+(using their <a href="tabs.html#type-Tab">Tab</a> objects).
+</p>
+
+<p class="note">
+<b>Note:</b>
+As of Google Chrome 7,
+you no longer need to specify the "tabs" permission
+just to call <code>chrome.tabs.create()</code>
+or <code>chrome.tabs.update()</code>.
+</p>
+
+<p>
+The following table lists the warning messages
+that users can see,
+along with the manifest entries
+that trigger them.
+</p>
+
+<p>
+<table>
+  <tr>
+    <th> Warning message </th>
+    <th> Manifest entry that caused it </th>
+    <th> Notes </th>
+  </tr>
+<tr>
+  <td style="font-weight:bold">
+    <!-- IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS -->
+    Access all data on your computer and the websites you visit
+  </td>
+  <td>
+    "plugins"
+  </td>
+  <td>
+    The "plugins" permission is required by
+    <a href="npapi.html">NPAPI plugins</a>.
+  </td>
+</tr>
+
+<tr>
+  <td style="font-weight:bold">
+    <!-- IDS_EXTENSION_PROMPT_WARNING_BOOKMARKS -->
+    Read and modify your bookmarks
+  </td>
+  <td>
+    "bookmarks" permission
+  </td>
+  <td>
+    The "bookmarks" permission is required by the
+    <a href="bookmarks.html"><code>chrome.bookmarks</code></a> module.
+  </td>
+</tr>
+
+<tr>
+  <td style="font-weight:bold">
+    <!-- IDS_EXTENSION_PROMPT_WARNING_BROWSING_HISTORY -->
+    Read and modify your browsing history
+  </td>
+  <td>
+    <!-- HasEffectiveBrowsingHistoryPermission -->
+     Any of the following:
+    <ul>
+      <li> "history" permission </li>
+      <li> "topSites" permission </li>
+    </ul>
+  </td>
+  <td>
+    <p>
+      The "history" permission is required by
+      <a href="history.html"><code>chrome.history</code></a>.
+    </p>
+    <p>
+      The "topSites" permission is required by
+      <a href="topSites.html"><code>chrome.topSites</code></a>.
+    </p>
+  </td>
+</tr>
+
+<tr>
+  <td style="font-weight:bold">
+    <!-- IDS_EXTENSION_PROMPT_WARNING_TABS -->
+    Access your tabs and browsing activity
+  </td>
+  <td>
+    <!-- HasEffectiveBrowsingHistoryPermission -->
+    Any of the following:
+    <ul>
+      <li> "tabs" permission </li>
+      <li> "webNavigation" permission </li>
+    </ul>
+  </td>
+  <td>
+    <p>
+      The "tabs" permission is required by the
+      <a href="tabs.html"><code>chrome.tabs</code></a> and
+      <a href="windows.html"><code>chrome.windows</code></a> modules.
+    </p>
+    <p>
+      The "webNavigation" permission is required by the
+      <a href="webNavigation.html"><code>chrome.webNavigation</code></a> module.
+    </p>
+  </td>
+</tr>
+
+<tr>
+  <td style="font-weight:bold">
+    <!-- IDS_EXTENSION_PROMPT_WARNING_CONTENT_SETTINGS -->
+    Manipulate settings that specify whether websites can use features such as cookies, JavaScript, and plug-ins
+  </td>
+  <td>
+    <!-- HasEffectiveBrowsingHistoryPermission -->
+     "contentSettings" permission
+  </td>
+  <td>
+    <p>
+      The "contentSettings" permission is required by
+      <a href="contentSettings.html"><code>chrome.contentSettings</code></a>.
+    </p>
+  </td>
+</tr>
+
+<tr>
+  <td style="font-weight:bold">
+    <!-- IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS -->
+    Access your data on all websites
+  </td>
+  <td>
+    <!-- HasEffectiveAccessToAllHosts() -->
+    Any of the following:
+    <ul>
+      <li> "debugger" permission </li>
+      <li> "pageCapture" permission </li>
+      <li> "proxy" permission </li>
+      <li> A match pattern in the "permissions" field
+        that matches all hosts </li>
+      <li> A&nbsp;"content_scripts" field with a "matches" entry
+        that matches all hosts </li>
+      <li> "devtools_page" </li>
+    </ul>
+  </td>
+  <td>
+    <p>
+      The "debugger" permission is required by the experimental
+      <a href="experimental.debugger.html">debugger</a> module.
+    </p>
+
+    <p>
+      The "proxy" permission is required by the
+      <a href="proxy.html"><code>chrome.proxy</code></a> module.
+    </p>
+
+    <p>
+      Any of the following URLs match all hosts:
+    </p>
+    <ul>
+      <li> <code>http://*/*</code> </li>
+      <li> <code>https://*/*</code> </li>
+      <li> <code>*://*/*</code> </li>
+      <li> <code>&lt;all_urls&gt;</code> </li>
+    </ul>
+    <strong>Note that you may be able to avoid declaring all host permissions using the <code><a href="activeTab.html">activeTab</a></code> permission.</strong>
+  </td>
+</tr>
+<tr>
+  <td style="font-weight:bold">
+    <!-- IDS_EXTENSION_PROMPT_WARNING_?_HOST -->
+    <!-- IDS_EXTENSION_PROMPT_WARNING_4_OR_MORE_HOSTS -->
+    Access your data on <em>{list of websites}</em>
+  </td>
+  <td>
+    A match pattern in the "permissions" field
+    that specifies one or more hosts,
+    but not all hosts
+  </td>
+  <td>
+    <p>
+    Up to 3 sites are listed by name.
+    Subdomains aren't treated specially.
+    For example, <code>a.com</code> and <code>b.a.com</code>
+    are listed as different sites.
+    </p>
+
+    <p>
+    On autoupdate,
+    the user sees a permission warning
+    if the extension adds or changes sites.
+    For example, going from <code>a.com,b.com</code>
+    to <code>a.com,b.com,c.com</code>
+    triggers a warning.
+    Going from <code>b.a.com</code>
+    to <code>a.com</code>,
+    or vice versa,
+    also triggers a warning.
+    </p>
+  </td>
+</tr>
+
+<tr>
+  <td style="font-weight:bold">
+    <!-- IDS_EXTENSION_PROMPT_WARNING_MANAGEMENT -->
+    Manage your apps, extensions, and themes
+  </td>
+  <td>
+    "management" permission
+  </td>
+  <td>
+    The "management" permission is required by the
+    <a href="management.html"><code>chrome.management</code></a> module.
+  </td>
+</tr>
+
+<tr>
+  <td style="font-weight:bold">
+    <!-- IDS_EXTENSION_PROMPT_WARNING_GEOLOCATION -->
+    Detect your physical location
+  </td>
+  <td>
+    "geolocation" permission
+  </td>
+  <td>
+    Allows the extension to use the proposed HTML5
+    <a href="http://dev.w3.org/geo/api/spec-source.html">geolocation API</a>
+    without prompting the user for permission.
+  </td>
+</tr>
+
+<tr>
+  <td style="font-weight:bold">
+    <!-- IDS_EXTENSION_PROMPT_WARNING_CLIPBOARD-->
+    Access data you copy and paste
+  </td>
+  <td>
+    "clipboardRead" permission
+  </td>
+  <td>
+    Allows the extension to use the following editing commands with
+    <code>document.execCommand()</code>:
+    <ul>
+      <li> <code>"copy"</code> </li>
+      <li> <code>"cut"</code> </li>
+    </ul>
+  </td>
+</tr>
+
+<tr>
+  <td style="font-weight:bold">
+    <!-- IDS_EXTENSION_PROMPT_WARNING_PRIVACY-->
+    Manipulate privacy-related settings
+  </td>
+  <td>
+    "privacy" permission
+  </td>
+  <td>
+    The "privacy" permission is required by the
+    <a href="privacy.html"><code>chrome.privacy</code></a> module.
+  </td>
+</tr>
+
+<tr>
+  <td style="font-weight:bold">
+    <!-- IDS_EXTENSION_PROMPT_WARNING_TTS_ENGINE-->
+    Access all text spoken using synthesized speech
+  </td>
+  <td>
+    "ttsEngine" permission
+  </td>
+  <td>
+    The "ttsEngine" permission is required by the
+    <a href="ttsEngine.html"><code>chrome.ttsEngine</code></a> module.
+  </td>
+</tr>
+</table>
+</p>
+
+
+<h2 id="nowarning"> Permissions that don't cause warnings </h2>
+
+<p>
+The following permissions don't result in a warning:
+</p>
+
+<ul>
+  <li>"activeTab"</li>
+  <li>"browsingData"</li>
+  <li>"chrome://favicon/"</li>
+  <li>"clipboardWrite"</li>
+  <li>"contextMenus"</li>
+  <li>"cookies"</li>
+  <li>"experimental"</li>
+  <li>"idle"</li>
+  <li>"notifications"</li>
+  <li>"storage"</li>
+  <li>"unlimitedStorage"</li>
+  <li>"webRequest"</li>
+  <li>"webRequestBlocking"</li>
+</ul>
+
+<h2 id="test"> Testing permission warnings </h2>
+
+<p>
+If you'd like to see exactly which warnings your users will get,
+<a href="packaging.html">package your extension</a>
+into a <code>.crx</code> file,
+and install it.
+</p>
+
+<p>
+To see the warnings users will get when your extension is autoupdated,
+you can go to a little more trouble
+and set up an autoupdate server.
+To do this, first create an update manifest
+and point to it from your extension,
+using the "update_url" key
+(see <a href="autoupdate.html">Autoupdating</a>).
+Next, <a href="packaging.html">package the extension</a>
+into a new <code>.crx</code> file,
+and install the app from this <code>.crx</code> file.
+Now, change the extension's manifest to contain the new permissions,
+and <a href="packaging.html#update">repackage the extension</a>.
+Finally, update the extension
+(and all other extensions that have outstanding updates)
+by clicking the <b>chrome://extensions</b> page's
+<b>Update extensions now</b> button.
+</p>
+
+<h2 id="api">API</h2>
+
+<p>
+You can get a list of permission warnings for any manifest with
+<a href="management.html#method-getPermissionWarnings">chrome.management.getPermissionWarnings()</a>.
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/publish_app.html b/chrome/common/extensions/docs/templates/articles/publish_app.html
new file mode 100644
index 0000000..56a3c79
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/publish_app.html
@@ -0,0 +1,44 @@
+<h1>Publish</h1>
+<!--  -->
+
+<p>
+Packaged apps are still evolving.
+We're giving you an early look at how to build them,
+but it's a developer preview;
+they aren't ready yet for building production applications.
+</p>
+
+<p>This means that you can't just yet publish your packaged apps
+in the Chrome Web Store &mdash; but you will be able to do so very soon.</p>
+
+<p>What you can do (as described throughout this guide),
+is load your unpackaged app to the apps and extensions management page
+and try out your app from a new tab in the Chrome browser.</p>
+
+<p>In case you haven't tried it yet,
+here's how to load your app:</p>
+
+<ol>
+	<li style="margin-top:0" />
+      Bring up the apps and extensions management page
+      by clicking the wrench icon
+      <img src="{{static}}/images/hotdogmenu.png" width="29" height="29" alt=""
+        style="margin-top:0" />
+      and choosing <b>Tools > Extensions</b>.
+    </li>
+    <li>
+      Make sure the <b>Developer mode</b>
+      checkbox has been selected.
+    </li>
+    <li>
+      Click the <b>Load unpacked extension</b> button.
+      A file dialog appears.
+    </li>
+    <li>
+      In the file dialog,
+      navigate to your extension's folder
+      and click <b>OK</b>.
+    </li>
+</ol>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
diff --git a/chrome/common/extensions/docs/templates/articles/sandboxingEval.html b/chrome/common/extensions/docs/templates/articles/sandboxingEval.html
new file mode 100644
index 0000000..2f3ce12
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/sandboxingEval.html
@@ -0,0 +1,198 @@
+<h1>Using eval in Chrome Extensions. Safely.</h1>
+
+
+<p>
+  Chrome's extension system enforces a fairly strict default
+  <a href='contentSecurityPolicy.html'>
+    <strong>Content Security Policy (CSP)</strong>
+  </a>. The policy restrictions are straightforward: script must be moved
+  out-of-line into separate JavaScript files, inline event handlers must be
+  converted to use <code>addEventListener</code>, and <code>eval()</code> is
+  disabled. Chrome Apps have an
+  <a href='http://developer.chrome.com/trunk/apps/app_csp.html'>even more strict
+  policy</a>, and we're quite happy with the security properties these policies
+  provide.
+</p>
+
+<p>
+  We recognize, however, that a variety of libraries use <code>eval()</code> and
+  <code>eval</code>-like constructs such as <code>new Function()</code> for
+  performance optimization and ease of expression. Templating libraries are
+  especially prone to this style of implementation. While some (like
+  <a href='http://angularjs.org/'>Angular.js</a>) support CSP out of the box,
+  many popular frameworks haven't yet updated to a mechanism that is compatible
+  with extensions' <code>eval</code>-less world. Removing support for that
+  functionality has therefore proven <a href='http://crbug.com/107538'>more
+  problematic than expected</a> for developers.
+</p>
+
+<p>
+  This document introduces sandboxing as a safe mechanism to include these
+  libraries in your projects without compromising on security. For brevity,
+  we'll be using the term <em>extensions</em> throughout, but the concept
+  applies equally to applications.
+</p>
+
+<h2 id="why_sandbox">Why sandbox?</h2>
+
+<p>
+  <code>eval</code> is dangerous inside an extension because the code it
+  executes has access to everything in the extension's high-permission
+  environment. A slew of powerful <code>chrome.*</code> APIs are available that
+  could severely impact a user's security and privacy; simple data exfiltration
+  is the least of our worries. The solution on offer is a sandbox in which
+  <code>eval</code> can execute code without access either to the extension's
+  data or the extension's high-value APIs. No data, no APIs, no problem.
+</p>
+
+<p>
+  We accomplish this by listing specific HTML files inside the extension package
+  as being sandboxed. Whenever a sandboxed page is loaded, it will be moved to a
+  <a href='http://www.whatwg.org/specs/web-apps/current-work/multipage/origin-0.html#sandboxed-origin-browsing-context-flag'>unique origin</a>,
+  and will be denied access to <code>chrome.*</code> APIs. If we load this
+  sandboxed page into our extension via an <code>iframe</code>, we can pass it
+  messages, let it act upon those messages in some way, and wait for it to pass
+  us back a result. This simple messaging mechanism gives us everything we need
+  to safely include <code>eval</code>-driven code in our extension's workflow.
+</p>
+
+<h2 id="creating_and_using">Creating and using a sandbox.</h2>
+
+<p>
+  If you'd like to dive straight into code, please grab the
+  <a href='http://code.google.com/chrome/extensions/samples.html#3c6dfba67f6a7480d931b5a4a646c151ad1a049b'>sandboxing
+  sample extension and take off</a>. It's a working example of a tiny messaging
+  API built on top of the <a href='http://handlebarsjs.com'>Handlebars</a>
+  templating library, and it should give you everything you need to get going.
+  For those of you who'd like a little more explanation, let's walk through that
+  sample together here.
+</p>
+
+<h3 id="list_files">List files in manifest</h3>
+
+<p>
+  Each file that ought to be run inside a sandbox must be listed in the
+  extension manifest by adding a <code>sandbox</code> property. This is a
+  critical step, and it's easy to forget, so please double check that your
+  sandboxed file is listed in the manifest. In this sample, we're sandboxing the
+  file cleverly named "sandbox.html". The manifest entry looks like this:
+</p>
+
+<pre>{
+  ...,
+  "sandbox": {
+     "pages": ["sandbox.html"]
+  },
+  ...
+}</pre>
+
+<h3 id="load_file">Load the sandboxed file</h3>
+
+<p>
+  In order to do something interesting with the sandboxed file, we need to load
+  it in a context where it can be addressed by the extension's code. Here,
+  <a href='http://code.google.com/chrome/extensions/examples/howto/sandbox/sandbox.html'>sandbox.html</a>
+  has been loaded into the extension's <a href='http://code.google.com/chrome/extensions/dev/event_pages.html'>Event
+  Page</a> (<a href='http://code.google.com/chrome/extensions/examples/howto/sandbox/eventpage.html'>eventpage.html</a>)
+  via an <code>iframe</code>. <a href='http://code.google.com/chrome/extensions/examples/howto/sandbox/eventpage.js'>eventpage.js</a>
+  contains code that sends a message into the sandbox whenever the browser
+  action is clicked by finding the <code>iframe</code> on the page, and
+  executing the <code>postMessage</code> method on its
+  <code>contentWindow</code>. The message is an object containing two
+  properties: <code>context</code> and <code>command</code>. We'll dive into
+  both in a moment.
+</p>
+
+<pre>chrome.browserAction.onClicked.addListener(function() {
+ var iframe = document.getElementById('theFrame');
+ var message = {
+   command: 'render',
+   context: {thing: 'world'}
+ };
+ iframe.contentWindow.postMessage(message, '*');
+});</pre>
+
+<p class="note">
+  For general information about the <code>postMessage</code> API, take a look at
+  the <a href="https://developer.mozilla.org/en/DOM/window.postMessage">
+    <code>postMessage</code> documentation on MDN
+  </a>. It's quite complete and worth reading. In particular, note that data can
+  only be passed back and forth if it's serializable. Functions, for instance,
+  are not.
+</p>
+
+<h3 id="do_something">Do something dangerous</h3>
+
+<p>
+  When <code>sandbox.html</code> is loaded, it loads the Handlebars library, and
+  creates and compiles an inline template in the way Handlebars suggests:
+</p>
+
+<pre>&lt;script src="handlebars-1.0.0.beta.6.js"&gt;&lt;/script&gt;
+ &lt;script id="hello-world-template" type="text/x-handlebars-template"&gt;
+   &lt;div class="entry"&gt;
+     &lt;h1&gt;Hello, &#123&#123thing&#125&#125!&lt;/h1&gt;
+   &lt;/div&gt;
+ &lt;/script&gt;
+ &lt;script&gt;
+   var templates = [];
+   var source = document.getElementById('hello-world-template').innerHTML;
+   templates['hello'] = Handlebars.compile(source);
+ &lt;/script&gt;</pre>
+
+<p>
+  This doesn't fail! Even though <code>Handlebars.compile</code> ends up using
+  <code>new Function</code>, things work exactly as expected, and we end up with
+  a compiled template in <code>templates[‘hello']</code>.
+</p>
+
+<h3 id="pass_result">Pass the result back</h3>
+
+<p>
+  We'll make this template available for use by setting up a message listener
+  that accepts commands from the Event Page. We'll use the <code>command</code>
+  passed in to determine what ought to be done (you could imagine doing more
+  than simply rendering; perhaps creating templates? Perhaps managing them in
+  some way?), and the <code>context</code> will be passed into the template
+  directly for rendering. The rendered HTML will be passed back to the Event
+  Page so the extension can do something useful with it later on:
+</p>
+
+<pre>window.addEventListener('message', function(event) {
+  var command = event.data.command;
+  var name = event.data.name || 'hello';
+  switch(command) {
+    case 'render':
+      event.source.postMessage({
+        name: name,
+        html: templates[name](event.data.context)
+      }, event.origin);
+      break;
+
+    // case 'somethingElse':
+    //   ...
+  }
+});</pre>
+
+<p>
+  Back in the Event Page, we'll receive this message, and do something
+  interesting with the <code>html</code> data we've been passed. In this case,
+  we'll just echo it out via a <a href='http://code.google.com/chrome/extensions/notifications.html'>Desktop
+  Notification</a>, but it's entirely possible to use this HTML safely as part
+  of the extension's UI. Inserting it via <code>innerHTML</code> doesn't pose a
+  significant security risk, as even a complete compromise of the sandboxed code
+  through some clever attack would be unable to inject dangerous script or
+  plugin content into the high-permission extension context.
+</p>
+
+<p>
+  This mechanism makes templating straightforward, but it of course isn't
+  limited to templating. Any code that doesn't work out of the box under a
+  strict Content Security Policy can be sandboxed; in fact, it's often useful
+  to sandbox components of your extensions that <em>would</em> run correctly in
+  order to restrict each piece of your program to the smallest set of privileges
+  necessary for it to properly execute. The
+  <a href="http://www.youtube.com/watch?v=GBxv8SaX0gg">Writing Secure Web Apps
+  and Chrome Extensions</a> presentation from Google I/O 2012 gives some good
+  examples of these technique in action, and is worth 56 minutes of your time.
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/search_results.html b/chrome/common/extensions/docs/templates/articles/search_results.html
new file mode 100644
index 0000000..e5ab50e
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/search_results.html
@@ -0,0 +1,14 @@
+<h1>Search Results</h1>
+
+<div id="search_results">
+  <script>
+    (function() {
+      var cx = '002967670403910741006:61_cvzfqtno';
+      var gcse = document.createElement('script'); gcse.type = 'text/javascript'; gcse.async = true;
+      gcse.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') +
+        '//www.google.com/cse/cse.js?cx=' + cx;
+      var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(gcse, s);
+    })();
+  </script>
+  <gcse:searchresults-only></gcse:searchresults-only> 
+</div>
diff --git a/chrome/common/extensions/docs/templates/articles/sencha_framework.html b/chrome/common/extensions/docs/templates/articles/sencha_framework.html
new file mode 100644
index 0000000..a56f24d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/sencha_framework.html
@@ -0,0 +1,578 @@
+<meta name="doc-family" content="apps">
+<h1>Build Apps with Sencha Ext JS</h1>
+
+<p>
+The goal of this doc is to get you started
+on building packaged apps with the
+<a href="http://www.sencha.com/products/extjs">Sencha Ext JS</a> framework.
+To achieve this goal,
+we will dive into a media player app built by Sencha.
+The <a href="https://github.com/GoogleChrome/sencha-video-player-app">source code</a>
+and <a href="http://senchaprosvcs.github.com/GooglePlayer/docs/output/#!/api">API Documentation</a> are available on GitHub.
+</p>
+
+<p>
+This app discovers a user's available media servers,
+including media devices connected to the pc and
+software that manages media over the network. 
+Users can browse media, play over the network,
+or save offline.
+</p>
+
+<p>Here are the key things you must do
+to build a media player app using Sencha Ext JS:
+</p>
+
+<ul>
+  <li>Create manifest, <code>manifest.json</code>.</li>
+  <li>Create <a href="app_lifecycle.html#eventpage">event page</a>,
+    <code>background.js</code>.</li>
+  <li><a href="app_external.html#sandboxing">Sandbox</a> app's logic.</li>
+  <li>Communicate between packaged app and sandboxed files.</li>
+  <li>Discover media servers.</li>
+  <li>Explore and play media.</li>
+  <li>Save media offline.</li>
+</ul>
+
+<h2 id="first">Create manifest</h2>
+
+<p>
+All packaged apps require a
+<a href="manifest.html">manifest file</a>
+which contains the information Chrome needs to launch apps.
+As indicated in the manifest,
+the media player app is "offline_enabled";
+media assets can be saved locally,
+accessed and played regardless of connectivity.
+</p>
+
+<p>
+The "sandbox" field is used
+to sandbox the app's main logic in a unique origin.
+All sandboxed content is exempt from the packaged app
+<a href="app_csp.html">Content Security Policy</a>,
+but cannot directly access the packaged app APIs. 
+The manifest also includes the "socket" permission;
+the media player app uses the <a href="socket.html">socket API</a>
+to connect to a media server over the network.
+</p>
+
+<pre>
+{
+    "name": "Video Player",
+    "description": "Features network media discovery and playlist management",
+    "version": "1.0.0",
+    "manifest_version": 2,
+    "offline_enabled": true,
+    "app": {
+        "background": {
+            "scripts": [
+                "background.js"
+            ]
+        }
+    },
+    ...
+
+    "sandbox": {
+        "pages": ["sandbox.html"]
+    },
+    "permissions": [
+        "experimental",
+        "http://*/*",
+        "unlimitedStorage",
+        {
+            "socket": [
+                "tcp-connect",
+                "udp-send-to",
+                "udp-bind"
+            ]
+        }
+    ]
+}
+</pre>
+
+<h2 id="second">Create event page</h2>
+
+<p>
+All packaged apps require <code>background.js</code>
+to launch the application.
+The media player's main page, <code>index.html</code>,
+opens in a window with the specified dimensions:
+</p>
+
+<pre>
+chrome.app.runtime.onLaunched.addListener(function(launchData) {
+    var opt = {
+        width: 1000,
+        height: 700
+    };
+
+    chrome.app.window.create('index.html', opt, function (win) {
+        win.launchData = launchData;
+    });
+
+});
+</pre>
+
+<h2 id="three">Sandbox app's logic</h2>
+
+<p>Packaged apps run in a controlled environment
+that enforces a strict <a href="app_csp.html">Content Security Policy (CSP)</a>.
+The media player app needs some higher privileges to render the Ext JS components.
+To comply with CSP and execute the app logic,
+the app's main page, <code>index.html</code>, creates an iframe
+that acts as a sandbox environment:
+
+<pre>
+&lt;iframe id="sandbox-frame" class="sandboxed" sandbox="allow-scripts" src="sandbox.html">&lt;/iframe>
+</pre>
+
+<p>The iframe points to <a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/sandbox.html">sandbox.html</a> which includes the files required for the Ext JS application:
+</p>
+
+<pre>
+&lt;html>
+&lt;head>
+    &lt;link rel="stylesheet" type="text/css" href="resources/css/app.css" />'
+    &lt;script src="sdk/ext-all-dev.js">&lt;/script>'
+    &lt;script src="lib/ext/data/PostMessage.js">&lt;/script>'
+    &lt;script src="lib/ChromeProxy.js">&lt;/script>'
+    <script src="app.js"></script>
+&lt;/head>
+&lt;body></body>
+&lt;/html>
+</pre>
+
+<p>
+The <a href="http://senchaprosvcs.github.com/GooglePlayer/docs/output/source/app.html#VP-Application">app.js</a> script executes all the Ext JS code and renders the media player views.
+Since this script is sandboxed, it cannot directly access the packaged app APIs.
+Communication between <code>app.js</code> and non-sandboxed files is done using the
+<a href="https://developer.mozilla.org/en-US/docs/DOM/window.postMessage">HTML5 Post Message API</a>.
+</p>
+
+<h2 id="four">Communicate between files</h2>
+
+<p>
+In order for the media player app to access packaged app APIs,
+like query the network for media servers, <code>app.js</code> posts messages
+to <a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/index.js">index.js</a>.
+Unlike the sandboxed <code>app.js</code>,
+<code>index.js</code> can directly access the packaged app APIs.
+</p>
+
+<p>
+<code>index.js</code> creates the iframe:
+</p>
+
+<pre>
+var iframe = document.getElementById('sandbox-frame');
+
+iframeWindow = iframe.contentWindow;
+</pre>
+
+<p>
+And listens for messages from the sandboxed files:
+</p>
+
+<pre>
+window.addEventListener('message', function(e) {
+    var data= e.data,
+        key = data.key;
+
+    console.log('[index.js] Post Message received with key ' + key);
+
+    switch (key) {
+        case 'extension-baseurl':
+            extensionBaseUrl(data);
+            break;
+
+        case 'upnp-discover':
+            upnpDiscover(data);
+            break;
+
+        case 'upnp-browse':
+            upnpBrowse(data);
+            break;
+
+        case 'play-media':
+            playMedia(data);
+            break;
+
+        case 'download-media':
+            downloadMedia(data);
+            break;
+
+        case 'cancel-download':
+            cancelDownload(data);
+            break;
+
+        default:
+            console.log('[index.js] unidentified key for Post Message: "' + key + '"');
+    }
+}, false);
+</pre>
+
+<p>
+In the following example,
+<code>app.js</code> sends a message to <code>index.js</code>
+requesting the key 'extension-baseurl':
+</p>
+
+<pre>
+Ext.data.PostMessage.request({
+    key: 'extension-baseurl',
+    success: function(data) {
+        //...
+    }
+});
+</pre>
+
+<p>
+<code>index.js</code> receives the request, assigns the result,
+and replies by sending the Base URL back:
+</p>
+
+<pre>
+function extensionBaseUrl(data) {
+    data.result = chrome.extension.getURL('/');
+    iframeWindow.postMessage(data, '*');
+}
+</pre>
+
+<h2 id="five">Discover media servers</h2>
+
+<p>
+There's a lot that goes into discovering media servers.
+At a high level, the discovery workflow is initiated
+by a user action to search for available media servers.
+The <a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/app/controller/MediaServers.js">MediaServer controller</a>
+posts a message to <code>index.js</code>;
+<code>index.js</code> listens for this message and when received,
+calls <a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/lib/Upnp.js">Upnp.js</a>.
+</p>
+
+<p>
+The <code>Upnp library</code> uses the packaged app
+<a href="app_network.html">socket API</a>
+to connect the media player app with any discovered media servers
+and receive media data from the media server.
+<code>Upnp.js</code> also uses
+<a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/lib/soapclient.js">soapclient.js</a>
+to parse the media server data.
+The remainder of this section describes this workflow in more detail.
+</p>
+
+<h3 id="post">Post message</h3>
+
+<p>
+When a user clicks the Media Servers button in the center of the media player app,
+<code>MediaServers</code> calls <code>discoverServers()</code>.
+This function first checks for any outstanding discovery requests,
+and if true, aborts them so the new request can be initiated.
+Next, the controller posts a message to <code>index.js</code>
+with a key upnp-discovery, and two callback listeners:
+</p>
+
+<pre>
+me.activeDiscoverRequest = Ext.data.PostMessage.request({
+    key: 'upnp-discover',
+    success: function(data) {
+        var items = [];
+        delete me.activeDiscoverRequest;
+
+        if (serversGraph.isDestroyed) {
+            return;
+        }
+
+        mainBtn.isLoading = false;
+        mainBtn.removeCls('pop-in');
+        mainBtn.setIconCls('ico-server');
+        mainBtn.setText('Media Servers');
+
+        //add servers
+        Ext.each(data, function(server) {
+            var icon,
+                urlBase = server.urlBase;
+
+            if (urlBase) {
+                if (urlBase.substr(urlBase.length-1, 1) === '/'){
+                        urlBase = urlBase.substr(0, urlBase.length-1);
+                }
+            }
+
+            if (server.icons && server.icons.length) {
+                if (server.icons[1]) {
+                    icon = server.icons[1].url;
+                }
+                else {
+                    icon = server.icons[0].url;
+                }
+
+                icon = urlBase + icon;
+            }
+
+            items.push({
+                itemId: server.id,
+                text: server.friendlyName,
+                icon: icon,
+                data: server
+            });
+        });
+
+        ...
+    },
+    failure: function() {
+        delete me.activeDiscoverRequest;
+                
+        if (serversGraph.isDestroyed) {
+            return;
+        }
+                
+        mainBtn.isLoading = false;
+        mainBtn.removeCls('pop-in');
+        mainBtn.setIconCls('ico-error');
+        mainBtn.setText('Error...click to retry');
+    }
+});
+</pre>
+
+<h3 id="call">Call upnpDiscover()</h3>
+
+<p>
+<code>index.js</code> listens
+for the 'upnp-discover' message from <code>app.js</code>
+and responds by calling <code>upnpDiscover()</code>.
+When a media server is discovered,
+<code>index.js</code> extracts the media server domain from the parameters,
+saves the server locally, formats the media server data,
+and pushes the data to the <code>MediaServer</code> controller.
+</p>
+
+<h3 id="parse">Parse media server data</h3>
+
+<p>
+When <code>Upnp.js</code> discovers a new media server,
+it then retrieves a description of the device
+and sends a Soaprequest to browse and parse the media server data;
+<code>soapclient.js</code> parses the media elements by tag name
+into a document.
+</p>
+
+<h3 id="connect">Connect to media server</h3>
+
+<p>
+<code>Upnp.js</code> connects to discovered media servers
+and receives media data using the packaged app socket API:
+</p>
+
+<pre>
+socket.create("udp", {}, function(info) {
+    var socketId = info.socketId;
+        
+    //bind locally
+    socket.bind(socketId, "0.0.0.0", 0, function(info) {
+            
+        //pack upnp message
+        var message = String.toBuffer(UPNP_MESSAGE);
+            
+        //broadcast to upnp
+        socket.sendTo(socketId, message, UPNP_ADDRESS, UPNP_PORT, function(info) {
+                
+            // Wait 1 second
+            setTimeout(function() {
+                    
+                //receive
+                socket.recvFrom(socketId, function(info) {
+
+                    //unpack message
+                    var data        = String.fromBuffer(info.data),
+                        servers     = [],
+                        locationReg = /^location:/i;
+                            
+                    //extract location info
+                    if (data) {
+                        data = data.split("\r\n");
+                            
+                        data.forEach(function(value) {
+                            if (locationReg.test(value)){
+                                servers.push(value.replace(locationReg, "").trim());
+                            }
+                        });
+                    }
+                            
+                    //success
+                    callback(servers);
+                });
+                    
+            }, 1000);
+        });
+    });
+});
+</pre>
+
+
+<h2 id="six">Explore and play media</h2>
+
+<p>
+The
+<a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/app/controller/MediaExplorer.js">MediaExplorer controller</a>
+lists all the media files inside a media server folder
+and is responsible for updating the breadcrumb navigation
+in the media player app window.
+When a user selects a media file,
+the controller posts a message to <code>index.js</code>
+with the 'play-media' key:
+</p>
+
+<pre>
+onFileDblClick: function(explorer, record) {
+    var serverPanel, node,
+        type    = record.get('type'),
+        url     = record.get('url'),
+        name    = record.get('name'),
+        serverId= record.get('serverId');
+            
+    if (type === 'audio' || type === 'video') {
+        Ext.data.PostMessage.request({
+            key     : 'play-media',
+            params  : {
+                url: url,
+                name: name,
+                type: type
+            }
+        });
+    }
+},
+</pre>
+
+<p>
+<code>index.js</code> listens for this post message and
+responds by calling <code>playMedia()</code>:
+</p>
+
+<pre>
+function playMedia(data) {
+    var type        = data.params.type,
+        url         = data.params.url,
+        playerCt    = document.getElementById('player-ct'),
+        audioBody   = document.getElementById('audio-body'),
+        videoBody   = document.getElementById('video-body'),
+        mediaEl     = playerCt.getElementsByTagName(type)[0],
+        mediaBody   = type === 'video' ? videoBody : audioBody,
+        isLocal     = false;
+
+    //save data
+    filePlaying = {
+        url : url,
+        type: type,
+        name: data.params.name
+    };
+
+    //hide body els
+    audioBody.style.display = 'none';
+    videoBody.style.display = 'none';
+
+    var animEnd = function(e) {
+
+        //show body el
+        mediaBody.style.display = '';
+
+        //play media
+        mediaEl.play();
+
+        //clear listeners
+        playerCt.removeEventListener( 'webkitTransitionEnd', animEnd, false );
+        animEnd = null;
+    };
+
+    //load media
+    mediaEl.src = url;
+    mediaEl.load();
+
+    //animate in player
+    playerCt.addEventListener( 'webkitTransitionEnd', animEnd, false );
+    playerCt.style.webkitTransform = "translateY(0)";
+
+    //reply postmessage
+    data.result = true;
+    sendMessage(data);
+}
+</pre>
+
+<h2 id="seven">Save media offline</h2>
+
+<p>
+Most of the hard work to save media offline is done by the
+<a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/lib/filer.js">filer.js library</a>.
+You can read more this library in
+<a href="http://ericbidelman.tumblr.com/post/14866798359/introducing-filer-js">Introducing filer.js</a>.
+</p>
+
+<p>
+The process kicks off when a user selects one or more files
+and initiates the 'Take offline' action.
+The
+<a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/app/controller/MediaExplorer.js">MediaExplorer controller</a> posts a message to <code>index.js</code>
+with a key 'download-media'; <code>index.js</code> listens for this message
+and calls the <code>downloadMedia()</code> function
+to initiate the download process:
+</p>
+
+<pre>
+function downloadMedia(data) {
+        DownloadProcess.run(data.params.files, function() {
+            data.result = true;
+            sendMessage(data);
+        });
+    }
+</pre>
+
+<p>
+The <code>DownloadProcess</code> utility method creates an xhr request
+to get data from the media server and waits for completion status.
+This initiates the onload callback which checks the received content
+and saves the data locally using the <code>filer.js</code> function:
+</p>
+
+<pre>
+filer.write(
+    saveUrl,
+    {
+        data: Util.arrayBufferToBlob(fileArrayBuf),
+        type: contentType
+    },
+    function(fileEntry, fileWriter) {
+
+        console.log('file saved!');
+
+        //increment downloaded
+        me.completedFiles++;
+
+        //if reached the end, finalize the process
+        if (me.completedFiles === me.totalFiles) {
+
+            sendMessage({
+                key             : 'download-progresss',
+                totalFiles      : me.totalFiles,
+                completedFiles  : me.completedFiles
+            });
+
+            me.completedFiles = me.totalFiles = me.percentage = me.downloadedFiles = 0;
+            delete me.percentages;
+
+            //reload local
+            loadLocalFiles(callback);
+        }
+    },
+    function(e) {
+        console.log(e);
+    }
+);
+</pre>
+
+<p>
+When the download process is finished,
+<code>MediaExplorer</code> updates the media file list and the media player tree panel.
+</p>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/themes.html b/chrome/common/extensions/docs/templates/articles/themes.html
new file mode 100644
index 0000000..d496ba7
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/themes.html
@@ -0,0 +1,147 @@
+<h1 class="page_title">Themes</h1><p>
+A <em>theme</em> is a special kind of extension
+that changes the way the browser looks.
+Themes are <a href="packaging.html">packaged</a> like regular extensions,
+but they don't contain JavaScript or HTML code.
+</p>
+
+<p>
+You can find and try a bunch of themes at the
+<a href="https://chrome.google.com/webstore/category/themes">Chrome Web Store</a>.
+</p>
+
+<img src="{{static}}/images/themes-1.gif"
+     width="100" height="80" alt="grassy theme" />
+
+<img src="{{static}}/images/themes-2.gif"
+     width="100" height="80" alt="dark theme" />
+
+<img src="{{static}}/images/themes-3.gif"
+     width="100" height="80" alt="foggy theme" />
+
+<h2 id="manifest"> Manifest </h2>
+<p>
+Here is an example
+<a href="manifest.html"><code>manifest.json</code></a>
+file for a theme:
+</p>
+
+
+
+<pre>
+{
+&nbsp;&nbsp;"version": "2.6",
+&nbsp;&nbsp;"name": "camo theme",
+<b>&nbsp;&nbsp;"theme": {
+&nbsp;&nbsp; &nbsp;"<a href="#images">images</a>" : {
+&nbsp;&nbsp; &nbsp; &nbsp;"theme_frame" : "images/theme_frame_camo.png",
+&nbsp;&nbsp; &nbsp; &nbsp;"theme_frame_overlay" : "images/theme_frame_stripe.png",
+&nbsp;&nbsp; &nbsp; &nbsp;"theme_toolbar" : "images/theme_toolbar_camo.png",
+&nbsp;&nbsp; &nbsp; &nbsp;"theme_ntp_background" : "images/theme_ntp_background_norepeat.png",
+&nbsp;&nbsp; &nbsp; &nbsp;"theme_ntp_attribution" : "images/attribution.png"
+&nbsp;&nbsp; &nbsp;},
+&nbsp;&nbsp; &nbsp;"<a href="#colors">colors</a>" : {
+&nbsp;&nbsp; &nbsp; &nbsp;"frame" : [71, 105, 91],
+&nbsp;&nbsp; &nbsp; &nbsp;"toolbar" : [207, 221, 192],
+&nbsp;&nbsp; &nbsp; &nbsp;"ntp_text" : [20, 40, 0],
+&nbsp;&nbsp; &nbsp; &nbsp;"ntp_link" : [36, 70, 0],
+&nbsp;&nbsp; &nbsp; &nbsp;"ntp_section" : [207, 221, 192],
+&nbsp;&nbsp; &nbsp; &nbsp;"button_background" : [255, 255, 255]
+&nbsp;&nbsp; &nbsp;},
+&nbsp;&nbsp; &nbsp;"<a href="#tints">tints</a>" : {
+&nbsp;&nbsp; &nbsp; &nbsp;"buttons" : [0.33, 0.5, 0.47]
+&nbsp;&nbsp; &nbsp;},
+&nbsp;&nbsp; &nbsp;"<a href="#properties">properties</a>" : {
+&nbsp;&nbsp; &nbsp; &nbsp;"ntp_background_alignment" : "bottom"
+&nbsp;&nbsp; &nbsp;}
+&nbsp;&nbsp;}</b>
+}
+</pre>
+
+<h3 id="colors">colors</h3>
+
+<p>
+Colors are in RGB format.
+To find the strings you can use within the "colors" field,
+look for kColor* strings in
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/browser/themes/theme_service.cc"><code>theme_service.cc</code></a>.
+</p>
+
+<h3 id="images">images</h3>
+
+<p>
+Image resources use paths relative to the root of the extension.
+You can override any of the images that are specified by
+<code>kThemeableImages</code> in
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/browser/themes/theme_service.cc"><code>theme_service.cc</code></a>.
+Just remove the "IDR_"
+and convert the remaining characters to lowercase.
+For example, <code>IDR_THEME_NTP_BACKGROUND</code>
+(which <code>kThemeableImages</code> uses
+to specify the background of the new tab pane)
+corresponds to "theme_ntp_background".
+</p>
+
+<h3 id="properties">properties</h3>
+
+<p>
+This field lets you specify
+properties such as background alignment,
+background repeat,
+and an alternate logo.
+To see the properties and the values they can have, see
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/browser/themes/theme_service.cc"><code>theme_service.cc</code></a>.
+
+</p>
+
+<h3 id="tints">tints</h3>
+
+<p>
+You can specify tints to be applied to parts of the UI
+such as buttons, the frame, and the background tab.
+Google Chrome supports tints, not images,
+because images don't work across platforms
+and are brittle in the case of adding new buttons.
+To find the strings you can use within the "tints" field,
+look for kTint* strings in
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/browser/themes/theme_service.cc"><code>theme_service.cc</code></a>.
+</p>
+
+<p>
+Tints are in Hue-Saturation-Lightness (HSL) format,
+using floating-point numbers in the range 0 - 1.0:
+</p>
+
+<ul>
+  <li>
+    <b>Hue</b> is an absolute value, with 0 and 1 being red.
+  </li>
+  <li>
+    <b>Saturation</b> is relative to the currently provided image.
+    0.5 is <em>no change</em>,
+    0 is <em>totally desaturated</em>,
+    and 1 is <em>full saturation</em>.
+  </li>
+  <li>
+    <b>Lightness</b> is also relative,
+    with 0.5 being <em>no change</em>,
+    0 as <em>all pixels black</em>,
+    and 1 as <em>all pixels white</em>.
+  </li>
+</ul>
+
+<p>
+You can alternatively use <code>-1.0</code> for any of the HSL values
+to specify <em>no change</em>.
+</p>
+
+
+<h2 id="moredoc"> Additional documentation </h2>
+
+<p>
+Community-written documentation to help you write themes is here:
+</p>
+
+<blockquote>
+<a href="http://code.google.com/p/chromium/wiki/ThemeCreationGuide">http://code.google.com/p/chromium/wiki/ThemeCreationGuide</a>
+</blockquote>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/tut_analytics.html b/chrome/common/extensions/docs/templates/articles/tut_analytics.html
new file mode 100644
index 0000000..ff0adca
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/tut_analytics.html
@@ -0,0 +1,214 @@
+<h1>Tutorial: Google Analytics</h1>
+
+
+<p>This tutorial demonstrates using Google Analytics to track the usage of your
+extension.</p>
+
+<h2 id="toc-requirements">Requirements</h2>
+<p>
+  This tutorial expects that you have some familiarity writing extensions for
+  Google Chrome.  If you need information on how to write an extension, please
+  read the <a href="gettingstarted.html">Getting Started tutorial</a>.
+</p>
+
+<p>
+  You will also need a <a href="http://www.google.com/analytics">Google
+  Analytics account</a> set up to track your extension. Note that when setting
+  up the account, you can use any value in the Website's URL field, as your
+  extension will not have an URL of its own.
+</p>
+
+<p style="text-align: center">
+  <img src="{{static}}/images/tut_analytics/screenshot01.png"
+       style="width:400px;height:82px;"
+       alt="The analytics setup with info for a chrome extension filled out." />
+</p>
+
+<h2 id="toc-installing">Installing the tracking code</h2>
+
+<p>
+  The standard Google Analytics tracking code snippet fetches a file named
+  <code>ga.js</code> from an SSL protected URL if the current page
+  was loaded using the <code>https://</code> protocol.  <strong>Chrome
+  extensions and applications may <em>only</em> use the SSL-protected version of
+  <code>ga.js</code></strong>. Loading <code>ga.js</code> over insecure HTTP is
+  disallowed by Chrome's default <a href="contentSecurityPolicy.html">Content
+  Security Policy</a>. This, plus the fact that Chrome extensions are hosted
+  under the <code>chrome-extension://</code> schema, requires a slight
+  modification to the usual tracking snippet to pull <code>ga.js</code> directly
+  from <code>https://ssl.google-analytics.com/ga.js</code> instead of the
+  default location.
+</p>
+
+<p>
+  Below is a modified snippet for the
+  <a href="http://code.google.com/apis/analytics/docs/tracking/asyncTracking.html">asynchronous
+  tracking API</a> (the modified line is bolded):
+</p>
+
+<pre>
+(function() {
+  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+  <strong>ga.src = 'https://ssl.google-analytics.com/ga.js';</strong>
+  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+})();
+</pre>
+
+<p>
+  You'll also need to ensure that your extension has access to load the resource
+  by relaxing the default content security policy. The policy definition in your
+  <a href="manifest.html"><code>manifest.json</code></a> might look like:
+</p>
+
+<pre>{
+  ...,
+  "content_security_policy": "script-src 'self' https://ssl.google-analytics.com; object-src 'self'",
+  ...
+}</pre>
+
+<p>
+  Here is a popup page (<code>popup.html</code>) which loads the asynchronous
+  tracking code via an external JavaScript file (<code>popup.js</code>) and
+  tracks a single page view:
+</p>
+
+<pre>popup.js:
+=========
+
+var _gaq = _gaq || [];
+_gaq.push(['_setAccount', 'UA-XXXXXXXX-X']);
+_gaq.push(['_trackPageview']);
+
+(function() {
+  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+  ga.src = 'https://ssl.google-analytics.com/ga.js';
+  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+})();
+
+popup.html:
+===========
+&lt;!DOCTYPE html>
+&lt;html>
+ &lt;head>
+   ...
+  &lt;script src="popup.js">&lt;/script>
+ &lt;/head>
+ &lt;body>
+   ...
+ &lt;/body>
+&lt;/html>
+</pre>
+
+<p>
+  Keep in mind that the string <code>UA-XXXXXXXX-X</code> should be replaced
+  with your own Google Analytics account number.
+</p>
+
+<h2 id="toc-tracking-pageviews">Tracking page views</h2>
+
+<p>
+  The <code>_gaq.push(['_trackPageview']);</code> code will track a single
+  page view.  This code may be used on any page in your extension.  When
+  placed on a background page, it will register a view once per browser
+  session.  When placed on a popup, it will register a view once every time
+  the popup is opened.
+</p>
+
+<p>
+  By looking at the page view data for each page in your extension, you can
+  get an idea of how many times your users interact with your extension per
+  browser session:
+</p>
+
+<p style="text-align: center">
+  <img src="{{static}}/images/tut_analytics/screenshot02.png"
+       style="width:300px;height:119px;"
+       alt="Analytics view of the top content for a site." />
+</p>
+
+<h2 id="toc-debugging">Monitoring analytics requests</h2>
+
+<p>
+  To ensure that tracking data from your extension is being sent to Google
+  Analytics, you can inspect the pages of your extension in the
+  Developer Tools window (see the
+  <a href="tut_debugging.html">debugging tutorial</a> for more information).
+  As the following figure shows, you should see requests for a file named
+  <strong>__utm.gif</strong> if everything is set up correctly.
+</p>
+
+<p style="text-align: center">
+  <img src="{{static}}/images/tut_analytics/screenshot04.png"
+       style="width:683px;height:418px;"
+       alt="Developer Tools window showing the __utm.gif request" />
+</p>
+
+<h2 id="toc-tracking-events">Tracking events</h2>
+
+<p>
+  By configuring event tracking, you can determine which parts of your
+  extension your users interact with the most.  For example, if you have
+  three buttons users may click:
+</p>
+
+<pre>
+  &lt;button id='button1'>Button 1&lt;/button>
+  &lt;button id='button2'>Button 2&lt;/button>
+  &lt;button id='button3'>Button 3&lt;/button>
+</pre>
+
+<p>
+  Write a function that sends click events to Google Analytics:
+</p>
+
+<pre>
+  function trackButton(e) {
+    _gaq.push(['_trackEvent', e.target.id, 'clicked']);
+  };
+</pre>
+
+<p>
+  And use it as an event handler for each button's click:
+</p>
+
+<pre>
+  var buttons = document.querySelectorAll('button');
+  for (var i = 0; i < buttons.length; i++) {
+    buttons[i].addEventListener('click', trackButtonClick);
+  }
+</pre>
+
+<p>
+  The Google Analytics event tracking overview page will give you metrics
+  regarding how many times each individual button is clicked:
+</p>
+
+<p style="text-align: center">
+  <img src="{{static}}/images/tut_analytics/screenshot03.png"
+       style="width:300px;height:482px;"
+       alt="Analytics view of the event tracking data for a site." />
+</p>
+
+<p>
+  By using this approach, you can see which parts of your extension are
+  under-or-overutilized.  This information can help guide decisions about UI
+  redesigns or additional functionality to implement.
+</p>
+
+<p>
+  For more information about using event tracking, see the
+  Google Analytics
+  <a href="https://developers.google.com/analytics/devguides/collection/gajs/eventTrackerGuide">developer
+  documentation</a>.
+</p>
+
+<h2 id="toc-samplecode">Sample code</h2>
+
+<p>
+  A sample extension that uses these techniques is
+  available in the Chromium source tree:
+</p>
+
+<blockquote>
+  <a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/tutorials/analytics/">.../examples/tutorials/analytics/</a>
+</blockquote>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/tut_debugging.html b/chrome/common/extensions/docs/templates/articles/tut_debugging.html
new file mode 100644
index 0000000..68233ad
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/tut_debugging.html
@@ -0,0 +1,262 @@
+<h1>Tutorial: Debugging</h1>
+
+
+<p>
+This tutorial introduces you to using
+Google Chrome's built-in Developer Tools
+to interactively debug an extension.
+</p>
+
+
+<h2 id="extension-info"> View extension information </h2>
+
+<p>
+To follow this tutorial, you need
+the Hello World extension that was featured in
+<a href="getstarted.html">Getting Started</a>.
+In this section,
+you'll load the extension
+and take a look at its information
+in the Extensions page.
+</p>
+
+<ol>
+  <li>
+    <p>
+      Load the Hello World extension if it isn't already running.
+      If the extension is running,
+      you'll see the Hello World icon
+      <img src="examples/tutorials/getstarted/icon.png"
+           width="19" height="19" alt=""
+           style="margin:0" />
+      to the right of
+      your browser's address bar.
+    </p>
+
+    <p>
+      If the Hello World extension isn't already running,
+      find the extension files and load them.
+      If you don't have a handy copy of the files,
+      extract them from this
+      <a href="examples/tutorials/getstarted.zip" download="getstarted.zip">ZIP file</a>.
+      See Getting Started if you need
+      <a href="getstarted.html#load-ext">instructions
+      for loading the extension</a>.
+    </p>
+  </li>
+
+  <li>
+  Go to the Extensions page
+  (<b>chrome://extensions</b>),
+  and make sure the page is in Developer mode.
+  </li>
+
+  <li>
+  Look at the Hello World extension's information on that page.
+  You can see details such as the extension's
+  name, description, and ID.
+  </li>
+</ol>
+
+<h2 id="inspect-popup"> Inspect the popup </h2>
+
+<p>
+As long as your browser is in Developer mode, it's easy to inspect popups.
+</p>
+
+<ol>
+  <li>
+  Go to the Extensions page (<b>chrome://extensions</b>), and make sure Developer
+  mode is still enabled. The Extensions page doesn't need to be open
+  for the following to work. The browser remembers the setting,
+  even when the page isn't shown.
+  </li>
+  <li>
+  Right-click the Hello World icon
+  <img src="examples/tutorials/getstarted/icon.png"
+       width="19" height="19" alt=""
+       style="margin:0" />
+  and choose the <b>Inspect popup</b> menu item. The popup appears,
+  and a Developer Tools window like the following should display the code
+  for <b>popup.html</b>.
+
+  <p>
+     <img src="{{static}}/images/devtools-1.gif" alt=""
+          width="500" height="294" />
+     </p> 
+  The popup remains open as long as the Developer Tools window does.  
+  </li>
+  <li>
+  If the <strong>Scripts</strong> button isn't already selected,
+  click it.
+  
+  </li>
+  <li>
+  Click the console button
+  <img src="{{static}}/images/console-button.gif"
+       style="margin:0; padding:0" align="absmiddle"
+       width="26" height="22" alt="" />(second
+  from left,
+  at the bottom of the Developer Tools window)
+  so that you can see both the code and the console.
+  </li>
+</ol>
+
+<h2 id="debug"> Use the debugger </h2>
+
+<p>
+In this section,
+you'll follow the execution of the popup page
+as it adds images to itself.
+</p>
+
+<ol>
+  <li>
+  Set a breakpoint inside the image-adding loop
+  by searching for the string <b>img.src</b>
+  and clicking the number of the line where it occurs
+  (for example, <strong>37</strong>):
+  <p>
+  <img src="{{static}}/images/devtools-2.gif" alt=""
+       width="500" height="294" />
+  </p>
+  </li>
+
+  <li>
+  Make sure you can see the <b>popup.html</b> tab.
+  It should show 20 "hello world" images.
+  </li>
+
+  <li>
+  At the console prompt, reload the popup page
+  by entering <b>location.reload(true)</b>:
+
+<pre>
+> <b>location.reload(true)</b>
+</pre>
+
+  <p>
+  The popup page goes blank as it begins to reload,
+  and its execution stops at line 37.
+  </p>
+  </li>
+
+  <li>
+  In the upper right part of the tools window,
+  you should see the local scope variables.
+  This section shows the current values of all variables in the current scope.
+  For example, in the following screenshot
+  the value of <code>i</code> is 0, and
+  <code>photos</code> is a node list
+  that contains at least a few elements.
+  (In fact, it contains 20 elements at indexes 0 through 19,
+  each one representing a photo.)
+
+  <p>
+  <img src="{{static}}/images/devtools-localvars.gif" alt=""
+       width="225" height="215" />
+  </p>
+  </li>
+
+  <li>
+  Click the play/pause button
+  <img src="{{static}}/images/play-button.gif"
+       style="margin:0; padding:0" align="absmiddle"
+       width="22" height="20" alt="" />(near
+  the top right of the Developer Tools window)
+  to go through the image-processing loop a single time.
+  Each time you click that button,
+  the value of <code>i</code> increments and
+  another icon appears in the popup page.
+  For example, when <code>i</code> is 10,
+  the popup page looks something like this:
+  </li>
+
+  <p>
+  <img src="{{static}}/images/devtools-3.gif"
+       width="500" height="245"
+       alt="the popup page with 10 images" />
+  </p>
+
+  <li>
+  Use the buttons next to the play/pause button
+  to step over, into, and out of function calls.
+  To let the page finish loading,
+  click line <b>37</b> to disable the breakpoint,
+  and then press play/pause
+  <img src="{{static}}/images/play-button.gif"
+       style="margin:0; padding:0" align="absmiddle"
+       width="22" height="20" alt="" />to
+  continue execution.
+  </li>
+
+</ol>
+
+
+<h2 id="summary">Summary</h2>
+
+<p>
+This tutorial demonstrated some techniques you can use
+to debug your extensions:
+</p>
+
+<ul>
+  <li>
+    Find your extension's ID and links to its pages in
+    the <b>Extensions</b> page
+    (<b>chrome://extensions</b>).
+  </li>
+  <li>
+    View hard-to-reach pages
+    (and any other file in your extension) using
+    <b>chrome-extension://</b><em>extensionId</em><b>/</b><em>filename</em>.
+  </li>
+  <li>
+    Use Developer Tools to inspect
+    and step through a page's JavaScript code.
+  </li>
+  <li>
+    Reload the currently inspected page from the console
+    using <b>location.reload(true)</b>.
+  </li>
+</ul>
+
+
+<h2 id="next">Now what?</h2>
+
+<p>
+Now that you've been introduced to debugging,
+here are suggestions for what to do next:
+</p>
+
+<ul>
+  <li>
+    Watch the extensions video
+    <a href="http://www.youtube.com/watch?v=IP0nMv_NI1s&feature=PlayList&p=CA101D6A85FE9D4B&index=5">Developing and Debugging</a>.
+  </li>
+  <li>
+    Try installing and inspecting other extensions,
+    such as the
+    <a href="samples.html">samples</a>.
+  </li>
+  <li>
+    Try using widely available debugging APIs such as
+    <code>console.log()</code> and <code>console.error()</code>
+    in your extension's JavaScript code.
+    Example: <code>console.log("Hello, world!")</code>
+  </li>
+  <li>
+    Follow the <a href="http://www.chromium.org/devtools/google-chrome-developer-tools-tutorial">Developer Tools tutorial</a>,
+    explore the
+    <a href="http://www.chromium.org/devtools">Developer Tools site</a>,
+    and watch some video tutorials.
+  </li>
+</ul>
+
+
+
+<p>
+For more ideas,
+see the <a href="getstarted.html#summary">Now what?</a> section
+of Getting Started.
+</p>
diff --git a/chrome/common/extensions/docs/templates/articles/tut_migration_to_manifest_v2.html b/chrome/common/extensions/docs/templates/articles/tut_migration_to_manifest_v2.html
new file mode 100644
index 0000000..5b02a5e
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/tut_migration_to_manifest_v2.html
@@ -0,0 +1,358 @@
+<h1>Tutorial: Migrate to Manifest V2</h1>
+
+<p>
+Manifest version 1 was deprecated in Chrome 18,
+and support will be phased out according to the
+<a href="manifestVersion.html">manifest version 1 support schedule</a>.
+The changes from version 1 to version 2
+fall under two broad categories:
+API changes and Security changes. 
+</p>
+
+<p>
+This document provides checklists for migrating your Chrome extensions
+from manifest version 1 to version 2,
+followed by more detailed summaries of what these changes mean and why they were made.
+</p>
+
+<h2 id="api-checklist">API changes checklist</h2>
+
+<ul>
+  <li>Are you using the <code>browser_actions</code> property
+    or the <code>chrome.browserActions</code> API?</li>
+    <ul>
+      <li>Replace <code>browser_actions</code>
+        with the singular <code>browser_action</code> property.</li>
+      <li>Replace <code>chrome.browserActions</code> with
+        <code>chrome.browserAction</code>.</li>
+      <li>Replace the <code>icons</code> property with <code>default_icon</code>.</li>
+      <li>Replace the <code>name</code> property with <code>default_title</code>.</li>
+      <li>Replace the <code>popup</code> property with
+        <code>default_popup</code> (and it now must be a string).</li>
+    </ul>
+  <li>Are you using the <code>page_actions</code> property
+    or the <code>chrome.pageActions</code> API?</li>
+    <ul>
+      <li>Replace <code>page_actions</code> with <code>page_action</code>.</li>
+      <li>Replace <code>chrome.pageActions</code> with <code>chrome.pageAction</code>.</li>
+      <li>Replace the <code>icons</code> property with <code>default_icon</code>.</li>
+      <li>Replace the <code>name</code> property with <code>default_title</code>.</li>
+      <li>Replace the <code>popup</code> property with
+        <code>default_popup </code>(and it now must be a string).</li>
+    </ul>
+  <li>Are you using the <code>chrome.self</code> property?</li>
+    <ul>
+      <li>Replace with <code>chrome.extension</code>.</li>
+    </ul>
+  <li>Are you using the <code>Port.tab</code> property?</li>
+    <ul>
+      <li>Replace with <code>Port.sender</code>.</li>
+    </ul>
+  <li>Are you using the <code>chrome.extension.getTabContentses()</code>
+    or the <code>chrome.extension.getExtensionTabs()</code> APIs?</li>
+    <ul>
+      <li>Replace with <code>chrome.extension.getViews( { “type” : “tab” } )</code>.</li>
+    </ul>
+  <li>Does your extension use a background page?</li>
+    <ul>
+      <li>Replace the <code>background_page</code> property
+        with a <code>background</code> property.</li>
+      <li>Add a <code>scripts</code> or <code>page</code>
+        property that contains the code for the page.</li>
+      <li>Add a <code>persistent</code> property and set it
+        to <code>false</code> to convert your background page
+        to an <a href="event_pages.html">event page</a></li>
+    </ul>
+</ul>
+
+<h2 id="security-checklist">Security changes checklist</h2>
+
+<ul>
+  <li>Are you using inline script blocks in HTML pages?</li>
+    <ul>
+      <li>Remove JS code contained within &lt;script> tags
+        and place it within an external JS file.</li>
+    </ul>
+  <li>Are you using inline event handlers (like onclick, etc)?</li>
+    <ul>
+      <li>Remove them from the HTML code,
+        move them into an external JS file and
+        use <code>addEventListener()</code> instead.</li>
+    </ul>
+  <li>Does your extension inject content scripts into Web pages
+    that need to access resources (like images and scripts)
+    that are contained in the extension’s package?</li>
+    <ul>
+      <li>Define the <a href="manifest.html#web_accessible_resources">web_accessible_resources</a>
+        property and list the resources
+        (and optionally a separate Content Security Policy for those resources).</li>
+    </ul>
+  <li>Does your extension embed external Web pages?</li>
+    <ul>
+      <li>Define the <a href="manifest.html#sandbox">sandbox</a> property.</li>
+    </ul>
+  <li>Is your code or library using <code>eval()</code>, new <code>Function()</code>,
+    <code>innerHTML</code>, <code>setTimeout()</code>, or otherwise passing strings
+    of JS code that are dynamically evaluated?</li>
+    <ul>
+      <li>Use <code>JSON.parse()</code>
+        if you’re parsing JSON code into an object.</li>
+      <li>Use a CSP-friendly library,
+        for example, <a href="http://angularjs.org/">AngularJS</a>.</li>
+      <li>Create a sandbox entry in your manifest and
+        run the affected code in the sandbox,
+        using <code>postMessage()</code> to communicate
+        with the sandboxed page.</li>
+    </ul>
+  <li>Are you loading external code,
+    such as jQuery or Google Analytics?</li>
+    <ul>
+      <li>Consider downloading the library and
+        packaging it in your extension,
+        then loading it from the local package.</li>
+      <li>Whitelist the HTTPS domain that serves the resource
+        in the "content_security_policy” part of your manifest.</li>
+    </ul>
+</ul>
+
+<h2 id="api-summary">Summary of API changes</h2>
+
+<p>
+Manifest version 2 introduces a few changes
+to the browser action and page action APIs,
+and replaces a few old APIs with newer ones.
+</p>
+
+<h3 id="browser_actions">Changes to browser actions</h3>
+
+<p>
+The browser actions API introduces some naming changes:</p>
+
+<ul>
+  <li>The <code>browser_actions</code> and
+    <code>chrome.browserActions</code> properties have been replaced
+    with their singular counterparts <code>browser_action</code>
+    and <code>chrome.browserAction</code>.</li>
+  <li>Under the old <code>browser_actions</code> property,
+    there were <code>icons</code>, <code>name</code>, and
+    <code>popup</code> properties.
+    These have been replaced with:</li>
+  <ul>
+    <li><code>default_icon</code> for the browser action badge icon</li>
+    <li><code>default_name</code> for the text that appears in the tooltip when you hover over the badge</li>
+    <li><code>default_popup</code> for the HTML page that represents the UI
+      for the browser action (and this must now be a string, it cannot be an object)</li>
+  </ul>
+</ul>
+
+<h3 id="page_actions">Changes to page actions</h3>
+
+<p>Similar to the changes for browser actions,
+  the page actions API has also changed:</p>
+<ul>
+  <li>The <code>page_actions</code> and <code>chrome.pageActions</code> properties
+    have been replaced with their singular counterparts
+    <code>page_action</code> and <code>chrome.pageAction</code>.</li>
+  <li>Under the old <code>page_actions</code> property,
+    there were <code>icons</code>, <code>name</code>,
+    and <code>popup</code> properties.
+    These have been replaced with:</li>
+  <ul>
+    <li><code>default_icon</code> for the page action badge icon</li>
+    <li><code>default_name</code> for the text
+      that appears in the tooltip when you hover over the badge</li>
+    <li><code>default_popup</code> for the HTML page
+      that represents the UI for the page action
+      (and this must now be a string, it cannot be an object)</li>
+  </ul>
+</ul>
+
+<h3 id="removed_and_changed">Removed and changed APIs</h3>
+
+<p>
+A few extension APIs have been removed and replaced with new counterparts:
+</p>
+
+<ul>
+  <li>The <code>background_page</code> property has been replaced
+    by <a href="background_pages.html">background</a>.</li>
+  <li>The <code>chrome.self</code> property has been removed,
+    use <code>chrome.extension</code>.</li>
+  <li>The <code>Port.tab</code> property has been replaced
+    with <code>Port.sender</code>.</li>
+  <li>The <code>chrome.extension.getTabContentses()</code> and the
+    <code>chrome.extension.getExtensionTabs()</code> APIs have been replaced
+    by <code>chrome.extension.getViews( { “type” : “tab” } )</code>.</li>
+</ul>
+
+<h2 id="security-summary">Summary of security changes</h2>
+
+<p>
+There are a number of security-related changes
+that accompany the move from manifest version 1 to version 2.
+Many of these changes stem from Chrome’s adoption of
+<a href="contentSecurityPolicy.html#H3-1">Content Security Policy</a>;
+you should read more about this policy to understand its implications.
+</p>
+
+<h3 id="inline_scripts">Inline scripts and event handlers disallowed</h3>
+
+<p>
+Due to the use of <a href="contentSecurityPolicy.html">Content Security Policy</a>,
+you can no longer use &lt;script> tags that are inline with the HTML content.
+These must be moved to external JS files.
+In addition, inline event handlers are also not supported.
+For example, suppose you had the following code in your extension:
+</p>
+
+<pre>
+&lt;html>
+&lt;head>
+  &lt;script>
+    function myFunc() { ... }
+  &lt;/script>
+&lt;/head>
+&lt;/html>
+</pre>
+
+<p>
+This code would cause an error at runtime.
+To fix this, move &lt;script> tag contents to external files
+and reference them with a <code>src=’path_to_file.js’</code> attribute.
+</p>
+
+<p>
+Similarly, inline event handlers,
+which are a common occurrence and convenience feature
+used by many Web developers, will not execute.
+For example, consider common instances such as:
+</p>
+
+<pre>
+&lt;body onload=”initialize()”>
+&lt;button onclick=”handleClick()” id=”button1”>
+</pre>
+
+<p>
+These will not work in manifest V2 extensions.
+Remove the inline event handlers,
+place them in your external JS file and use <code>addEventListener()</code>
+to register event handlers for them instead.
+For example, in your JS code, use:
+</p>
+
+<pre>
+window.addEventListener(“load”, initialize);
+...
+document.getElementById(“button1”).addEventListener(“click”,handleClick);
+</pre>
+
+<p>
+This is a much cleaner way of separating
+your extension’s behavior from its user interface markup.
+</p>
+
+<h3 id="embedding">Embedding content</h3>
+
+<p>
+There are some scenarios where your extension might embed content
+that can be used externally or come from an external source. 
+</p>
+
+<p>
+<strong>Extension content in web pages:</strong><br>
+If your extension embeds resources (like images, script, CSS styles, etc)
+that are used in content scripts that are injected into web pages,
+you need to use the
+<a href="manifest.html#web_accessible_resources">web_accessible_resources</a> property
+to whitelist these resources so that external Web pages can use them:
+</p>
+
+<pre>
+{ // manifest file
+...
+  "<strong>web_accessible_resources</strong>": [
+    "images/image1.png",
+    "script/myscript.js"
+  ],
+...
+}
+</pre>
+
+<p>
+<strong>Embedding external content:</strong><br>
+The Content Security Policy only allows local script
+and objects to loaded from your package,
+which prevents external attackers from introducing unknown code to your extension.
+However, there are times when you want to load externally served resources,
+such as jQuery or Google Analytics code.
+There are two ways to do this:
+</p>
+
+<ol>
+  <li>Download the relevant library locally (like jQuery)
+    and package it with your extension.</li>
+    <li>You can relax the CSP in a limited way by whitelisting HTTPS origins
+      in the “content_security_policy” section of your manifest.
+      To include a library like Google Analytics, this is the approach to take:
+      <pre>
+        { // manifest file
+          ...,
+          "content_security_policy": "script-src 'self'
+          <strong>https://ssl.google-analytics.com</strong>; object-src 'self'",
+          ...
+        }
+      </pre></li>
+</ol>
+
+<h3 id="using">Using dynamic script evaluation</h3>
+
+<p>
+Perhaps one of the biggest changes in the new manifest v2 scheme is
+that extensions can no longer use dynamic script evaluation techniques like
+<code>eval()</code> or new <code>Function()</code>,
+or pass strings of JS code to functions
+that will cause an <code>eval()</code> to be used,
+like <code>setTimeout()</code>.
+In addition, certain commonly used JavaScript libraries,
+such as Google Maps and certain templating libraries,
+are known to use some of these techniques.
+</p>
+
+<p>
+Chrome provides a sandbox for pages to run in their own origin,
+which are denied access to chrome.* APIs.
+In order to use <code>eval()</code>
+and the like under the new Content Security Policy:
+</p>
+
+<ol>
+  <li>Create a sandbox entry in your manifest file.</li>
+  <li>In the sandbox entry,
+    list the pages you want to run in the sandbox.</li>
+  <li>Use message passing via <code>postMessage()</code>
+    to communicate with the sandboxed page.</li>
+</ol>
+
+<p>
+For more details on how to do this,
+see the <a href="sandboxingEval.html">Sandboxing Eval</a> documentation.
+</p>
+
+<h2 id="further-reading">Further reading</h2>
+
+<p>
+The changes in manifest version 2 are designed to guide developers
+toward building more secure and robustly-architected extensions and apps.
+To see a complete list of changes from manifest version 1 to version 2,
+see the <a href="manifestVersion.html">manifest file</a> documentation.
+For more information about using sandboxing to isolate unsafe code,
+read the <a href="sandboxingEval.html">sandboxing eval</a> article.
+You can learn more about Content Security Policy
+by visiting our extensions-related tutorial and a
+<a href="http://www.html5rocks.com/en/tutorials/security/content-security-policy/">good introduction on HTML5Rocks</a>.
+</p>
+
+
+
+
diff --git a/chrome/common/extensions/docs/templates/articles/tut_oauth.html b/chrome/common/extensions/docs/templates/articles/tut_oauth.html
new file mode 100644
index 0000000..b0f009f
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/tut_oauth.html
@@ -0,0 +1,215 @@
+<h1>Tutorial: OAuth</h1>
+
+
+<p>
+<a href="http://oauth.net/">OAuth</a> is an open protocol that aims to standardize the way desktop and web applications access a user's private data. OAuth provides a mechanism for users to grant access to private data without sharing their private credentials (username/password). Many sites have started enabling APIs to use OAuth because of its security and standard set of libraries.
+</p>
+<p>
+This tutorial will walk you through the necessary steps for creating a Google Chrome Extension that uses OAuth to access an API. It leverages a library that you can reuse in your extensions.
+</p>
+<p>
+This tutorial uses the <a href="http://code.google.com/apis/documents/">Google Documents List Data API</a> as an example OAuth-enabled API endpoint.
+</p>
+
+<h2 id="requirements">Requirements</h2>
+
+<p>
+This tutorial expects that you have some experience writing extensions for Google Chrome and some familiarity with the <a href="http://code.google.com/apis/accounts/docs/OAuth.html">3-legged OAuth</a> flow. Although you don’t need a background in the <a href="http://code.google.com/apis/documents/">Google Documents List Data API</a> (or the other <a href="http://code.google.com/apis/gdata/">Google Data APIs</a> for that matter), having a understanding of the protocol may be helpful.
+</p>
+
+<h2 id="getting-started">Getting started</h2>
+
+<p>
+First, copy over the three library files from the Chromium source tree at <a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/oauth_contacts/">.../examples/extensions/oauth_contacts/</a>:
+</p>
+<ul>
+<li><strong><a href="examples/extensions/oauth_contacts/chrome_ex_oauth.html" download="chrome_ex_oauth.html">chrome_ex_oauth.html</a></strong> - interstitial page for the oauth_callback URL</li>
+<li><strong><a href="examples/extensions/oauth_contacts/chrome_ex_oauth.js" download="chrome_ex_oauth.js">chrome_ex_oauth.js</a></strong> - core OAuth library</li>
+<li><strong><a href="examples/extensions/oauth_contacts/chrome_ex_oauthsimple.js" download="chrome_ex_oauthsimple.js">chrome_ex_oauthsimple.js</a></strong> - helpful wrapper for chrome_ex_oauth.js</li>
+</ul>
+
+<p>Place the three library files in the root of your extension directory (or wherever your JavaScript is stored). Then include both .js files in your background page in the following order:</p>
+
+<pre>
+&lt;script type="text/javascript" src="chrome_ex_oauthsimple.js"&gt;&lt;/script&gt;
+&lt;script type="text/javascript" src="chrome_ex_oauth.js"&gt;&lt;/script&gt;
+</pre>
+
+<p>Your background page will manage the OAuth flow.</p>
+
+<h2 id="oauth-dance">The OAuth dance in an extension</h2>
+
+<p>
+If you are familiar with the OAuth protocol, you'll recall that the OAuth dance consists of three steps:
+</p>
+
+<ol>
+<li>fetching an initial request token</li>
+<li>having the user authorize the request token</li>
+<li>fetching an access token</li>
+</ol>
+
+<p>In the context of an extension, this flow gets a bit tricky. Namely, there is no established consumer key/secret between the service provider and the application. That is, there is no web application URL for the user to be redirected to after the approval process.
+</p>
+
+<p>
+Luckily, Google and a few other companies have been working on an <a href="http://code.google.com/apis/accounts/docs/OAuthForInstalledApps.html">OAuth for installed applications</a> solution that you can use from an extension environment. In the installed applications OAuth dance, the consumer key/secret are ‘anonymous’/’anonymous’ and you provide an <em>application name</em> for the user to grant access to (instead of an application URL). The end result is the same: your background page requests the initial token, opens a new tab to the approval page, and finally makes the asynchronous call for the access token.
+</p>
+
+<h3 id="set-code">Setup code</h3>
+
+<p>To initialize the library, create a <code>ChromeExOAuth</code> object in the background page:</p>
+
+<pre>
+var oauth = ChromeExOAuth.initBackgroundPage({
+  'request_url': &lt;OAuth request URL&gt;,
+  'authorize_url': &lt;OAuth authorize URL&gt;,
+  'access_url': &lt;OAuth access token URL&gt;,
+  'consumer_key': &lt;OAuth consumer key&gt;,
+  'consumer_secret': &lt;OAuth consumer secret&gt;,
+  'scope': &lt;scope of data access, not used by all OAuth providers&gt;,
+  'app_name': &lt;application name, not used by all OAuth providers&gt;
+});
+</pre>
+
+<p>In the case of the Documents List API and Google’s OAuth endpoints, a possible initialization may be:</p>
+
+<pre>
+var oauth = ChromeExOAuth.initBackgroundPage({
+  'request_url': 'https://www.google.com/accounts/OAuthGetRequestToken',
+  'authorize_url': 'https://www.google.com/accounts/OAuthAuthorizeToken',
+  'access_url': 'https://www.google.com/accounts/OAuthGetAccessToken',
+  'consumer_key': 'anonymous',
+  'consumer_secret': 'anonymous',
+  'scope': 'https://docs.google.com/feeds/',
+  'app_name': 'My Google Docs Extension'
+});
+</pre>
+
+<p>
+To use the OAuth library,
+you must declare the "tabs" permision in the
+<a href="http://code.google.com/chrome/extensions/manifest.html">extension manifest</a>.
+You must also declare the sites you are using
+including the request URL, the authorize URL, access URL,
+and, if necessary, the scope URL.
+For example:
+</p>
+
+<pre>
+"permissions": [ "tabs", "https://docs.google.com/feeds/*",
+  "https://www.google.com/accounts/OAuthGetRequestToken",
+  "https://www.google.com/accounts/OAuthAuthorizeToken",
+  "https://www.google.com/accounts/OAuthGetAccessToken"
+]
+</pre>
+
+<h3 id="request-token">Fetching and authorizing a request token</h3>
+
+<p>
+Once you have your background page set up, call the <code>authorize()</code> function to begin the OAuth dance and redirect the user to the OAuth provider. The client library abstracts most of this process, so all you need to do is pass a callback to the <code>authorize()</code> function, and a new tab will open and redirect the user.
+</p>
+
+<pre>
+oauth.authorize(function() {
+  // ... Ready to fetch private data ...
+});
+</pre>
+
+<p>
+You don't need to provide any additional logic for storing the token and secret, as this library already stores these values in the browser’s <code>localStorage</code>. If the library already has an access token stored for the current scope, then no tab will be opened. In either case, the callback will be called.
+</p>
+
+<h3 id="signed-requests">Sending signed API requests</h3>
+
+<p>
+Once your specified callback is executed, call the <code>sendSignedRequest()</code> function to send signed requests to your API endpoint(s). <code>sendSignedRequest()</code> takes three arguments: a URI, a callback function, and an optional parameter object. The callback is passed two arguments: the response text and the <code>XMLHttpRequest</code> object that was used to make the request.
+</p>
+
+<p>This example sends an HTTP <code>GET</code>:</p>
+
+<pre>
+function callback(resp, xhr) {
+  // ... Process text response ...
+};
+
+function onAuthorized() {
+  var url = 'https://docs.google.com/feeds/default/private/full';
+  var request = {
+    'method': 'GET',
+    'parameters': {'alt': 'json'}
+  };
+
+  // Send: GET https://docs.google.com/feeds/default/private/full?alt=json
+  oauth.sendSignedRequest(url, callback, request);
+};
+
+oauth.authorize(onAuthorized);
+</pre>
+
+<p>A more complex example using an HTTP <code>POST</code> might look like this:</p>
+
+<pre>
+function onAuthorized() {
+  var url = 'https://docs.google.com/feeds/default/private/full';
+  var request = {
+    'method': 'POST',
+    'headers': {
+      'GData-Version': '3.0',
+      'Content-Type': 'application/atom+xml'
+    },
+    'parameters': {
+      'alt': 'json'
+    },
+    'body': 'Data to send'
+  };
+
+  // Send: POST https://docs.google.com/feeds/default/private/full?alt=json
+  oauth.sendSignedRequest(url, callback, request);
+};
+</pre>
+
+<p>
+By default, the <code>sendSignedRequest()</code> function sends the <code>oauth_*</code> parameters in the URL (by calling <code>oauth.signURL()</code>). If you prefer to send the <code>oauth_*</code> parameters in the <code>Authorization</code> header (or need direct access to the generated header), use <code>getAuthorizationHeader()</code>. Its arguments are a URI, an HTTP method, and an optional object of URL query parameters as key/value pairs.
+</p>
+
+<p>Here is the example above using <code>getAuthorizationHeader()</code> and an <code>XMLHttpRequest</code> object:</p>
+
+<pre>
+function stringify(parameters) {
+  var params = [];
+  for(var p in parameters) {
+    params.push(encodeURIComponent(p) + '=' +
+                encodeURIComponent(parameters[p]));
+  }
+  return params.join('&');
+};
+
+function onAuthorized() {
+  var method = 'POST';
+  var url = 'https://docs.google.com/feeds/default/private/full';
+  var params = {'alt': 'json'};
+
+  var xhr = new XMLHttpRequest();
+  xhr.onreadystatechange = function(data) {
+    callback(xhr, data);
+  };
+  xhr.open(method, url + '?' + stringify(params), true);
+  xhr.setRequestHeader('GData-Version', '3.0');
+  xhr.setRequestHeader('Content-Type', 'application/atom+xml');
+  xhr.setRequestHeader('Authorization', oauth.getAuthorizationHeader(url, method, params));
+
+  xhr.send('Data to send');
+};
+</pre>
+
+<h2 id="sample-code">Sample code</h2>
+
+<p>
+Sample extensions that use these techniques are available in the Chromium source tree:
+</p>
+
+<ul>
+<li><a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/gdocs/">.../examples/extensions/gdocs/</a></li>
+<li><a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/oauth_contacts/">.../examples/extensions/oauth_contacts/</a></li>
+</ul>
diff --git a/chrome/common/extensions/docs/templates/articles/tutorials.html b/chrome/common/extensions/docs/templates/articles/tutorials.html
new file mode 100644
index 0000000..79fbf87
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/tutorials.html
@@ -0,0 +1,29 @@
+<h1>Tutorials</h1>
+
+<p>
+Follow these to get started
+developing Google Chrome Extensions:
+</p>
+
+<ul>
+  <li> <a href="getstarted.html">Getting Started</a> </li>
+  <li> <a href="tut_debugging.html">Debugging</a> </li>
+</ul>
+
+<p>
+The remaining tutorials cover
+more specialized topics:
+</p>
+
+<ul>
+  <li> <a href="tut_analytics.html">Google Analytics</a> -
+  Track the usage of your extension.
+  </li>
+  <li> <a href="tut_oauth.html">OAuth</a> -
+  Learn how your extension can use APIs
+  (such as <a href="http://code.google.com/apis/gdata/">Google Data APIs</a>)
+  that support OAuth.
+  This tutorial features an OAuth library
+  that you can reuse in your extension.
+  </li>
+</ul>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/articles/whats_new.html b/chrome/common/extensions/docs/templates/articles/whats_new.html
new file mode 100644
index 0000000..4a74128
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/whats_new.html
@@ -0,0 +1,633 @@
+<h1>What's New in Extensions?</h1>
+<!--  -->
+
+<p>
+This page lists the API and manifest changes
+made in recent releases.
+</p>
+
+<h2 id="22"> Google Chrome 22 </h2>
+
+<h4> New APIs </h4>
+  <ul>
+    <li>The
+      <a href="alarms.html">alarms API</a>
+      lets you schedule code to run
+      periodically or at a specified time
+      in the future.</li>
+  </ul>
+  <ul>
+    <li>The
+      <a href="runtime.html">runtime API</a>
+      contains useful methods for dealing
+      with system-wide events.
+  </ul>
+
+<h4> Additions to Existing Features </h4>
+  <ul>
+    <li>Background pages can optionally be
+      non-persistent, using a feature we call
+      <a href="event_pages.html">event pages</a>.
+      Event pages run only while they're being used,
+      and will unload when idle to save resources.
+  </ul>
+
+<h2 id="21"> Google Chrome 21 </h2>
+
+<h4> New Features </h4>
+  <ul>
+    <li>The <a href="manifest.html#sandbox">sandbox manifest entry</a> allows
+      you to define some pages within your extension that are automatically run
+      in a low-privilege sandbox. This sandbox is not bound by the
+      <a href="contentSecurityPolicy.html">content_security_policy</a>.
+    <li>The <a href="input.ime.html">IME</a> API allows extensions to implement
+      <a href="http://en.wikipedia.org/wiki/Input_method">input method
+      editors</a> on Chrome OS.
+  </ul>
+
+<h4> Additions to Existing Features </h4>
+  <ul>
+    <li>The <a href="types.html#method-ChromeSetting-set"
+      >ChromeSetting.set()</a> method now has a
+      <code>regular_only</code> scope.
+    <li>The <a href="browsingData.html#type-RemovalOptions"
+      >browsingData.RemovalOptions</a> now has an <code>originTypes</code>
+      property.
+    <li>The <a href="management.html#method-uninstall"
+      >management.uninstall()</a> method now has a <code
+      >showConfirmDialog</code> parameter.
+    <li>The <a href="contextMenus.html#method-create"
+      >contextMenus.create()</a> method now allows you to specify unique
+      IDs for each item. This is intended to be used with the new
+      <a href="contextMenus.html#event-onClicked"
+      >contextMenus.onClicked</a> event, to distinguish the clicked item.
+    <li>The <a href="browserAction.html#method-setIcon"
+      >browserAction.setIcon()</a> and
+      <a href="pageAction.html#method-setIcon"
+      >pageAction.setIcon()</a> methods now accept optional callbacks.
+    <li>The <a href="privacy.html#property-websites"
+      >privacy.websites</a> namespace now has a <code
+      >protectedContentEnabled</code> property.
+    <li>The <code>index</code> parameter to the <a href="tabs.html#method-move"
+      >tabs.move()</a> method now accepts <code>-1</code> to indicate
+      that the tab should be placed at the end.
+    <li>The <code>windowId</code> parameter to the
+      <a href="tabs.html#method-highlight">tabs.highlight()</a>
+      method is now optional.
+    <li><a href="manifest.html#intents">Web intents</a> can now be used to
+      register content-type viewers.
+  </ul>
+
+<h2 id="20"> Google Chrome 20 </h2>
+
+<h4> New APIs </h4>
+  <ul>
+    <li>The
+      <a href="storage.html">storage API</a>
+      lets you store, retrieve, and
+      track changes to user data.</li>
+  </ul>
+
+<h4> Manifest changes </h4>
+  <ul>
+    <li>Manifest version 1 was deprecated in Chrome 18
+      and will be phased out according to the
+      <a href="manifestVersion.html">Manifest version 1 support schedule</a>.</li>
+  </ul>
+
+<h4> Additions to existing APIs </h4>
+  <ul>
+    <li>The <code>chrome.contextMenus</code>
+      <a href="contextMenus.html#method-create">create()</a> and
+      <a href="contextMenus.html#method-update">update()</a>
+      methods now have an <code>enabled</code> parameter.</li>
+    <li>The privacy API's <a href="privacy.html#property-services">services</a> object
+      now has a <code>spellingServiceEnabled</code> setting.</li>
+    <li>The <code>chrome.tabs</code>
+      <a href="tabs.html#method-executeScript">executeScript()</a> and
+      <a href="tabs.html#method-insertCSS">insertCSS()</a>
+      now accept a <code>runAt</code> parameter.</li>
+  </ul>
+
+<h4> Changes to existing APIs </h4>
+
+  <ul>
+    <li>The <code>sendRequest()</code> method
+      has been deprecated in favor of the
+      <code>sendMessage()</code> method for both the
+      <a href="extension.html#method-sendMessage">extension API</a> and
+      <a href="tabs.html#method-sendMessage">tabs API</a>.</li>
+    <li>The <a href="external_extensions.html">external_extensions.json file</a>
+      has been deprecated in favor of individual
+      <code>&lt;extension-id&gt;.json</code> files.</li>
+  </ul>
+
+<h2 id="19"> Google Chrome 19 </h2>
+
+<h4> New APIs </h4>
+   <ul>
+     <li>The <a href="browsingData.html">browsing data API</a>
+      lets you remove browsing data from a user's local profile.
+    <li>The <a href="topSites.html">top sites API</a>
+      gives you access to the top sites
+      that are displayed on the new tab page.
+   </ul>
+
+<h4> Manifest changes </h4>
+  <ul>
+    <li>The new <a href="manifest.html#intents">intents</a>
+      field specifies all intent handlers provided by the extension.
+  </ul>
+
+ <h4> Additions to existing APIs </h4>
+  <ul>
+    <li>The window API's <a href="windows.html#type-Window">Window</a>
+      object now has an <code>alwaysOnTop</code> property and
+       supports the "fullscreen" state.
+     <li>The <code>chrome.tabs</code>
+       <a href="tabs.html#method-query">query()</a> method
+       now has the <code>currentWindow</code> and
+      <code>lastFocusedWindow</code> parameters.
+     <li>The <a href="browserAction.html">browser action API</a>
+       has the following new getter functions:
+       <a href="browserAction.html#method-getTitle">getTitle</a>,
+       <a href="browserAction.html#method-getBadgeText">getBadgeText</a>,
+       <a href="browserAction.html#method-getBadgeBackgroundColor">getBadgeBackgr
+     oundColor</a>, and
+       <a href="browserAction.html#method-getPopup">getPopup</a>.
+    <li>The <a href="pageAction.html">page action API</a>
+      has the following new getter functions:
+       <a href="pageAction.html#method-getTitle">getTitle</a> and
+       <a href="pageAction.html#method-getPopup">getPopup</a>.
+   </ul>
+
+<h2 id="18"> Google Chrome 18 </h2>
+
+<h4> New APIs </h4>
+  <ul>
+    <li>The <a href="debugger.html">debugger API</a>
+      lets you use the Chrome Developer Tools
+      on one or more tabs remotely.
+    <li>The <a href="devtools.html">developer tools APIs</a>
+      provide support for extending Chrome Developer Tools.
+      The inspected window API lets you interact with the inspected window.
+      The network API lets you retrive information about network requests.
+      The panels API lets you add panels and sidebars.
+    <li>The <a href="pageCapture.html">page capture API</a>
+      lets you save a tab as MHTML.
+    <li> The <a href="privacy.html">privacy API</a>
+      lets you control usage of the features
+      in Chrome that can affect a user's privacy policy.
+  </ul>
+
+<h4> Manifest changes </h4>
+  <ul>
+    <li>The new
+      <a href="manifest.html#manifest_version">manifest version</a>
+      field specifies the version of the manifest that your package requires.
+      As of Chrome 18, you should use
+      <a href="manifestVersion.html">manifest version 2</a>.
+    <li>The new
+      <a href="contentSecurityPolicy.html">Content Security Policy (CSP)</a>
+      field is used to define an extension's policies
+      towards the types of content
+      that can be loaded and executed by the extension.
+    <li>Most background pages only include a list of script files.
+      For these background pages,
+      you can use the new
+      <a href="background_pages.html">background.scripts</a> property
+      and Chrome will generate a background page for you.
+    <li>The new
+      <a href="manifest.html#web_accessible_resources">web_accessible_resources</a>
+      field specifies the paths of packaged resources
+      that are expected to be usable in the context of a web page.
+  </ul>
+
+<h4> Additions to existing APIs </h4>
+  <ul>
+    <li>The <a href="windows.html#property-WINDOW_ID_CURRENT">windows API</a>
+    now has a <code>WINDOW_ID_CURRENT</code> property.
+    <li>The <code>chrome.tabs</code>
+    <a href="tabs.html#method-create">create()</a> and
+    <a href="tabs.html#method-update">update()</a> methods
+    now have an <code>openerTabId</code> parameter.
+  </ul>
+
+<h2 id="17"> Google Chrome 17 </h2>
+
+<h4> New APIs </h4>
+  <ul>
+    <li>The
+      <a href="webRequest.html">web request API</a>
+      lets you intercept, block, or
+      modify requests in-flight and
+      to observe and analyze traffic.
+  </ul>
+
+<h4> Manifest changes </h4>
+  <ul>
+    <li>Permissions can be
+      <a href="permissions.html">optional</a>
+      for the content setting API, the web navigation API, and
+      the new web request API.
+  </ul>
+
+<h4> Additions to existing APIs </h4>
+  <ul>
+    <li>The management API's
+    <a href="management.html#type-ExtensionInfo">ExtensionInfo</a> object
+    now has a <code>disabledReason</code> property.
+  </ul>
+
+<h4> Changes to existing APIs </h4>
+
+  <ul>
+    <li>The <a href="omnibox.html">omnibox API</a>
+    now works in
+    <a href="manifest.html#incognito">split incognito mode</a>.
+  </ul>
+
+<h2 id="16"> Google Chrome 16 </h2>
+
+<h4> New APIs </h4>
+  <ul>
+    <li>The
+      <a href="webNavigation.html">web navigation API</a>
+      lets extensions receive notifications about the status
+      of navigation requests.
+      You can use this API to track navigation events.
+    <li>The
+      <a href="permissions.html">optional permissions API</a>
+      lets you control when users are presented with permission requests.
+    <li>The
+      <a href="contentSettings.html">content settings API</a>
+      lets extensions customize Chrome’s behavior
+      on a per-site basis instead of globally.
+      You can use this API to control whether websites can use features
+      such as cookies, JavaScript, and plug-ins.
+  </ul>
+
+<h4> Manifest changes </h4>
+  <ul>
+    <li>The new
+      <a href="manifest.html#requirements">requirements</a> field
+      allows you to declare extension requirements up front.
+      For example, you can use this field
+      to specify that your app requires 3D graphics support
+      in order to use features such as CSS 3D Tranforms or WebGL.
+  </ul>
+
+<h4> Additions to existing APIs </h4>
+  <ul>
+    <li>The new
+    <a href="tabs.html#method-query">chrome.tabs.query()</a> method
+    gets all tabs that have the specified properties or
+    all tabs if no properties are specified.
+    <li>The new
+    <a href="tabs.html#method-reload">chrome.tabs.reload()</a> method
+    reloads a tab and includes the option
+    to preserve the local cache of the reloaded tab.
+    <li>The management API's
+    <a href="management.html#type-ExtensionInfo">ExtensionInfo</a> object
+    now has an <code>updateURL</code> property.
+    <li>You can now limit the supported locales for an
+    <a href="external_extensions.html">external extension</a>
+    by adding the <code>supported_locales</code> attribute
+    to the <code>external_extensions.json</code>.
+  </ul>
+
+<h4> Changes to existing APIs </h4>
+  <ul>
+    <li>The methods <code>getAllInWindow()</code> and
+    <code>getSelected()</code> have been deprecated.
+    To get details about all tabs in the specified window, use
+    <a href="tabs.html#method-query">chrome.tabs.query()</a>
+    with the argument <code>{'windowId': windowID}</code>.
+    To get the tab that is selected in the specified window, use
+    <code>chrome.tabs.query()</code>
+    with the argument <code>{'active': true}</code>.
+    <li>You are no longer required
+    to specify the <code>tabID</code> for the
+    <a href="tabs.html#method-update">chrome.tabs.update()</a> method.
+    When not provided,
+    the <code>tabID</code> defaults
+    to the selected tab of the current window.
+    <li>External extension files on Mac OS can now be owned by users
+    within a wheel group (or an admin group).
+    <li>The "experimental" permission is no longer required
+    for the
+    <a href="windows.html#type-Window">window "panel"</a> type.
+    By default, the "panel" type creates a popup
+    unless the <code>--enable-panels</code> flag is set.
+  </ul>
+
+<h2 id="15"> Google Chrome 15 </h2>
+
+<h4> New APIs </h4>
+
+<ul>
+ <li>The <a href="webstore.html">webstore API</a>
+ lets you initiate app and extensions installations
+ <a href="http://code.google.com/chrome/webstore/docs/inline_installation.html">"inline"</a>
+ from your site.
+</ul>
+
+<h4> Manifest changes </h4>
+  <ul>
+    <li>The new
+      <a href="manifest.html#offline_enabled">offline_enabled</a> field
+      lets you specify that your app works well even without an internet
+      connection.
+  </ul>
+
+<h4> Additions to existing APIs </h4>
+  <ul>
+    <li>You can retrieve permission warnings using the new management API methods
+      <a href="management.html#method-getPermissionWarningsById">getPermissionWarningsById()</a>
+      and
+      <a href="management.html#method-getPermissionWarningsByManifest">getPermissionWarningsByManifest()</a>.
+    <li>The management API’s
+      <a href="management.html#type-ExtensionInfo">ExtensionInfo</a> object has
+      a new field, <code>offlineEnabled</code>.
+    <li>You can now <a href="i18n.html">internationalize</a> content script CSS
+      files by using <code>__MSG_messagename__</code> placeholders.
+  </ul>
+
+<h4> Changes to existing APIs </h4>
+  <ul>
+    <li>The callback for the <a href="tabs.html#method-update">tabs.update</a>
+      method is passed <code>null</code> instead of the tab details if the
+      extension does not have the "tabs" permission.
+  </ul>
+
+<h2 id="14"> Google Chrome 14 </h2>
+
+<h4> New APIs </h4>
+  <ul>
+    <li>The <a href="tts.html">tts API</a>
+      lets extensions generate speech from text.
+    <li>The <a href="ttsEngine.html">ttsEngine API</a>
+      lets extensions implement text-to-speech (TTS) engines.
+    <li>Extensions that are distributed through the Chrome Web Store
+      can now include Native Client modules.
+      No special manifest entry is necessary, as you can see from the
+      <a href="http://code.google.com/p/naclports/source/browse#svn%2Ftrunk%2Fsrc%2Fpackages%2Flibraries%2Fnethack-3.4.3">NetHack example</a>.
+      For more information, see the
+      <a href="http://blog.chromium.org/2011/08/native-client-brings-sandboxed-native.html">announcement</a> and the
+      <a href="http://code.google.com/chrome/nativeclient/">Native Client documentation</a>.
+  </ul>
+
+<h4> Manifest changes </h4>
+  <ul>
+    <li>The new
+      <a href="contentSecurityPolicy.html">content_security_policy</a> field
+      can help prevent
+      <a href="http://blog.chromium.org/2011/07/writing-extensions-more-securely.html">cross-site scripting vulnerabilities</a>
+      in your extension.
+    <li>The new <a href="manifest.html#nacl_modules">nacl_modules</a> field
+      lets you register Native Client modules
+      as content handlers for MIME types.
+  </ul>
+
+<h4> Additions to existing APIs </h4>
+  <ul>
+    <li><a href="contextMenus.html">Context menu</a> items
+      can now appear even in documents
+      that have file:// or chrome:// URLs.
+      Previously, they were restricted to documents with
+      http:// or https:// URLs.
+    <li>An optional <code>drawAttention</code> field in
+      <a href="windows.html#method-update">chrome.windows.update()</a>'s
+      <code>updateInfo</code> object lets you specify that the window
+      should entice the user to change focus to it.
+    <li>The new
+      <a href="bookmarks.html#method-getSubTree">bookmarks.getSubTree()</a>
+      function lets you retrieve just part of the Bookmarks hierarchy.
+  </ul>
+
+<h4> Changes to existing APIs </h4>
+  <ul>
+    <li>The "tabs" permission is no longer required for
+      <a href="tabs.html#method-remove">tabs.remove</a> and
+      <a href="tabs.html#event-onRemoved">tabs.onRemoved</a>.
+  </ul>
+
+<h2 id="13"> Google Chrome 13 </h2>
+
+<h4> New APIs </h4>
+  <ul>
+    <li>The <a href="proxy.html">proxy API</a>
+      lets extensions manage Chrome's proxy settings.
+    <li>The
+      <a href="types.html#type-ChromeSetting">chrome.types.ChromeSetting</a> prototype
+      provides an interface to browser settings;
+      it's used by the proxy API.
+    <li> <em>Chrome OS only:</em>
+      The <a href="fileBrowserHandler.html">fileBrowserHandler API</a>
+      lets you extend the Chrome OS file browser.
+  </ul>
+
+<h4> Manifest changes </h4>
+  <ul>
+    <li>A new <code>exclude_matches</code> item
+      in the "content_scripts" field
+      lets you target your content script more precisely.
+      For details, see
+      <a href="content_scripts.html#match-patterns-globs">Match patterns and globs</a>.
+    <li>New "clipboardRead" and "clipboardWrite"
+      <a href="manifest.html#permissions">permissions</a>
+      specify capabilities for <code>document.execCommand()</code>.
+  </ul>
+
+<h4> Additions to existing APIs </h4>
+  <ul>
+    <li>Content scripts can now make
+      <a href="xhr.html">cross-origin XMLHttpRequests</a>
+      to the same sites that their parent extension can,
+      eliminating the need to relay these requests
+      through a background page.
+    <li>You can now use <code>@run-at</code>
+      in an imported Greasemonkey script
+      to control when the script is injected.
+      It works the same way as
+      <a href="content_scripts.html#run_at"><code>run_at</code></a>
+      in content scripts.
+  </ul>
+
+<h2 id="12"> Google Chrome 12 </h2>
+
+<h4> Additions to existing APIs </h4>
+  <ul>
+    <li>Two new <code>chrome.extension</code>
+      methods&mdash;<a href="extension.html#method-isAllowedFileSchemeAccess">isAllowedFileSchemeAccess()</a> and
+      <a href="extension.html#method-isAllowedIncognitoAccess">isAllowedIncognitoAccess()</a>&mdash;let you
+      determine whether your extension has increased access,
+      which the user specifies using the extensions management page
+      (<b>chrome://extensions</b>).
+    <li>The <a href="windows.html#method-create">chrome.windows.create()</a>
+      method can now take a <code>focused</code> value.
+      Previously, all new windows had the keyboard focus;
+      now you can create windows without interrupting the user's typing.
+    <li>If the manifest specifies "experimental" permission,
+      your extension can specify "panel" as the value of
+      the <code>type</code> field in
+      the <a href="windows.html#method-create">chrome.windows.create()</a>
+      method
+      or the <a href="windows.html#type-Window">Window</a> type.
+    <li>The <a href="cookies.html#event-onChanged">onChanged</a>
+      event of <code>chrome.cookies</code>
+      now has a <code>cause</code> parameter.
+    <li>The <code>chrome.contextMenus</code>
+      <a href="contextMenus.html#method-create">create()</a> and
+      <a href="contextMenus.html#method-update">update()</a>
+      methods now let you specify a context value of "frame".
+  </ul>
+
+<h2 id="11"> Google Chrome 11 </h2>
+
+<h4> Changes to existing APIs </h4>
+  <ul>
+    <li>For security reasons, you can no longer call
+      <a href="tabs.html#method-captureVisibleTab">chrome.tab.captureVisibleTab()</a>
+      on just any tab.
+      Instead, you now must have
+      <a href="manifest.html#permissions">host permission</a>
+      for the URL displayed by that tab.
+      To get the previous behavior,
+      specify <code>&lt;all_urls></code> for the host permission.
+  </ul>
+
+<h4> Additions to existing APIs </h4>
+  <ul>
+    <li>The management API's
+      <a href="management.html#type-ExtensionInfo">ExtensionInfo</a> object
+      now has a <code>homepageUrl</code> property.
+    <li>The management API now lets you get the icons of
+      disabled apps and extensions.
+      Also, you can now modify the regular icon's URL
+      to get its disabled equivalent.
+      See <a href="management.html#type-IconInfo">IconInfo</a> for details.
+    <li>The cookies API
+      <a href="cookies.html#method-set">set()</a>
+      and <a href="cookies.html#method-remove">remove()</a> methods
+      can now take callbacks.
+  </ul>
+
+<h2 id="10"> Google Chrome 10 </h2>
+
+<h4> Manifest changes </h4>
+  <ul>
+    <li>The new <a href="manifest.html#bg">background</a> permission
+      extends the life of Chrome,
+      allowing your extension or app
+      to run even when Chrome has no windows open.
+  </ul>
+
+<h4> Additions to existing APIs </h4>
+  <ul>
+    <li>The <a href="windows.html#method-create">chrome.windows.create()</a>
+      method now has a <code>tabId</code> field.
+      You can use it to move a tab or panel into a new window.
+      <p class="note">
+      <b>Note:</b>
+      This change was incorrectly attributed to Chrome 9
+      in previous release notes.
+  </ul>
+
+<h2 id="9"> Google Chrome 9 </h2>
+
+<h4> New APIs </h4>
+  <ul>
+    <li>The <a href="omnibox.html">omnibox API</a> allows you to
+      register a keyword with Chrome's address bar.
+  </ul>
+
+<h4> Manifest changes </h4>
+  <ul>
+    <li>The <a href="manifest.html#homepage_url">homepage_url</a> field
+      lets you specify the extension or app's homepage.
+  </ul>
+
+<h4> Additions to existing APIs </h4>
+  <ul>
+    <li>The <a href="tabs.html#type-Tab">Tab</a> object
+      now has a <code>pinned</code> property
+      that's reflected in various <code>chrome.tabs</code> methods.
+      For example,
+      you can <a href="tabs.html#method-create">create</a>
+      a pinned tab.
+    <li>The <a href="windows.html#method-create">chrome.windows.create()</a>
+      method can now take a list of URLs,
+      letting you create multiple tabs in the new window.
+    <li>The new
+      <a href="management.html#method-get">chrome.management.get()</a> method
+      lets you get information about the specified extension or app.
+  </ul>
+
+
+<h2 id="8"> Google Chrome 8 </h2>
+
+<p>
+We took a break for Chrome 8.
+No API or manifest changes worth noting.
+</p>
+
+
+<h2 id="7"> Google Chrome 7 </h2>
+
+<h4> New APIs </h4>
+  <ul>
+    <li>All users can now install
+      <a href="http://code.google.com/chrome/apps/index.html">apps</a>;
+      packaged apps can use extension APIs.
+    <li>The <a href="management.html">management API</a>
+      lets you work with installed apps and extensions.
+  </ul>
+
+<h4> Manifest changes </h4>
+  <ul>
+    <li>Introduced <a href="manifest.html#incognito">split incognito</a>
+      mode as the default for installable web apps
+      (also available to extensions).
+    <li>The <a href="tabs.html">tabs API</a>
+      <code>create()</code> and <code>update()</code> methods
+      no longer require the "tabs" permission, removing one common cause of
+      <a href="permission_warnings.html">scary dialogs</a>.
+  </ul>
+
+
+<h2 id="6">Google Chrome 6</h2>
+
+<h4> New APIs </h4>
+  <ul>
+    <li>The <a href="contextMenus.html">context menus API</a> allows you to
+      add context menus to pages or specific objects on a page.
+    <li>The <a href="cookies.html">cookies API</a> allows you to manage the
+      browser's cookie system.
+    <li>The <a href="idle.html">idle API</a> allows you to detect when the
+      machine's idle state changes.
+  </ul>
+
+<h4> Additions to existing APIs </h4>
+  <ul>
+    <li>The <a
+      href="extension.html#method-getViews">chrome.extension.getViews()</a>
+      method can now return popup views.
+    <li>A new <a
+      href="windows.html#property-WINDOW_ID_NONE">WINDOW_ID_NONE</a> constant
+      identifies when focus shifts away from the browser.
+    <li>The new <a
+      href="tabs.html#method-getCurrent">chrome.tabs.getCurrent()</a> method
+      returns the tab associated with the currently executing script.
+  </ul>
+
+<h4> Manifest changes </h4>
+  <ul>
+    <li>The <a href="manifest.html#geolocation">geolocation</a> permission
+      gives an extension access to the user's physical location.
+    <li><a href="match_patterns.html">Match patterns</a> can now select all
+      schemes or all URLs.
+    <li>Access to file:/// URLs no longer triggers the "access to your machine"
+      security warning, but now requires user opt-in from the extensions
+      management page.
+  </ul>
diff --git a/chrome/common/extensions/docs/templates/articles/xhr.html b/chrome/common/extensions/docs/templates/articles/xhr.html
new file mode 100644
index 0000000..72d3b92
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/xhr.html
@@ -0,0 +1,162 @@
+<h1>Cross-Origin XMLHttpRequest</h1>
+
+
+<p id="classSummary">
+Regular web pages can use the
+<a href="http://www.w3.org/TR/XMLHttpRequest/">XMLHttpRequest</a>
+object to send and receive data from remote servers,
+but they're limited by the
+<a href="http://en.wikipedia.org/wiki/Same_origin_policy">same origin policy</a>.
+Extensions aren't so limited.
+An extension can talk to remote servers outside of its origin,
+as long as it first requests cross-origin permissions.</p>
+
+<h2 id="extension-origin">Extension origin</h2>
+<p>Each running extension exists within its own separate security origin. Without
+requesting additional privileges, the extension can use
+XMLHttpRequest to get resources within its installation. For example, if
+an extension contains a JSON configuration file called <code>config.json</code>,
+in a <code>config_resources</code> folder, the extension can retrieve the file's contents like
+this:</p>
+
+<pre>
+var xhr = new XMLHttpRequest();
+xhr.onreadystatechange = handleStateChange; // Implemented elsewhere.
+xhr.open("GET", chrome.extension.getURL('/config_resources/config.json'), true);
+xhr.send();
+</pre>
+
+<p>If the extension attempts to use a security origin other than itself,
+say http://www.google.com,
+the browser disallows it
+unless the extension has requested the appropriate cross-origin permissions.
+</p>
+
+<h2 id="requesting-permission">Requesting cross-origin permissions</h2>
+
+<p>By adding hosts or host match patterns (or both) to the
+<a href="manifest.html#permissions">permissions</a> section of the
+<a href="manifest.html">manifest</a> file, the extension can request access to
+remote servers outside of its origin.</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "http://www.google.com/"
+  ]</b>,
+  ...
+}</pre>
+
+<p>Cross-origin permission values can be fully qualified host names,
+like these:</p>
+
+<ul>
+  <li> "http://www.google.com/" </li>
+  <li> "http://www.gmail.com/" </li>
+</ul>
+
+<p>Or they can be match patterns, like these:</p>
+
+<ul>
+  <li> "http://*.google.com/" </li>
+  <li> "http://*/" </li>
+</ul>
+
+<p>
+A match pattern of "http://*/" allows HTTP access to all reachable domains.
+Note that here,
+match patterns are similar to <a href="match_patterns.html">content script
+match patterns</a>,
+but any path information following the host is ignored.</p>
+
+<p>Also note that access is granted both by host and by scheme. If an extension
+wants both secure and non-secure HTTP access to a given host or set
+of hosts, it must declare the permissions separately:</p>
+
+<pre>"permissions": [
+  "http://www.google.com/",
+  "https://www.google.com/"
+]
+</pre>
+
+<h2 id="security-considerations">Security considerations</h2>
+
+<p>
+When using resources retrieved via XMLHttpRequest, your background page should
+be careful not to fall victim to <a
+href="http://en.wikipedia.org/wiki/Cross-site_scripting">cross-site
+scripting</a>.  Specifically, avoid using dangerous APIs such as the below:
+</p>
+<pre>background.html
+===============
+var xhr = new XMLHttpRequest();
+xhr.open("GET", "http://api.example.com/data.json", true);
+xhr.onreadystatechange = function() {
+  if (xhr.readyState == 4) {
+    // WARNING! Might be evaluating an evil script!
+    var resp = eval("(" + xhr.responseText + ")");
+    ...
+  }
+}
+xhr.send();
+
+background.html
+===============
+var xhr = new XMLHttpRequest();
+xhr.open("GET", "http://api.example.com/data.json", true);
+xhr.onreadystatechange = function() {
+  if (xhr.readyState == 4) {
+    // WARNING! Might be injecting a malicious script!
+    document.getElementById("resp").innerHTML = xhr.responseText;
+    ...
+  }
+}
+xhr.send();
+</pre>
+<p>
+Instead, prefer safer APIs that do not run scripts:
+</p>
+<pre>background.html
+===============
+var xhr = new XMLHttpRequest();
+xhr.open("GET", "http://api.example.com/data.json", true);
+xhr.onreadystatechange = function() {
+  if (xhr.readyState == 4) {
+    // JSON.parse does not evaluate the attacker's scripts.
+    var resp = JSON.parse(xhr.responseText);
+  }
+}
+xhr.send();
+
+background.html
+===============
+var xhr = new XMLHttpRequest();
+xhr.open("GET", "http://api.example.com/data.json", true);
+xhr.onreadystatechange = function() {
+  if (xhr.readyState == 4) {
+    // innerText does not let the attacker inject HTML elements.
+    document.getElementById("resp").innerText = xhr.responseText;
+  }
+}
+xhr.send();
+</pre>
+<p>
+Additionally, be especially careful of resources retrieved via HTTP.  If your
+extension is used on a hostile network, an network attacker (aka a <a
+href="http://en.wikipedia.org/wiki/Man-in-the-middle_attack">"man-in-the-middle"</a>)
+could modify the response and, potentially, attack your extension.  Instead,
+prefer HTTPS whenever possible.
+</p>
+
+<h3 id="interaction-with-csp">Interaction with Content Security Policy</h3>
+
+<p>
+If you modify the default <a href="contentSecurityPolicy.html">Content
+Security Policy</a> for apps or extensions by adding a
+<code>content_security_policy</code> attribute to your manifest, you'll need to
+ensure that any hosts to which you'd like to connect are allowed. While the
+default policy doesn't restrict connections to hosts, be careful when explicitly
+adding either the <code>connect-src</code> or <code>default-src</code>
+directives.
+</p>
diff --git a/chrome/common/extensions/docs/templates/intros/bookmarks.html b/chrome/common/extensions/docs/templates/intros/bookmarks.html
new file mode 100644
index 0000000..7ebe4f1
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/bookmarks.html
@@ -0,0 +1,89 @@
+<p id="classSummary">
+Use the <code>chrome.bookmarks</code> module to create, organize,
+and otherwise manipulate bookmarks.
+Also see <a href="override.html">Override Pages</a>,
+which you can use to create a custom Bookmark Manager page.
+</p>
+
+<img src="{{static}}/images/bookmarks.png"
+     width="210" height="147" alt="Clicking the star adds a bookmark" />
+
+<h2 id="manifest">Manifest</h2>
+<p>You must declare the "bookmarks" permission
+in the <a href="manifest.html">extension manifest</a>
+to use the bookmarks API.
+For example:</p>
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "bookmarks"
+  ]</b>,
+  ...
+}</pre>
+
+<h2 id="description">Objects and properties</h2>
+
+<p>
+Bookmarks are organized in a tree,
+where each node in the tree
+is either a bookmark or a folder
+(sometimes called a <em>group</em>).
+Each node in the tree
+is represented by a
+<a href="#type-BookmarkTreeNode"><code>BookmarkTreeNode</code></a> object.
+</p>
+
+<p>
+<code>BookmarkTreeNode</code> properties
+are used throughout the <code>chrome.bookmarks</code> API.
+For example, when you call
+<a href="#method-create"><code>create()</code></a>,
+you pass in the new node's parent (<code>parentId</code>),
+and, optionally, the node's
+<code>index</code>, <code>title</code>, and <code>url</code> properties.
+See <a href="#type-BookmarkTreeNode"><code>BookmarkTreeNode</code></a>
+for information about the properties a node can have.
+</p>
+
+<p class="note"><b>Note:</b> You cannot use this API to add or remove entries
+in the root folder.  You also cannot rename, move, or remove the special
+"Bookmarks Bar" and "Other Bookmarks" folders.</p>
+
+<h2 id="overview-examples">Examples</h2>
+
+<p>
+The following code creates a folder with the title "Extension bookmarks".
+The first argument to <code>create()</code> specifies properties
+for the new folder.
+The second argument defines a function
+to be executed after the folder is created.
+</p>
+
+<pre>
+chrome.bookmarks.create({'parentId': bookmarkBar.id,
+                         'title': 'Extension bookmarks'},
+                        function(newFolder) {
+  console.log("added folder: " + newFolder.title);
+});
+</pre>
+
+<p>
+The next snippet creates a bookmark pointing to
+the developer documentation for extensions.
+Since nothing bad will happen if creating the bookmark fails,
+this code doesn't bother to define a callback function.
+</p>
+
+<pre>
+chrome.bookmarks.create({'parentId': extensionsFolderId,
+                         'title': 'Extensions doc',
+                         'url': 'http://code.google.com/chrome/extensions'});
+</pre>
+
+<p>
+For an example of using this API, see the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/bookmarks/basic/">basic bookmarks sample</a>.
+For other examples and for help in viewing the source code, see
+<a href="samples.html">Samples</a>.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/browserAction.html b/chrome/common/extensions/docs/templates/intros/browserAction.html
new file mode 100644
index 0000000..55460e7
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/browserAction.html
@@ -0,0 +1,194 @@
+<p>Use browser actions to put icons
+  in the main Google Chrome toolbar,
+  to the right of the address bar.
+  In addition to its <a href="#icon">icon</a>,
+  a browser action can also have
+  a <a href="#tooltip">tooltip</a>,
+  a <a href="#badge">badge</a>,
+  and a <a href="#popups">popup</a>.
+  </p>
+
+<p>
+In the following figure,
+the multicolored square
+to the right of the address bar
+is the icon for a browser action.
+A popup is below the icon.
+</p>
+
+<img src="{{static}}/images/browser-action.png"
+  width="363" height="226" />
+
+<p>
+If you want to create an icon that isn't always visible,
+use a <a href="pageAction.html">page action</a>
+instead of a browser action.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+
+<p>
+Register your browser action in the
+<a href="manifest.html">extension manifest</a>
+like this:
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"browser_action": {
+    "default_icon": {                    <em>// optional</em>
+      "19": "images/icon19.png",           <em>// optional</em>
+      "38": "images/icon38.png"            <em>// optional</em>
+    },
+    "default_title": "Google Mail",      <em>// optional; shown in tooltip</em>
+    "default_popup": "popup.html"        <em>// optional</em>
+  }</b>,
+  ...
+}</pre>
+
+<p>
+If you only provide one of the 19px or 38px icon size, the extension system will
+scale the icon you provide to the pixel density of the user's display, which
+can lose detail or make it look fuzzy. The old syntax for registering the
+default icon is still supported:
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"browser_action": {
+    ...
+    "default_icon": "images/icon19.png"  <em>// optional</em>
+    <em>// equivalent to "default_icon": { "19": "images/icon19.png" }</em>
+  }</b>,
+  ...
+}</pre>
+
+<h2 id="ui">Parts of the UI</h2>
+
+<p>
+A browser action can have an <a href="#icon">icon</a>,
+a <a href="#tooltip">tooltip</a>,
+a <a href="#badge">badge</a>,
+and a <a href="#popups">popup</a>.
+</p>
+
+<h3 id="icon">Icon</h3>
+
+<p>Browser action icons can be up to 19 dips (device-independent pixels)
+  wide and high. Larger icons are resized to fit, but for best results,
+  use a 19-dip square icon.</p>
+
+<p>You can set the icon in two ways:
+  using a static image or using the
+  HTML5 <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html">canvas element</a>.
+  Using static images is easier for simple applications,
+  but you can create more dynamic UIs &mdash;
+  such as smooth animation &mdash;
+  using the canvas element.
+  </p>
+
+<p>Static images can be in any format WebKit can display,
+  including BMP, GIF, ICO, JPEG, or PNG.
+  </p>
+
+<p>To set the icon,
+  use the <b>default_icon</b> field of <b>browser_action</b>
+  in the <a href="#manifest">manifest</a>,
+  or call the <a href="#method-setIcon">setIcon()</a> method.
+  </p>
+
+<p>To properly display icon when screen pixel density (ratio
+  <code>size_in_pixel / size_in_dip</code>) is different than 1,
+  the icon can be defined as set of images with different sizes.
+  The actual image to display will be selected from the set to
+  best fit the pixel size of 19 dip icon.
+  At the moment, the icon set can contain images with pixel sizes 19 and 38.
+  </p>
+
+<h3 id="tooltip">Tooltip</h3>
+
+<p>
+To set the tooltip,
+use the <b>default_title</b> field of <b>browser_action</b>
+in the <a href="#manifest">manifest</a>,
+or call the <a href="#method-setTitle">setTitle()</a> method.
+You can specify locale-specific strings for the <b>default_title</b> field;
+see <a href="i18n.html">Internationalization</a> for details.
+</p>
+
+<h3 id="badge">Badge</h3>
+
+<p>Browser actions can optionally display a <em>badge</em> &mdash;
+  a bit of text that is layered over the icon.
+  Badges make it easy to update the browser action
+  to display a small amount of information
+  about the state of the extension.</p>
+
+<p>Because the badge has limited space,
+   it should have 4 characters or less.
+  </p>
+
+<p>
+Set the text and color of the badge using
+<a href="#method-setBadgeText">setBadgeText()</a> and
+<a href="#method-setBadgeBackgroundColor">setBadgeBackgroundColor()</a>,
+respectively.
+
+</p>
+
+
+<h3 id="popups">Popup</h3>
+
+<p>If a browser action has a popup,
+  the popup appears when the user clicks the icon.
+  The popup can contain any HTML contents that you like,
+  and it's automatically sized to fit its contents.
+  </p>
+
+<p>
+To add a popup to your browser action,
+create an HTML file with the popup's contents.
+Specify the HTML file in the <b>default_popup</b> field of <b>browser_action</b>
+in the <a href="#manifest">manifest</a>, or call the
+<a href="#method-setPopup">setPopup()</a> method.
+</p>
+
+<h2 id="tips">Tips</h2>
+
+<p>For the best visual impact,
+  follow these guidelines:</p>
+
+<ul>
+  <li><b>Do</b> use browser actions for features
+    that make sense on most pages.
+  <li><b>Don't</b> use browser actions for features
+    that make sense for only a few pages.
+    Use <a href="pageAction.html">page actions</a> instead.
+  <li><b>Do</b> use big, colorful icons that make the most of
+    the 19x19-pixel space.
+    Browser action icons should seem a little bigger
+    and heavier than page action icons.
+  <li><b>Don't</b> attempt to mimic
+    Google Chrome's monochrome "wrench" icon.
+    That doesn't work well with themes, and anyway,
+    extensions should stand out a little.
+  <li><b>Do</b> use alpha transparency
+    to add soft edges to your icon.
+    Because many people use themes,
+    your icon should look nice
+    on a variety of background colors.
+  <li><b>Don't</b> constantly animate your icon.
+    That's just annoying.
+</ul>
+
+<h2 id="examples"> Examples </h2>
+
+<p>
+You can find simple examples of using browser actions in the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/browserAction/">examples/api/browserAction</a>
+directory.
+For other examples and for help in viewing the source code, see
+<a href="samples.html">Samples</a>.
+</p>
diff --git a/chrome/common/extensions/docs/templates/intros/browsingData.html b/chrome/common/extensions/docs/templates/intros/browsingData.html
new file mode 100644
index 0000000..71f9990
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/browsingData.html
@@ -0,0 +1,158 @@
+<p id="classSummary">
+  Use the <code>chrome.browsingData</code> module to remove browsing data from a
+  user's local profile.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+
+<p>
+  You must declare the "browsingData" permission in the
+  <a href="manifest.html">extension manifest</a> to use this API.
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "browsingData",
+  ]</b>,
+  ...
+}</pre>
+
+<h2 id="usage">Usage</h2>
+
+<p>
+  The simplest use-case for this API is a a time-based mechanism for clearing a
+  user's browsing data. Your code should provide a timestamp which indicates the
+  historical date after which the user's browsing data should be removed. This
+  timestamp is formatted as the number of milliseconds since the Unix epoch
+  (which can be retrieved from a JavaScript <code>Date</code> object via the
+  <code>getTime</code> method).
+</p>
+
+<p>
+  For example, to clear all of a user's browsing data from the last week, you
+  might write code as follows:
+</p>
+
+<pre>var callback = function () {
+  // Do something clever here once data has been removed.
+};
+
+var millisecondsPerWeek = 1000 * 60 * 60 * 24 * 7;
+var oneWeekAgo = (new Date()).getTime() - millisecondsPerWeek;
+chrome.browsingData.remove({
+  "since": oneWeekAgo
+}, {
+  "appcache": true,
+  "cache": true,
+  "cookies": true,
+  "downloads": true,
+  "fileSystems": true,
+  "formData": true,
+  "history": true,
+  "indexedDB": true,
+  "localStorage": true,
+  "pluginData": true,
+  "passwords": true,
+  "webSQL": true
+}, callback);</pre>
+
+<p>
+  The <code>chrome.browsingData.remove</code> method allows you to remove
+  various types of browsing data with a single call, and will be much faster
+  than calling multiple more specific methods. If, however, you only want to
+  clear one specific type of browsing data (cookies, for example), the more
+  granular methods offer a readable alternative to a call filled with JSON.
+</p>
+
+<pre>var callback = function () {
+  // Do something clever here once data has been removed.
+};
+
+var millisecondsPerWeek = 1000 * 60 * 60 * 24 * 7;
+var oneWeekAgo = (new Date()).getTime() - millisecondsPerWeek;
+chrome.browsingData.removeCookies({
+  "since": oneWeekAgo
+}, callback);</pre>
+
+<p class="caution">
+  <strong>Important</strong>: Removing browsing data involves a good deal of
+  heavy lifting in the background, and can take <em>tens of seconds</em> to
+  complete, depending on a user's profile. You should use the callback mechanism
+  to keep your users up to date on the removal's status.
+</p>
+
+<h2 id="origin_types">Origin Types</h2>
+
+<p>
+  Adding an <code>originTypes</code> property to the API's options object allows
+  you to specify which types of origins ought to be effected. Currently, origins
+  are divided into three categories:
+</p>
+<ul>
+  <li>
+    <code>unprotectedWeb</code> covers the general case of websites that users
+    visit without taking any special action. If you don't specify an
+    <code>originTypes</code>, the API defaults to removing data from unprotected
+    web origins.
+  </li>
+  <li>
+    <code>protectedWeb</code> covers those web origins that have been installed
+    as hosted applications. Installing <a href="https://chrome.google.com/webstore/detail/aknpkdffaafgjchaibgeefbgmgeghloj">
+    Angry Birds</a>, for example, protects the origin
+    <code>http://chrome.angrybirds.com</code>, and removes it from the
+    <code>unprotectedWeb</code> category. Please do be careful when triggering
+    deletion of data for these origins: make sure your users know what they're
+    getting, as this will irrevocably remove their game data. No one wants to
+    knock tiny pig houses over more often than necessary.
+  </li>
+  <li>
+    <code>extension</code> covers origins under the
+    <code>chrome-extensions:</code> scheme. Removing extension data is, again,
+    something you should be very careful about.
+  </li>
+</ul>
+<p>
+  We could adjust the previous example to remove only data from protected
+  websites as follows:
+</p>
+<pre>var callback = function () {
+  // Do something clever here once data has been removed.
+};
+
+var millisecondsPerWeek = 1000 * 60 * 60 * 24 * 7;
+var oneWeekAgo = (new Date()).getTime() - millisecondsPerWeek;
+chrome.browsingData.remove({
+  "since": oneWeekAgo,
+  <b>"originTypes": {
+    "protectedWeb": true
+  }</b>
+}, {
+  "appcache": true,
+  "cache": true,
+  "cookies": true,
+  "downloads": true,
+  "fileSystems": true,
+  "formData": true,
+  "history": true,
+  "indexedDB": true,
+  "localStorage": true,
+  "serverBoundCertificates": true,
+  "pluginData": true,
+  "passwords": true,
+  "webSQL": true
+}, callback);</pre>
+
+<p class="caution">
+  <strong>Seriously</strong>: Be careful with <code>protectedWeb</code> and
+  <code>extension</code>. These are destructive operations that your users
+  will write angry email about if they're not well-informed about what to
+  expect when your extension removes data on their behalf.
+</p>
+
+<h2 id="samples">Examples</h2>
+<p>
+  Samples for the <code>browsingData</code> API are available
+  <a href="http://code.google.com/chrome/extensions/trunk/samples.html#chrome.browsingData">on the samples page</a>.
+</p>
diff --git a/chrome/common/extensions/docs/templates/intros/commands.html b/chrome/common/extensions/docs/templates/intros/commands.html
new file mode 100644
index 0000000..dd11e4a
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/commands.html
@@ -0,0 +1,65 @@
+<p>
+The commands API allows you to add keyboard shortcuts that trigger actions in
+your extension. An action can be opening the browser action or page action popup
+or sending a command to the extension.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+<p>
+You must set manifest_version to (at least) 2 to use this API.
+</p>
+
+<h2 id="usage">Usage</h2>
+<p>The commands API allows you to define specific commands, and bind them to a
+default key combination. Each command your extension accepts must be listed in
+the manifest as an attribute of the 'commands' manifest key. Note: Combinations
+that involve Ctrl+Alt are not permitted in order to avoid conflicts with the
+AltGr key. Also note that on Mac 'Ctrl' is automatically converted to 'Command'.
+If you want 'Ctrl' instead, please specify 'MacCtrl'.</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+<b>  "commands": {
+    "toggle-feature-foo": {
+      "suggested_key": {
+        "default": "Ctrl+Shift+Y",
+        "mac": "Command+Shift+Y"
+      },
+      "description": "Toggle feature foo"
+    },
+    "_execute_browser_action": {
+      "suggested_key": {
+        "windows": "Ctrl+Shift+Y",
+        "mac": "Command+Shift+Y",
+        "chromeos": "Ctrl+Shift+U",
+        "linux": "Ctrl+Shift+J"
+      }
+    },
+    "_execute_page_action": {
+      "suggested_key": {
+        "default": "Ctrl+E"
+        "windows": "Alt+P",
+        "mac": "Option+P",
+      }
+    }
+  }</b>,
+  ...
+}</pre>
+
+<p>In your background page, you can bind a handler to each of the commands
+defined in the manifest (except for '_execute_browser_action' and
+'_execute_page_action') via onCommand.addListener. For example:</p>
+
+<pre>
+chrome.commands.onCommand.addListener(function(command) {
+  console.log('Command:', command);
+});
+</pre>
+
+<p>The '_execute_browser_action' and '_execute_page_action' commands are
+reserved for the action of opening your extension's popups. They won't normally
+generate events that you can handle. If you need to take action based on your
+popup opening, consider listening for an 'onDomReady' event inside your popup's
+code.
+</p>
diff --git a/chrome/common/extensions/docs/templates/intros/contentSettings.html b/chrome/common/extensions/docs/templates/intros/contentSettings.html
new file mode 100644
index 0000000..2353b15
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/contentSettings.html
@@ -0,0 +1,148 @@
+<p>
+The content settings module allows you to change settings that control whether
+websites can use features such as cookies, JavaScript, and plug-ins.
+More generally speaking, content settings allow you to customize Chrome's
+behavior on a per-site basis instead of globally.</p>
+<p>
+</p>
+
+<h2 id="manifest">Manifest</h2>
+<p>You must declare the "contentSettings" permission
+in your extension's manifest to use the API.
+For example:</p>
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "contentSettings"
+  ]</b>,
+  ...
+}</pre>
+
+
+<h2 id="patterns">Content setting patterns</h2>
+<p>
+You can use patterns to specify the websites that each content setting affects.
+For example, <code>http://*.youtube.com/*</code> specifies youtube.com and all
+of its subdomains. The syntax for content setting patterns is the same as for
+<a href="match_patterns.html">match patterns</a>, with a few differences:
+<ul><li>For <code>http</code>,
+<code>https</code>, and <code>ftp</code> URLs, the path must be a wildcard
+(<code>/*</code>). For <code>file</code> URLs, the path must be completely
+specified and <strong>must not</strong> contain wildcards.</li>
+<li>In contrast to match patterns, content setting patterns can specify a port
+number. If a port number is specified, the pattern only matches websites with
+that port. If no port number is specified, the pattern matches all ports.
+</li>
+</ul>
+</p>
+
+<h3 id="pattern-precedence">Pattern precedence</h3>
+<p>
+When more than one content setting rule applies for a given site, the rule with
+the more specific pattern takes precedence.
+</p>
+<p>For example, the following patterns are ordered by precedence:</p>
+<ol>
+<li><code>http://www.example.com/*</code></li>
+<li><code>http://*.example.com/*</code> (matching
+example.com and all subdomains)</li>
+<li><code>&lt;all_urls&gt;</code> (matching every URL)</li>
+</ol>
+<p>
+Three kinds of wildcards affect how specific a pattern is:
+</p>
+<ul>
+<li>Wildcards in the port (for example
+<code>http://www.example.com:*/*</code>)</li>
+<li>Wildcards in the scheme (for example
+<code>*://www.example.com:123/*</code>)</li>
+<li>Wildcards in the hostname (for example
+<code>http://*.example.com:123/*</code>)</li>
+</ul>
+<p>
+If a pattern is more specific than another pattern in one part but less specific
+in another part, the different parts are checked in the following order:
+hostname, scheme, port. For example, the following patterns are ordered by
+precedence:</p>
+<ol>
+<li><code>http://www.example.com:*/*</code><br>
+Specifies the hostname and scheme.</li>
+<li><code>*:/www.example.com:123/*</code><br>
+Not as high, because although it specifies the hostname, it doesn't specify
+the scheme.</li>
+<li><code>http://*.example.com:123/*</code><br>
+Lower because although it specifies the port and scheme, it has a wildcard
+in the hostname.</li>
+</ol>
+
+<h2 id="primary-secondary">Primary and secondary patterns</h2>
+<p>
+The URL taken into account when deciding which content setting to apply depends
+on the content type. For example, for
+<a href="#property-notifications">notifications</a> settings are
+based on the URL shown in the omnibox. This URL is called the "primary" URL.</p>
+<p>
+Some content types can take additional URLs into account. For example,
+whether a site is allowed to set a
+<a href="#property-cookies">cookie</a> is decided based on the URL
+of the HTTP request (which is the primary URL in this case) as well as the URL
+shown in the omnibox (which is called the "secondary" URL).
+</p>
+<p>
+If multiple rules have primary and secondary patterns, the rule with the more
+specific primary pattern takes precedence. If there multiple rules have the same
+primary pattern, the rule with the more specific secondary pattern takes
+precedence. For example, the following list of primary/secondary pattern pairs
+is ordered by precedence:</p>
+<table>
+<tr><th>Precedence</th><th>Primary pattern</th><th>Secondary pattern</th>
+<tr>
+  <td>1</td>
+  <td><code>http://www.moose.com/*</code>, </td>
+  <td><code>http://www.wombat.com/*</code></td>
+</tr><tr>
+  <td>2</td>
+  <td><code>http://www.moose.com/*</code>, </td>
+  <td><code>&lt;all_urls&gt;</code></td>
+</tr><tr>
+  <td>3</td>
+  <td><code>&lt;all_urls&gt;</code>, </td>
+  <td><code>http://www.wombat.com/*</code></td>
+</tr><tr>
+  <td>4</td>
+  <td><code>&lt;all_urls&gt;</code>, </td>
+  <td><code>&lt;all_urls&gt;</code></td>
+</tr>
+</table>
+
+<h2 id="resource-identifiers">Resource identifiers</h2>
+<p>
+Resource identifiers allow you to specify content settings for specific
+subtypes of a content type. Currently, the only content type that supports
+resource identifiers is <a href="#property-plugins"><code>plugins</code></a>,
+where a resource identifier identifies a specific plug-in. When applying content
+settings, first the settings for the specific plug-in are checked. If there are
+no settings found for the specific plug-in, the general content settings for
+plug-ins are checked.
+</p>
+<p>
+For example, if a content setting rule has the resource identifier
+<code>adobe-flash-player</code> and the pattern <code>&lt;all_urls&gt;</code>,
+it takes precedence over a rule without a resource identifier and the pattern
+<code>http://www.example.com/*</code>, even if that pattern is more specific.
+</p>
+<p>
+You can get a list of resource identifiers for a content type by calling the
+<a href="contentSettings.html#method-ContentSetting-getResourceIdentifiers">
+<code>getResourceIdentifiers()</code></a> method. The returned list
+can change with the set of installed plug-ins on the user's machine, but Chrome
+tries to keep the identifiers stable across plug-in updates.
+</p>
+
+<h2 id="examples">Examples</h2>
+
+<p>
+You can find samples of this API on the
+<a href="samples.html#contentSettings">sample page</a>.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/contextMenus.html b/chrome/common/extensions/docs/templates/intros/contextMenus.html
new file mode 100644
index 0000000..685ef16
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/contextMenus.html
@@ -0,0 +1,55 @@
+<p>
+The context menus module allows you
+to add items to Google Chrome's context menu.
+</p>
+
+<p>
+You can choose what types of objects
+your context menu additions apply to,
+such as images, hyperlinks, and pages.
+</p>
+
+<p>
+You can create as many context menu items
+as you need, but if more than one
+from your extension is visible at once,
+Google Chrome automatically collapses them
+into a single parent menu.
+</p>
+
+<p>
+Context menu items can appear in any document
+(or frame within a document),
+even those with file:// or chrome:// URLs.
+To control which documents your items can appear in,
+specify the documentUrlPatterns field
+when you call the create() or update() method.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+<p>You must declare the "contextMenus" permission
+in your extension's manifest to use the API.
+Also, you should specify a 16x16-pixel icon
+for display next to your menu item.
+For example:
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  "permissions": [
+    <b>"contextMenus"</b>
+  ],
+  "icons": {
+    <b>"16": "icon-bitty.png",</b>
+    "48": "icon-small.png",
+    "128": "icon-large.png"
+  },
+  ...
+}</pre>
+
+<h2 id="examples">Examples</h2>
+
+<p>
+You can find samples of this API on the
+<a href="samples.html#contextMenus">sample page</a>.
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/cookies.html b/chrome/common/extensions/docs/templates/intros/cookies.html
new file mode 100644
index 0000000..2e7c2dc
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/cookies.html
@@ -0,0 +1,30 @@
+<h2 id="manifest">Manifest</h2>
+
+<p>To use the cookies API,
+you must declare the "cookies" permission in your manifest,
+along with <a href="manifest.html#permissions">host permissions</a>
+for any hosts whose cookies
+you want to access.
+For example:</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "cookies",
+    "*://*.google.com"
+  ]</b>,
+  ...
+}</pre>
+
+<h2 id="examples"> Examples </h2>
+
+<p>
+You can find a simple example
+of using the cookies API in the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/cookies/">examples/api/cookies</a>
+directory.
+For other examples
+and for help in viewing the source code,
+see <a href="samples.html">Samples</a>.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/debugger.html b/chrome/common/extensions/docs/templates/intros/debugger.html
new file mode 100644
index 0000000..a815425
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/debugger.html
@@ -0,0 +1,41 @@
+<h2 id="manifest">Notes</h2>
+
+<p>
+Debugger API serves as an alternate transport for Chrome's
+<a href="http://code.google.com/chrome/devtools/docs/remote-debugging.html">
+remote debugging protocol</a>. Use <code>chrome.debugger</code>
+to attach to one or more tabs to instrument network interaction, debug
+JavaScript, mutate the DOM  and CSS, etc. Use the Debuggee tabId to target tabs
+with sendCommand and route events by <code>tabId</code> from onEvent callbacks. 
+</p>
+
+<p>
+As of today, attaching to the tab by means of the debugger API
+and using embedded Chrome DevTools with that tab are mutually exclusive.
+If user invokes Chrome DevTools while extension is attached to the tab,
+debugging session is terminated. Extension can re-establish it later.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+
+<p>
+You must declare the "debugger" permission in your extension's manifest
+to use this API.
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+<b>  "permissions": [
+    "debugger",
+  ]</b>,
+  ...
+}</pre>
+
+
+<h2 id="examples">Examples</h2>
+
+<p>
+You can find samples of this API in
+<a href="samples.html#debugger">Samples</a>.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/declarativeWebRequest.html b/chrome/common/extensions/docs/templates/intros/declarativeWebRequest.html
new file mode 100644
index 0000000..8e68e66
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/declarativeWebRequest.html
@@ -0,0 +1,243 @@
+<h2 id="notes">Notes</h2>
+
+<p>
+Use the <code>chrome.declarativeWebRequest</code> module to intercept, block, or
+modify requests in-flight. It is significantly faster than the <a
+  href="webRequest.html"><code>chrome.webRequest</code> API</a> because you can
+register rules that are evaluated in the browser rather than the
+JavaScript engine which reduces roundtrip latencies and allows very high
+efficiency.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+
+<p>
+You must declare the "declarativeWebRequest" permission in the
+<a href="manifest.html">extension manifest</a> to use this API,
+along with <a href="manifest.html#permissions">host permissions</a> for any
+hosts whose network requests you want to access.
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+<b>  "permissions": [
+    "declarativeWebRequest",
+    "*://*.google.com"
+  ]</b>,
+  ...
+}</pre>
+
+<p>
+Note that certain types of non-sensitive requests do not require host
+permissions:
+<ul>
+  <li><code>CancelRequest</code>
+  <li><code>IgnoreRules</code>
+  <li><code>RedirectToEmptyDocument</code>
+  <li><code>RedirectToTransparentImage</code>
+  <li><code>RedirectByRegEx</code> when the redirect destination has
+    the same domain as the original request
+  <li><code>RedirectRequest</code> when the redirect destination has
+    the same domain as the original request
+</ul>
+
+<h2 id="rules">Rules</h2>
+
+<p>
+The Declarative Web Request API follows the concepts of the <a
+  href="events.html#declarative">Declarative API</a>. You can register rules to
+the <code>chrome.declarativeWebRequest.onRequest</code> event object.
+</p>
+
+<p>
+The Declarative Web Request API supports a single type of match criteria, the
+<code>RequestMatcher</code>. The <code>RequestMatcher</code> matches network
+requests if and only if all listed criteria are met. The following
+<code>RequestMatcher</code> would match a network request when the user enters
+"http://www.example.com" in the URL bar:
+</p>
+
+<pre>
+var matcher = new chrome.declarativeWebRequest.RequestMatcher({
+  url: { hostSuffix: 'example.com', schemes: ['http'] },
+  resourceType: ['main_frame']
+  });
+</pre>
+
+<p>
+Requests to "https://www.example.com" would be rejected by the
+<code>RequestMatcher</code> due to the scheme. Also all requests for an embedded
+iframe would be rejected due to the <code>resourceType</code>.
+</p>
+
+<p class="note">
+<strong>Note:</strong> All conditions and actions are created via a constructor
+as shown in the example above.
+<p>
+
+<p>
+In order to cancel all requests to "example.com", you can define a rule as
+follows:
+</p>
+<pre>
+var rule = {
+  conditions: [
+    new chrome.declarativeWebRequest.RequestMatcher({
+      url: { hostSuffix: 'example.com' } })
+  ],
+  actions: [
+    new chrome.declarativeWebRequest.CancelRequest()
+  ]};
+</pre>
+
+<p>
+In order to cancel all requests to "example.com" and "foobar.com", you can add a
+second condition, as each condition is sufficient to trigger all specified
+actions:
+</p>
+<pre>
+var rule2 = {
+  conditions: [
+    new chrome.declarativeWebRequest.RequestMatcher({
+      url: { hostSuffix: 'example.com' } }),
+    new chrome.declarativeWebRequest.RequestMatcher({
+      url: { hostSuffix: 'foobar.com' } })
+  ],
+  actions: [
+    new chrome.declarativeWebRequest.CancelRequest()
+  ]};
+</pre>
+
+<p>
+Register rules as follows:
+</p>
+<pre>
+chrome.declarativeWebRequest.onRequest.addRules([rule2]);
+</pre>
+
+<p class="note">
+<strong>Note:</strong> You should always register or unregister rules in bulk rather than
+individually because each of these operations recreates internal data
+structures.  This re-creation is computationally expensive but facilitates a
+very fast URL matching algorithm for hundreds of thousands of URLs.
+</p>
+
+
+<h2 id="evaluation">Evaluation of conditions and actions</h2>
+
+<p>
+The Declarative Web Request API follows the
+<a href="webRequest.html#life_cycle">Life cycle model for web requests</a> of
+the <a href="webRequest.html">Web Request API</a>. This means that conditions
+can only be tested at specific stages of a web request and, likewise, actions
+can also only be executed at specific stages. The following tables list the
+request stages that are compatible with conditions and actions.
+</p>
+
+<p>
+<table>
+  <tr>
+    <th colspan="5">Request stages during which condition attributes can be processed.
+  </tr>
+  <tr>
+    <th>Condition attribute
+    <th>onBeforeRequest
+    <th>onBeforeSendHeaders
+    <th>onHeadersReceived
+    <th>onAuthRequired
+  </tr>
+  <tr><td>url<td>✓<td>✓<td>✓<td>✓
+  <tr><td>resourceType<td>✓<td>✓<td>✓<td>✓
+  <tr><td>contentType<td><td><td>✓<td>
+  <tr><td>excludeContentType<td><td><td>✓<td>
+  <tr><td>responseHeaders<td><td><td>✓<td>
+  <tr><td>excludeResponseHeaders<td><td><td>✓<td>
+  <tr><td>requestHeaders<td><td>✓<td><td>
+  <tr><td>excludeRequestHeaders<td><td>✓<td><td>
+  <tr><td>thirdPartyForCookies<td>✓<td>✓<td>✓<td>✓
+  <tr>
+    <th colspan="5" style="padding-top:2em">Request stages during which actions can be executed.
+  </tr>
+  <tr>
+    <th>Event
+    <th>onBeforeRequest
+    <th>onBeforeSendHeaders
+    <th>onHeadersReceived
+    <th>onAuthRequired
+  </tr>
+  <tr><td>AddRequestCookie<td><td>✓<td><td>
+  <tr><td>AddResponseCookie<td><td><td>✓<td>
+  <tr><td>AddResponseHeader<td><td><td>✓<td>
+  <tr><td>CancelRequest<td>✓<td>✓<td>✓<td>✓
+  <tr><td>EditRequestCookie<td><td>✓<td><td>
+  <tr><td>EditResponseCookie<td><td><td>✓<td>
+  <tr><td>IgnoreRules<td>✓<td>✓<td>✓<td>✓
+  <tr><td>RedirectByRegEx<td>✓<td><td><td>
+  <tr><td>RedirectRequest<td>✓<td><td><td>
+  <tr><td>RedirectToEmptyDocument<td>✓<td><td><td>
+  <tr><td>RedirectToTransparentImage<td>✓<td><td><td>
+  <tr><td>RemoveRequestCookie<td><td>✓<td><td>
+  <tr><td>RemoveRequestHeader<td><td>✓<td><td>
+  <tr><td>RemoveResponseCookie<td><td><td>✓<td>
+  <tr><td>RemoveResponseHeader<td><td><td>✓<td>
+  <tr><td>SetRequestHeader<td><td>✓<td><td>
+</table>
+</p>
+
+<p>
+<strong>Note:</strong> Applicable stages can be further constrained by using the
+"stages" attribute.
+</p>
+<p>
+<strong>Example:</strong> It is possible to combine a
+<code>new chrome.declarativeWebRequest.RequestMatcher({contentType: ["image/jpeg"]})</code>
+condition with a <code>new chrome.declarativeWebRequest.CancelRequest()</code>
+action because both of them can be evaluated in the onHeadersReceived stage.
+It is, however, impossible to combine the request matcher with a
+<code>new chrome.declarativeWebRequest.RedirectToTransparentImage()</code>
+because redirects cannot be executed any more by the time the content
+type has been determined.
+</p>
+
+<h2 id="precedences">Using priorities to override rules</h2>
+
+<p>
+Rules can be associated with priorities as described in the
+<a href="events.html#declarative">Events API</a>. This mechanism can be used
+to express exceptions. The following example will block all requests to
+images named "evil.jpg" except on the server "myserver.com".
+</p>
+
+<pre>
+var rule1 = {
+  priority: 100,
+  conditions: [
+    new chrome.declarativeWebRequest.RequestMatcher({
+        url: { pathEquals: 'evil.jpg' } })
+  ],
+  actions: [
+    new chrome.declarativeWebRequest.CancelRequest()
+  ]
+};
+var rule2 = {
+  priority: 1000,
+  conditions: [
+    new chrome.declarativeWebRequest.RequestMatcher({
+      url: { hostSuffix: '.myserver.com' } })
+  ],
+  actions: [
+    new chrome.declarativeWebRequest.IgnoreRules({
+      lowerPriorityThan: 1000 })
+  ]
+};
+chrome.declarativeWebRequest.onRequest.addRules([rule1, rule2]);
+</pre>
+
+<p>
+It is important to recognize that the <code>IgnoreRules</code> action is not
+persisted across <a href="#evaluation">request stages</a>. All conditions of
+all rules are evaluated at each stage of a web request. If an
+<code>IgnoreRules</code> action is executed, it applies only to other actions
+that are executed for the same web request in the same stage.
+</p>
diff --git a/chrome/common/extensions/docs/templates/intros/devtools_inspectedWindow.html b/chrome/common/extensions/docs/templates/intros/devtools_inspectedWindow.html
new file mode 100644
index 0000000..30c4384
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/devtools_inspectedWindow.html
@@ -0,0 +1,97 @@
+<p>
+Use <code>chrome.devtools.inspectedWindow</code>
+to interact with the inspected window:
+obtain the tab ID for the inspected page,
+evaluate the code in the context of inspected window,
+reload the page,
+or obtain the list of resources within the page.
+</p><p>
+See <a href="devtools.html">DevTools APIs summary</a> for
+general introduction to using Developer Tools APIs.
+</p>
+
+<h2 id="overview">Overview</h2>
+<p>
+The <a href="#property-tabId"><code>tabId</code></a> property
+provides the tab identifier that you can use with the
+<a href="tabs.html"><code>chrome.tabs.*</code></a> API calls.
+However, please note that <code>chrome.tabs.*</code> API is not
+exposed to the Developer Tools extension pages due to security considerations
+&mdash; you will need to pass the tab ID to the background page and invoke
+the <code>chrome.tabs.*</code> API functions from there.
+</p>
+<p>
+The <code>eval()</code> method provides the ability for extensions to execute
+JavaScript code in the context of the main frame of the inspected page.
+This method is powerful when used in the right context
+and dangerous when used inappropriately.
+Use the <code>chrome.tabs.executeScript()</code> method
+unless you need the specific functionality
+that the <code>eval()</code> method provides.
+</p>
+<p>Here are the main differences between the
+<code>eval()</code> and <code>chrome.tabs.executeScript()</code> methods:
+</p><ul>
+<li>The <code>eval()</code> method does not
+use an isolated world for the code being evaluated, so the JavaScript state
+of the inspected window is accessible to the code.
+Use this method when access to the JavaScript state of the inspected page
+is required.
+</li><li>
+The execution context of the code being evaluated includes the
+<a href="http://code.google.com/chrome/devtools/docs/console.html">Developer
+Tools console API</a>.
+For example,
+the code can use <code>inspect()</code> and <code>$0</code>.
+</li><li>
+The evaluated code may return a value that is passed to the extension callback.
+The returned value has to be a valid JSON object (it may contain only
+primitive JavaScript types and acyclic references to other JSON
+objects).
+
+<em>Please observe extra care while processing the data received from the
+inspected page &mdash; the execution context is essentially controlled by the
+inspected page; a malicious page may affect the data being returned to the
+extension.</em>
+</li></ul>
+<p class="caution">
+<strong>Important:</strong>
+Due to the security considerations explained above, the
+<a href="tabs.html#method-executeScript"><code
+>chrome.tabs.executeScript()</code></a> method is the preferred way for an
+extension to access DOM data of the inspected page in cases where the access to
+JavaScript state of the inspected page is not required.</em>
+</p><p>
+The <code>reload()</code> method may be used to reload the inspected page.
+Additionally, the caller can specify an override for the user agent string,
+a script that will be injected early upon page load, and an option to force
+reload of cached resources.
+</p><p>
+Use the <code>getResources()</code> call and the <code>onResourceContent</code>
+event to obtain the list of resources (documents, stylesheets, scripts, images
+etc) within the inspected page. The <code>getContent()</code> and <code
+>setContent()</code> methods of the <code>Resource</code> class along with the
+<code>onResourceContentCommitted</code> event may be used to support
+modification of the resource content, for example, by an external editor.
+</p>
+
+<h2 id="overview-examples">Examples</h2>
+<p>The following code checks for the version of jQuery used by the inspected
+page:
+
+<pre>
+chrome.devtools.inspectedWindow.eval(
+    "jQuery.fn.jquery",
+     function(result, isException) {
+       if (isException)
+         console.log("the page is not using jQuery");
+       else
+         console.log("The page is using jQuery v" + result);
+     }
+);
+</pre>
+
+<p>
+You can find more examples that use Developer Tools APIs in
+<a href="samples.html#devtools">Samples</a>.
+</p>
diff --git a/chrome/common/extensions/docs/templates/intros/devtools_network.html b/chrome/common/extensions/docs/templates/intros/devtools_network.html
new file mode 100644
index 0000000..3343946
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/devtools_network.html
@@ -0,0 +1,53 @@
+<p id="classSummary">
+Use the <code>chrome.devtools.network</code> module to retrieve
+the information about network requests displayed by the Developer Tools
+in the Network panel.
+</p><p>
+See <a href="devtools.html">DevTools APIs summary</a> for
+general introduction to using Developer Tools APIs</a>.
+</p>
+
+<h2 id="overview">Overview</h2>
+
+<p>
+Network requests information is represented in the HTTP Archive format
+(<em>HAR</em>). The description of HAR is outside of scope of this document,
+please refer to <a href=
+"http://www.softwareishard.com/blog/har-12-spec/">
+HAR v1.2 Specification</a>.
+</p><p>
+In terms of HAR, the
+<code>chrome.devtools.network.getHAR()</code> method returns
+entire <em>HAR log</em>, while
+<code>chrome.devtools.network.onRequestFinished</code> event
+provides <em>HAR entry</em> as an argument to the event callback.
+</p>
+<p>Note that request content is not provided as part of HAR for efficieny
+reasons. You may call request's <code>getContent()</code> method to retrieve
+content.
+<p>If the Developer Tools window is opened after the page is loaded,
+some requests may be missing
+in the array of entries returned by <code>getHAR()</code>.
+Reload the page to get all requests.
+In general, the list of
+requests returned by <code>getHAR()</code> should match that displayed in
+the Network panel.
+<h2 id="overview-examples">Examples</h2>
+
+<p>The following code logs URLs of all images larger than 40KB as they are
+loaded:</p>
+
+<pre>
+chrome.devtools.network.onRequestFinished.addListener(
+    function(request) {
+      if (request.response.bodySize > 40*1024)
+      chrome.experimental.devtools.console.addMessage(
+          chrome.experimental.devtools.console.Severity.Warning,
+          "Large image: " + request.request.url);
+});
+</pre>
+
+<p>
+You can find more examples that use this API in
+<a href="samples.html#devtools.network">Samples</a>.
+</p>
diff --git a/chrome/common/extensions/docs/templates/intros/devtools_panels.html b/chrome/common/extensions/docs/templates/intros/devtools_panels.html
new file mode 100644
index 0000000..6de12cf
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/devtools_panels.html
@@ -0,0 +1,64 @@
+<p id="classSummary">
+Use the <code>chrome.devtools.panels</code> module to integrate
+your extension into Developer Tools window UI: create your own panels, access
+existing panels, and add sidebars.
+</p><p>
+See <a href="devtools.html">DevTools APIs summary</a> for
+general introduction to using Developer Tools APIs.
+</p>
+
+<h2 id="overview">Overview</h2>
+
+<p>
+Each extension panel and sidebar is displayed as a separate HTML page. All
+extension pages displayed in the Developer Tools window have access to all
+modules in <code>chrome.devtools</code> API, as well as to
+<a href="extension.html">chrome.extension</a> API. Other extension APIs are not
+available to the pages within Developer Tools window, but you may invoke them
+by sending a request to the background page of your extension, similarly to how
+it's done in the <a href="overview.html#contentScripts">content scripts</a>.
+</p><p>
+You can use the <code><a href="#method-setOpenResourceHandler"
+>setOpenResourceHandler()</a></code> method to install a
+callback function that handles user requests to open a resource (typically,
+a click on a resource link in the Developer Tools window). At most one of the
+installed handlers gets called; users can specify (using the Developer Tools
+Settings dialog) either the default behavior or an extension to handle resource
+open requests. If an extension calls <code>setOpenResourceHandler()</code>
+multiple times, only the last handler is retained.
+</p>
+<h2 id="overview-examples">Examples</h2>
+<p>The following code adds a panel contained in <code>Panel.html</code>,
+represented by <code>FontPicker.png</code> on the Developer Tools toolbar
+and labeled as <em>Font Picker</em>:</p>
+
+<pre>
+chrome.devtools.panels.create("Font Picker",
+                              "FontPicker.png",
+                              "Panel.html"
+                              function(panel) { ... });
+</pre>
+<p>The following code adds a sidebar pane contained in
+<code>Sidebar.html</code> and titled <em>Font Properties</em> to the Elements
+panel, then sets its height to <code>8ex</code>:
+<pre>
+chrome.devtools.panels.elements.createSidebarPane("Font Properties",
+    function(sidebar) {
+      sidebar.setPage("Sidebar.html");
+      sidebar.setHeight("8ex");
+    });
+</pre>
+<p>
+This screenshot demonstrates the effect the above examples would have on
+Developer Tools window:
+
+<img src="{{static}}/images/devtools-panels.png"
+     style="margin-left: 20px"
+     width="686" height="289"
+     alt="Extension icon panel on DevTools toolbar" />
+</p>
+
+<p>
+You can find examples that use this API in
+<a href="samples.html#Chrome&#32;Query">Samples</a>.
+</p>
diff --git a/chrome/common/extensions/docs/templates/intros/downloads.html b/chrome/common/extensions/docs/templates/intros/downloads.html
new file mode 100644
index 0000000..ee107d1
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/downloads.html
@@ -0,0 +1,33 @@
+<p>The downloads API allows you to programmatically initiate, monitor,
+manipulate, and search for downloads.</p>
+
+<h2 id='manifest'>Manifest</h2>
+
+<p> You must declare the 'downloads' permission in the <a
+href='manifest.html'>extension manifest</a> to use this API, along with <a
+href='manifest.html#permissions'>host permissions</a> for any hosts that you
+may pass to <a href='#method-download'>download()</a>.</p>
+
+<pre>{
+  'name': 'My extension',
+  ...
+<b>  'permissions': [
+    'downloads',
+    '*://*.google.com'
+  ]</b>,
+  ...
+}</pre>
+
+<p>If the URL's hostname is not specified in the permissions, then <a
+href='#method-download'>download()</a> will call its callback with a null
+<code>downloadId</code> and set the <a
+href='extension.html#property-lastError'>chrome.extensions.lastError</a>
+object to indicate that the extension does not have permission to access that
+hostname.</p>
+
+<h2 id='examples'>Examples</h2>
+
+<p>You can find simple examples of using the downloads module in the <a
+href='http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/downloads/'>examples/api/downloads</a>
+directory. For other examples and for help in viewing the source code, see <a
+href='samples.html'>Samples</a>.</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/events.html b/chrome/common/extensions/docs/templates/intros/events.html
new file mode 100644
index 0000000..d40e231
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/events.html
@@ -0,0 +1,212 @@
+<p>
+An <code>Event</code> is an object
+that allows you to be notified
+when something interesting happens.
+Here's an example of using the
+<code>chrome.tabs.onCreated</code> event
+to be notified whenever there's a new tab:
+</p>
+
+<pre>
+chrome.tabs.onCreated.<b>addListener(function(</b>tab<b>) {</b>
+  appendToLog('tabs.onCreated --'
+              + ' window: ' + tab.windowId
+              + ' tab: '    + tab.id
+              + ' index: '  + tab.index
+              + ' url: '    + tab.url);
+<b>});</b>
+</pre>
+
+<p>
+As the example shows,
+you register for notification using <code>addListener()</code>.
+The argument to <code>addListener()</code>
+is always a function that you define to handle the event,
+but the parameters to the function depend on
+which event you're handling.
+Checking the documentation for
+<a href="tabs.html#event-onCreated"><code>chrome.tabs.onCreated</code></a>,
+you can see that the function has a single parameter:
+a <a href="tabs.html#type-Tab">Tab</a> object
+that has details about the newly created tab.
+</p>
+
+
+{{^is_apps}}
+<div class="doc-family extensions">
+<h2 id="declarative">Declarative Event Handlers</h2>
+
+<p>
+The declarative event handlers provide a means to define rules consisting of
+declarative conditions and actions. Conditions are evaluated in the browser
+rather than the JavaScript engine which reduces roundtrip latencies and allows
+for very high efficiency.
+</p>
+
+<p>Declarative event handlers are used for example in the <a
+  href="declarativeWebRequest.html">Declarative Web Request API</a> and possibly
+further extension APIs in the future. This page describes the underlying
+concepts of all declarative event handlers.
+</p>
+
+<h3 id="rules">Rules</h3>
+
+<p>The simplest possible rule consists of one or more conditions and one or more
+actions:</p>
+<pre>
+var rule = {
+  conditions: [ /* my conditions */ ],
+  actions: [ /* my actions */ ]
+};
+</pre>
+
+<p>If any of the conditions is fulfilled, all actions are executed.</p>
+
+<p>In addition to conditions and actions you may give each rule an identifier,
+which simplifies unregistering previously registered rules, and a priority to
+define precedences among rules. Priorities are only considered if rules conflict
+each other or need to be executed in a specific order. Actions are executed in
+decendending order of the priority of their rules.</p>
+
+<pre>
+var rule = {
+  id: "my rule",  // optional, will be generated if not set.
+  priority: 100,  // optional, defaults to 100.
+  conditions: [ /* my conditions */ ],
+  actions: [ /* my actions */ ]
+};
+</pre>
+
+<h3 id="eventobjects">Event objects</h3>
+
+<p>
+<a href="events.html">Event objects</a> may support rules. These event objects
+don't call a callback function when events happen but test whether any
+registered rule has at least one fulfilled condition and execute the actions
+associated with this rule. Event objects supporting the declarative API have
+three relevant methods: <a href="#method-addRules"><code>addRules()</code></a>,
+<a href="#method-removeRules"><code>removeRules()</code></a>, and
+<a href="#method-getRules"><code>getRules()</code></a>.
+</p>
+
+<h3 id="addingrules">Adding rules</h3>
+
+<p>
+To add rules call the <code>addRules()</code> function of the event object. It
+takes an array of rule instances as its first parameter and a callback function
+that is called on completion.
+</p>
+
+<pre>
+var rule_list = [rule1, rule2, ...];
+function addRules(rule_list, function callback(details) {...});
+</pre>
+
+<p>
+If the rules were inserted successfully, the <code>details</code> parameter
+contains an array of inserted rules appearing in the same order as in the passed
+<code>rule_list</code> where the optional parameters <code>id</code> and
+<code>priority</code> were filled with the generated values. If any rule is
+invalid, e.g., because it contained an invalid condition or action, none of the
+rules are added and the <a
+  href="extension.html#property-lastError">lastError</a> variable is set when
+the callback function is called. Each rule in <code>rule_list</code> must
+contain a unique identifier that is not currently used by another rule or an
+empty identifier.
+</p>
+
+<p class="note">
+<strong>Note:</strong> Rules are persistent across browsing sessions. Therefore,
+you should install rules during extension installation time using the
+<code><a href="runtime.html#event-onInstalled">chrome.runtime.onInstalled</a></code>
+event. Note that this event is also triggered when an extension is updated.
+Therefore, you should first clear previously installed rules and then register
+new rules.
+</p>
+
+<h3 id="removingrules">Removing rules</h3>
+
+<p>
+To remove rules call the <code>removeRules()</code> function. It accepts an
+optional array of rule identifiers as its first parameter and a callback
+function as its second parameter.
+</p>
+
+<pre>
+var rule_ids = ["id1", "id2", ...];
+function removeRules(rule_ids, function callback() {...});
+</pre>
+
+<p>
+If <code>rule_ids</code> is an array of identifiers, all rules having
+identifiers listed in the array are removed. If <code>rule_ids</code> lists an
+identifier, that is unknown, this identifier is silently ignored. If
+<code>rule_ids</code> is <code>undefined</code>, all registered rules of this
+extension are removed. The <code>callback()</code> function is called when the
+rules were removed.
+</p>
+
+<h3 id="retrievingrules">Retrieving rules</h3>
+
+<p>
+To retrieve a list of currently registered rules, call the
+<code>getRules()</code> function. It accepts an optional array of rule
+identifiers with the same semantics as <code>removeRules</code> and a callback
+function.
+</p>
+
+<pre>
+var rule_ids = ["id1", "id2", ...];
+function getRules(rule_ids, function callback(details) {...});
+</pre>
+
+<p>
+The <code>details</code> parameter passed to the <code>calback()</code> function
+refers to an array of rules including filled optional parameters.
+</p>
+</div>
+{{/is_apps}}
+
+{{^is_apps}}
+<div class="doc-family extensions">
+<h2 id="filtered">Filtered events</h2>
+
+<p>Filtered events are a mechanism that allows listeners to specify a subset of
+events that they are interested in. A listener that makes use of a filter won't
+be invoked for events that don't pass the filter, which makes the listening
+code more declarative and efficient - an <a href="event_pages.html">event
+  page</a> page need not be woken up to handle events it doesn't care
+about.</p>
+
+<p>Filtered events are intended to allow a transition from manual filtering
+code like this:</p>
+
+<pre>
+chrome.webNavigation.onCommitted.addListener(function(e) {
+  if (hasHostSuffix(e.url, 'google.com') ||
+      hasHostSuffix(e.url, 'google.com.au')) {
+    // ...
+  }
+});
+</pre>
+
+<p>into this:</p>
+
+<pre>
+chrome.webNavigation.onCommitted.addListener(function(e) {
+  // ...
+}, {url: [{hostSuffix: 'google.com'},
+          {hostSuffix: 'google.com.au'}]});
+</pre>
+
+<p>Events support specific filters that are meaningful to that event. The list
+of filters that an event supports will be listed in the documentation for that
+event in the "filters" section.</p>
+
+<p>When matching URLs (as in the example above), event filters support the same
+URL matching capabilities as expressable with a <a
+  href="events.html#type-UrlFilter">UrlFilter</a>, except for scheme and port
+matching.</p>
+
+</div>
+{{/is_apps}}
diff --git a/chrome/common/extensions/docs/templates/intros/experimental_app.html b/chrome/common/extensions/docs/templates/intros/experimental_app.html
new file mode 100644
index 0000000..458c28c
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/experimental_app.html
@@ -0,0 +1 @@
+<p>The current methods allow applications to generate passive notifications.</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/experimental_devtools_audits.html b/chrome/common/extensions/docs/templates/intros/experimental_devtools_audits.html
new file mode 100644
index 0000000..4a3ad70
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/experimental_devtools_audits.html
@@ -0,0 +1,72 @@
+<p id="classSummary">
+Use the <code>chrome.experimental.devtools.audits</code> module to add new audit
+categories to Developer Tools' Audit panel.
+</p><p>
+See <a href="experimental.devtools.html">DevTools APIs summary</a> for
+general introduction to using Developer Tools APIs</a>.
+</p>
+
+<h2 id="overview">Overview</h2>
+
+<p>
+Each audit category is represented by a line on <em>Select audits to run</em>
+screen in the Audits panel. The following example adds a category named
+<em>Readability</em>:</p>
+<pre>
+var category = chrome.experimental.devtools.audits.addCategory("Readability", 2);
+</pre>
+<img src="{{static}}/images/devtools-audits-category.png"
+     style="margin-left: 20px"
+     width="489" height="342"
+     alt="Extension audit category on the launch screen of Audits panel" />
+<p>
+If the category's checkbox is checked, the <code>onAuditStarted</code> event of
+that category will be fired when user clicks the <em>Run</em> button.
+</p>
+<p>The event handler in your extension receives <code>AuditResults</code>
+as an argument and should add one or more results using <code>addResult()</code>
+method. This may be done asynchronously, i.e. after the handler returns. The
+run of the category is considered to be complete once the extension adds the
+number of results declared when adding the  category with
+<code>addCategory()</code> or
+calls AuditResult's <code>done()</code> method.
+</p>
+<p>The results may include additional details visualized as an expandable
+tree by the Audits panel. You may build the details tree using the
+<code>createResult()</code> and <code>addChild()</code> methods. The child node
+may include specially formatted fragments created by the
+<code>auditResults.createSnippet()</code>
+and <code>auditResults.createURL()</code> methods.
+</p>
+
+<h2 id="examples">Examples</h2>
+<p>The following example adds a handler for onAuditStarted event that creates
+two audit results and populates one of them with the additional details:
+</p>
+
+<pre>
+category.onAuditStarted.addListener(function(results) {
+  var details = results.createResult("Details...");
+  var styles = details.addChild("2 styles with small font");
+  var elements = details.addChild("3 elements with small font");
+
+  results.addResult("Font Size (5)",
+      "5 elements use font size below 10pt",
+      results.Severity.Severe,
+      details);
+  results.addResult("Contrast",
+                    "Text should stand out from background",
+                    results.Severity.Info);
+});
+</pre>
+<p>The audit result tree produced by the snippet above will look like this:
+</p>
+<img src="{{static}}/images/devtools-audits-results.png"
+     style="margin-left: 20px"
+     width="330" height="169"
+     alt="Audit results example" />
+
+<p>
+You can find more examples that use this API in
+<a href="samples.html#devtools.audits">Samples</a>.
+</p>
diff --git a/chrome/common/extensions/docs/templates/intros/experimental_devtools_console.html b/chrome/common/extensions/docs/templates/intros/experimental_devtools_console.html
new file mode 100644
index 0000000..cc9b81a
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/experimental_devtools_console.html
@@ -0,0 +1,7 @@
+<p>
+Use <code>chrome.experimental.devtools.console</code> to retrieve messages from
+the inspected page console and post messages there. <code>getMessages()</code>
+returns the list of messages on the console, <code>onMessageAdded</code>
+event provides notifications on new messages, and <code>addMessage()</code>
+allows an extension to add new messages.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/experimental_discovery.html b/chrome/common/extensions/docs/templates/intros/experimental_discovery.html
new file mode 100644
index 0000000..288bac5
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/experimental_discovery.html
@@ -0,0 +1 @@
+<p>The current methods allow extensions to inject suggested links in the recommended pane of the New Tab Page.</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/experimental_infobars.html b/chrome/common/extensions/docs/templates/intros/experimental_infobars.html
new file mode 100644
index 0000000..d3d0476
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/experimental_infobars.html
@@ -0,0 +1,48 @@
+<p>
+The infobars API allows you to add a
+horizontal panel just above a tab's contents,
+as the following screenshot shows.
+</p>
+
+<p>
+<img src="{{static}}/images/infobar.png"
+  width="566" height="150"
+  alt="An infobar asking whether the user wants to translate the current page" />
+</p>
+
+<p>
+Use an infobar to tell the reader
+something about a particular page.
+When the user leaves the page for which the infobar is displayed,
+Google Chrome automatically closes the infobar.
+</p>
+
+<p>
+You implement the content of your
+infobar using HTML. Because infobars are ordinary pages inside an extension,
+they can
+<a href="overview.html#pageComm">communicate with other extension pages</a>.
+</p>
+
+
+<h2 id="manifest">Manifest</h2>
+
+<p>
+The infobars API is currently
+experimental, so you must declare the "experimental"
+permission to use it. Also, you should specify
+a 16x16-pixel icon for display next to your infobar.
+For example:
+</p>
+
+<pre>{
+  "name": "Andy's infobar extension",
+  "version": "1.0",
+  <b>"permissions": ["experimental"],</b>
+  <b>"icons": {</b>
+    <b>"16": "16.png"</b>
+  <b>},</b>
+  "background": {
+    "scripts": ["background.js"]
+  }
+}</pre>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/experimental_speechInput.html b/chrome/common/extensions/docs/templates/intros/experimental_speechInput.html
new file mode 100644
index 0000000..af6b8e9
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/experimental_speechInput.html
@@ -0,0 +1,109 @@
+<p id="classSummary">
+The <code>chrome.experimental.speechInput</code> module provides
+one-shot speech recognition to Chrome extensions.
+This module is still experimental. For information on how to use experimental
+APIs, see the <a href="experimental.html">chrome.experimental.* APIs</a> page.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+<p>You must declare the "experimental" permission in the <a
+  href="manifest.html">extension manifest</a> to use the speech input
+API.
+For example:</p>
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "experimental"
+  ]</b>,
+  ...
+}</pre>
+
+<h2 id="howToStart">How to start speech recognition</h2>
+<p>To start recognizing speech an extension must call the <code>start()</code>
+method. If provided, your callback will be called once recording has
+successfully started. In case of error <code>chrome.extension.lastError</code>
+will be set.</p>
+
+<p>This API provides exclusive access to the default recording device to the
+first extension requesting it. Consequently, any calls to <code>start()</code>
+when the device is being used by another extension or web page will fail and set
+<code>chrome.extension.lastError</code>. The message <code>requestDenied</code>
+will be set if another extension in the same profile is making use of the API.
+Otherwise <code>noRecordingDeviceFound</code>, <code>recordingDeviceInUse</code>
+or <code>unableToStart</code> will be set depending on the situation.</p>
+
+<p>To check whether recording is currently active, call the
+<code>isRecording()</code> method. Please note that it only checks for audio
+recording within Chrome.</p>
+
+
+<h2 id="howToGetResults">How to get speech recognition results</h2>
+<p>Listen to the <code>onResult</code> event to receive speech recognition
+results.</p>
+
+<pre>
+var callback = function(result) { ... };
+
+chrome.experimental.speechInput.onResult.addListener(callback);
+</pre>
+
+<p>The <code>result</code> object contains an array of
+<a href="#type-SpeechInputResultHypothesis">SpeechInputResultHypothesis</a>
+sorted by decreasing likelihood.</p>
+
+<p>Recording automatically stops when results are received. It is safe to call
+<code>start()</code> again from the results callback.</p>
+
+<p>To handle errors during speech recognition listen for the
+<code>onError</code> event.</p>
+
+<pre>
+var callback = function(error) { ... };
+
+chrome.experimental.speechInput.onError.addListener(callback);
+</pre>
+
+</p>Recording will automatically stop in case of error.
+It is safe to call <code>start()</code> again from the error callback.</p>
+
+
+<h2 id="howToStop">How to stop recording</h2>
+<p>To stop speech recognition call the <code>stop()</code> method. If provided,
+the callback function will be called once recording has successfully stopped.
+In case of error <code>chrome.extension.lastError</code> will be set.
+</p>
+
+
+<h2 id="otherFeatures">Other features</h2>
+<ul><li>
+<code>onSoundStart</code> - Event generated when start of sound is detected
+(from previously being silent).
+</li><li>
+<code>onSoundEnd</code> - Event generated when end of sound is detected (a
+continued period of silence).
+</li></ul>
+
+
+<h2 id="examples">Examples</h2>
+<p>The following example illustrates how to show a JavaScript alert with the
+most likely recognition result.</p>
+<pre>
+function checkStart() {
+  if (chrome.extension.lastError) {
+    alert("Couldn't start speech input: " + chrome.extension.lastError.message);
+  }
+}
+
+function recognitionFailed(error) {
+  alert("Speech input failed: " + error.code);
+}
+
+function recognitionSucceeded(result) {
+  alert("Recognized '" + result.hypotheses[0].utterance + "' with confidence " + result.hypotheses[0].confidence);
+}
+
+chrome.experimental.speechInput.onError.addListener(recognitionFailed);
+chrome.experimental.speechInput.onResult.addListener(recognitionSucceeded);
+chrome.experimental.speechInput.start({ "language": "en" }, checkStart);
+</pre>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/extension.html b/chrome/common/extensions/docs/templates/intros/extension.html
new file mode 100644
index 0000000..2ab3f08
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/extension.html
@@ -0,0 +1,44 @@
+<p id="classSummary">
+The <code>chrome.extension</code> module
+has utilities that can be used by any extension page.
+It includes support for exchanging messages
+between an extension and its content scripts
+or between extensions,
+as described in detail in
+<a href="messaging.html">Message Passing</a>.
+</p>
+
+<h2 id="content scripts">Support for content scripts</h2>
+<p>
+Unlike the other chrome.* APIs,
+parts of <code>chrome.extension</code>
+can be used by content scripts:
+</p>
+
+<dl>
+  <dt>
+    <a href="#method-sendMessage"><code>sendMessage()</code></a> and
+    <a href="#event-onMessage"><code>onMessage</code></a>
+  </dt>
+  <dd>
+    Simple communication with extension pages
+  </dd>
+  <dt>
+    <a href="#method-connect"><code>connect()</code></a> and
+    <a href="#event-onConnect"><code>onConnect</code></a>
+  </dt>
+  <dd>
+    Extended communication with extension pages
+  </dd>
+  <dt>
+    <a href="#method-getURL"><code>getURL()</code></a>
+  </dt>
+  <dd>
+    Access to extension resources such as image files
+  </dd>
+</dl>
+
+<p>
+For details, see
+<a href="content_scripts.html">Content Scripts</a>.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/fileBrowserHandler.html b/chrome/common/extensions/docs/templates/intros/fileBrowserHandler.html
new file mode 100644
index 0000000..40d681a
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/fileBrowserHandler.html
@@ -0,0 +1,154 @@
+<p id="classSummary">
+Use the <code>chrome.fileBrowserHandler</code> module to
+extend the Chrome OS file browser.
+For example, you can use this API
+to enable users to upload files to your website.
+</p>
+
+<p class="caution">
+<b>Important:</b>
+This API works <b>only on Chrome OS</b>.
+</p>
+
+<p>
+The Chrome OS file browser comes up when
+the user either presses Ctrl+M
+or connects an external storage device,
+such as an SD card, USB key, external drive, or digital camera.
+Besides showing the files on external devices,
+the file browser can also display files
+that the user has previously saved to the system.
+</p>
+
+<p>
+When the user selects one or more files,
+the file browser adds buttons
+representing the valid handlers for those files.
+For example, in the following screenshot,
+selecting a file with a ".jpg" suffix
+results in an "Upload to Picasa" button that the user can click.
+</p>
+
+<p>
+<img src="{{static}}/images/filebrowserhandler.png"
+  width="640" height="400" alt="file browser screenshot" />
+</p>
+
+
+<h2 id="manifest">Manifest</h2>
+
+<p>
+You must declare the "fileBrowserHandler" permission
+in the <a href="manifest.html">extension manifest</a>,
+and you must use the "file_browser_handlers" field
+to register the extension as a handler of at least one file type.
+You should also provide a 16x16 icon
+to be displayed on the button.
+For example:
+</p>
+
+<pre>
+{
+  "name": "My extension",
+  ...
+  <b>"file_browser_handlers"</b>: [
+    {
+      <b>"id"</b>: "upload",
+      <b>"default_title"</b>: "Save to Gallery", <em>// What the button will display</em>
+      <b>"file_filters"</b>: [
+        "filesystem:*.jpg", <em>// To match all files, use "filesystem:*.*"</em>
+        "filesystem:*.jpeg",
+        "filesystem:*.png"
+      ]
+    }
+  ]</b>,
+  "permissions" : [
+    <b>"fileBrowserHandler"</b>
+  ],
+  "icons": { <b>"16"</b>: "icon16.png",
+             "48": "icon48.png",
+            "128": "icon128.png" },
+  ...
+}</pre>
+
+<p class="note">
+<b>Note:</b>
+You can specify locale-specific strings for the value of "default_title".
+See <a href="i18n.html">Internationalization (i18n)</a> for details.
+</p>
+
+<h2 id="code">Implementing a file browser handler</h2>
+
+<p>
+To use this API,
+you must implement a function that handles the <code>onExecute</code> event
+of <code>chrome.fileBrowserHandler</code>.
+Your function will be called whenever the user clicks the button
+that represents your file browser handler.
+In your function, use the HTML5
+<a href="http://www.html5rocks.com/tutorials/file/filesystem/">FileSystem API</a>
+to get access to the file contents.
+Here is an example:
+</p>
+
+<pre>
+<em>//In background.html:</em>
+chrome.fileBrowserHandler.onExecute.addListener(function(id, details) {
+  if (id == 'upload') {
+    var fileEntries = details.entries;
+    for (var i = 0, entry; entry = fileEntries[i]; ++i) {
+      entry.file(function(file) {
+        <em>// send file somewhere</em>
+      });
+    }
+  }
+});
+</pre>
+
+<p>
+Your event handler is passed two arguments:
+</p>
+
+<dl>
+  <dt> id </dt>
+  <dd> The "id" value from the manifest file.
+       If your extension implements multiple handlers,
+       you can check the <code>id</code> value
+       to see which handler has been triggered.
+       </dd>
+  <dt> details </dt>
+  <dd> An object describing the event.
+       You can get the file or files that the user has selected
+       from the <code>entries</code> field of this object,
+       which is an array of
+       FileSystem <code>Entry</code> objects.
+       </dd>
+</p>
+
+
+<!--
+<h2 id="manifest_details">Manifest details</h2>
+
+<p class="authornote">
+{PENDING: give details about "id" and "default_title".
+It should be unique within your extension -- don't worry about other extensions.
+"default_title" implies that you can change the title,
+but you can't aside from internationalizing it.
+</p>
+
+<p class="authornote">
+{PENDING: give details about the file_filters entry.
+File filters are currently case-sensitive, but we plan to change that.
+Mention <code>filesystem:*.*</code>.
+</p>
+-->
+
+<!--
+<h2 id="examples">Examples</h2>
+
+<p>
+For examples of using this API, see ...
+For other examples and for help in viewing the source code, see
+<a href="samples.html">Samples</a>.
+</p>
+-->
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/fontSettings.html b/chrome/common/extensions/docs/templates/intros/fontSettings.html
new file mode 100644
index 0000000..1b16e63
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/fontSettings.html
@@ -0,0 +1,56 @@
+<p>The Font Settings API allows you to manage Chrome's font settings.</p>
+
+<h2 id="manifest">Manifest</h2>
+<p>To use the Font Settings API, you must declare the "fontSettings" permission
+in the <a href="manifest.html">extension manifest</a>.
+For example:</p>
+<pre>{
+  "name": "My Font Settings Extension",
+  "description": "Customize your fonts",
+  "version": "0.2",
+  "permissions": ["fontSettings"]
+}</pre>
+
+<h2 id="scripts">Generic Font Families and Scripts</h2>
+<p>Chrome allows for some font settings to depend on certain generic font
+families and language scripts. For example, the font used for sans-serif
+Simplified Chinese may be different than the font used for serif Japanese.</p>
+
+<p>The generic font families supported by Chrome are based on
+<a href="http://www.w3.org/TR/CSS21/fonts.html#generic-font-families">CSS generic font families</a>
+and are listed in the API reference below. When a webpage specifies a generic
+font family, Chrome selects the font based on the corresponding setting. If no
+generic font family is specified, Chrome uses the setting for the "standard"
+generic font family.</p>
+
+<p>When a webpage specifies a language, Chrome selects the font based on the
+setting for the corresponding language script. If no language is specified,
+Chrome uses the setting for the default, or global, script.</p>
+
+<p>The supported language scripts are specified by ISO 15924 script code and
+listed in the API reference below. Technically, Chrome settings are not strictly
+per-script but also depend on language. For example, Chrome chooses the font for
+Cyrillic (ISO 15924 script code "Cyrl") when a webpage specifies the Russian
+language, and uses this font not just for Cyrillic script but for everything the
+font covers, such as Latin.</p>
+
+<h2 id="examples">Examples</h2>
+<p>The following code gets the standard font for Arabic.</p>
+<pre>
+chrome.fontSettings.getFont(
+  { genericFamily: 'standard', script: 'Arab' },
+  function(details) { console.log(details.fontId); }
+);
+</pre>
+
+<p>The next snippet sets the sans-serif font for Japanese.</p>
+<pre>
+chrome.fontSettings.setFont(
+  { genericFamily: 'sansserif', script: 'Jpan', fontId: 'MS PGothic' }
+);
+</pre>
+
+<p>You can find a sample extension using the Font Settings API in the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/fontSettings/">examples/api/fontSettings</a>
+directory. For other examples and for help in viewing the source code, see
+<a href="samples.html">Samples</a>.</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/history.html b/chrome/common/extensions/docs/templates/intros/history.html
new file mode 100644
index 0000000..9bac472
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/history.html
@@ -0,0 +1,153 @@
+<p id="classSummary">
+Use the <code>chrome.history</code> module to interact with the 
+browser's record of visited pages.  You can add, remove, and query
+for URLs in the browser's history.
+To override the history page with your own version, see 
+<a href="override.html">Override Pages</a>.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+
+<p>You must declare the "history" permission
+in the <a href="manifest.html">extension manifest</a>
+to use the history API.
+For example:</p>
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "history"
+  ]</b>,
+  ...
+}</pre>
+
+<h2 id="transition_types">Transition types</h2>
+
+<p>
+The history API uses a <em>transition type</em> to describe
+how the browser navigated to a particular URL
+on a particular visit.
+For example, if a user visits a page
+by clicking a link on another page,
+the transition type is "link".
+</p>
+
+<p>
+The following table describes each transition type.
+</p>
+
+<table>
+<tr>
+  <th> Transition type </th> <th> Description </th>
+</tr>
+<tr id="tt_link">
+  <td>"link"</td>
+  <td>
+    The user got to this page by clicking a link on another page.
+  </td>
+</tr>
+<tr id="tt_typed">
+  <td>"typed"</td>
+  <td>
+    The user got this page by typing the URL in the address bar.
+    Also used for other explicit navigation actions.
+    See also <a href="#tt_generated">generated</a>,
+    which is used for cases where the user selected a choice
+    that didn't look at all like a URL.
+  </td>
+</tr>
+<tr id="tt_auto_bookmark">
+  <td>"auto_bookmark"</td>
+  <td>
+    The user got to this page through a suggestion in the UI &mdash;
+    for example, through a menu item.
+  </td>
+</tr>
+<tr id="tt_auto_subframe">
+  <td>"auto_subframe"</td>
+  <td>
+    Subframe navigation.
+    This is any content that is automatically
+    loaded in a non-top-level frame.
+    For example, if a page consists of
+    several frames containing ads,
+    those ad URLs have this transition type.
+    The user may not even realize the content in these pages
+    is a separate frame, and so may not care about the URL
+    (see also <a href="#tt_manual_subframe">manual_subframe</a>).
+  </td>
+</tr>
+<tr id="tt_manual_subframe">
+  <td>"manual_subframe"</td>
+  <td>
+    For subframe navigations that are explicitly requested by the user
+    and generate new navigation entries in the back/forward list.
+    An explicitly requested frame is probably more important than
+    an automatically loaded frame
+    because the user probably cares about the fact that
+    the requested frame was loaded.
+  </td>
+</tr>
+<tr id="tt_generated">
+  <td>"generated"</td>
+  <td>
+    The user got to this page by typing in the address bar
+    and selecting an entry that did not look like a URL.
+    For example, a match might have the URL of a Google search result page,
+    but it might appear to the user as "Search Google for ...".
+    These are not quite the same as <a href="#tt_typed">typed</a> navigations
+    because the user didn't type or see the destination URL.
+    See also <a href="#tt_keyword">keyword</a>.
+  </td>
+</tr>
+<tr id="tt_start_page">
+  <td>"start_page"</td>
+  <td>
+    The page was specified in the command line or is the start page.
+  </td>
+</tr>
+<tr id="tt_form_submit">
+  <td>"form_submit"</td>
+  <td>
+    The user filled out values in a form and submitted it.
+    Note that in some situations &mdash;
+    such as when a form uses script to submit contents &mdash;
+    submitting a form does not result in this transition type.
+  </td>
+</tr>
+<tr id="tt_reload">
+  <td>"reload"</td>
+  <td>
+    The user reloaded the page,
+    either by clicking the reload button
+    or by pressing Enter in the address bar.
+    Session restore and Reopen closed tab use this transition type, too.
+  </td>
+</tr>
+<tr id="tt_keyword">
+  <td>"keyword"</td>
+  <td>
+    The URL was generated from a replaceable keyword
+    other than the default search provider.
+    See also
+    <a href="#tt_keyword_generated">keyword_generated</a>.
+  </td>
+</tr>
+<tr id="tt_keyword_generated">
+  <td>"keyword_generated"</td>
+  <td>
+    Corresponds to a visit generated for a keyword.
+    See also <a href="#tt_keyword">keyword</a>.
+  </td>
+</tr>
+</table>
+
+<h2 id="examples">Examples</h2>
+
+<p>
+For examples of using this API, see the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/history/">history sample directory</a> and the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/test/data/extensions/api_test/history/">history API test directory</a>.
+For other examples and for help in viewing the source code, see
+<a href="samples.html">Samples</a>.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/i18n.html b/chrome/common/extensions/docs/templates/intros/i18n.html
new file mode 100644
index 0000000..4c0c2be
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/i18n.html
@@ -0,0 +1,570 @@
+<!--
+[NOTEs for editors:
+ * Try to be consistent about string vs. message (it's probably not yet).
+-->
+
+
+<p id="classSummary">
+An <em>internationalized</em> extension or app
+can be easily
+<em>localized</em> &mdash;
+adapted to languages and regions
+that it didn't originally support.
+</p>
+
+<p>
+You need to put all of its user-visible strings into a file
+named <a href="i18n-messages.html"><code>messages.json</code></a>.
+Each time you add a new locale,
+you add a messages file
+under a directory
+named <code>_locales/<em>localeCode</em></code>,
+where <em>localeCode</em> is a code such as
+<code>en</code> for English.
+</p>
+
+<p>
+Here's the file hierarchy
+for an internationalized extension that supports
+English (<code>en</code>),
+Spanish (<code>es</code>), and
+Korean (<code>ko</code>):
+</p>
+
+<img src="{{static}}/images/i18n-hierarchy.gif"
+ alt='In the extension directory: manifest.json, *.html, *.js, _locales directory. In the _locales directory: en, es, and ko directories, each with a messages.json file.'
+ width="385" height="77" />
+
+
+<h2 id="l10">How to support multiple languages</h2>
+
+<p>
+Say you have an extension
+with the files shown in the following figure:
+</p>
+
+<img src="{{static}}/images/i18n-before.gif"
+ alt='A manifest.json file and a file with JavaScript. The .json file has "name": "Hello World". The JavaScript file has title = "Hello World";'
+ width="323" height="148">
+
+<p>
+To internationalize this extension,
+you name each user-visible string
+and put it into a messages file.
+The extension's manifest,
+CSS files,
+and JavaScript code
+use each string's name to get its localized version.
+</p>
+
+<p>
+Here's what the extension looks like when it's internationalized
+(note that it still has only English strings):
+</p>
+
+<img src="{{static}}/images/i18n-after-1.gif"
+ alt='In the manifest.json file, "Hello World" has been changed to "__MSG_extName__", and a new "default_locale" item has the value "en". In the JavaScript file, "Hello World" has been changed to chrome.i18n.getMessage("extName"). A new file named _locales/en/messages.json defines "extName".'
+ width="782" height="228">
+
+<p class="note">
+<b>Important:</b>
+If an extension has a <code>_locales</code> directory,
+the <a href="manifest.html">manifest</a>
+<b>must</b> define "default_locale".
+</p>
+
+<p>
+Some notes about internationalizing:
+</p>
+
+<ul>
+  <li><p>
+    You can use any of the <a href="#overview-locales">supported locales</a>.
+    If you use an unsupported locale,
+    Google Chrome ignores it.
+  </p></li>
+
+  <li>
+    In <code>manifest.json</code>
+    and CSS files,
+    refer to a string named <em>messagename</em> like this:
+    <pre>__MSG_<em>messagename</em>__</pre>
+  </li>
+
+  <li>
+    In your extension or app's JavaScript code,
+    refer to a string named <em>messagename</em>
+    like this:
+    <pre>chrome.i18n.getMessage("<em>messagename</em>")</pre>
+
+  <li> <p>
+    In each call to <code>getMessage()</code>,
+    you can supply up to 9 strings
+    to be included in the message.
+    See <a href="#examples-getMessage">Examples: getMessage</a>
+    for details.
+    </p>
+  </li>
+
+  <li><p>
+    Some messages, such as <code>@@bidi_dir</code> and <code>@@ui_locale</code>,
+    are provided by the internationalization system.
+    See the <a href="#overview-predefined">Predefined messages</a> section
+    for a full list of predefined message names.
+    </p>
+  </li>
+
+  <li>
+    In <code>messages.json</code>,
+    each user-visible string has a name, a "message" item,
+    and an optional "description" item.
+    The name is a key
+    such as "extName" or "search_string"
+    that identifies the string.
+    The "message" specifies
+    the value of the string in this locale.
+    The optional "description"
+    provides help to translators,
+    who might not be able to see how the string is used in your extension.
+    For example:
+<pre>
+{
+  "search_string": {
+    "message": "hello%20world",
+    "description": "The string we search for. Put %20 between words that go together."
+  },
+  ...
+}</pre>
+
+<p>
+For more information, see
+<a href="i18n-messages.html">Formats: Locale-Specific Messages</a>.
+</p>
+  </li>
+</ul>
+
+<p>
+Once an extension or app is internationalized,
+translating it is simple.
+You copy <code>messages.json</code>,
+translate it,
+and put the copy into a new directory under <code>_locales</code>.
+For example, to support Spanish,
+just put a translated copy of <code>messages.json</code>
+under <code>_locales/es</code>.
+The following figure shows the previous extension
+with a new Spanish translation.
+</p>
+
+<img src="{{static}}/images/i18n-after-2.gif"
+ alt='This looks the same as the previous figure, but with a new file at _locales/es/messages.json that contains a Spanish translation of the messages.'
+ width="782" height="358">
+
+
+<h2 id="overview-predefined">Predefined messages</h2>
+
+<p>
+The internationalization system provides a few predefined
+messages to help you localize.
+These include <code>@@ui_locale</code>,
+so you can detect the current UI locale,
+and a few <code>@@bidi_...</code> messages
+that let you detect the text direction.
+The latter messages have similar names to constants in the
+<a href="http://code.google.com/apis/gadgets/docs/i18n.html#BIDI">
+gadgets BIDI (bi-directional) API</a>.
+</p>
+
+<p>
+The special message <code>@@extension_id</code>
+can be used in the CSS and JavaScript files,
+whether or not the extension or app is localized.
+This message doesn't work in manifest files.
+</p>
+
+<p>
+The following table describes each predefined message.
+</p>
+
+<table>
+<tr>
+  <th>Message name</th> <th>Description</th>
+</tr>
+<tr>
+  <td> <code>@@extension_id</code> </td>
+  <td>The extension or app ID;
+    you might use this string to construct URLs
+    for resources inside the extension.
+    Even unlocalized extensions can use this message.
+    <br>
+    <b>Note:</b> You can't use this message in a manifest file.
+    </td>
+</tr>
+<tr>
+  <td> <code>@@ui_locale</code> </td>
+  <td>The current locale;
+    you might use this string to construct locale-specific URLs. </td>
+</tr>
+<tr>
+  <td> <code>@@bidi_dir</code> </td>
+  <td> The text direction for the current locale,
+       either "ltr" for left-to-right languages such as English
+       or "rtl" for right-to-left languages such as Japanese. </td>
+</tr>
+<tr>
+  <td> <code>@@bidi_reversed_dir</code> </td>
+  <td> If the <code>@@bidi_dir</code> is "ltr", then this is "rtl";
+       otherwise, it's "ltr". </td>
+</tr>
+<tr>
+  <td> <code>@@bidi_start_edge</code> </td>
+  <td> If the <code>@@bidi_dir</code> is "ltr", then this is "left";
+       otherwise, it's "right". </td>
+</tr>
+<tr>
+  <td> <code>@@bidi_end_edge</code> </td>
+  <td> If the <code>@@bidi_dir</code> is "ltr", then this is "right";
+       otherwise, it's "left". </td>
+</tr>
+</table>
+
+<p>
+Here's an example of using <code>@@extension_id</code> in a CSS file
+to construct a URL:
+</p>
+
+<pre>
+body {
+  <b>background-image:url('chrome-extension://__MSG_@@extension_id__/background.png');</b>
+}
+</pre>
+
+<p>
+If the extension ID is abcdefghijklmnopqrstuvwxyzabcdef,
+then the bold line in the previous code snippet becomes:
+</p>
+
+<pre>
+background-image:url('chrome-extension://abcdefghijklmnopqrstuvwxyzabcdef/background.png');
+</pre>
+
+<p>
+Here's an example of using <code>@@bidi_*</code> messages in a CSS file:
+</p>
+
+<pre>
+body {
+  <b>direction: __MSG_@@bidi_dir__;</b>
+}
+
+div#header {
+  margin-bottom: 1.05em;
+  overflow: hidden;
+  padding-bottom: 1.5em;
+  <b>padding-__MSG_@@bidi_start_edge__: 0;</b>
+  <b>padding-__MSG_@@bidi_end_edge__: 1.5em;</b>
+  position: relative;
+}
+</pre>
+
+<p>
+For left-to-right languages such as English,
+the bold lines become:
+</p>
+
+<pre>
+dir: ltr;
+padding-left: 0;
+padding-right: 1.5em;
+</pre>
+
+
+<h2 id="overview-locales">Locales</h2>
+
+<p>
+You can choose from many locales,
+including some (such as <code>en</code>)
+that let a single translation support multiple variations of a language
+(such as <code>en_GB</code> and <code>en_US</code>).
+</p>
+
+
+<h3 id="locales-supported">Supported locales</h3>
+
+<p>
+You can use any of the
+<a href="http://code.google.com/chrome/webstore/docs/i18n.html#localeTable">locales that the Chrome Web Store supports</a>.
+</p>
+
+
+<h3 id="locales-usage">Searching for messages</h3>
+
+<p>
+You don't have to define every string for every supported locale.
+As long as the default locale's <code>messages.json</code> file
+has a value for every string,
+your extension or app will run no matter how sparse a translation is.
+Here's how the extension system searches for a message:
+</p>
+
+<ol>
+  <li>
+     Search the messages file (if any)
+     for the user's preferred locale.
+     For example, when Google Chrome's locale is set to
+     British English (<code>en_GB</code>),
+     the system first looks for the message in
+     <code>_locales/en_GB/messages.json</code>.
+     If that file exists and the message is there,
+     the system looks no further.
+  </li>
+  <li>
+     If the user's preferred locale has a region
+     (that is, the locale has an underscore: _),
+     search the locale without that region.
+     For example, if the <code>en_GB</code> messages file
+     doesn't exist or doesn't contain the message,
+     the system looks in the <code>en</code> messages file.
+     If that file exists and the message is there,
+     the system looks no further.
+  </li>
+  <li>
+     Search the messages file for the default locale.
+     For example, if the extension's "default_locale" is set to "es",
+     and neither <code>_locales/en_GB/messages.json</code>
+     nor <code>_locales/en/messages.json</code> contains the message,
+     the extension uses the message from
+     <code>_locales/es/messages.json</code>.
+  </li>
+</ol>
+
+<p>
+In the following figure,
+the message named "colores" is in all three locales
+that the extension supports,
+but "extName" is in only two of the locales.
+Wherever a user running Google Chrome in US English sees the label "Colors",
+a user of British English sees "Colours".
+Both US English and British English users
+see the extension name "Hello World".
+Because the default language is Spanish,
+users running Google Chrome in any non-English language
+see the label "Colores" and the extension name "Hola mundo".
+</p>
+
+<img src="{{static}}/images/i18n-strings.gif"
+ alt='Four files: manifest.json and three messages.json files (for es, en, and en_GB).  The es and en files show entries for messages named "extName" and "colores"; the en_GB file has just one entry (for "colores").'
+ width="493" height="488" />
+
+<h3 id="locales-testing">How to set your browser's locale</h3>
+
+<p>
+To test translations, you might want to set your browser's locale.
+This section tells you how to set the locale in
+<a href="#testing-win">Windows</a>,
+<a href="#testing-mac">Mac OS X</a>, and
+<a href="#testing-linux">Linux</a>.
+</p>
+
+<h4 id="testing-win">Windows</h4>
+
+<p>
+You can change the locale using either
+a locale-specific shortcut
+or the Google Chrome UI.
+The shortcut approach is quicker, once you've set it up,
+and it lets you use several languages at once.
+</p>
+
+<h5 id="win-shortcut">Using a locale-specific shortcut</h5>
+
+<p>
+To create and use a shortcut that launches Google Chrome
+with a particular locale:
+</p>
+
+<ol>
+  <li>
+    Make a copy of the Google Chrome shortcut
+    that's already on your desktop.
+  </li>
+  <li>
+    Rename the new shortcut to match the new locale.
+  </li>
+  <li>
+    Change the shortcut's properties
+    so that the Target field specifies the
+    <code>--lang</code> and
+    <code>--user-data-dir</code> flags.
+    The target should look something like this:
+
+<pre><em>path_to_chrome.exe</em> --lang=<em>locale</em> --user-data-dir=c:\<em>locale_profile_dir</em></pre>
+  </li>
+
+  <li>
+    Launch Google Chrome by double-clicking the shortcut.
+  </li>
+</ol>
+
+<p>
+For example, to create a shortcut
+that launches Google Chrome in Spanish (<code>es</code>),
+you might create a shortcut named <code>chrome-es</code>
+that has the following target:
+</p>
+
+<pre><em>path_to_chrome.exe</em> --lang=es --user-data-dir=c:\chrome-profile-es</pre>
+
+<p>
+You can create as many shortcuts as you like,
+making it easy to test in multiple languages.
+For example:
+</p>
+
+<pre><em>path_to_chrome.exe</em> --lang=en --user-data-dir=c:\chrome-profile-en
+<em>path_to_chrome.exe</em> --lang=en_GB --user-data-dir=c:\chrome-profile-en_GB
+<em>path_to_chrome.exe</em> --lang=ko --user-data-dir=c:\chrome-profile-ko</pre>
+
+<p class="note">
+<b>Note:</b>
+Specifying <code>--user-data-dir</code> is optional but handy.
+Having one data directory per locale
+lets you run the browser
+in several languages at the same time.
+A disadvantage is that because the locales' data isn't shared,
+you have to install your extension multiple times &mdash; once per locale,
+which can be challenging when you don't speak the language.
+For more information, see
+<a href="http://www.chromium.org/developers/creating-and-using-profiles">Creating and Using Profiles</a>.
+</p>
+
+
+<h5 id="win-ui">Using the UI</h5>
+
+<p>
+Here's how to change the locale using the UI on Google Chrome for Windows:
+</p>
+
+<ol>
+  <li> Wrench icon > <b>Options</b> </li>
+  <li> Choose the <b>Under the Hood</b> tab </li>
+  <li> Scroll down to <b>Web Content</b> </li>
+  <li> Click <b>Change font and language settings</b> </li>
+  <li> Choose the <b>Languages</b> tab </li>
+  <li> Use the drop down to set the <b>Google Chrome language</b> </li>
+  <li> Restart Chrome </li>
+</ol>
+
+
+<h4 id="testing-mac">Mac OS X</h4>
+
+<p>
+To change the locale on Mac,
+you use the system preferences.
+</p>
+
+<ol>
+  <li> From the Apple menu, choose <b>System Preferences</b> </li>
+  <li> Under the <b>Personal</b> section, choose <b>International</b> </li>
+  <li> Choose your language and location </li>
+  <li> Restart Chrome </li>
+</ol>
+
+
+<h4 id="testing-linux">Linux</h4>
+
+<p>
+To change the locale on Linux,
+first quit Google Chrome.
+Then, all in one line,
+set the LANGUAGE environment variable
+and launch Google Chrome.
+For example:
+</p>
+
+<pre>
+LANGUAGE=es ./chrome
+</pre>
+
+
+<h2 id="overview-examples">Examples</h2>
+
+<p>
+You can find simple examples of internationalization in the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/i18n/">examples/api/i18n</a>
+directory.
+For a complete example, see
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/news/">examples/extensions/news</a>.
+For other examples and for help in viewing the source code, see
+<a href="samples.html">Samples</a>.
+</p>
+
+
+<h3 id="examples-getMessage">Examples: getMessage</h3>
+
+<!--
+[PENDING: improve this section. it should probably start with a
+one-variable example that includes the messages.json code.]
+-->
+
+<p>
+The following code gets a localized message from the browser
+and displays it as a string.
+It replaces two placeholders within the message with the strings
+"string1" and "string2".
+</p>
+
+<pre>
+function getMessage() {
+  var message = chrome.i18n.getMessage("click_here", ["string1", "string2"]);
+  document.getElementById("languageSpan").innerHTML = message;
+}
+</pre>
+
+<p>
+Here's how you'd supply and use a single string:
+</p>
+
+<pre>
+<em>// In JavaScript code</em>
+status.innerText = chrome.i18n.getMessage("error", errorDetails);
+
+<em>// In messages.json</em>
+"error": {
+  "message": "Error: $details$",
+  "description": "Generic error template. Expects error parameter to be passed in.",
+  "placeholders": {
+    "details": {
+      "content": "$1",
+      "example": "Failed to fetch RSS feed."
+    }
+  }
+}
+</pre>
+
+<p>
+For more information about placeholders, see the
+<a href="i18n-messages.html">Locale-Specific Messages</a> page.
+For details on calling <code>getMessage()</code>, see the
+<a href="#method-getMessage">API reference</a>.
+</p>
+
+<h3 id="example-accept-languages">Example: getAcceptLanguages</h3>
+<p>
+The following code gets accept-languages from the browser and displays them as a
+string by separating each accept-language with ','.
+</p>
+
+<pre>
+function getAcceptLanguages() {
+  chrome.i18n.getAcceptLanguages(function(languageList) {
+    var languages = languageList.join(",");
+    document.getElementById("languageSpan").innerHTML = languages;
+  })
+}
+</pre>
+
+<p>
+For details on calling <code>getAcceptLanguages()</code>, see the
+<a href="#method-getAcceptLanguages">API reference</a>.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/idle.html b/chrome/common/extensions/docs/templates/intros/idle.html
new file mode 100644
index 0000000..0eedf22
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/idle.html
@@ -0,0 +1,13 @@
+<h2 id="manifest">Manifest</h2>
+<p>You must declare the "idle" permission in your extension's manifest to use the idle API.
+For example:
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "idle"
+  ]</b>,
+  ...
+}</pre>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/input_ime.html b/chrome/common/extensions/docs/templates/intros/input_ime.html
new file mode 100644
index 0000000..9a75f44
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/input_ime.html
@@ -0,0 +1,54 @@
+<p id="classSummary">
+Use the <code>chrome.input.ime</code> module to implement a custom IME for
+Chrome OS.
+
+This allows your extension to handle keystrokes, set the composition, and
+manage the candidate window.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+<p>You must declare the "input" permission
+in the <a href="manifest.html">extension manifest</a>
+to use the input.ime API.
+For example:</p>
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "input"
+  ]</b>,
+  ...
+}</pre>
+
+<h2 id="overview-examples">Examples</h2>
+
+<p>
+The following code creates an IME that converts typed letters to upper case.
+</p>
+
+<pre>
+var context_id = -1;
+
+chrome.input.ime.onFocus.addListener(function(context) {
+  context_id = context.contextID;
+});
+
+chrome.input.ime.onKeyEventAsync.addListener(
+  function(engineID, keyData) {
+    if (keyData.type == "keydown" && keyData.key.match(/^[a-z]$/)) {
+      chrome.input.ime.commitText({"contextID": context_id,
+                                   "text": keyData.key.toUpperCase()});
+      return true;
+    } else {
+      return false;
+    }
+  });
+</pre>
+
+<p>
+For an example of using this API, see the
+<a
+  href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/input.ime/basic/">basic input.ime sample</a>.
+For other examples and for help in viewing the source code, see
+<a href="samples.html">Samples</a>.
+</p>
diff --git a/chrome/common/extensions/docs/templates/intros/management.html b/chrome/common/extensions/docs/templates/intros/management.html
new file mode 100644
index 0000000..ae5076a
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/management.html
@@ -0,0 +1,23 @@
+<p id="classSummary">
+The <code>chrome.management</code> module provides ways to manage the list of extensions/apps that are installed and running. It is particularly useful for extensions that <a href="override.html">override</a> the built-in New Tab page.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+
+<p>You must declare the "management" permission
+in the <a href="manifest.html">extension manifest</a>
+to use the management API.
+For example:</p>
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "management"
+  ]</b>,
+  ...
+}</pre>
+
+<p>
+The one method that doesn't require the "management" permission is
+<a href="#method-getPermissionWarningsByManifest"><code>getPermissionWarningsByManifest</code></a>
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/mediaGalleries.html b/chrome/common/extensions/docs/templates/intros/mediaGalleries.html
new file mode 100644
index 0000000..892d9f2
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/mediaGalleries.html
@@ -0,0 +1,50 @@
+<p>
+The media galleries API allows you to access media files (images,
+video, audio) from the user's local disks (with the user's consent).
+</p>
+
+<p>
+Using the API, you can prompt the user for permission to access the media
+galleries. The permission dialog will contain common media locations for
+the platform and will allow the user to add additional locations. From those
+locations, only media files will be present in the file system objects.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+<p>The media galleries API has two axes of permission parameters; the locations
+that can be accessed, and the type of access (read-only, read-write,
+add-files).</p>
+
+<p>On the location axis, specifying no location-type permission parameters
+means that
+no media galleries are accessible until the user grants permission to
+specific media galleries at runtime using the media gallery configuration
+dialog. This dialog can be programmatically triggered.  Alternatively,
+specifying the "allAutoDetected" permission parameter grants access to all
+auto-detected media galleries on the user's computer. However, this
+permission displays an install time prompt indicating that the extension
+will have access to all of the user's media files.</p>
+
+<p>On the access type axis, the "read" permission parameter grants the
+extension the right to read files. This permission does not trigger an install
+time permission prompt because the user must still grant access to particular
+galleries, either with the "allAutoDetected" permission parameter or at
+runtime by using the media gallery management dialog. For example:</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  "permissions": [
+    <b>{ "mediaGalleries": ["read", "allAutoDetected"] }</b>
+  ],
+  ...
+}</pre>
+
+<p>This permission will trigger an install time permission prompt
+and let the extension read from all auto-detected media galleries on the
+user's computer. The user may add or remove galleries using the
+media gallery management dialog, after which the extension will be able
+to read all the media files from galleries that the user has selected.</p>
+
+<p>Currently "read" is the only access type supported by
+this API. Read-write and add-file access with be implemented soon.</p>
diff --git a/chrome/common/extensions/docs/templates/intros/omnibox.html b/chrome/common/extensions/docs/templates/intros/omnibox.html
new file mode 100644
index 0000000..9c34655
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/omnibox.html
@@ -0,0 +1,75 @@
+<p>
+The omnibox API allows you to register a
+keyword with Google Chrome's address bar,
+which is also known as the omnibox.
+</p>
+
+<p>
+<img src="{{static}}/images/omnibox.png" width="300" height="150"
+  alt="A screenshot showing suggestions related to the keyword 'Chromium Search'"/>
+</p>
+
+<p>
+When the user enters your extension's
+keyword, the user starts
+interacting solely with your extension.
+Each keystroke is sent to your
+extension, and you can provide suggestions
+in response.
+</p>
+
+<p>
+The suggestions can be richly formatted
+in a variety of ways.
+
+When the user accepts
+a suggestion, your extension is notified
+and can take action.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+
+<p>
+You must include an <code>omnibox</code> <code>keyword</code> field
+in the <a href="manifest.html">manifest</a> 
+to use the omnibox API.
+You should also
+specify a 16x16-pixel icon, which will be
+displayed in the address bar when suggesting that users
+enter keyword mode.
+</p>
+
+<p>
+For example:
+</p>
+
+<pre>{
+  "name": "Aaron's omnibox extension",
+  "version": "1.0",
+  <b>"omnibox": { "keyword" : "aaron" },</b>
+  <b>"icons": {</b>
+    <b>"16": "16-full-color.png"</b>
+  <b>},</b>
+  "background": {
+    "persistent": false,
+    "scripts": ["background.js"]
+  }
+}</pre>
+
+<p class="note">
+<strong>Note:</strong>
+Chrome automatically creates a grayscale version of
+your 16x16-pixel icon. You should provide
+a full-color version so that it can also be
+used in other situations that require color.
+For example, the <a href="contextMenus.html"
+>context menus API</a> also uses a 16x16-pixel
+icon, but it is displayed in color.
+</p>
+
+
+<h2 id="examples">Examples</h2>
+
+<p>
+You can find samples of this API on the
+<a href="samples.html#omnibox">sample page</a>.
diff --git a/chrome/common/extensions/docs/templates/intros/pageAction.html b/chrome/common/extensions/docs/templates/intros/pageAction.html
new file mode 100644
index 0000000..cc95f44
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/pageAction.html
@@ -0,0 +1,123 @@
+<p>
+Use page actions to put icons inside the address bar.
+Page actions represent actions
+that can be taken on the current page,
+but that aren't applicable to all pages.
+Some examples:
+</p>
+<ul>
+  <li> Subscribe to this page's RSS feed </li>
+  <li> Make a slideshow out of this page's photos </li>
+</ul>
+
+<p>
+The RSS icon in the following screenshot
+represents a page action
+that lets you subscribe to
+the RSS feed for the current page.
+</p>
+
+<img src="{{static}}/images/page-action.png"
+  width="361" height="79" />
+
+<p>
+If you want the extension's icon to always be visible,
+use a <a href="browserAction.html">browser action</a> instead.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+
+<p>
+Register your page action in the
+<a href="manifest.html">extension manifest</a>
+like this:
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"page_action": {
+    "default_icon": {                    <em>// optional</em>
+      "19": "images/icon19.png",           <em>// optional</em>
+      "38": "images/icon38.png"            <em>// optional</em>
+    },
+    "default_title": "Google Mail",      <em>// optional; shown in tooltip</em>
+    "default_popup": "popup.html"        <em>// optional</em>
+  }</b>,
+  ...
+}</pre>
+
+<p>
+If you only provide one of the 19px or 38px icon size, the extension system will
+scale the icon you provide to the pixel density of the user's display, which
+can lose detail or make it look fuzzy. The old syntax for registering the
+default icon is still supported:
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"page_action": {
+    ...
+    "default_icon": "images/icon19.png"  <em>// optional</em>
+    <em>// equivalent to "default_icon": { "19": "images/icon19.png" }</em>
+  }</b>,
+  ...
+}</pre>
+
+<h2 id="ui">Parts of the UI</h2>
+
+<p>
+Like browser actions,
+page actions can have an icon,
+a tooltip, and popup;
+they can't have badges, however.
+In addition, page actions can appear and disappear.
+You can find information about icons, tooltips, and popups
+by reading about the
+<a href="browserAction.html#ui">browser action UI</a>.
+</p>
+
+<p>
+You make a page action appear and disappear using the
+<a href="#method-show">show()</a> and
+<a href="#method-hide">hide()</a> methods, respectively.
+By default, a page action is hidden.
+When you show it, you specify the tab
+in which the icon should appear.
+The icon remains visible
+until the tab is closed
+or starts displaying a different URL
+(because the user clicks a link, for example).
+</p>
+
+
+
+
+<h2 id="tips">Tips</h2>
+
+<p>For the best visual impact,
+follow these guidelines:</p>
+
+<ul>
+  <li><b>Do</b> use page actions
+    for features that make sense
+    for only a few pages.
+  <li><b>Don't</b> use page actions
+    for features that make sense
+    for most pages.
+    Use <a href="browserAction.html">browser actions</a> instead.
+  <li><b>Don't</b> constantly animate your icon.
+    That's just annoying.
+</ul>
+
+
+<h2 id="examples"> Examples </h2>
+
+<p>
+You can find simple examples of using page actions in the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/pageAction/">examples/api/pageAction</a>
+directory.
+For other examples and for help in viewing the source code, see
+<a href="samples.html">Samples</a>.
+</p>
diff --git a/chrome/common/extensions/docs/templates/intros/pageCapture.html b/chrome/common/extensions/docs/templates/intros/pageCapture.html
new file mode 100644
index 0000000..abb0af3
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/pageCapture.html
@@ -0,0 +1,30 @@
+<p>
+The pageCapture API allows you to save a tab as MHTML.
+</p>
+
+<p>
+MHTML is a <a href="http://tools.ietf.org/html/rfc2557">standard format</a>
+supported by most browsers. It encapsulates in a single file a page and all
+its resources (CSS files, images..).
+</p>
+
+<p>
+Note that for security reasons a MHTML file can only be loaded from the file
+system and that it can only be loaded in the main frame.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+
+
+<p>You must declare the "pageCapture" permission
+in the <a href="manifest.html">extension manifest</a>
+to use the history API.
+For example:</p>
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "pageCapture"
+  ]</b>,
+  ...
+}</pre>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/permissions.html b/chrome/common/extensions/docs/templates/intros/permissions.html
new file mode 100644
index 0000000..aefbc3a
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/permissions.html
@@ -0,0 +1,173 @@
+<p id="classSummary">
+  Use the <code>chrome.permissions</code> module
+  to implement optional permissions.
+  As of Chrome 16, you can request optional permissions during your
+  extension's regular application flow rather than at install time,
+  so users understand why the permissions are needed
+  and use only those that are necessary.
+</p>
+
+<p>
+  For general information about permissions and details about each permission,
+  see the <a href="declare_permissions.html">declare permissions</a> documentation.
+</p>
+
+<h2 id="howto"> Implementing optional permissions </h2>
+
+<h3 id="types">
+  Step 1: Decide which permissions are optional and required
+</h3>
+<p>
+  Extensions should generally require permissions when they are needed for the
+  extension's basic functionality and employ optional permissions for optional
+  features.
+</p>
+
+<p>
+  Advantages of optional permissions:
+  <ul>
+    <li>
+      Users run with less permissions since they enable only what is needed.
+    </li>
+    <li>
+      The extension can help explain why it needs particular permissions by
+      requesting them when the user enables the relevant feature.
+    </li>
+    <li>
+      Chrome can avoid disabling extensions that upgrade if they add permissions
+      as optional rather than required.
+    </li>
+  </ul>
+</p>
+
+<p>
+  Advantages of required permissions:
+  <ul>
+    <li>
+      The extension can prompt the user once to accept all permissions.
+    </li>
+    <li>
+      They simplify extension development by guaranteeing which permissions are
+      present.
+    </li>
+  </ul>
+</p>
+
+
+<h3 id="manifest"> Step 2: Declare optional permissions in the manifest </h3>
+<p>
+  Declare optional permissions in your <a href="manifest.html">extension
+  manifest</a> with the <code>optional_permissions</code> key, using the
+  same format as the <a href="manifest.html#permissions">permissions</a>
+  field:
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"optional_permissions": [ "tabs", "http://www.google.com/" ],</b>
+  ...
+}</pre>
+</p>
+
+<p>
+You can specify any of the following as optional permissions:
+<ul>
+  <li><i>host permissions</i></li>
+  <li>appNotifications</li>
+  <li>background</li>
+  <li>bookmarks</li>
+  <li>clipboardRead</li>
+  <li>clipboardWrite</li>
+  <li>contentSettings</li>
+  <li>contextMenus</li>
+  <li>cookies</li>
+  <li>debugger</li>
+  <li>history</li>
+  <li>idle</li>
+  <li>management</li>
+  <li>notifications</li>
+  <li>pageCapture</li>
+  <li>tabs</li>
+  <li>topSites</li>
+  <li>webNavigation</li>
+  <li>webRequest</li>
+  <li>webRequestBlocking</li>
+</ul>
+</p>
+
+<h3 id="request"> Step 3: Request optional permissions </h3>
+<p>
+  Request the permissions from within a user gesture using
+  <code>permissions.request()</code>:
+<pre>
+document.querySelector('#my-button').addEventListener('click', function(event) {
+  // Permissions must be requested from inside a user gesture, like a button's
+  // click handler.
+  chrome.permissions.request({
+    permissions: ['tabs'],
+    origins: ['http://www.google.com/']
+  }, function(granted) {
+    // The callback argument will be true if the user granted the permissions.
+    if (granted) {
+      doSomething();
+    } else {
+      doSomethingElse();
+    }
+  });
+});
+</pre>
+</p>
+
+<p>
+  Chrome prompts the user if adding the permissions results in different
+  <a href="permission_warnings.html">warning messages</a> than the user has
+  already seen and accepted. For example, the previous code might result in
+  a prompt like this:
+</p>
+
+<p style="text-align: center">
+  <img src="{{static}}/images/perms-optional.png"
+       alt="example permission confirmation prompt"
+       width="416" height="234">
+</p>
+
+<h3 id="contains"> Step 4: Check the extension's current permissions </h3>
+<p>
+  To check whether your extension has a specific permission or set of
+  permissions, use <code>permission.contains()</code>:
+</p>
+
+<pre>
+chrome.permissions.contains({
+  permissions: ['tabs'],
+  origins: ['http://www.google.com/']
+}, function(result) {
+  if (result) {
+    // The extension has the permissions.
+  } else {
+    // The extension doesn't have the permissions.
+  }
+});
+</pre>
+
+<h3 id="remove"> Step 5: Remove the permissions </h3>
+<p>
+  You should remove permissions when you no longer need them.
+  After a permission has been removed, calling
+  <code>permissions.request()</code> usually adds the permission back without
+  prompting the user.
+</p>
+
+<pre>
+chrome.permissions.remove({
+  permissions: ['tabs'],
+  origins: ['http://www.google.com/']
+}, function(removed) {
+  if (removed) {
+    // The permissions have been removed.
+  } else {
+    // The permissions have not been removed (e.g., you tried to remove
+    // required permissions).
+  }
+});
+</pre>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/privacy.html b/chrome/common/extensions/docs/templates/intros/privacy.html
new file mode 100644
index 0000000..c7657ea
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/privacy.html
@@ -0,0 +1,209 @@
+<p id="classSummary">
+  Use the <code>chrome.privacy</code> module to control usage of the features in
+  Chrome that can affect a user's privacy. This module relies on the
+  <a href="types.html#ChromeSetting">ChromeSetting prototype of the type API</a>
+  for getting and setting Chrome's configuration.
+</p>
+
+<p class="note">
+  The <a href="http://www.google.com/intl/en/landing/chrome/google-chrome-privacy-whitepaper.pdf">Chrome Privacy Whitepaper</a>
+  gives background detail regarding the features which this API can control.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+<p>
+  You must declare the "privacy" permission in your extension's
+  <a href="manifest.html">manifest</a> to use the API. For example:
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "privacy"
+  ]</b>,
+  ...
+}</pre>
+
+<h2 id="usage">Usage</h2>
+
+<p>
+  Reading the current value of a Chrome setting is straightforward. You'll first
+  need to find the property you're interested in, then you'll call
+  <code>get()</code> on that object in order to retrieve its current value and
+  your extension's level of control. For example, to determine if Chrome's
+  Autofill feature is enabled, you'd write:
+</p>
+
+<pre>chrome.privacy.services.autofillEnabled.get({}, function(details) {
+  if (details.value)
+    console.log('Autofill is on!');
+  else
+    console.log('Autofill is off!');
+});</pre>
+
+<p>
+  Changing the value of a setting is a little bit more complex, simply because
+  you first must verify that your extension can control the setting. The user
+  won't see any change to her settings if your extension toggles a setting that
+  is either locked to a specific value by enterprise policies
+  (<code>levelOfControl</code> will be set to "not_controllable"), or if another
+  extension is controlling the value (<code>levelOfControl</code> will be set to
+  "controlled_by_other_extensions"). The <code>set()</code> call will succeed,
+  but the setting will be immediately overridden. As this might be confusing, it
+  is advisable to warn the user when the settings they've chosen aren't
+  practically applied.
+</p>
+
+<p class="note">
+  Full details about extensions' ability to control <code>ChromeSetting</code>s
+  can be found under
+  <a href="types.html#ChromeSetting">
+    <code>chrome.types.ChromeSetting</code></a>.
+</p>
+
+<p>
+  This means that you ought to use the <code>get()</code> method to determine
+  your level of access, and then only call <code>set()</code> if your extension
+  can grab control over the setting (in fact if your extension can't control the
+  setting it's probably a good idea to visually disable the functionality to
+  reduce user confusion):
+</p>
+
+<pre>chrome.privacy.services.autofillEnabled.get({}, function(details) {
+  if (details.levelOfControl === 'controllable_by_this_extension') {
+    chrome.privacy.services.autofillEnabled.set({ value: true }, function() {
+      if (chrome.extension.lastError === undefined)
+        console.log("Hooray, it worked!");
+      else
+        console.log("Sadness!", chrome.extension.lastError);
+    }
+  }
+});</pre>
+
+<p>
+  If you're interested in changes to a setting's value, add a listener to its
+  <code>onChange</code> event. Among other uses, this will allow you to warn the
+  user if a more recently installed extension grabs control of a setting, or if
+  enterprise policy overrides your control. To listen for changes to Autofill's
+  status, for example, the following code would suffice:
+</p>
+
+<pre>chrome.privacy.services.autofillEnabled.onChange.addListener(
+    function (details) {
+      // The new value is stored in `details.value`, the new level of control
+      // in `details.levelOfControl`, and `details.incognitoSpecific` will be
+      // `true` if the value is specific to Incognito mode.
+    });</pre>
+
+<h2 id="examples">Examples</h2>
+<p>
+  For example code, see the
+  <a href="samples.html#privacy">Privacy API samples</a>.
+</p>
+
+
+
+<p id="classSummary">
+  Use the <code>chrome.privacy</code> module to control usage of the features in
+  Chrome that can affect a user's privacy. This module relies on the
+  <a href="types.html#ChromeSetting">ChromeSetting prototype of the type API</a>
+  for getting and setting Chrome's configuration.
+</p>
+
+<p class="note">
+  The <a href="http://www.google.com/intl/en/landing/chrome/google-chrome-privacy-whitepaper.pdf">Chrome Privacy Whitepaper</a>
+  gives background detail regarding the features which this API can control.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+<p>
+  You must declare the "privacy" permission in your extension's
+  <a href="manifest.html">manifest</a> to use the API. For example:
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "privacy"
+  ]</b>,
+  ...
+}</pre>
+
+<h2 id="usage">Usage</h2>
+
+<p>
+  Reading the current value of a Chrome setting is straightforward. You'll first
+  need to find the property you're interested in, then you'll call
+  <code>get()</code> on that object in order to retrieve its current value and
+  your extension's level of control. For example, to determine if Chrome's
+  Autofill feature is enabled, you'd write:
+</p>
+
+<pre>chrome.privacy.services.autofillEnabled.get({}, function(details) {
+  if (details.value)
+    console.log('Autofill is on!');
+  else
+    console.log('Autofill is off!');
+});</pre>
+
+<p>
+  Changing the value of a setting is a little bit more complex, simply because
+  you first must verify that your extension can control the setting. The user
+  won't see any change to her settings if you extension toggles a setting that
+  is either locked to a specific value by enterprise policies
+  (<code>levelOfControl</code> will be set to "not_controllable"), or if another
+  extension is controlling the value (<code>levelOfControl</code> will be set to
+  "controlled_by_other_extensions"). The <code>set()</code> call will succeed,
+  but the setting will be immediately overridden. As this might be confusing, it
+  is advisable to warn the user when the settings they've chosen aren't
+  practically applied.
+</p>
+
+<p class="note">
+  Full details about extensions' ability to control <code>ChromeSetting</code>s
+  can be found under
+  <a href="types.html#ChromeSetting">
+    <code>chrome.types.ChromeSetting</code></a>.
+</p>
+
+<p>
+  This means that you ought to use the <code>get()</code> method to determine
+  your level of access, and then only call <code>set()</code> if your extension
+  can grab control over the setting (in fact if your extension can't control the
+  setting it's probably a good idea to visibly disable the functionality to
+  reduce user confusion):
+</p>
+
+<pre>chrome.privacy.services.autofillEnabled.get({}, function(details) {
+  if (details.levelOfControl === 'controllable_by_this_extension') {
+    chrome.privacy.services.autofillEnabled.set({ value: true }, function() {
+      if (chrome.extension.lastError === undefined)
+        console.log("Hooray, it worked!");
+      else
+        console.log("Sadness!", chrome.extension.lastError);
+    }
+  }
+});</pre>
+
+<p>
+  If you're interested in changes to a setting's value, add a listener to its
+  <code>onChange</code> event. Among other uses, this will allow you to warn the
+  user if a more recently installed extension grabs control of a setting, or if
+  enterprise policy overrides your control. To listen for changes to Autofill's
+  status, for example, the following code would suffice:
+</p>
+
+<pre>chrome.privacy.services.autofillEnabled.onChange.addListener(
+    function (details) {
+      // The new value is stored in `details.value`, the new level of control
+      // in `details.levelOfControl`, and `details.incognitoSpecific` will be
+      // `true` if the value is specific to Incognito mode.
+    });</pre>
+
+<h2 id="examples">Examples</h2>
+<p>
+  For example code, see the
+  <a href="samples.html#privacy">Privacy API samples</a>.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/proxy.html b/chrome/common/extensions/docs/templates/intros/proxy.html
new file mode 100644
index 0000000..9cfea92
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/proxy.html
@@ -0,0 +1,214 @@
+<p id="classSummary">
+Use the <code>chrome.proxy</code> module to manage Chrome's
+proxy settings. This module relies on the <a href="types.html#ChromeSetting">
+ChromeSetting prototype of the type API</a> for getting and setting the proxy
+configuration.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+<p>You must declare the "proxy" permission
+in the <a href="manifest.html">extension manifest</a>
+to use the proxy settings API.
+For example:</p>
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "proxy"
+  ]</b>,
+  ...
+}</pre>
+
+<h2 id="description">Objects and properties</h2>
+
+<p>
+Proxy settings are defined in a
+<a href="#type-ProxyConfig"><code>ProxyConfig</code></a> object. Depending on
+Chrome's proxy settings, the settings may contain
+<a href="#type-ProxyRules"><code>ProxyRules</code></a> or a <a
+  href="#type-PacScript"><code>PacScript</code></a>.
+</p>
+
+<h3 id="proxy_modes">Proxy modes</h3>
+
+<p>
+A ProxyConfig object's <code>mode</code> attribute determines the overall
+behavior of Chrome with regards to proxy usage. It can take the following
+values:
+<dl>
+  <dt><code>direct</code></dt>
+  <dd>In <code>direct</code> mode all connections are created directly, without
+  any proxy involved. This mode allows no further parameters in the
+  <code>ProxyConfig</code> object.</dd>
+
+  <dt><code>auto_detect</code></dt>
+  <dd>In <code>auto_detect</code> mode the proxy configuration is determined by
+  a PAC script that can be downloaded at
+  <a href="http://wpad/wpad.dat">http://wpad/wpad.dat</a>.
+  This mode allows no further parameters in the <code>ProxyConfig</code>
+  object.</dd>
+
+  <dt><code>pac_script</code></dt>
+  <dd>In <code>pac_script</code> mode the proxy configuration is determined by
+  a PAC script that is either retrieved from the URL specified in the
+  <a href="#type-PacScript"><code>PacScript</code></a> object or
+  taken literally from the <code>data</code> element specified in the
+  <a href="#type-PacScript"><code>PacScript</code></a> object.
+  Besides this, this mode allows no further parameters in the
+  <code>ProxyConfig</code> object.</dd>
+
+  <dt><code>fixed_servers</code></dt>
+  <dd>In <code>fixed_servers</code> mode the proxy configuration is codified in
+  a <a href="#type-ProxyRules><code>ProxyRules"><code>ProxyRules</code></a>
+  object. Its structure is described in <a href="#proxy_rules">Proxy rules</a>.
+  Besides this, the <code>fixed_servers</code> mode allows no further parameters
+  in the <code>ProxyConfig</code> object.</dd>
+
+  <dt><code>system</code></dt>
+  <dd>In <code>system</code> mode the proxy configuration is taken from the
+  operating system. This mode allows no further parameters in the
+  <code>ProxyConfig</code> object. Note that the <code>system</code> mode is
+  different from setting no proxy configuration. In the latter case, Chrome
+  falls back to the system settings only if no command-line options influence
+  the proxy configuration.</dd>
+</dl>
+</p>
+
+<h3 id="proxy_rules">Proxy rules</h3>
+
+<p>
+The <a href="#type-ProxyRules"><code>ProxyRules</code></a> object can contain
+either a <code>singleProxy</code> attribute or a subset of
+<code>proxyForHttp</code>, <code>proxyForHttps</code>, <code>proxyForFtp</code>,
+and <code>fallbackProxy</code>.
+</p>
+
+<p>
+In the first case, HTTP, HTTPS and FTP traffic is proxied through the specified
+proxy server. Other traffic is sent directly. In the latter case the behavior is
+slightly more subtle: If a proxy server is configured for the HTTP, HTTPS or FTP
+protocol, the respective traffic is proxied through the specified server. If no
+such proxy server is specified or traffic uses a different protocol than HTTP,
+HTTPS or FTP, the <code>fallbackProxy</code> is used. If no
+<code>fallbackProxy</code> is specified, traffic is sent directly without a
+proxy server.
+</p>
+
+<h3 id="proxy_server_objects">Proxy server objects</h3>
+
+<p>
+A proxy server is configured in a
+<a href="#type-ProxyServer"><code>ProxyServer</code></a> object. The connection
+to the proxy server (defined by the <code>host</code> attribute) uses the
+protocol defined in the <code>scheme</code> attribute. If no <code>scheme</code>
+is specified, the proxy connection defaults to <code>http</code>.
+</p>
+
+<p>
+If no <code>port</code> is defined in a
+<a href="#type-ProxyServer"><code>ProxyServer</code></a> object, the port is
+derived from the scheme. The default ports are:
+<table>
+  <tr><th>Scheme</th><th>Port</th></tr>
+  <tr><td>http</td><td>80</td></tr>
+  <tr><td>https</td><td>443</td></tr>
+  <tr><td>socks4</td><td>1080</td></tr>
+  <tr><td>socks5</td><td>1080</td></tr>
+</table>
+</p>
+
+<h3 id="bypass_list">Bypass list</h3>
+
+<p>
+Individual servers may be excluded from being proxied with the
+<code>bypassList</code>. This list may contain the following entries:
+<dl>
+  <dt><code>[<em>&lt;scheme&gt;</em>://]<em>&lt;host-pattern&gt;</em>[:<em>&lt;port&gt;</em>]</code></dt>
+  <dd>Match all hostnames that match the pattern <em>&lt;host-pattern&gt;</em>.<br>
+  Examples: <code>"foobar.com", "*foobar.com", "*.foobar.com", "*foobar.com:99",
+    "https://x.*.y.com:99"</code></dd>
+
+  <dt><code>[<em>&lt;scheme&gt;</em>://]<em>&lt;ip-literal&gt;</em>[:<em>&lt;port&gt;</em>]</code></dt>
+  <dd>Match URLs that are IP address literals.<br>
+  Conceptually this is the similar to the first case, but with special cases
+  to handle IP literal canonicalization. For example, matching
+  on "[0:0:0::1]" is the same as matching on "[::1]" because
+  the IPv6 canonicalization is done internally.<br>
+  Examples: <code>"127.0.1", "[0:0::1]", "[::1]", "http://[::1]:99"</code></dd>
+
+  <dt><code><em>&lt;ip-literal&gt;</em>/<em>&lt;prefix-length-in-bits&gt;</em></code></dt>
+  <dd>Match any URL containing an IP literal within the given range. The IP
+  range is specified using CIDR notation.<br>
+  Examples: <code>"192.168.1.1/16", "fefe:13::abc/33"</code></dd>
+
+  <dt><code>&lt;local&gt;</code></dt>
+  <dd>Match local addresses. An address is local if the host is "127.0.0.1",
+  "::1", or "localhost".<br>
+  Example: <code>"&lt;local&gt;"</code></dd>
+</dl>
+
+
+<h2 id="overview-examples">Examples</h2>
+
+<p>
+The following code sets a SOCKS 5 proxy for HTTP connections to all servers but
+foobar.com and uses direct connections for all other protocols. The settings
+apply to regular and incognito windows, as incognito windows inherit settings
+from regular windows. Please also consult the <a
+  href="types.html#ChromeSetting">Types API</a> documentation.
+</p>
+
+<pre>
+var config = {
+  mode: "fixed_servers",
+  rules: {
+    proxyForHttp: {
+      scheme: "socks5",
+      host: "1.2.3.4"
+    },
+    bypassList: ["foobar.com"]
+  }
+};
+chrome.proxy.settings.set(
+    {value: config, scope: 'regular'},
+    function() {});
+</pre>
+
+<p>
+The following code sets a custom PAC script.
+</p>
+
+<pre>
+var config = {
+  mode: "pac_script",
+  pacScript: {
+    data: "function FindProxyForURL(url, host) {\n" +
+          "  if (host == 'foobar.com')\n" +
+          "    return 'PROXY blackhole:80';\n" +
+          "  return 'DIRECT';\n" +
+          "}"
+  }
+};
+chrome.proxy.settings.set(
+    {value: config, scope: 'regular'},
+    function() {});
+</pre>
+
+<p>
+The next snippet queries the currently effective proxy settings. The effective
+proxy settings can be determined by another extension or by a policy. See the <a
+  href="types.html#ChromeSetting">Types API</a> documentation for details.
+</p>
+
+<pre>
+chrome.proxy.settings.get(
+    {'incognito': false},
+    function(config) {console.log(JSON.stringify(config));});
+</pre>
+
+<p>
+Note that the <code>value</code> object passed to <code>set()</code> is not
+identical to the <code>value</code> object passed to callback function of
+<code>get()</code>. The latter will contain a
+<code>rules.proxyForHttp.port</code> element.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/socket.html b/chrome/common/extensions/docs/templates/intros/socket.html
new file mode 100644
index 0000000..10b59a4
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/socket.html
@@ -0,0 +1,10 @@
+<p id="classSummary">
+Use the <code>chrome.socket</code> module
+to send and receive data over the network
+using TCP and UDP connections.
+</p>
+
+<p>
+Read the <a href="app_network.html">Network Communications</a> documentation
+to find out how to use this API.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/storage.html b/chrome/common/extensions/docs/templates/intros/storage.html
new file mode 100644
index 0000000..6a48a32
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/storage.html
@@ -0,0 +1,141 @@
+<p id="classSummary">
+Use the <code>chrome.storage</code> module
+to store, retrieve, and track changes to user data.
+This API has been optimized
+to meet the specific storage needs of extensions.
+It provides the same storage capabilities as the
+<a href="https://developer.mozilla.org/en/DOM/Storage#localStorage">localStorage API</a>
+with the following key differences:
+</p>
+
+<ul>
+  <li>User data can be automatically synced with Chrome sync
+  (using <code>storage.sync</code>).</li>
+  <li>Your extension's content scripts can directly access user data
+  without the need for a background page.</li>
+  <li>A user's extension settings can be persisted
+  even when using
+  <a href="manifest.html#incognito">split incognito behavior</a>.</li>
+  <li>It's asynchronous with bulk read and write operations, and therefore
+  faster than the blocking and serial <code>localStorage API</code>.
+  <li>User data can be stored as objects
+  (the <code>localStorage API</code> stores data in strings).</li>
+</ul>
+
+<h2 id="manifest">Manifest</h2>
+<p>You must declare the "storage" permission in the <a
+  href="manifest.html">extension manifest</a>
+  to use the storage API.
+  For example:</p>
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "storage"
+  ]</b>,
+  ...
+}</pre>
+
+<h2 id="using-sync">Usage</h2>
+
+<p>
+To store user data for your extension,
+you can use either
+<code>storage.sync</code> or
+<code>storage.local</code>.
+When using <code>storage.sync</code>,
+the stored data will automatically be synced
+to any Chrome browser that the user is logged into,
+provided the user has sync enabled.
+</p>
+
+<p>
+When Chrome is offline,
+Chrome stores the data locally.
+The next time the browser is online,
+Chrome syncs the data.
+Even if a user disables syncing,
+<code>storage.sync</code> will still work.
+In this case, it will behave identically
+to <code>storage.local</code>.
+</p>
+
+<p class="warning">
+Confidential user information should not be stored!
+The storage area isn't encrypted.
+</p>
+
+<h2 id="limits">Storage and throttling limits</h2>
+
+<p><code>chrome.storage</code> is not a big truck.
+It's a series of tubes.
+And if you don't understand,
+those tubes can be filled,
+and if they are filled
+when you put your message in,
+it gets in line,
+and it's going to be delayed
+by anyone that puts into that tube
+enormous amounts of material.
+
+<p>For details on the current limits
+of the storage API, and what happens
+when those limits are exceeded, please
+see the <a href="#apiReference">API reference</a>.
+
+
+<h2 id="examples">Examples</h2>
+
+<p>
+The following example checks for
+CSS code saved by a user on a form,
+and if found,
+stores it.
+</p>
+
+<pre>
+function saveChanges() {
+  // Get a value saved in a form.
+  var theValue = textarea.value;
+  // Check that there's some code there.
+  if (!theValue) {
+    message('Error: No value specified');
+    return;
+  }
+  // Save it using the Chrome extension storage API.
+  chrome.storage.sync.set({'value': theValue}, function() {
+    // Notify that we saved.
+    message('Settings saved');
+  });
+}
+</pre>
+
+<p>
+If you're interested in tracking changes made
+to a data object,
+you can add a listener
+to its <code>onChanged</code> event.
+Whenever anything changes in storage,
+that event fires.
+Here's sample code
+to listen for saved changes:
+</p>
+
+<pre>
+chrome.storage.onChanged.addListener(function(changes, namespace) {
+  for (key in changes) {
+    var storageChange = changes[key];
+    console.log('Storage key "%s" in namespace "%s" changed. ' +
+                'Old value was "%s", new value is "%s".',
+                key,
+                namespace,
+                storageChange.oldValue,
+                storageChange.newValue);
+  }
+});
+</pre>
+
+<p>
+You can find examples that use this API on the
+<a href="samples.html#sty">Samples page</a>.
+</p>
diff --git a/chrome/common/extensions/docs/templates/intros/tabs.html b/chrome/common/extensions/docs/templates/intros/tabs.html
new file mode 100644
index 0000000..8002b16
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/tabs.html
@@ -0,0 +1,45 @@
+<p id="classSummary">
+Use the <code>chrome.tabs</code> module
+to interact with the browser's tab system.
+You can use this module to
+create, modify, and rearrange tabs in the browser.
+</p>
+
+<img src="{{static}}/images/tabs.png"
+     width="323" height="50" alt="Two tabs in a window" />
+
+<h2 id="manifest">Manifest</h2>
+<p>
+Almost all <code>chrome.tabs</code> methods require you to
+declare the "tabs" permission
+in the <a href="manifest.html">extension manifest</a>.
+For example:
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "tabs"
+  ]</b>,
+  ...
+}</pre>
+
+<p>
+Three methods (<a href="#method-create"><code>create</code></a>,
+<a href="#method-update"><code>update</code></a> and
+<a href="#method-remove"><code>remove</code></a>) and one event
+(<a href="#event-onRemoved"><code>onRemoved</code></a>) don't require the "tabs"
+permission.
+</p>
+
+
+<h2 id="examples"> Examples </h2>
+
+<p>
+You can find simple examples of using the tabs module in the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/tabs/">examples/api/tabs</a>
+directory.
+For other examples and for help in viewing the source code, see
+<a href="samples.html">Samples</a>.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/topSites.html b/chrome/common/extensions/docs/templates/intros/topSites.html
new file mode 100644
index 0000000..f5eb1e5
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/topSites.html
@@ -0,0 +1,30 @@
+<h2 id="manifest">Notes</h2>
+
+<p>
+The top sites module allows access to the top sites that are displayed on
+the new tab page.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+
+<p>
+You must declare the "topSites" permission in your extension's manifest
+to use this API.
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+<b>  "permissions": [
+    "topSites",
+  ]</b>,
+  ...
+}</pre>
+
+
+<h2 id="examples">Examples</h2>
+
+<p>
+You can find samples of this API in
+<a href="samples.html#topsites">Samples</a>.
+</p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/tts.html b/chrome/common/extensions/docs/templates/intros/tts.html
new file mode 100644
index 0000000..93b88ac
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/tts.html
@@ -0,0 +1,186 @@
+<p id="classSummary">
+Use the <code>chrome.tts</code> module to play synthesized
+text-to-speech (TTS).
+See also the related
+<a href="ttsEngine.html">ttsEngine</a>
+module, which allows an extension to implement a speech engine.
+</p>
+
+
+<h2 id="overview">Overview</h2>
+
+<p>You must declare the "tts" permission
+in your extension's manifest to use this API.
+</p>
+
+<p>Chrome provides native support for speech on Windows (using SAPI
+5), Mac OS X, and Chrome OS, using speech synthesis capabilities
+provided by the operating system. On all platforms, the user can
+install extensions that register themselves as alternative speech
+engines.</p>
+
+<h2 id="generating_speech">Generating speech</h2>
+
+<p>Call <code>speak()</code> from your extension or
+packaged app to speak. For example:</p>
+
+<pre>chrome.tts.speak('Hello, world.');</pre>
+
+<p>To stop speaking immediately, just call <code>stop()</code>:
+
+<pre>chrome.tts.stop();</pre>
+
+<p>You can provide options that control various properties of the speech,
+such as its rate, pitch, and more. For example:</p>
+
+<pre>chrome.tts.speak('Hello, world.', {'rate': 2.0});</pre>
+
+<p>It's also a good idea to specify the language so that a synthesizer
+supporting that language (and regional dialect, if applicable) is chosen.</p>
+
+<pre>chrome.tts.speak(
+    'Hello, world.', {'lang': 'en-US', 'rate': 2.0});</pre>
+
+<p>By default, each call to <code>speak()</code> interrupts any
+ongoing speech and speaks immediately. To determine if a call would be
+interrupting anything, you can call <code>isSpeaking()</code>.  In
+addition, you can use the <code>enqueue</code> option to cause this
+utterance to be added to a queue of utterances that will be spoken
+when the current utterance has finished.</p>
+
+<pre>chrome.tts.speak(
+    'Speak this first.');
+chrome.tts.speak(
+    'Speak this next, when the first sentence is done.', {'enqueue': true});
+</pre>
+
+<p>A complete description of all options can be found in the
+<a href="#method-speak">speak() method documentation</a> below.
+Not all speech engines will support all options.</p>
+
+<p>To catch errors and make sure you're calling <code>speak()</code>
+correctly, pass a callback function that takes no arguments. Inside
+the callback, check
+<a href="extension.html#property-lastError">chrome.extension.lastError</a>
+to see if there were any errors.</p>
+
+<pre>chrome.tts.speak(
+    utterance,
+    options,
+    function() {
+      if (chrome.extension.lastError) {
+        console.log('Error: ' + chrome.extension.lastError.message);
+      }
+    });</pre>
+
+<p>The callback returns right away, before the engine has started
+generating speech. The purpose of the callback is to alert you to
+syntax errors in your use of the TTS API, not to catch all possible
+errors that might occur in the process of synthesizing and outputting
+speech. To catch these errors too, you need to use an event listener,
+described below.</p>
+
+<h2 id="events">Listening to events</h2>
+
+<p>To get more real-time information about the status of synthesized speech,
+pass an event listener in the options to <code>speak()</code>, like this:</p>
+
+<pre>chrome.tts.speak(
+    utterance,
+    {
+      onEvent: function(event) {
+        console.log('Event ' + event.type ' at position ' + event.charIndex);
+        if (event.type == 'error') {
+          console.log('Error: ' + event.errorMessage);
+        }
+      }
+    },
+    callback);</pre>
+
+<p>Each event includes an event type, the character index of the current
+speech relative to the utterance, and for error events, an optional
+error message. The event types are:</p>
+
+<ul>
+  <li><code>'start'</code>: The engine has started speaking the utterance.
+  <li><code>'word'</code>: A word boundary was reached. Use
+          <code>event.charIndex</code> to determine the current speech
+          position.
+  <li><code>'sentence'</code>: A sentence boundary was reached. Use
+          <code>event.charIndex</code> to determine the current speech
+          position.
+  <li><code>'marker'</code>: An SSML marker was reached. Use
+          <code>event.charIndex</code> to determine the current speech
+          position.
+  <li><code>'end'</code>: The engine has finished speaking the utterance.
+  <li><code>'interrupted'</code>: This utterance was interrupted by another
+          call to <code>speak()</code> or <code>stop()</code> and did not
+          finish.
+  <li><code>'cancelled'</code>: This utterance was queued, but then
+          cancelled by another call to <code>speak()</code> or
+          <code>stop()</code> and never began to speak at all.
+  <li><code>'error'</code>: An engine-specific error occurred and
+          this utterance cannot be spoken.
+          Check <code>event.errorMessage</code> for details.
+</ul>
+
+<p>Four of the event types&mdash;<code>'end'</code>, <code>'interrupted'</code>,
+<code>'cancelled'</code>, and <code>'error'</code>&mdash;are <i>final</i>.
+After one of those events is received, this utterance will no longer
+speak and no new events from this utterance will be received.</p>
+
+<p>Some voices may not support all event types, and some voices may not
+send any events at all. If you do not want to use a voice unless it sends
+certain events, pass the events you require in the
+<code>requiredEventTypes</code> member of the options object, or use
+<code>getVoices()</code> to choose a voice that meets your requirements.
+Both are documented below.</p>
+
+<h2 id="ssml">SSML markup</h2>
+
+<p>Utterances used in this API may include markup using the
+<a href="http://www.w3.org/TR/speech-synthesis">Speech Synthesis Markup
+Language (SSML)</a>. If you use SSML, the first argument to
+<code>speak()</code> should be a complete SSML document with an XML
+header and a top-level <code>&lt;speak&gt;</code> tag, not a document
+fragment.</p>
+
+<p>For example:</p>
+
+<pre>chrome.tts.speak(
+    '&lt;?xml version="1.0"?&gt;' +
+    '&lt;speak&gt;' +
+    '  The &lt;emphasis&gt;second&lt;/emphasis&gt; ' +
+    '  word of this sentence was emphasized.' +
+    '&lt;/speak&gt;');</pre>
+
+<p>Not all speech engines will support all SSML tags, and some may not support
+SSML at all, but all engines are required to ignore any SSML they don't
+support and to still speak the underlying text.</p>
+
+<h2 id="choosing_voice">Choosing a voice</h2>
+
+<p>By default, Chrome chooses the most appropriate voice for each
+utterance you want to speak, based on the language and gender. On most
+Windows, Mac OS X, and Chrome OS systems, speech synthesis provided by
+the operating system should be able to speak any text in at least one
+language. Some users may have a variety of voices available, though,
+from their operating system and from speech engines implemented by other
+Chrome extensions. In those cases, you can implement custom code to choose
+the appropriate voice, or to present the user with a list of choices.</p>
+
+<p>To get a list of all voices, call <code>getVoices()</code> and pass it
+a function that receives an array of <code>TtsVoice</code> objects as its
+argument:</p>
+
+<pre>chrome.tts.getVoices(
+    function(voices) {
+      for (var i = 0; i < voices.length; i++) {
+        console.log('Voice ' + i + ':');
+        console.log('  name: ' + voices[i].voiceName);
+        console.log('  lang: ' + voices[i].lang);
+        console.log('  gender: ' + voices[i].gender);
+        console.log('  extension id: ' + voices[i].extensionId);
+        console.log('  event types: ' + voices[i].eventTypes);
+      }
+    });</pre>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/ttsEngine.html b/chrome/common/extensions/docs/templates/intros/ttsEngine.html
new file mode 100644
index 0000000..38febe7
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/ttsEngine.html
@@ -0,0 +1,155 @@
+<p id="classSummary">
+Use the <code>chrome.ttsEngine</code> module to
+implement a text-to-speech (TTS) engine using an extension. If your
+extension registers using this API, it will receive events containing
+an utterance to be spoken and other parameters when any extension or packaged
+app uses the
+<a href="tts.html">tts</a>
+module to generate speech. Your extension can then use any available
+web technology to synthesize and output the speech, and send events back
+to the calling function to report the status.
+</p>
+
+
+<h2 id="overview">Overview</h2>
+
+<p>An extension can register itself as a speech engine. By doing so, it
+can intercept some or all calls to functions such as
+<a href="tts.html#method-speak"><code>speak()</code></a> and
+<a href="tts.html#method-stop"><code>stop()</code></a>
+and provide an alternate implementation.
+Extensions are free to use any available web technology
+to provide speech, including streaming audio from a server, HTML5 audio,
+Native Client, or Flash. An extension could even do something different
+with the utterances, like display closed captions in a pop-up window or
+send them as log messages to a remote server.</p>
+
+<h2 id="manifest">Manifest</h2>
+
+<p>To implement a TTS engine, an extension must
+declare the "ttsEngine" permission and then declare all voices
+it provides in the extension manifest, like this:</p>
+
+<pre>{
+  "name": "My TTS Engine",
+  "version": "1.0",
+  <b>"permissions": ["ttsEngine"],
+  "tts_engine": {
+    "voices": [
+      {
+        "voice_name": "Alice",
+        "lang": "en-US",
+        "gender": "female",
+        "event_types": ["start", "marker", "end"]
+      },
+      {
+        "voice_name": "Pat",
+        "lang": "en-US",
+        "event_types": ["end"]
+      }
+    ]
+  },</b>
+  "background": {
+    "page": "background.html",
+    "persistent": false
+  }
+}</pre>
+
+<p>An extension can specify any number of voices.</p>
+
+<p>The <code>voice_name</code> parameter is required. The name should be
+descriptive enough that it identifies the name of the voice and the
+engine used. In the unlikely event that two extensions register voices
+with the same name, a client can specify the ID of the extension that
+should do the synthesis.</p>
+
+<p>The <code>gender</code> parameter is optional. If your voice corresponds
+to a male or female voice, you can use this parameter to help clients
+choose the most appropriate voice for their application.</p>
+
+<p>The <code>lang</code> parameter is optional, but highly recommended.
+Almost always, a voice can synthesize speech in just a single language.
+When an engine supports more than one language, it can easily register a
+separate voice for each language. Under rare circumstances where a single
+voice can handle more than one language, it's easiest to just list two
+separate voices and handle them using the same logic internally. However,
+if you want to create a voice that will handle utterances in any language,
+leave out the <code>lang</code> parameter from your extension's manifest.</p>
+
+<p>Finally, the <code>event_types</code> parameter is required if the engine can
+send events to update the client on the progress of speech synthesis.
+At a minimum, supporting the <code>'end'</code> event type to indicate
+when speech is finished is highly recommended, otherwise Chrome cannot
+schedule queued utterances.</p>
+
+<p class="note">
+<strong>Note:</strong> If your TTS engine does not support
+the <code>'end'</code> event type, Chrome cannot queue utterances
+because it has no way of knowing when your utterance has finished. To
+help mitigate this, Chrome passes an additional boolean <code>enqueue</code>
+option to your engine's onSpeak handler, giving you the option of
+implementing your own queueing.  This is discouraged because then
+clients are unable to queue utterances that should get spoken by different
+speech engines.</p>
+
+<p>The possible event types that you can send correspond to the event types
+that the <code>speak()</code> method receives:</p>
+
+<ul>
+  <li><code>'start'</code>: The engine has started speaking the utterance.
+  <li><code>'word'</code>: A word boundary was reached. Use
+          <code>event.charIndex</code> to determine the current speech
+          position.
+  <li><code>'sentence'</code>: A sentence boundary was reached. Use
+          <code>event.charIndex</code> to determine the current speech
+          position.
+  <li><code>'marker'</code>: An SSML marker was reached. Use
+          <code>event.charIndex</code> to determine the current speech
+          position.
+  <li><code>'end'</code>: The engine has finished speaking the utterance.
+  <li><code>'error'</code>: An engine-specific error occurred and
+          this utterance cannot be spoken.
+          Pass more information in <code>event.errorMessage</code>.
+</ul>
+
+<p>The <code>'interrupted'</code> and <code>'cancelled'</code> events are
+not sent by the speech engine; they are generated automatically by Chrome.</p>
+
+<p>Text-to-speech clients can get the voice information from your
+extension's manifest by calling
+<a href="tts.html#method-getVoices">getVoices()</a>,
+assuming you've registered speech event listeners as described below.</p>
+
+<h2 id="handling_speech_events">Handling speech events</h2>
+
+<p>To generate speech at the request of clients, your extension must
+register listeners for both <code>onSpeak</code> and <code>onStop</code>,
+like this:</p>
+
+<pre>var speakListener = function(utterance, options, sendTtsEvent) {
+  sendTtsEvent({'event_type': 'start', 'charIndex': 0})
+
+  // (start speaking)
+
+  sendTtsEvent({'event_type': 'end', 'charIndex': utterance.length})
+};
+
+var stopListener = function() {
+  // (stop all speech)
+};
+
+chrome.ttsEngine.onSpeak.addListener(speakListener);
+chrome.ttsEngine.onStop.addListener(stopListener);</pre>
+
+<p class="warning">
+<b>Important:</b>
+If your extension does not register listeners for both
+<code>onSpeak</code> and <code>onStop</code>, it will not intercept any
+speech calls, regardless of what is in the manifest.</p>
+
+<p>The decision of whether or not to send a given speech request to an
+extension is based solely on whether the extension supports the given voice
+parameters in its manifest and has registered listeners
+for <code>onSpeak</code> and <code>onStop</code>. In other words,
+there's no way for an extension to receive a speech request and
+dynamically decide whether to handle it.</p>
diff --git a/chrome/common/extensions/docs/templates/intros/types.html b/chrome/common/extensions/docs/templates/intros/types.html
new file mode 100644
index 0000000..c2cf8b4
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/types.html
@@ -0,0 +1,99 @@
+<p id="classSummary">
+The <code>chrome.types</code> module contains type declarations for Chrome.
+Currently this comprises only a prototype for giving other
+modules access to manage Chrome browser settings. This prototype is used,
+for example, for <a
+  href="proxy.html#property-settings"><code>chrome.proxy.settings</code></a>.
+</p>
+
+<h2 id="ChromeSetting">Chrome settings</h2>
+
+<p>
+The <code>ChromeSetting</code> prototype provides a common set of functions
+(<code>get()</code>, <code>set()</code>, and <code>clear()</code>) as
+well as an event publisher (<code>onChange</code>) for settings of the
+Chrome browser. The <a href="proxy.html#overview-examples">proxy settings
+ examples</a> demonstrate how these functions are intended to be used.
+</p>
+
+<h3 id="ChromeSetting-lifecycle">Scope and life cycle</h3>
+
+<p>
+Chrome distinguishes between three different scopes of browser settings:
+<dl>
+  <dt><code>regular</code></dt>
+  <dd>Settings set in the <code>regular</code> scope apply to regular
+  browser windows and are inherited by incognito windows if they are not
+  overwritten. These settings are stored to disk and remain in place until
+  they are cleared by the governing extension, or the governing extension is
+  disabled or uninstalled.</dd>
+
+  <dt><code>incognito_persistent</code></dt>
+  <dd>Settings set in the <code>incognito_persistent</code> scope apply only
+  to incognito windows. For these, they override <code>regular</code>
+  settings. These settings are stored to disk and remain in place until
+  they are cleared by the governing extension, or the governing extension is
+  disabled or uninstalled.</dd>
+
+  <dt><code>incognito_session_only</code></dt>
+  <dd>Settings set in the <code>incognito_session_only</code> scope apply only
+  to incognito windows. For these, they override <code>regular</code> and
+  <code>incognito_session_only</code> settings. These settings are not
+  stored to disk and are cleared when the last incognito window is closed. They
+  can only be set when at least one incognito window is open.</dd>
+
+</dl>
+</p>
+
+<h3 id="ChromeSetting-precedence">Precedence</h3>
+
+<p>
+Chrome manages settings on different layers. The following list describes the
+layers that may influence the effective settings, in increasing order of
+precedence.
+<ol>
+  <li>System settings provided by the operating system</li>
+  <li>Command-line parameters</li>
+  <li>Settings provided by extensions</li>
+  <li>Policies</li>
+</ol>
+</p>
+
+<p>
+As the list implies, policies might overrule any changes that you specify with
+your extension. You can use the <code>get()</code> function to determine whether
+your extension is capable of providing a setting or whether this setting would
+be overridden.
+</p>
+
+<p>
+As discussed above, Chrome allows using different settings for regular
+windows and incognito windows. The following example illustrates the behavior.
+Assume that no policy overrides the settings and that an extension can set
+settings for regular windows <b>(R)</b> and settings for incognito windows
+<b>(I)</b>.
+</p>
+
+<p>
+<ul>
+  <li>If only <b>(R)</b> is set, these settings are effective for both
+  regular and incognito windows.</li>
+  <li>If only <b>(I)</b> is set, these settings are effective for only
+  incognito windows. Regular windows use the settings determined by the lower
+  layers (command-line options and system settings).</li>
+  <li>If both <b>(R)</b> and <b>(I)</b> are set, the respective settings are
+  used for regular and incognito windows.</li>
+</ul>
+</p>
+
+<p>
+If two or more extensions want to set the same setting to different values,
+the extension installed most recently takes precedence over the other
+extensions.  If the most recently installed extension sets only <b>(I)</b>, the
+settings of regular windows can be defined by previously installed extensions.
+</p>
+
+<p>
+The <em>effective</em> value of a setting is the one that results from
+considering the precedence rules. It is used by Chrome.
+<p>
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/intros/webNavigation.html b/chrome/common/extensions/docs/templates/intros/webNavigation.html
new file mode 100644
index 0000000..b89f50d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/webNavigation.html
@@ -0,0 +1,169 @@
+<p id="classSummary">
+Use the <code>chrome.webNavigation</code> module to receive
+notifications about the status of navigations requests in-flight.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+<p>
+All <code>chrome.webNavigation</code> methods and events require you to declare
+the "webNavigation" permission in the <a href="manifest.html">extension
+manifest</a>.
+For example:
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "webNavigation"
+  ]</b>,
+  ...
+}</pre>
+
+
+<h2 id="examples">Examples</h2>
+
+<p>
+You can find simple examples of using the tabs module in the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/webNavigation/">examples/api/webNavigation</a>
+directory.
+For other examples and for help in viewing the source code, see
+<a href="samples.html">Samples</a>.
+</p>
+
+<h2 id="event_order">Event order</h2>
+<p>
+For a navigation that is successfully completed, events are fired in the
+following order:
+<pre>
+onBeforeNavigate -&gt; onCommitted -&gt; onDOMContentLoaded -&gt; onCompleted
+</pre>
+</p>
+<p>
+Any error that occurs during the process results in an
+<code>onErrorOccurred</code> event. For a specific navigation, there are no
+further events fired after <code>onErrorOccurred</code>.
+</p>
+<p>
+If a navigating frame contains subframes, its <code>onCommitted</code> is fired
+before any of its children's <code>onBeforeNavigate</code>; while
+<code>onCompleted</code> is fired after all of its children's
+<code>onCompleted</code>.
+</p>
+<p>
+If the reference fragment of a frame is changed, a
+<code>onReferenceFragmentUpdated</code> event is fired. This event can fire any
+time after <code>onDOMContentLoaded</code>, even after
+<code>onCompleted</code>.
+</p>
+<p>
+If the history API is used to modify the state of a frame (e.g. using
+<code>history.pushState()</code>, a <code>onHistoryStateUpdated</code> event is
+fired. This event can fire any time after <code>onDOMContentLoaded</code>.
+</p>
+<p>
+If a navigation was triggered via <a
+href="https://support.google.com/chrome/bin/answer.py?answer=177873">Chrome
+Instant</a> or <a
+href="https://support.google.com/chrome/bin/answer.py?answer=1385029">Instant
+Pages</a>, a completely loaded page is swapped into the current tab. In that
+case, an <code>onTabReplaced</code> event is fired.
+</p>
+
+<h2 id="relation_to_webRequest">Relation to webRequest events</h2>
+<p>
+There is no defined ordering between events of the <a
+href="webRequest.html">webRequest API</a> and the events of the
+webNavigation API. It is possible that webRequest events are still received for
+frames that already started a new navigation, or that a navigation only
+proceeds after the network resources are already fully loaded.
+</p>
+<p>
+In general, the webNavigation events are closely related to the navigation
+state that is displayed in the UI, while the webRequest events correspond to
+the state of the network stack which is generally opaque to the user.
+</p>
+
+<h2 id="tab_ids">A note about tab IDs</h2>
+<p>
+Not all navigating tabs correspond to actual tabs in Chrome's UI, e.g., a tab
+that is being pre-rendered. Such tabs are not accessible via the
+<a href="tabs.html">tabs API</a> nor can you request information about them via
+<code>webNavigation.getFrame</code> or <code>webNavigation.getAllFrames</code>.
+Once such a tab is swapped in, an <code>onTabReplaced</code> event is fired and
+they become accessible via these APIs.
+</p>
+
+<h2 id="timestamps">A note about timestamps</h2>
+<p>
+It's important to note that some technical oddities in the OS's handling
+of distinct Chrome processes can cause the clock to be skewed between the
+browser itself and extension processes. That means that WebNavigation's events'
+<code>timeStamp</code> property is only guaranteed to be <i>internally</i>
+consistent. Comparing one event to another event will give you the correct
+offset between them, but comparing them to the current time inside the
+extension (via <code>(new Date()).getTime()</code>, for instance) might give
+unexpected results.
+</p>
+
+<h2 id="frame_ids">A note about frame and process IDs</h2>
+<p>
+Due to the multi-process nature of Chrome, a tab might use different processes
+to render the source and destination of a web page. Therefore, if a navigation
+takes place in a new process, you might receive events both from the new and
+the old page until the new navigation is committed (i.e. the
+<code>onCommitted</code> event is send for the new main frame). Because frame
+IDs are only unique for a given process, the webNavigation events include a
+process ID, so you can still determine which frame a navigation came from.
+</p>
+<p>
+Also note that during a provisional load the process might be switched several
+times. This happens when the load is redirected to a different site. In this
+case, you will receive repeated <code>onBeforeNavigate</code> and
+<code>onErrorOccurred</code> events, until you receive the final
+<code>onCommitted</code> event.
+</p>
+
+<h2 id="transition_types">Transition types and qualifiers</h2>
+<p>
+The webNavigation API's <code>onCommitted</code> event has a
+<code>transitionType</code> and a <code>transitionQualifiers</code> property.
+The <em>transition type</em> is the same as used in the <a
+href="history.html#transition_types">history API</a> describing how the browser
+navigated to this particular URL. In addition, several <em>transition
+qualifiers</em> can be returned that further define the navigation.
+</p>
+<p>
+The following transition qualifiers exist:
+</p>
+<table>
+<tr>
+  <th> Transition qualifier </th> <th> Description </th>
+</tr>
+<tr>
+  <td>"client_redirect"</td>
+  <td>
+    One or more redirects caused by JavaScript or meta refresh tags on the page
+    happened during the navigation.
+  </td>
+</tr>
+<tr>
+  <td>"server_redirect"</td>
+  <td>
+    One or more redirects caused by HTTP headers sent from the server happened
+    during the navigation.
+  </td>
+</tr>
+<tr>
+  <td>"forward_back"</td>
+  <td>
+    The user used the Forward or Back button to initiate the navigation.
+  </td>
+</tr>
+<tr>
+  <td>"from_address_bar"</td>
+  <td>
+    The user initiated the navigation from the address bar (aka Omnibox).
+  </td>
+</tr>
+</table>
diff --git a/chrome/common/extensions/docs/templates/intros/webRequest.html b/chrome/common/extensions/docs/templates/intros/webRequest.html
new file mode 100644
index 0000000..9bbcb7f
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/webRequest.html
@@ -0,0 +1,299 @@
+<p id="classSummary">
+Use the <code>chrome.webRequest</code> module to intercept, block,
+or modify requests in-flight and to observe and analyze traffic.
+</p>
+
+<h2 id="manifest">Manifest</h2>
+<p>You must declare the "webRequest" permission in the <a
+  href="manifest.html">extension manifest</a> to use the web request
+API, along with <a href="manifest.html#permissions">host permissions</a>
+for any hosts whose network requests you want to access. If you want to
+use the web request API in a blocking fashion, you need to request
+the "webRequestBlocking" permission in addition.
+For example:</p>
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": [
+    "webRequest",
+    "*://*.google.com"
+  ]</b>,
+  ...
+}</pre>
+
+<h2 id="life_cycle">Life cycle of requests</h2>
+
+<p>
+The web request API defines a set of events that follow the life cycle of a web
+request. You can use these events to observe and analyze traffic. Certain
+synchronous events will allow you to intercept, block, or modify a request.
+</p>
+
+<p>
+The event life cycle for successful requests is illustrated here, followed by
+event definitions:<br/>
+<img src="{{static}}/images/webrequestapi.png"
+  width="385" height="503"
+  alt="Life cycle of a web request from the perspective of the webrequest API"
+  style="margin-left: auto; margin-right: auto; display: block"/>
+</p>
+
+<p>
+<dl>
+  <dt><code>onBeforeRequest</code> (optionally synchronous)</dt>
+  <dd>Fires when a request is about to occur. This event is sent before any TCP
+  connection is made and can be used to cancel or redirect requests.</dd>
+  <dt><code>onBeforeSendHeaders</code> (optionally synchronous)</dt>
+  <dd>Fires when a request is about to occur and the initial headers have been
+  prepared. The event is intended to allow extensions to add, modify, and delete
+  request headers <a href="#life_cycle_footnote">(*)</a>. The
+  <code>onBeforeSendHeaders</code> event is passed to all subscribers, so
+  different subscribers may attempt to modify the request; see the <a
+    href="#implementation">Implementation details</a> section for how this is
+  handled. This event can be used to cancel the request.</dd>
+  <dt><code>onSendHeaders</code></dt>
+  <dd>Fires after all extensions have had a chance to modify the request
+  headers, and presents the final <a href="#life_cycle_footnote">(*)</a>
+  version. The event is triggered before the headers are sent to the network.
+  This event is informational and handled asynchronously. It does not allow
+  modifying or cancelling the request.</dd>
+  <dt><code>onHeadersReceived</code> (optionally synchronous)</dt>
+  <dd>Fires each time that an HTTP(S) response header is received. Due
+  to redirects and authentication requests this can happen multiple times per
+  request. This event is intended to allow extensions to add, modify, and delete
+  response headers, such as incoming Set-Cookie headers.</dd>
+  <dt><code>onAuthRequired</code> (optionally synchronous)</dt>
+  <dd>Fires when a request requires authentication of the user. This event can
+  be handled synchronously to provide authentication credentials. Note that
+  extensions may provide invalid credentials. Take care not to enter an infinite
+  loop by repeatedly providing invalid credentials.</dd>
+  <dt><code>onBeforeRedirect</code></dt>
+  <dd>Fires when a redirect is about to be executed. A redirection can be
+  triggered by an HTTP response code or by an extension. This event is
+  informational and handled asynchronously. It does not allow you to modify or
+  cancel the request. </dd>
+  <dt><code>onResponseStarted</code></dt>
+  <dd>Fires when the first byte of the response body is received. For HTTP
+  requests, this means that the status line and response headers are
+  available. This event is informational and handled asynchronously. It does not
+  allow modifying or cancelling the request.</dd>
+  <dt><code>onCompleted</code></dt>
+  <dd>Fires when a request has been processed successfully.</dd>
+  <dt><code>onErrorOccurred</code></dt>
+  <dd>Fires when a request could not be processed successfully.</dd>
+</dl>
+The web request API guarantees that for each request either
+<code>onCompleted</code> or <code>onErrorOccurred</code> is fired as the final
+event with one exception: If a request is redirected to a <code>data://</code>
+URL, <code>onBeforeRedirect</code> is the last reported event.
+</p>
+
+<p id="life_cycle_footnote">(*) Note that the web request API presents an
+abstraction of the network stack to the extension. Internally, one URL request
+can be split into several HTTP requests (for example to fetch individual byte
+ranges from a large file) or can be handled by the network stack without
+communicating with the network. For this reason, the API does not provide the
+final HTTP headers that are sent to the network. For example, all headers that
+are related to caching are invisible to the extension.</p>
+
+<p>The following headers are currently <b>not provided</b> to the
+<code>onBeforeSendHeaders</code> event. This list is not guaranteed to be
+complete nor stable.
+<ul>
+  <li>Authorization</li>
+  <li>Cache-Control</li>
+  <li>Connection</li>
+  <li>Content-Length</li>
+  <li>Host</li>
+  <li>If-Modified-Since</li>
+  <li>If-None-Match</li>
+  <li>If-Range</li>
+  <li>Partial-Data</li>
+  <li>Pragma</li>
+  <li>Proxy-Authorization</li>
+  <li>Proxy-Connection</li>
+  <li>Transfer-Encoding</li>
+</ul>
+</p>
+
+<p>
+The webRequest API only exposes requests that the extension has
+permission to see, given its
+<a href="manifest.html#permissions">host permissions</a>.
+Moreover, only the following schemes are accessible:
+<code>http://</code>,
+<code>https://</code>,
+<code>ftp://</code>,
+<code>file://</code>, or
+<code>chrome-extension://</code>.
+In addition, even certain requests with URLs using one of the above schemes
+are hidden, e.g.,
+<code>chrome-extension://other_extension_id</code> where
+<code>other_extension_id</code> is not the ID of the extension to handle
+the request,
+<code>https://www.google.com/chrome</code>,
+and others (this list is not complete). Also synchronous XMLHttpRequests from
+your extension are hidden from blocking event handlers in order to prevent
+deadlocks.
+</p>
+
+<h2 id="concepts">Concepts</h2>
+
+<p>As the following sections explain, events in the web request API use request
+IDs, and you can optionally specify filters and extra information when you
+register event listeners.</p>
+
+<h3 id="Request IDs">Request IDs</h3>
+
+<p>Each request is identified by a request ID. This ID is unique within a
+browser session and the context of an extension. It remains constant during the
+the life cycle of a request and can be used to match events for the same
+request. Note that several HTTP requests are mapped to one web request in case
+of HTTP redirection or HTTP authentication.</p>
+
+<h3 id="subscription">Registering event listeners</h3>
+
+<p>To register an event listener for a web request, you use a variation on the
+<a href="events.html">usual <code>addListener()</code> function</a>.
+In addition to specifying a callback function,
+you have to specify a filter argument and you may specify an optional extra info
+argument.</p>
+
+<p>The three arguments to the web request API's <code>addListener()</code> have
+the following definitions:</p>
+<pre>
+var callback = function(details) {...};
+var filter = {...};
+var opt_extraInfoSpec = [...];
+</pre>
+
+<p>Here's an example of listening for the <code>onBeforeRequest</code>
+event:</p>
+<pre>
+chrome.webRequest.onBeforeRequest.addListener(
+  callback, filter, opt_extraInfoSpec);
+</pre>
+
+<p>Each <code>addListener()</code> call takes a mandatory callback function as
+the first parameter. This callback function is passed a dictionary containing
+information about the current URL request. The information in this dictionary
+depends on the specific event type as well as the content of
+<code>opt_extraInfoSpec</code>.</p>
+
+<p>If the optional <code>opt_extraInfoSpec</code> array contains the string
+<code>'blocking'</code> (only allowed for specific events), the callback
+function is handled synchronously. That means that the request is blocked until
+the callback function returns. In this case, the callback can return a <a
+  href="#type-BlockingResponse">BlockingResponse</a> that determines the further
+life cycle of the request. Depending on the context, this response allows
+cancelling or redirecting a request (<code>onBeforeRequest</code>), cancelling a
+request or modifying headers (<code>onBeforeSendHeaders</code>,
+<code>onHeadersReceived</code>), or providing authentication credentials
+(<code>onAuthRequired</code>).</p>
+
+<p>The <a href="#type-RequestFilter">RequestFilter</a>
+<code>filter</code> allows limiting the requests for which events are
+triggered in various dimensions:
+<dl>
+  <dt>URLs</dt>
+  <dd><a href="match_patterns.html">URL patterns</a> such as
+  <code>*://www.google.com/foo*bar</code>.</dd>
+  <dt>Types</dt>
+  <dd>Request types such as <code>main_frame</code> (a document that is loaded
+  for a top-level frame), <code>sub_frame</code> (a document that is loaded for
+  an embedded frame), and <code>image</code> (an image on a web site).
+  See <a href="#type-RequestFilter">RequestFilter</a>.</dd>
+  <dt>Tab ID</dt>
+  <dd>The identifier for one tab.</dd>
+  <dt>Window ID</dt>
+  <dd>The identifier for a window.</dd>
+</p>
+
+<p>Depending on the event type, you can specify strings in
+<code>opt_extraInfoSpec</code> to ask for additional information about the
+request. This is used to provide detailed information on request's data only
+if explicitly requested.</p>
+
+<h2 id="implementation">Implementation details</h2>
+
+<p>Several implementation details can be important to understand when developing
+an extension that uses the web request API:</p>
+
+<h3 id="conflict_resolution">Conflict resolution</h3>
+<p>In the current implementation of the web request API, a request is considered
+as cancelled if at least one extension instructs to cancel the request. If
+an extension cancels a request, all extensions are notified by an
+<code>onErrorOccurred</code> event. Only one extension is allowed to redirect a
+request or modify a header at a time. If more than one extension attempts to
+modify the request, the most recently installed extension wins and all others
+are ignored. An extension is not notified if its instruction to modify or
+redirect has been ignored.</p>
+
+<h3 id="caching">Caching</h3>
+<p>
+Chrome employs two caches &mdash; an on-disk cache and a very fast in-memory
+cache.  The lifetime of an in-memory cache is attached to the lifetime of a
+render process, which roughly corresponds to a tab. Requests that are answered
+from the in-memory cache are invisible to the web request API. If a request
+handler changes its behavior (for example, the behavior according to which
+requests are blocked), a simple page refresh might not respect this changed
+behavior.  To make sure the behavior change goes through, call
+<code>handlerBehaviorChanged()</code> to flush the in-memory cache. But don't do
+it often; flushing the cache is a very expensive operation. You don't need to
+call <code>handlerBehaviorChanged()</code> after registering or unregistering an
+event listener.</p>
+
+<h3 id="timestamps">Timestamps</h3>
+<p>
+The <code>timestamp</code> property of web request events is only guaranteed to
+be <i>internally</i> consistent. Comparing one event to another event will give
+you the correct offset between them, but comparing them to the current time
+inside the extension (via <code>(new Date()).getTime()</code>, for instance)
+might give unexpected results.
+</p>
+
+<h2 id="examples">Examples</h2>
+
+<p>The following example illustrates how to block all requests to
+<code>www.evil.com</code>:</p>
+<pre>
+chrome.webRequest.onBeforeRequest.addListener(
+  function(details) {
+    return {cancel: details.url.indexOf("://www.evil.com/") != -1};
+  },
+  {urls: ["&lt;all_urls&gt;"]},
+  ["blocking"]);
+</pre>
+
+<p>As this function uses a blocking event handler, it requires the "webRequest"
+as well as the "webRequestBlocking" permission in the manifest file.</p>
+
+<p>The following example achieves the same goal in a more efficient way because
+requests that are not targeted to <code>www.evil.com</code> do not need to be
+passed to the extension:</p>
+<pre>
+chrome.webRequest.onBeforeRequest.addListener(
+  function(details) { return {cancel: true}; },
+  {urls: ["*://www.evil.com/*"]},
+  ["blocking"]);
+</pre>
+
+<p>The following example illustrates how to delete the User-Agent header from
+all requests:</p>
+<pre>
+chrome.webRequest.onBeforeSendHeaders.addListener(
+  function(details) {
+    for (var i = 0; i < details.requestHeaders.length; ++i) {
+      if (details.requestHeaders[i].name === 'User-Agent') {
+        details.requestHeaders.splice(i, 1);
+        break;
+      }
+    }
+    return {requestHeaders: details.requestHeaders};
+  },
+  {urls: ["&lt;all_urls&gt;"]},
+  ["blocking", "requestHeaders"]);
+</pre>
+
+<p> For more example code, see the <a href="samples.html#webrequest">web request
+samples</a>.</p>
diff --git a/chrome/common/extensions/docs/templates/intros/windows.html b/chrome/common/extensions/docs/templates/intros/windows.html
new file mode 100644
index 0000000..2b38b65
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/intros/windows.html
@@ -0,0 +1,69 @@
+<p id="classSummary">
+Use the <code>chrome.windows</code> module
+to interact with browser windows.
+You can use this module to
+create, modify, and rearrange windows in the browser.
+</p>
+
+<img src="{{static}}/images/windows.png"
+     width="256" height="76" alt="Two windows, each with one tab" />
+
+<h2 id="manifest">Manifest</h2>
+<p>
+To use the windows API,
+you must declare the "tabs" permission
+in <a href="manifest.html">manifest.json</a>.
+(No, that isn't a typo &mdash;
+the window and tabs modules interact so closely we
+decided to just share one permission between them.)
+For example:
+</p>
+
+<pre>{
+  "name": "My extension",
+  ...
+  <b>"permissions": ["tabs"]</b>,
+  ...
+}</pre>
+
+<h2 id="current-window">The current window</h2>
+
+<p>Many functions in the extension system
+take an optional <var>windowId</var> parameter,
+which defaults to the current window.
+</p>
+
+<p>The <em>current window</em> is the window that
+contains the code that is currently executing.
+It's important to realize that this can be
+different from the topmost or focused window.
+</p>
+
+<p>For example, say an extension
+creates a few tabs or windows from a single HTML file,
+and that the HTML file
+contains a call to
+<a href="tabs.html#method-getSelected">chrome.tabs.getSelected</a>.
+The current window is the window that contains the page that made
+the call, no matter what the topmost window is.
+</p>
+
+<p>In the case of the <a href="event_pages.html">event page</a>,
+the value of the current window falls back to the last active window. Under some
+circumstances, there may be no current window for background pages.
+</p>
+
+<h2 id="examples"> Examples </h2>
+
+<p>
+You can find simple examples of using the windows module in the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/windows/">examples/api/windows</a>
+directory.
+Another example is in the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/tabs/inspector/tabs_api.html?content-type=text/plain">tabs_api.html</a> file
+of the
+<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/tabs/inspector/">inspector</a>
+example.
+For other examples and for help in viewing the source code, see
+<a href="samples.html">Samples</a>.
+</p>
diff --git a/chrome/common/extensions/docs/templates/private/api_property.html b/chrome/common/extensions/docs/templates/private/api_property.html
new file mode 100644
index 0000000..5f35ea8
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/api_property.html
@@ -0,0 +1,28 @@
+<div>
+  <h3 id="{{id}}">{{name}}</h3>
+  <div class="summary">
+    <span>chrome.{{api.name}}.{{name}}</span>
+  </div>
+  <div>
+    <dt><span class="property">{{name}}</span> {{+partials.type_name}}</dt>
+    {{?description}}<dd>
+      {{{description}}}
+    </dd>{{/description}}
+    {{?properties}}
+    <h4 id="{{property.name}}-properties">
+      Properties of <a href="#property-{{property.name}}">{{property.name}}</a>
+    </h4>
+    <dd><dl>{{#properties}}
+      {{+partials.parameter_full}}
+    {{/}}</dl></dd>
+    {{/properties}}
+    {{?functions}}
+    <h4 id="{{property.name}}-functions">
+      Methods of <a href="#property-{{property.name}}">{{property.name}}</a>
+    </h4>
+    <dd><dl>{{#functions}}
+      {{+partials.function}}
+    {{/}}</dl></dd>
+    {{/functions}}
+  </div>
+</div>
diff --git a/chrome/common/extensions/docs/templates/private/api_reference.html b/chrome/common/extensions/docs/templates/private/api_reference.html
new file mode 100644
index 0000000..ff3ca63
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/api_reference.html
@@ -0,0 +1,36 @@
+<h2 id="apiReference">API Reference: chrome.{{api.name}}</h2>
+<div class="api_reference">
+  {{?api.types}}
+    <h3 id="types">Types</h3>
+    {{#api.types}}
+      {{+partials.type api:api type:@}}
+    {{/}}
+  {{/api.types}}
+  {{?api.properties}}
+    <h3 id="properties">Properties</h3>
+    {{#api.properties}}
+      {{+partials.api_property api:api property:@}}
+    {{/}}
+  {{/api.properties}}
+  {{?api.functions}}
+    <h3 id="methods">Methods</h3>
+    {{#api.functions}}
+      {{+partials.function api:api}}
+    {{/}}
+  {{/api.functions}}
+  {{?api.events}}
+    <h3 id="events">Events</h3>
+    {{#api.events}}
+      {{+partials.event api:api}}
+    {{/}}
+  {{/api.events}}
+</div>
+{{?samples_list}}
+  <h2 id="samples">Sample {{title}} that use chrome.{{api.name}}</h2>
+  {{#samples_list}}
+    <li><strong><a href="samples.html#{{id}}">{{name}}</a></strong> &ndash;
+    {{?description}}
+    {{description}}
+    {{/description}}</li>
+  {{/samples_list}}
+{{/samples_list}}
diff --git a/chrome/common/extensions/docs/templates/private/apps_footer.html b/chrome/common/extensions/docs/templates/private/apps_footer.html
new file mode 100644
index 0000000..f997b85
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/apps_footer.html
@@ -0,0 +1,8 @@
+<script>
+  window.bootstrap = {
+    api_names: {{*api_list.apps.chrome}}.concat(
+        {{*api_list.apps.experimental}}),
+    branchInfo: {{*branchInfo}}
+  };
+</script>
+{{+partials.footer}}
diff --git a/chrome/common/extensions/docs/templates/private/apps_sidenav.html b/chrome/common/extensions/docs/templates/private/apps_sidenav.html
new file mode 100644
index 0000000..0f68663
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/apps_sidenav.html
@@ -0,0 +1,58 @@
+<div class="g-unit g-first" id="gc-sidebar">
+  <ul>
+    <li><span>Getting Started</span>
+      <ul>
+        <li><a href="about_apps.html">What Are Packaged Apps?</a></li>
+        <li><a href="app_architecture.html">Understand the Architecture</a></li>
+        <li><a href="first_app.html">Create Your First App</a></li>
+      </ul>
+    </li>
+    <li><span>Developing</span>
+      <ul>
+          <li><a href="develop_apps.html">Before You Start</a></li>
+          <li><span>The Fundamentals</span>
+            <ul toggleable>
+              <li><a href="app_lifecycle.html">Manage App Lifecycle</a></li>
+              <li><a href="app_storage.html">Manage Data</a></li>
+              <li><a href="offline_apps.html">Offline First</a></li>
+              <li><a href="app_external.html">Embed Content</a></li>
+            </ul>
+          </li>
+          <li><span>Security &amp; Privacy</span>
+            <ul toggleable>
+              <li><a href="app_identity.html">Identify User</a></li>
+              <li><a href="app_csp.html">Comply with CSP</a></li>
+            </ul>
+          </li>
+          <li><span>Advanced Technologies</span>
+            <ul toggleable>
+              <li><a href="app_network.html">Network Communications</a></li>
+              <li><a href="app_hardware.html">Access Hardware Devices</a></li>
+              <li><a href="app_intents.html">Connect Apps with Web Intents</a></li>
+            </ul>
+          </li>
+          <li><span><a href="app_frameworks.html">MVC Architecture</a></span>
+            <ul>
+              <li><a href="angular_framework.html">Build Apps with AngularJS</a></li>
+              <li><a href="sencha_framework.html">Build Apps with Sencha Ext JS</a></li>
+            </ul>
+          </li>
+        </ul>
+      </li>
+      <li><span>Deploying</span>
+        <ul>
+          <li><a href="publish_app.html">Publish</a></li>
+        </ul>
+      </li>
+      <li><span>Reference</span>
+        <ul>
+          <li><a href="manifest.html">Manifest Files</a></li>
+          <li><a href="api_index.html">Chrome JavaScript APIs</a></li>
+          <li><a href="api_other.html">Supported Libraries</a></li>
+          <li><a href="app_deprecated.html">Disabled Web Features</a></li>
+        </ul>
+      </li>
+      <li><span><a href="samples.html">Samples</a></span></li>
+      <li><span><a href="app_known_issues.html">Known Issues</a></span></li>
+  </ul>
+</div>
diff --git a/chrome/common/extensions/docs/templates/private/callback.html b/chrome/common/extensions/docs/templates/private/callback.html
new file mode 100644
index 0000000..11c7a99
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/callback.html
@@ -0,0 +1,17 @@
+<h4>Callback function</h4>
+<p>
+  {{?optional}}
+      If you specify the <em>{{name}}</em> parameter, it should
+      specify a function that looks like this:
+  {{:}}
+      The <em>{{name}}</em> parameter should specify a function
+      that looks like this:
+  {{/}}
+</p>
+<pre>function({{#parameters}}{{+partials.variable_type}} {{name}}{{^last}}, {{/}}{{/}}) <span class="subdued">{...}</span>;</pre>
+{{?description}}<p>
+  {{description}}
+</p>{{/}}
+<dl>
+  {{#parameters}}{{+partials.parameter_full}}{{/}}
+</dl>
diff --git a/chrome/common/extensions/docs/templates/private/event.html b/chrome/common/extensions/docs/templates/private/event.html
new file mode 100644
index 0000000..100e8b4
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/event.html
@@ -0,0 +1,41 @@
+<div>
+  <h4 id="{{id}}">{{name}}</h4>
+  <div class="summary">
+    <span class="subdued">{{?parent_name}}{{parent_name}}{{:}}chrome.{{api.name}}{{/}}.</span>{{name}}<span class="subdued">.addListener</span>(function({{#parameters}}{{+partials.parameter_item}}{{^last}}, {{/}}{{/}})<span class="subdued"> {...}</span>);
+  </div>
+  <div class="description">
+    {{?description}}<p>
+      {{{description}}}
+    </p>{{/description}}
+    {{?supportsRules}}
+      <p>See <a href="events.html#method-Event-addRules"><code>chrome.events.Event.addRules()</code></a>.</p>
+    {{/supportsRules}}
+    {{?conditions}}
+      <h4>Supported conditions</h4>
+      <dl>
+        {{#conditions}}<div><dt>{{+partials.ref_link}}</dt></div>{{/}}
+      </dl>
+    {{/conditions}}
+    {{?actions}}
+      <h4>Supported actions</h4>
+      <dl>
+        {{#actions}}<div><dt>{{+partials.ref_link}}</dt></div>{{/}}
+      </dl>
+    {{/actions}}
+    {{?parameters}}
+      <h4>Listener Parameters</h4>
+      <dl>
+        {{#parameters}}{{+partials.parameter_full}}{{/}}
+      </dl>
+      {{?callback}}
+      {{+partials.callback}}
+      {{/callback}}
+    {{/parameters}}
+    {{?filters}}
+      <h4>Filters</h4>
+      <dl>
+        {{#filters}}{{+partials.parameter_full}}{{/}}
+      </dl>
+    {{/filters}}
+  </div>
+</div>
diff --git a/chrome/common/extensions/docs/templates/private/extensions_footer.html b/chrome/common/extensions/docs/templates/private/extensions_footer.html
new file mode 100644
index 0000000..14d498a
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/extensions_footer.html
@@ -0,0 +1,8 @@
+<script>
+  window.bootstrap = {
+    api_names: {{*api_list.extensions.chrome}}.concat(
+        {{*api_list.extensions.experimental}}),
+    branchInfo: {{*branchInfo}}
+  };
+</script>
+{{+partials.footer}}
diff --git a/chrome/common/extensions/docs/templates/private/extensions_sidenav.html b/chrome/common/extensions/docs/templates/private/extensions_sidenav.html
new file mode 100644
index 0000000..f46f75c
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/extensions_sidenav.html
@@ -0,0 +1,81 @@
+<div class="g-unit g-first" id="gc-sidebar">
+  <ul>
+    <li><a href="getstarted.html">Getting Started</a></li>
+    <li><a href="overview.html">Overview</a></li>
+    <li><a href="whats_new.html">What's New?</a></li>
+    <li><a href="devguide.html">Developer's Guide</a>
+      <ul>
+        <li><span>Browser UI</span>
+          <ul toggleable>
+            <li><a href="browserAction.html">Browser Actions</a></li>
+            <li><a href="contextMenus.html">Context Menus</a></li>
+            <li><a href="notifications.html">Desktop Notifications</a></li>
+            <li><a href="omnibox.html">Omnibox</a></li>
+            <li><a href="options.html">Options Pages</a></li>
+            <li><a href="override.html">Override Pages</a></li>
+            <li><a href="pageAction.html">Page Actions</a></li>
+          </ul>
+        </li>
+        <li><span>Browser Interaction</span>
+          <ul toggleable>
+            <li><a href="bookmarks.html">Bookmarks</a></li>
+            <li><a href="cookies.html">Cookies</a></li>
+            <li><a href="devtools.html">Developer Tools</a></li>
+            <li><a href="events.html">Events</a></li>
+            <li><a href="history.html">History</a></li>
+            <li><a href="management.html">Management</a></li>
+            <li><a href="tabs.html">Tabs</a></li>
+            <li><a href="windows.html">Windows</a></li>
+          </ul>
+        </li>
+        <li><span>Implementation</span>
+          <ul toggleable>
+            <li><a href="a11y.html">Accessibility</a></li>
+            <li><a href="event_pages.html">Event Pages</a></li>
+            <li><a href="contentSecurityPolicy.html">Content Security Policy</a></li>
+            <li><a href="content_scripts.html">Content Scripts</a></li>
+            <li><a href="xhr.html">Cross-Origin XHR</a></li>
+            <li><a href="i18n.html">Internationalization</a></li>
+            <li><a href="messaging.html">Message Passing</a></li>
+            <li><a href="permissions.html">Optional Permissions</a></li>
+            <li><a href="npapi.html">NPAPI Plugins</a></li>
+          </ul>
+        </li>
+        <li><span>Finishing</span>
+          <ul toggleable>
+            <li><a href="hosting.html">Hosting</a></li>
+            <li><a href="external_extensions.html">Other Deployment Options</a></li>
+          </ul>
+        </li>
+      </ul>
+    </li>
+    <li><a href="tutorials.html">Tutorials</a>
+      <ul>
+        <li><a href="tut_migration_to_manifest_v2.html">Manifest V2</a></li>
+        <li><a href="tut_debugging.html">Debugging</a></li>
+        <li><a href="tut_analytics.html">Google Analytics</a></li>
+        <li><a href="tut_oauth.html">OAuth</a></li>
+      </ul>
+    </li>
+    <li><span>Reference</span>
+      <ul>
+        <li><span>Formats</span>
+          <ul toggleable>
+            <li><a href="manifest.html">Manifest Files</a></li>
+            <li><a href="match_patterns.html">Match Patterns</a></li>
+          </ul>
+        </li>
+        <li><a href="permission_warnings.html">Permission Warnings</a></li>
+        <li><a href="api_index.html">chrome.* APIs</a></li>
+        <li><a href="api_other.html">Other APIs</a></li>
+      </ul>
+    </li>
+    <li><span>More</span>
+      <ul>
+        <li><a href="http://code.google.com/chrome/webstore/docs/index.html">Chrome Web Store</a></li>
+        <li><a href="http://code.google.com/chrome/apps/docs/developers_guide.html">Hosted Apps</a></li>
+        <li><a href="themes.html">Themes</a></li>
+      </ul>
+    </li>
+  </ul>
+</div>
diff --git a/chrome/common/extensions/docs/templates/private/filter_item.html b/chrome/common/extensions/docs/templates/private/filter_item.html
new file mode 100644
index 0000000..570feaf
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/filter_item.html
@@ -0,0 +1 @@
+<span><a href="javascript:void(0)">{{name}}</a>{{^last}} | {{/}}</span>
diff --git a/chrome/common/extensions/docs/templates/private/footer.html b/chrome/common/extensions/docs/templates/private/footer.html
new file mode 100644
index 0000000..3c4acc8
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/footer.html
@@ -0,0 +1,52 @@
+<div id="gc-footer">
+  <div class="text">
+    <p>
+      Except as otherwise <a href="http://code.google.com/policies.html#restrictions">noted</a>,
+      the content of this page is licensed under the <a rel="license" href="http://creativecommons.org/licenses/by/3.0/">Creative Commons
+      Attribution 3.0 License</a>, and code samples are licensed under the
+      <a rel="license" href="http://code.google.com/google_bsd_license.html">BSD License</a>.
+    </p>
+    <p>
+      ©2012 Google
+    </p>
+    <script src="{{static}}/js/branch.js" type="text/javascript"></script>
+    <script src="{{static}}/js/sidebar.js" type="text/javascript"></script>
+    <script src="{{static}}/js/prettify.js" type="text/javascript"></script>
+    <script>
+      (function() {
+        // Auto syntax highlight all pre tags.
+        var preElements = document.getElementsByTagName('pre');
+        for (var i = 0; i < preElements.length; i++)
+          preElements[i].classList.add('prettyprint');
+        prettyPrint();
+      })();
+    </script>
+    <script src="https://www.google-analytics.com/urchin.js" type="text/javascript"></script>
+    <script src="https://www.google-analytics.com/ga.js" type="text/javascript"></script>
+    <script type="text/javascript">
+      // chrome doc tracking
+      try {
+        var engdocs = _gat._getTracker("YT-10763712-2");
+        engdocs._trackPageview();
+      } catch(err) {}
+      // code.google.com site-wide tracking
+      try {
+        _uacct="UA-18071-1";
+        _uanchor=1;
+        _uff=0;
+        urchinTracker();
+      }
+      catch(e) {/* urchinTracker not available. */}
+    </script>
+    <script>
+      (function() {
+        var cx = '002967670403910741006:61_cvzfqtno';
+        var gcse = document.createElement('script'); gcse.type = 'text/javascript'; gcse.async = true;
+        gcse.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') +
+          '//www.google.com/cse/cse.js?cx=' + cx;
+        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(gcse, s);
+      })();
+    </script>
+
+  </div>
+</div>
diff --git a/chrome/common/extensions/docs/templates/private/function.html b/chrome/common/extensions/docs/templates/private/function.html
new file mode 100644
index 0000000..d49bccd
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/function.html
@@ -0,0 +1,18 @@
+<div>
+  <h4 id="{{id}}">{{name}}</h4>
+  <div class="summary{{^returns}} lower{{/}}">
+    <span>{{?returns}}{{+partials.variable_type}} {{/}}{{?parent_name}}{{parent_name}}{{:}}chrome.{{api.name}}{{/}}.{{name}}</span>({{#parameters}}{{+partials.parameter_item}}{{^last}}, {{/}}{{/}})
+  </div>
+  <div class="description">
+    {{?description}}<p>
+      {{{description}}}
+    </p>{{/description}}
+    {{?parameters}}
+    <h4>Parameters</h4>
+    <dl>
+      {{#parameters}}{{+partials.parameter_full}}{{/}}
+    </dl>
+    {{?callback}}{{+partials.callback}}{{/}}
+    {{/parameters}}
+  </div>
+</div>
diff --git a/chrome/common/extensions/docs/templates/private/header_body.html b/chrome/common/extensions/docs/templates/private/header_body.html
new file mode 100644
index 0000000..efe95cdf
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/header_body.html
@@ -0,0 +1,44 @@
+{{?branchInfo.showWarning}}
+<div id="branchWarning" class="displayModeWarning">
+  <span>WARNING: this is the {{branchInfo.current}} documentation. It may not work with the stable release of Chrome.</span>
+  <select id="branchChooser">
+    <option value="">Choose a different version...</option>
+    {{#branchInfo.channels}}
+    <option value="{{path}}">{{name}}</option>
+    {{/}}
+  </select>
+</div>
+{{/}}
+<a id="top"></a>
+<div id="header">
+  <span id="logo">
+    <a href="http://code.google.com/"><img src="{{static}}/images/chrome_logo.gif" alt="Google Code Labs"></a>
+  </span>
+  <span id="cse">
+    <gcse:searchbox-only enableAutoComplete="true" autoCompleteMatchTyple="prefix"></gcse:searchbox-only>
+  </span>
+</div>
+<a id="gc-topnav-anchor"></a>
+<div id="gc-topnav">
+  <h1>Google Chrome {{title}}</h1>
+  <ul id="home" class="gc-topnav-tabs">
+    <li id="home_link">
+      <a href="index.html" title="Google Chrome {{title}} home page"><div>Home</div></a>
+    </li>
+    <li id="docs_link">
+      <a href="docs.html" title="Official Google Chrome {{title}} documentation"><div>Docs</div></a>
+    </li>
+    <li id="faq_link">
+      <a href="faq.html" title="Answers to frequently asked questions about Google Chrome {{title}}"><div>FAQ</div></a>
+    </li>
+    <li id="samples_link">
+      <a href="samples.html" title="Sample {{title}} (with source code)"><div>Samples</div></a>
+    </li>
+    <li id="group_link">
+      <a href="http://groups.google.com/a/chromium.org/group/chromium-extensions" title="Google Chrome {{title}} developer forum"><div>Group</div></a>
+    </li>
+    <li id="so_link">
+      <a href="http://stackoverflow.com/questions/tagged/google-chrome-extension" title="[google-chrome-extension] tag on Stack Overflow"><div>Questions?</div></a>
+    </li>
+  </ul>
+</div>
diff --git a/chrome/common/extensions/docs/templates/private/header_head.html b/chrome/common/extensions/docs/templates/private/header_head.html
new file mode 100644
index 0000000..bc43325
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/header_head.html
@@ -0,0 +1,5 @@
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link href="{{static}}/css/site.css" rel="stylesheet" type="text/css">
+<link href="{{static}}/css/print.css" rel="stylesheet" type="text/css" media="print">
+<link href="{{static}}/css/prettify.css" rel="stylesheet" type="text/css">
+<link href="//www.google.com/images/icons/product/chrome-16.png" rel="icon" type="image/ico">
diff --git a/chrome/common/extensions/docs/templates/private/known_issues.html b/chrome/common/extensions/docs/templates/private/known_issues.html
new file mode 100644
index 0000000..6f389ee
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/known_issues.html
@@ -0,0 +1,27 @@
+<div id="known_issues">
+  <h3>Open known issues</h3>
+  {{?known_issues.open}}
+  <ul>
+    {{#known_issues.open}}
+    <li><a href="http://code.google.com/p/chromium/issues/detail?id={{id}}">
+      {{title}}
+    </a></li>
+    {{/known_issues.open}}
+  </ul>
+  {{:known_issues.open}}
+  <p>There are no open known issues at this time. You might be able to find more
+  on the <a href="http://code.google.com/p/chromium/issues/list?q=Hotlist%3DKnownIssue+Feature%3DApps">Chromium bug tracker</a>.</p>
+  {{/known_issues.open}}
+  <h3>Recently closed known issues</h3>
+  {{?known_issues.closed}}
+  <ul>
+    {{#known_issues.closed}}
+    <li><a href="http://code.google.com/p/chromium/issues/detail?id={{id}}">
+      {{title}}
+    </a></li>
+    {{/known_issues.closed}}
+  </ul>
+  {{:known_issues.closed}}
+  <p>There are no closed known issues at this time.</p>
+  {{/known_issues.closed}}
+</div>
diff --git a/chrome/common/extensions/docs/templates/private/parameter_full.html b/chrome/common/extensions/docs/templates/private/parameter_full.html
new file mode 100644
index 0000000..e91139f
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/parameter_full.html
@@ -0,0 +1,34 @@
+<div>
+  {{?parent_name}}<a name="{{id}}"></a>{{/}}
+  {{+partials.property}}
+  {{?properties}}
+  <dd>
+    <dl>
+    {{#properties}}
+      {{+partials.parameter_full}}
+    {{/}}
+    </dl>
+  </dd>
+  {{/properties}}
+  {{?parameters}}
+  <dd>
+    <dl>
+    <h5>Parameters</h5>
+    {{#parameters}}
+      {{+partials.parameter_full}}
+    {{/}}
+    </dl>
+    {{?callback}}{{+partials.callback}}{{/}}
+  </dd>
+  {{/parameters}}
+  {{?returns}}
+  <dd>
+    <dl>
+    <h5>Returns</h5>
+    <dt><span class="property">
+        {{+partials.variable_type}}
+    </span></dt>
+    </dl>
+  </dd>
+  {{/returns}}
+</div>
diff --git a/chrome/common/extensions/docs/templates/private/parameter_item.html b/chrome/common/extensions/docs/templates/private/parameter_item.html
new file mode 100644
index 0000000..8214388
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/parameter_item.html
@@ -0,0 +1 @@
+<span{{?optional}} class="optional"{{/}}>{{+partials.variable_type}} <span class="variable">{{name}}</span></span>
diff --git a/chrome/common/extensions/docs/templates/private/property.html b/chrome/common/extensions/docs/templates/private/property.html
new file mode 100644
index 0000000..843ea55
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/property.html
@@ -0,0 +1,12 @@
+<dt>
+  <span class="variable">{{name}}</span>
+  <span class="property">
+    (
+      {{?optional}}<span class="optional">optional</span>{{/}}
+      <span id="typeTemplate">{{+partials.variable_type}}</span>
+    )
+  </span>
+</dt>
+{{?description}}<dd>
+  {{{description}}}
+</dd>{{/description}}
diff --git a/chrome/common/extensions/docs/templates/private/ref_link.html b/chrome/common/extensions/docs/templates/private/ref_link.html
new file mode 100644
index 0000000..58873ef
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/ref_link.html
@@ -0,0 +1 @@
+<a href="{{href}}">{{text}}</a>
diff --git a/chrome/common/extensions/docs/templates/private/sample_item.html b/chrome/common/extensions/docs/templates/private/sample_item.html
new file mode 100644
index 0000000..e24f800
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/sample_item.html
@@ -0,0 +1,23 @@
+<div class="sample" tags="{{#api_calls}}{{name}} {{/}}">
+  <img class="icon" src="{{icon}}">
+  <h2 id="{{id}}"><a href="{{download_url}}">{{name}}</a></h2>
+  {{?description}}
+  {{description}}
+  {{/description}}
+  <div>
+    <span class="label">Calls:</span>
+    <ul>
+      {{#api_calls}}
+      <li><code><a href="{{link}}">{{name}}</a></code></li>
+      {{/}}
+    </ul>
+  </div>
+  <div>
+    <span class="label">Source Files:</span>
+    <ul>
+      {{#files}}
+      <li><code><a href="{{url}}/{{@}}">{{@}}</a></code></li>
+      {{/}}
+    </ul>
+  </div>
+</div>
diff --git a/chrome/common/extensions/docs/templates/private/samples.html b/chrome/common/extensions/docs/templates/private/samples.html
new file mode 100644
index 0000000..5f97504
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/samples.html
@@ -0,0 +1,40 @@
+{{- sidenav: the sidenav bar for either apps or extensions -}}
+{{- filter_list: the list of APIs that show up in the filter box -}}
+{{- samples_list: the list of samples shown on the page -}}
+{{- footer: the footer for either apps or extensions -}}
+<!DOCTYPE html>
+<html>
+  <head>
+    {{+partials.header_head}}
+    <link href="{{static}}/css/samples.css" rel="stylesheet" type="text/css">
+    <title>Sample {{title}} - Google Chrome {{title}}</title>
+  </head>
+  <body>
+    {{+partials.header_body}}
+    <div id="gc-container">
+      {{+sidenav}}
+      <div id="gc-pagecontent">
+        <h1 class="page_title">Sample {{title}}</h1>
+        <div id="controls">
+          <table class="controlbox">
+            <tr>
+              <td class="label">Filter by keyword:</td>
+              <td><input autofocus type="search" id="search_input" placeholder="Type to search"></td>
+            </tr>
+            <tr>
+              <td class="label">Filter by API:</td>
+              <td>
+                <div id="api_filter_items">
+                  {{#filter_list}}{{+partials.filter_item}}{{/}}
+                </div>
+              </td>
+            </tr>
+          </table>
+        </div>
+        {{#samples_list}}{{+partials.sample_item}}{{/}}
+      </div>
+    </div>
+  </body>
+  {{+footer}}
+  <script src="{{static}}/js/samples.js" type="text/javascript"></script>
+</html>
diff --git a/chrome/common/extensions/docs/templates/private/standard_apps_api.html b/chrome/common/extensions/docs/templates/private/standard_apps_api.html
new file mode 100644
index 0000000..4ad25b8
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/standard_apps_api.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    {{+partials.header_head}}
+    <link href="{{static}}/css/api.css" rel="stylesheet" type="text/css">
+    {{+partials.title}}
+  </head>
+  <body>
+    {{+partials.header_body title:apps_title}}
+    <div id="gc-container">
+      {{+partials.apps_sidenav}}
+      <div id="gc-pagecontent">
+        <h1 class="page_title">chrome.{{api.name}}</h1>
+        {{?api.permissions.trunk}}{{+partials.warning_trunk}}{{/}}
+        {{?api.permissions.dev}}{{+partials.warning_dev}}{{/}}
+        {{?api.permissions.beta}}{{+partials.warning_beta}}{{/}}
+        {{?intro.apps_toc}}
+        {{+partials.table_of_contents toc_items:intro.apps_toc
+                                      has_toc:true
+                                      api:api
+                                      samples_list:api.samples.apps
+                                      title:apps_title}}
+        {{:intro.apps_toc}}
+        {{+partials.table_of_contents has_toc:false
+                                      api:api
+                                      samples_list:api.samples.apps
+                                      title:apps_title}}
+        {{/intro.apps_toc}}
+{{- This is unindented because it contains <pre> tags -}}
+{{?intro}}
+{{+intro.intro is_apps:true}}
+{{/intro}}
+        {{+partials.api_reference samples_list:api.samples.apps
+                                  title:apps_title}}
+      </div>
+    </div>
+  </body>
+  {{+partials.apps_footer}}
+</html>
diff --git a/chrome/common/extensions/docs/templates/private/standard_apps_article.html b/chrome/common/extensions/docs/templates/private/standard_apps_article.html
new file mode 100644
index 0000000..5f1c513
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/standard_apps_article.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    {{+partials.header_head}}
+    <title>{{article.title}} - Google Chrome</title>
+  </head>
+  <body>
+    {{+partials.header_body title:apps_title}}
+    <div id="gc-container">
+      {{+partials.apps_sidenav}}
+      <div id="gc-pagecontent">
+        <h1 class="page_title">{{article.title}}</h1>
+        {{?article.apps_toc}}
+        {{+partials.table_of_contents toc_items:article.apps_toc
+                                      has_toc:true
+                                      title:apps_title}}
+        {{/article.apps_toc}}
+{{- This may contain <pre> tags so it is not indented -}}
+{{+article.intro is_apps:true}}
+      </div>
+    </div>
+  </body>
+  {{+partials.apps_footer}}
+</html>
diff --git a/chrome/common/extensions/docs/templates/private/standard_extensions_api.html b/chrome/common/extensions/docs/templates/private/standard_extensions_api.html
new file mode 100644
index 0000000..6212006
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/standard_extensions_api.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    {{+partials.header_head}}
+    <link href="{{static}}/css/api.css" rel="stylesheet" type="text/css">
+    {{+partials.title}}
+  </head>
+  <body>
+    {{+partials.header_body title:extensions_title}}
+    <div id="gc-container">
+      {{+partials.extensions_sidenav}}
+      <div id="gc-pagecontent">
+        <h1 class="page_title">chrome.{{api.name}}</h1>
+        {{?api.permissions.trunk}}{{+partials.warning_trunk}}{{/}}
+        {{?api.permissions.dev}}{{+partials.warning_dev}}{{/}}
+        {{?api.permissions.beta}}{{+partials.warning_beta}}{{/}}
+        {{?intro.extensions_toc}}
+        {{+partials.table_of_contents toc_items:intro.extensions_toc
+                                      has_toc:true
+                                      api:api
+                                      samples_list:api.samples.extensions
+                                      title:extensions_title}}
+        {{:intro.extensions_toc}}
+        {{+partials.table_of_contents has_toc:false
+                                      api:api
+                                      samples_list:api.samples.extensions
+                                      title:extensions_title}}
+        {{/intro.extensions_toc}}
+{{- This is unindented because it contains <pre> tags -}}
+{{?intro}}
+{{+intro.intro is_apps:false}}
+{{/intro}}
+        {{+partials.api_reference samples_list:api.samples.extensions
+                                  title:extensions_title}}
+      </div>
+    </div>
+  </body>
+  {{+partials.extensions_footer}}
+</html>
diff --git a/chrome/common/extensions/docs/templates/private/standard_extensions_article.html b/chrome/common/extensions/docs/templates/private/standard_extensions_article.html
new file mode 100644
index 0000000..c5f4c96
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/standard_extensions_article.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    {{+partials.header_head}}
+    <title>{{article.title}} - Google Chrome</title>
+  </head>
+  <body>
+    {{+partials.header_body title:extensions_title}}
+    <div id="gc-container">
+      {{+partials.extensions_sidenav}}
+      <div id="gc-pagecontent">
+        <h1 class="page_title">{{article.title}}</h1>
+        {{?article.extensions_toc}}
+        {{+partials.table_of_contents toc_items:article.extensions_toc
+                                      has_toc:true
+                                      title:extensions_title}}
+        {{/article.extensions_toc}}
+{{- This may contain <pre> tags so it is not indented -}}
+{{+article.intro is_apps:false}}
+      </div>
+    </div>
+  </body>
+  {{+partials.extensions_footer}}
+</html>
diff --git a/chrome/common/extensions/docs/templates/private/table_of_contents.html b/chrome/common/extensions/docs/templates/private/table_of_contents.html
new file mode 100644
index 0000000..576717b
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/table_of_contents.html
@@ -0,0 +1,31 @@
+<div id="toc">
+  <h2>Contents</h2>
+  <ol>
+    {{?has_toc}}
+    {{#toc_items}}
+    <li>
+      <a href=#{{link}}>{{{title}}}</a>
+      {{?subheadings}}
+      <ol>
+        {{#subheadings}}<li><a href=#{{link}}>{{{title}}}</a></li>{{/}}
+      </ol>
+      {{/}}
+    </li>
+    {{/toc_items}}
+    {{/has_toc}}
+    {{?api}}
+    <li>
+      <a href="#apiReference">API reference: chrome.{{api.name}}</a>
+      <ol>
+        {{#api}}
+          {{?api.types}}<li>{{+partials.toc_types}}</li>{{/}}
+          {{?api.properties}}<li>{{+partials.toc_properties}}</li>{{/}}
+          {{?api.functions}}<li>{{+partials.toc_functions}}</li>{{/}}
+          {{?api.events}}<li>{{+partials.toc_events}}</li>{{/}}
+          {{?samples_list}}<li>{{+partials.toc_samples title:title}}</li>{{/}}
+        {{/api}}
+      </ol>
+    </li>
+    {{/api}}
+  </ol>
+</div>
diff --git a/chrome/common/extensions/docs/templates/private/title.html b/chrome/common/extensions/docs/templates/private/title.html
new file mode 100644
index 0000000..8378aea
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/title.html
@@ -0,0 +1 @@
+<title>chrome.{{api.name}} - Google Chrome</title>
diff --git a/chrome/common/extensions/docs/templates/private/toc_events.html b/chrome/common/extensions/docs/templates/private/toc_events.html
new file mode 100644
index 0000000..e50086b
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/toc_events.html
@@ -0,0 +1,6 @@
+<a href="#{{?parent_name}}{{parent_name}}-{{/}}events">Events</a>
+<ol>
+  {{#@}}
+  <li><a href="#{{id}}">{{name}}</a></li>
+  {{/}}
+</ol>
diff --git a/chrome/common/extensions/docs/templates/private/toc_functions.html b/chrome/common/extensions/docs/templates/private/toc_functions.html
new file mode 100644
index 0000000..c6b3b43
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/toc_functions.html
@@ -0,0 +1,6 @@
+<a href="#{{?parent_name}}{{parent_name}}-{{/}}methods">Methods</a>
+<ol>
+  {{#@}}
+  <li><a href="#{{id}}">{{name}}</a></li>
+  {{/}}
+</ol>
diff --git a/chrome/common/extensions/docs/templates/private/toc_properties.html b/chrome/common/extensions/docs/templates/private/toc_properties.html
new file mode 100644
index 0000000..9c3b50b
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/toc_properties.html
@@ -0,0 +1,17 @@
+<a href="#{{?parent_name}}{{parent_name}}-{{/}}properties">Properties</a>
+<ol>
+  {{#@}}
+  <li>
+    <a href="#{{id}}">{{name}}</a>
+    {{?functions}}<ol><li>
+      {{+partials.toc_functions parent_name:name}}
+    </li></ol>{{/}}
+    {{?events}}<ol><li>
+      {{+partials.toc_events parent_name:name}}
+    </li></ol>{{/}}
+    {{?properties}}<ol><li>
+      {{+partials.toc_properties parent_name:name}}
+    </li></ol>{{/}}
+  </li>
+  {{/}}
+</ol>
diff --git a/chrome/common/extensions/docs/templates/private/toc_samples.html b/chrome/common/extensions/docs/templates/private/toc_samples.html
new file mode 100644
index 0000000..2a26449e
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/toc_samples.html
@@ -0,0 +1 @@
+<a href="#samples">Sample {{title}}</a>
diff --git a/chrome/common/extensions/docs/templates/private/toc_types.html b/chrome/common/extensions/docs/templates/private/toc_types.html
new file mode 100644
index 0000000..dc032da
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/toc_types.html
@@ -0,0 +1,14 @@
+<a href="#{{?parent_name}}{{parent_name}}-{{/}}types">Types</a>
+<ol>
+  {{#@}}
+  <li>
+    <a href="#{{id}}">{{name}}</a>
+    {{?functions}}<ol><li>
+      {{+partials.toc_functions parent_name:name}}
+    </li></ol>{{/}}
+    {{?events}}<ol><li>
+      {{+partials.toc_events parent_name:name}}
+    </li></ol>{{/}}
+  </li>
+  {{/}}
+</ol>
diff --git a/chrome/common/extensions/docs/templates/private/type.html b/chrome/common/extensions/docs/templates/private/type.html
new file mode 100644
index 0000000..b03bbb0
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/type.html
@@ -0,0 +1,33 @@
+<div>
+  <h3 id="{{id}}">{{name}}</h3>
+  <div>
+    <dt>{{+partials.type_name}}</dt>
+    {{?description}}<dd>
+      {{{description}}}
+    </dd>{{/description}}
+    {{?properties}}
+    <h4 id="{{type.name}}-properties">
+      Properties of <a href="#type-{{type.name}}">{{type.name}}</a>
+    </h4>
+    <dd><dl>{{#properties}}
+      {{+partials.parameter_full}}
+    {{/}}</dl></dd>
+    {{/properties}}
+    {{?functions}}
+    <h4 id="{{type.name}}-methods">
+      Methods of <a href="#type-{{type.name}}">{{type.name}}</a>
+    </h4>
+    <dd><dl>{{#functions}}
+      {{+partials.function api:api}}
+    {{/}}</dl></dd>
+    {{/functions}}
+    {{?events}}
+    <h4 id="{{type.name}}-events">
+      Events of <a href="#type-{{type.name}}">{{type.name}}</a>
+    </h4>
+    <dd><dl>{{#events}}
+      {{+partials.event api:api}}
+    {{/}}</dl></dd>
+    {{/events}}
+  </div>
+</div>
diff --git a/chrome/common/extensions/docs/templates/private/type_name.html b/chrome/common/extensions/docs/templates/private/type_name.html
new file mode 100644
index 0000000..54d3001
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/type_name.html
@@ -0,0 +1,6 @@
+<span class="type_name">
+  (
+  {{?optional}}<span class="optional">optional</span>{{/}}
+  <span id="typeTemplate">{{+partials.variable_type}}</span>
+  )
+</span>
diff --git a/chrome/common/extensions/docs/templates/private/variable_type.html b/chrome/common/extensions/docs/templates/private/variable_type.html
new file mode 100644
index 0000000..5fae2a9
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/variable_type.html
@@ -0,0 +1,20 @@
+{{?link}}
+{{+partials.ref_link}}
+{{/}}
+{{?choices}}
+{{#choices}}
+{{+partials.variable_type}}{{^@.last}} or {{/}}
+{{/}}
+{{/}}
+{{?array}}
+array of {{+partials.variable_type}}
+{{/}}
+{{?simple_type}}
+{{simple_type}}
+{{/}}
+{{?enum_values}}
+enumerated string [{{#enum_values}}"{{name}}"{{^last}}, {{/}}{{/}}]
+{{/}}
+{{?value}}
+<code>{{value}}</code>
+{{/}}
diff --git a/chrome/common/extensions/docs/templates/private/warning_beta.html b/chrome/common/extensions/docs/templates/private/warning_beta.html
new file mode 100644
index 0000000..d4c5edf
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/warning_beta.html
@@ -0,0 +1,10 @@
+<p class="warning">
+  <em>Warning:</em> This API is still under development. It is only
+  available for Chrome users on the
+  <span>
+    <strong>dev</strong>
+      <a href="http://www.chromium.org/getting-involved/beta-channel">early
+      release channel</a> and <strong>beta</strong>
+      <a href="https://www.google.com/landing/chrome/beta/"> release channel.
+      </span>
+</p>
diff --git a/chrome/common/extensions/docs/templates/private/warning_dev.html b/chrome/common/extensions/docs/templates/private/warning_dev.html
new file mode 100644
index 0000000..fa9b2e4
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/warning_dev.html
@@ -0,0 +1,8 @@
+<p class="warning">
+  <em>Warning:</em> This API is still under development. It is only
+  available for Chrome users on the
+  <span>
+    <strong>dev</strong>
+      <a href="http://www.chromium.org/getting-involved/dev-channel">early
+      release channel</a>.</span>
+</p>
diff --git a/chrome/common/extensions/docs/templates/private/warning_trunk.html b/chrome/common/extensions/docs/templates/private/warning_trunk.html
new file mode 100644
index 0000000..085bfa9
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/private/warning_trunk.html
@@ -0,0 +1,8 @@
+<p class="warning">
+  <em>Warning:</em> This API is still under development. It is only
+  available to developers building from
+  <span>
+    <strong>trunk</strong>.
+    <a href="http://www.chromium.org/developers/">More info</a>.
+  </span>
+</p>
diff --git a/chrome/common/extensions/docs/templates/public/404.html b/chrome/common/extensions/docs/templates/public/404.html
new file mode 100644
index 0000000..6bd1569
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/404.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    {{+partials.header_head}}
+    <link href="{{static}}/css/index.css" rel="stylesheet" type="text/css">
+    <title>404 Not Found - Google Chrome Extensions</title>
+  </head>
+  <body>
+    {{+partials.header_body title:extensions_title}}
+    <div id="gc-container">
+      <div id="gc-pagecontent">
+        <h1 class="page_title">404 - Not Found</h1>
+        <p>No API or URL found with that name.</p>
+      </div>
+    </div>
+  </body>
+  {{+partials.footer}}
+</html>
diff --git a/chrome/common/extensions/docs/templates/public/apps/a11y.html b/chrome/common/extensions/docs/templates/public/apps/a11y.html
new file mode 100644
index 0000000..5014292
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/a11y.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.a11y}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/about_apps.html b/chrome/common/extensions/docs/templates/public/apps/about_apps.html
new file mode 100644
index 0000000..134924c
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/about_apps.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.about_apps}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/alarms.html b/chrome/common/extensions/docs/templates/public/apps/alarms.html
new file mode 100644
index 0000000..d37eda3
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/alarms.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.alarms}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/angular_framework.html b/chrome/common/extensions/docs/templates/public/apps/angular_framework.html
new file mode 100644
index 0000000..292c852
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/angular_framework.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.angular_framework}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/api_index.html b/chrome/common/extensions/docs/templates/public/apps/api_index.html
new file mode 100644
index 0000000..4d7b209
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/api_index.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.api_index is_apps:true}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/api_other.html b/chrome/common/extensions/docs/templates/public/apps/api_other.html
new file mode 100644
index 0000000..b665d61
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/api_other.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.api_other}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/app_architecture.html b/chrome/common/extensions/docs/templates/public/apps/app_architecture.html
new file mode 100644
index 0000000..0e30758
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/app_architecture.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.app_architecture}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/app_csp.html b/chrome/common/extensions/docs/templates/public/apps/app_csp.html
new file mode 100644
index 0000000..d9e4ab0
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/app_csp.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.app_csp}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/app_deprecated.html b/chrome/common/extensions/docs/templates/public/apps/app_deprecated.html
new file mode 100644
index 0000000..c9fa3b6
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/app_deprecated.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.app_deprecated}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/app_external.html b/chrome/common/extensions/docs/templates/public/apps/app_external.html
new file mode 100644
index 0000000..d493284
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/app_external.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.app_external}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/app_frameworks.html b/chrome/common/extensions/docs/templates/public/apps/app_frameworks.html
new file mode 100644
index 0000000..af5c77d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/app_frameworks.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.app_frameworks}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/app_hardware.html b/chrome/common/extensions/docs/templates/public/apps/app_hardware.html
new file mode 100644
index 0000000..98db693
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/app_hardware.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.app_hardware}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/app_identity.html b/chrome/common/extensions/docs/templates/public/apps/app_identity.html
new file mode 100644
index 0000000..ce60fa5
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/app_identity.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.app_identity}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/app_intents.html b/chrome/common/extensions/docs/templates/public/apps/app_intents.html
new file mode 100644
index 0000000..e2d4c67
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/app_intents.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.app_intents}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/app_known_issues.html b/chrome/common/extensions/docs/templates/public/apps/app_known_issues.html
new file mode 100644
index 0000000..7083c1b
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/app_known_issues.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.app_known_issues}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/app_lifecycle.html b/chrome/common/extensions/docs/templates/public/apps/app_lifecycle.html
new file mode 100644
index 0000000..6b39b86
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/app_lifecycle.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.app_lifecycle}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/app_network.html b/chrome/common/extensions/docs/templates/public/apps/app_network.html
new file mode 100644
index 0000000..72d47bb
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/app_network.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.app_network}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/app_runtime.html b/chrome/common/extensions/docs/templates/public/apps/app_runtime.html
new file mode 100644
index 0000000..c3bb6ca
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/app_runtime.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.app_runtime}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/app_storage.html b/chrome/common/extensions/docs/templates/public/apps/app_storage.html
new file mode 100644
index 0000000..ecc8a1f
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/app_storage.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.app_storage}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/app_window.html b/chrome/common/extensions/docs/templates/public/apps/app_window.html
new file mode 100644
index 0000000..2bb1aa5
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/app_window.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.app_window}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/autoupdate.html b/chrome/common/extensions/docs/templates/public/apps/autoupdate.html
new file mode 100644
index 0000000..84551bc
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/autoupdate.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.autoupdate}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/background_pages.html b/chrome/common/extensions/docs/templates/public/apps/background_pages.html
new file mode 100644
index 0000000..f2d6f84
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/background_pages.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.background_pages}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/bluetooth.html b/chrome/common/extensions/docs/templates/public/apps/bluetooth.html
new file mode 100644
index 0000000..45ab21d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/bluetooth.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.bluetooth}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/commands.html b/chrome/common/extensions/docs/templates/public/apps/commands.html
new file mode 100644
index 0000000..a4393d5
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/commands.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.commands intro:intros.commands}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/contentSecurityPolicy.html b/chrome/common/extensions/docs/templates/public/apps/contentSecurityPolicy.html
new file mode 100644
index 0000000..b4c86ea
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/contentSecurityPolicy.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.contentSecurityPolicy}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/content_scripts.html b/chrome/common/extensions/docs/templates/public/apps/content_scripts.html
new file mode 100644
index 0000000..80f1fbc
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/content_scripts.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.content_scripts}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/contextMenus.html b/chrome/common/extensions/docs/templates/public/apps/contextMenus.html
new file mode 100644
index 0000000..c8e3649
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/contextMenus.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.context_menus intro:intros.contextMenus}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/crx.html b/chrome/common/extensions/docs/templates/public/apps/crx.html
new file mode 100644
index 0000000..d7c5b69
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/crx.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.crx}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/declare_permissions.html b/chrome/common/extensions/docs/templates/public/apps/declare_permissions.html
new file mode 100644
index 0000000..998bb68
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/declare_permissions.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.declare_permissions}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/develop_apps.html b/chrome/common/extensions/docs/templates/public/apps/develop_apps.html
new file mode 100644
index 0000000..0c86785
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/develop_apps.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.develop_apps}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/devguide.html b/chrome/common/extensions/docs/templates/public/apps/devguide.html
new file mode 100644
index 0000000..7be8259
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/devguide.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.devguide}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/devtools.html b/chrome/common/extensions/docs/templates/public/apps/devtools.html
new file mode 100644
index 0000000..06ec7a8
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/devtools.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.devtools}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/docs.html b/chrome/common/extensions/docs/templates/public/apps/docs.html
new file mode 100644
index 0000000..714521d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/docs.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.docs}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/event_pages.html b/chrome/common/extensions/docs/templates/public/apps/event_pages.html
new file mode 100644
index 0000000..d75de6f
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/event_pages.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.event_pages}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/events.html b/chrome/common/extensions/docs/templates/public/apps/events.html
new file mode 100644
index 0000000..69899cf
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/events.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.events intro:intros.events}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental.html b/chrome/common/extensions/docs/templates/public/apps/experimental.html
new file mode 100644
index 0000000..7341484
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.experimental is_apps:true}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_app.html b/chrome/common/extensions/docs/templates/public/apps/experimental_app.html
new file mode 100644
index 0000000..a2efc68
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_app.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.experimental_app intro:intros.experimental_app}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_contextMenus.html b/chrome/common/extensions/docs/templates/public/apps/experimental_contextMenus.html
new file mode 100644
index 0000000..4a6c637
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_contextMenus.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.experimental_contextMenus}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_devtools_inspectedWindow.html b/chrome/common/extensions/docs/templates/public/apps/experimental_devtools_inspectedWindow.html
new file mode 100644
index 0000000..aac2044
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_devtools_inspectedWindow.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.experimental_devtools_inspectedWindow}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_devtools_network.html b/chrome/common/extensions/docs/templates/public/apps/experimental_devtools_network.html
new file mode 100644
index 0000000..67ea061
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_devtools_network.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.experimental_devtools_network}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_devtools_panels.html b/chrome/common/extensions/docs/templates/public/apps/experimental_devtools_panels.html
new file mode 100644
index 0000000..4beef46
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_devtools_panels.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.experimental_devtools_panels}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_devtools_resources.html b/chrome/common/extensions/docs/templates/public/apps/experimental_devtools_resources.html
new file mode 100644
index 0000000..f9bf1ae
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_devtools_resources.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.experimental_devtools_resources}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_identity.html b/chrome/common/extensions/docs/templates/public/apps/experimental_identity.html
new file mode 100644
index 0000000..39ee7cb
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_identity.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.experimental_identity}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_inputUI.html b/chrome/common/extensions/docs/templates/public/apps/experimental_inputUI.html
new file mode 100644
index 0000000..79a28ce
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_inputUI.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.experimental_inputUI}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_mediaGalleries.html b/chrome/common/extensions/docs/templates/public/apps/experimental_mediaGalleries.html
new file mode 100644
index 0000000..880079e
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_mediaGalleries.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.experimental_media_galleries}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_record.html b/chrome/common/extensions/docs/templates/public/apps/experimental_record.html
new file mode 100644
index 0000000..e1bc956
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_record.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.experimental_record}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_storage.html b/chrome/common/extensions/docs/templates/public/apps/experimental_storage.html
new file mode 100644
index 0000000..69dda53
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_storage.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.experimental_storage}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_systemInfo_cpu.html b/chrome/common/extensions/docs/templates/public/apps/experimental_systemInfo_cpu.html
new file mode 100644
index 0000000..52cffba
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_systemInfo_cpu.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.experimental_system_info_cpu}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_systemInfo_storage.html b/chrome/common/extensions/docs/templates/public/apps/experimental_systemInfo_storage.html
new file mode 100644
index 0000000..b17be1e
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_systemInfo_storage.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.experimental_system_info_storage}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_webInspector.html b/chrome/common/extensions/docs/templates/public/apps/experimental_webInspector.html
new file mode 100644
index 0000000..549aeb8
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_webInspector.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.experimental_webInspector}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_webInspector_audits.html b/chrome/common/extensions/docs/templates/public/apps/experimental_webInspector_audits.html
new file mode 100644
index 0000000..c00981d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_webInspector_audits.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.experimental_webInspector_audits}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_webInspector_panels.html b/chrome/common/extensions/docs/templates/public/apps/experimental_webInspector_panels.html
new file mode 100644
index 0000000..6541843
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_webInspector_panels.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.experimental_webInspector_panels}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/experimental_webInspector_resources.html b/chrome/common/extensions/docs/templates/public/apps/experimental_webInspector_resources.html
new file mode 100644
index 0000000..96ea172
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/experimental_webInspector_resources.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.experimental_webInspector_resources}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/external_extensions.html b/chrome/common/extensions/docs/templates/public/apps/external_extensions.html
new file mode 100644
index 0000000..715bc1b
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/external_extensions.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.external_extensions}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/faq.html b/chrome/common/extensions/docs/templates/public/apps/faq.html
new file mode 100644
index 0000000..84fe405
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/faq.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.faq}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/fileSystem.html b/chrome/common/extensions/docs/templates/public/apps/fileSystem.html
new file mode 100644
index 0000000..c5062ef
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/fileSystem.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.file_system}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/first_app.html b/chrome/common/extensions/docs/templates/public/apps/first_app.html
new file mode 100644
index 0000000..664a89b
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/first_app.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.first_app}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/getstarted.html b/chrome/common/extensions/docs/templates/public/apps/getstarted.html
new file mode 100644
index 0000000..612efb3
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/getstarted.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.getstarted}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/hosting.html b/chrome/common/extensions/docs/templates/public/apps/hosting.html
new file mode 100644
index 0000000..7947899
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/hosting.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.hosting}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/i18n-messages.html b/chrome/common/extensions/docs/templates/public/apps/i18n-messages.html
new file mode 100644
index 0000000..ff5c891
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/i18n-messages.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.i18n-messages}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/i18n.html b/chrome/common/extensions/docs/templates/public/apps/i18n.html
new file mode 100644
index 0000000..1b82694
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/i18n.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.i18n intro:intros.i18n}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/idle.html b/chrome/common/extensions/docs/templates/public/apps/idle.html
new file mode 100644
index 0000000..1ed9950
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/idle.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.idle intro:intros.idle}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/index.html b/chrome/common/extensions/docs/templates/public/apps/index.html
new file mode 100644
index 0000000..a25d506
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/index.html
@@ -0,0 +1 @@
+<html><head><meta http-equiv="refresh" content="0;URL='about_apps.html'"></head></html>
diff --git a/chrome/common/extensions/docs/templates/public/apps/manifest.html b/chrome/common/extensions/docs/templates/public/apps/manifest.html
new file mode 100644
index 0000000..d073c92
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/manifest.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.manifest}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/manifestVersion.html b/chrome/common/extensions/docs/templates/public/apps/manifestVersion.html
new file mode 100644
index 0000000..b49d5fa
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/manifestVersion.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.manifestVersion}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/match_patterns.html b/chrome/common/extensions/docs/templates/public/apps/match_patterns.html
new file mode 100644
index 0000000..f327243
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/match_patterns.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.match_patterns}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/mediaGalleries.html b/chrome/common/extensions/docs/templates/public/apps/mediaGalleries.html
new file mode 100644
index 0000000..6468eb2
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/mediaGalleries.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.media_galleries intro:intros.mediaGalleries}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/messaging.html b/chrome/common/extensions/docs/templates/public/apps/messaging.html
new file mode 100644
index 0000000..e4c18e3
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/messaging.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.messaging}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/notifications.html b/chrome/common/extensions/docs/templates/public/apps/notifications.html
new file mode 100644
index 0000000..e2e19af
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/notifications.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.notifications}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/npapi.html b/chrome/common/extensions/docs/templates/public/apps/npapi.html
new file mode 100644
index 0000000..51eddac
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/npapi.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.npapi}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/offline_apps.html b/chrome/common/extensions/docs/templates/public/apps/offline_apps.html
new file mode 100644
index 0000000..dafda5c
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/offline_apps.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.offline_apps}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/options.html b/chrome/common/extensions/docs/templates/public/apps/options.html
new file mode 100644
index 0000000..267e080
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/options.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.options}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/override.html b/chrome/common/extensions/docs/templates/public/apps/override.html
new file mode 100644
index 0000000..6dbb5d6
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/override.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.override}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/overview.html b/chrome/common/extensions/docs/templates/public/apps/overview.html
new file mode 100644
index 0000000..8504647
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/overview.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.overview}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/packaging.html b/chrome/common/extensions/docs/templates/public/apps/packaging.html
new file mode 100644
index 0000000..c8d7fac
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/packaging.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.packaging}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/permission_warnings.html b/chrome/common/extensions/docs/templates/public/apps/permission_warnings.html
new file mode 100644
index 0000000..54248d6
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/permission_warnings.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.permission_warnings}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/permissions.html b/chrome/common/extensions/docs/templates/public/apps/permissions.html
new file mode 100644
index 0000000..85a114f
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/permissions.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.permissions intro:intros.permissions}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/publish_app.html b/chrome/common/extensions/docs/templates/public/apps/publish_app.html
new file mode 100644
index 0000000..d3b94c2
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/publish_app.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.publish_app}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/pushMessaging.html b/chrome/common/extensions/docs/templates/public/apps/pushMessaging.html
new file mode 100644
index 0000000..e42888d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/pushMessaging.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.pushMessaging}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/runtime.html b/chrome/common/extensions/docs/templates/public/apps/runtime.html
new file mode 100644
index 0000000..f7d5626
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/runtime.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.runtime}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/samples.html b/chrome/common/extensions/docs/templates/public/apps/samples.html
new file mode 100644
index 0000000..c91d0c3
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/samples.html
@@ -0,0 +1,5 @@
+{{+partials.samples sidenav:partials.apps_sidenav
+                    filter_list:api_list.apps.chrome
+                    samples_list:samples.apps
+                    footer:partials.apps_footer
+                    title:apps_title}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/sandboxingEval.html b/chrome/common/extensions/docs/templates/public/apps/sandboxingEval.html
new file mode 100644
index 0000000..3e0c1e3
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/sandboxingEval.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.sandboxingEval}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/search_results.html b/chrome/common/extensions/docs/templates/public/apps/search_results.html
new file mode 100644
index 0000000..9edb3b1
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/search_results.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.search_results}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/sencha_framework.html b/chrome/common/extensions/docs/templates/public/apps/sencha_framework.html
new file mode 100644
index 0000000..3fb3ddd
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/sencha_framework.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.sencha_framework}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/serial.html b/chrome/common/extensions/docs/templates/public/apps/serial.html
new file mode 100644
index 0000000..de040bf
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/serial.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.serial}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/socket.html b/chrome/common/extensions/docs/templates/public/apps/socket.html
new file mode 100644
index 0000000..d9e4809
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/socket.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.socket intro:intros.socket}}
\ No newline at end of file
diff --git a/chrome/common/extensions/docs/templates/public/apps/storage.html b/chrome/common/extensions/docs/templates/public/apps/storage.html
new file mode 100644
index 0000000..90c3fc2
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/storage.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.storage intro:intros.storage}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/themes.html b/chrome/common/extensions/docs/templates/public/apps/themes.html
new file mode 100644
index 0000000..ee72c5d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/themes.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.themes}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/tts.html b/chrome/common/extensions/docs/templates/public/apps/tts.html
new file mode 100644
index 0000000..6913c00
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/tts.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.tts intro:intros.tts}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/tut_analytics.html b/chrome/common/extensions/docs/templates/public/apps/tut_analytics.html
new file mode 100644
index 0000000..6e3a4fc
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/tut_analytics.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.tut_analytics}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/tut_debugging.html b/chrome/common/extensions/docs/templates/public/apps/tut_debugging.html
new file mode 100644
index 0000000..d9051b0
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/tut_debugging.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.tut_debugging}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/tut_oauth.html b/chrome/common/extensions/docs/templates/public/apps/tut_oauth.html
new file mode 100644
index 0000000..8a360bf
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/tut_oauth.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.tut_oauth}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/tutorials.html b/chrome/common/extensions/docs/templates/public/apps/tutorials.html
new file mode 100644
index 0000000..8f2389b
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/tutorials.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.tutorials}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/types.html b/chrome/common/extensions/docs/templates/public/apps/types.html
new file mode 100644
index 0000000..3023505
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/types.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.types intro:intros.types}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/usb.html b/chrome/common/extensions/docs/templates/public/apps/usb.html
new file mode 100644
index 0000000..e540bcc
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/usb.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.usb}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/webstore.html b/chrome/common/extensions/docs/templates/public/apps/webstore.html
new file mode 100644
index 0000000..f7cfb28
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/webstore.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_api api:apis.webstore}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/whats_new.html b/chrome/common/extensions/docs/templates/public/apps/whats_new.html
new file mode 100644
index 0000000..65959e8
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/whats_new.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.whats_new}}
diff --git a/chrome/common/extensions/docs/templates/public/apps/xhr.html b/chrome/common/extensions/docs/templates/public/apps/xhr.html
new file mode 100644
index 0000000..34be1c2
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/xhr.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:intros.xhr}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/a11y.html b/chrome/common/extensions/docs/templates/public/extensions/a11y.html
new file mode 100644
index 0000000..ffa8b22
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/a11y.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.a11y}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/activeTab.html b/chrome/common/extensions/docs/templates/public/extensions/activeTab.html
new file mode 100644
index 0000000..61f1820
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/activeTab.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.activeTab}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/alarms.html b/chrome/common/extensions/docs/templates/public/extensions/alarms.html
new file mode 100644
index 0000000..855b4b9
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/alarms.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.alarms}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/api_index.html b/chrome/common/extensions/docs/templates/public/extensions/api_index.html
new file mode 100644
index 0000000..794c225
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/api_index.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.api_index is_apps:false}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/api_other.html b/chrome/common/extensions/docs/templates/public/extensions/api_other.html
new file mode 100644
index 0000000..3072c0b
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/api_other.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.api_other}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/apps.html b/chrome/common/extensions/docs/templates/public/extensions/apps.html
new file mode 100644
index 0000000..898f3fc
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/apps.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.apps}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/autoupdate.html b/chrome/common/extensions/docs/templates/public/extensions/autoupdate.html
new file mode 100644
index 0000000..7ad2b5d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/autoupdate.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.autoupdate}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/background_pages.html b/chrome/common/extensions/docs/templates/public/extensions/background_pages.html
new file mode 100644
index 0000000..4f70c7d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/background_pages.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.background_pages}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/bookmarks.html b/chrome/common/extensions/docs/templates/public/extensions/bookmarks.html
new file mode 100644
index 0000000..d2a36f6
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/bookmarks.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.bookmarks intro:intros.bookmarks}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/browserAction.html b/chrome/common/extensions/docs/templates/public/extensions/browserAction.html
new file mode 100644
index 0000000..ae1b726
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/browserAction.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.browser_action intro:intros.browserAction}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/browsingData.html b/chrome/common/extensions/docs/templates/public/extensions/browsingData.html
new file mode 100644
index 0000000..18db5c8
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/browsingData.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.browsing_data intro:intros.browsingData}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/commands.html b/chrome/common/extensions/docs/templates/public/extensions/commands.html
new file mode 100644
index 0000000..99189c9
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/commands.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.commands intro:intros.commands}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/contentSecurityPolicy.html b/chrome/common/extensions/docs/templates/public/extensions/contentSecurityPolicy.html
new file mode 100644
index 0000000..46abbdc
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/contentSecurityPolicy.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.contentSecurityPolicy}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/contentSettings.html b/chrome/common/extensions/docs/templates/public/extensions/contentSettings.html
new file mode 100644
index 0000000..5aac9c9
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/contentSettings.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.content_settings intro:intros.contentSettings}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/content_scripts.html b/chrome/common/extensions/docs/templates/public/extensions/content_scripts.html
new file mode 100644
index 0000000..dfb657f
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/content_scripts.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.content_scripts}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/contextMenus.html b/chrome/common/extensions/docs/templates/public/extensions/contextMenus.html
new file mode 100644
index 0000000..075a30e
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/contextMenus.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.context_menus intro:intros.contextMenus}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/cookies.html b/chrome/common/extensions/docs/templates/public/extensions/cookies.html
new file mode 100644
index 0000000..c1d4a42
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/cookies.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.cookies intro:intros.cookies}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/crx.html b/chrome/common/extensions/docs/templates/public/extensions/crx.html
new file mode 100644
index 0000000..ad07520
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/crx.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.crx}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/debugger.html b/chrome/common/extensions/docs/templates/public/extensions/debugger.html
new file mode 100644
index 0000000..d3d4a09
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/debugger.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.debugger intro:intros.debugger}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/declarativeWebRequest.html b/chrome/common/extensions/docs/templates/public/extensions/declarativeWebRequest.html
new file mode 100644
index 0000000..839bd55
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/declarativeWebRequest.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.declarative_web_request intro:intros.declarativeWebRequest}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/declare_permissions.html b/chrome/common/extensions/docs/templates/public/extensions/declare_permissions.html
new file mode 100644
index 0000000..910cc34
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/declare_permissions.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.declare_permissions}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/devguide.html b/chrome/common/extensions/docs/templates/public/extensions/devguide.html
new file mode 100644
index 0000000..fc0357e
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/devguide.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.devguide}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/devtools.html b/chrome/common/extensions/docs/templates/public/extensions/devtools.html
new file mode 100644
index 0000000..7d763cb
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/devtools.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.devtools}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/devtools_inspectedWindow.html b/chrome/common/extensions/docs/templates/public/extensions/devtools_inspectedWindow.html
new file mode 100644
index 0000000..459e573
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/devtools_inspectedWindow.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.devtools/inspected_window intro:intros.devtools_inspectedWindow}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/devtools_network.html b/chrome/common/extensions/docs/templates/public/extensions/devtools_network.html
new file mode 100644
index 0000000..985744d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/devtools_network.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.devtools/network intro:intros.devtools_network}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/devtools_panels.html b/chrome/common/extensions/docs/templates/public/extensions/devtools_panels.html
new file mode 100644
index 0000000..0d7fae8
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/devtools_panels.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.devtools/panels intro:intros.devtools_panels}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/docs.html b/chrome/common/extensions/docs/templates/public/extensions/docs.html
new file mode 100644
index 0000000..f2089ae
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/docs.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.docs}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/downloads.html b/chrome/common/extensions/docs/templates/public/extensions/downloads.html
new file mode 100644
index 0000000..909d110
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/downloads.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.downloads intro:intros.downloads}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/event_pages.html b/chrome/common/extensions/docs/templates/public/extensions/event_pages.html
new file mode 100644
index 0000000..e205377
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/event_pages.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.event_pages}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/events.html b/chrome/common/extensions/docs/templates/public/extensions/events.html
new file mode 100644
index 0000000..d1eb774
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/events.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.events intro:intros.events}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental.html b/chrome/common/extensions/docs/templates/public/extensions/experimental.html
new file mode 100644
index 0000000..aebc122
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental is_apps:false}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_browsingData.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_browsingData.html
new file mode 100644
index 0000000..db17845
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_browsingData.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_browsingData}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_contentSettings.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_contentSettings.html
new file mode 100644
index 0000000..dfd3f8e
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_contentSettings.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_contentSettings}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_contextMenus.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_contextMenus.html
new file mode 100644
index 0000000..697a8ad
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_contextMenus.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_contextMenus}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_cookies.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_cookies.html
new file mode 100644
index 0000000..baad5da
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_cookies.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_cookies}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_debugger.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_debugger.html
new file mode 100644
index 0000000..782fe2f
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_debugger.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_debugger}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools.html
new file mode 100644
index 0000000..6d2581a
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_devtools}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_audits.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_audits.html
new file mode 100644
index 0000000..eca386d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_audits.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.devtools/experimental_audits intro:intros.experimental_devtools_audits}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_console.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_console.html
new file mode 100644
index 0000000..5e9e474
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_console.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.devtools/experimental_console intro:intros.experimental_devtools_console}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_inspectedWindow.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_inspectedWindow.html
new file mode 100644
index 0000000..a69609c
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_inspectedWindow.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_devtools_inspectedWindow}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_network.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_network.html
new file mode 100644
index 0000000..2812d5c
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_network.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_devtools_network}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_panels.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_panels.html
new file mode 100644
index 0000000..a083919
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_panels.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_devtools_panels}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_resources.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_resources.html
new file mode 100644
index 0000000..1e134a3
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_devtools_resources.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_devtools_resources}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_discovery.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_discovery.html
new file mode 100644
index 0000000..74e16f2
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_discovery.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.experimental_discovery intro:intros.experimental_discovery}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_history.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_history.html
new file mode 100644
index 0000000..5e6b703
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_history.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_history}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_identity.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_identity.html
new file mode 100644
index 0000000..9c9b563
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_identity.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.experimental_identity}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_infobars.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_infobars.html
new file mode 100644
index 0000000..194ecf3
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_infobars.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.experimental_infobars intro:intros.experimental_infobars}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_inputUI.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_inputUI.html
new file mode 100644
index 0000000..b530c88
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_inputUI.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_inputUI}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_offscreenTabs.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_offscreenTabs.html
new file mode 100644
index 0000000..f3540d7
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_offscreenTabs.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.experimental_offscreen_tabs}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_privacy.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_privacy.html
new file mode 100644
index 0000000..9b878ba
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_privacy.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_privacy}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_processes.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_processes.html
new file mode 100644
index 0000000..ee75b84
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_processes.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.experimental_processes}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_record.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_record.html
new file mode 100644
index 0000000..0dde07f
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_record.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.experimental_record}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_speechInput.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_speechInput.html
new file mode 100644
index 0000000..ac10ceb
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_speechInput.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.experimental_speech_input intro:intros.experimental_speechInput}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_storage.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_storage.html
new file mode 100644
index 0000000..d98bcee
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_storage.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_storage}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_systemInfo_cpu.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_systemInfo_cpu.html
new file mode 100644
index 0000000..58ff877
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_systemInfo_cpu.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.experimental_system_info_cpu}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_systemInfo_storage.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_systemInfo_storage.html
new file mode 100644
index 0000000..c87446c
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_systemInfo_storage.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.experimental_system_info_storage}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_webInspector.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_webInspector.html
new file mode 100644
index 0000000..46e98c1
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_webInspector.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_webInspector}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_webInspector_audits.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_webInspector_audits.html
new file mode 100644
index 0000000..4539a01
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_webInspector_audits.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_webInspector_audits}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_webInspector_panels.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_webInspector_panels.html
new file mode 100644
index 0000000..9841ada
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_webInspector_panels.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_webInspector_panels}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_webInspector_resources.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_webInspector_resources.html
new file mode 100644
index 0000000..45fe1a6
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_webInspector_resources.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_webInspector_resources}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/experimental_webRequest.html b/chrome/common/extensions/docs/templates/public/extensions/experimental_webRequest.html
new file mode 100644
index 0000000..083f3e5
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/experimental_webRequest.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.experimental_webRequest}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/extension.html b/chrome/common/extensions/docs/templates/public/extensions/extension.html
new file mode 100644
index 0000000..728ad75
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/extension.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.extension intro:intros.extension}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/external_extensions.html b/chrome/common/extensions/docs/templates/public/extensions/external_extensions.html
new file mode 100644
index 0000000..6503ca1
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/external_extensions.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.external_extensions}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/faq.html b/chrome/common/extensions/docs/templates/public/extensions/faq.html
new file mode 100644
index 0000000..ae611ce
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/faq.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.faq}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/fileBrowserHandler.html b/chrome/common/extensions/docs/templates/public/extensions/fileBrowserHandler.html
new file mode 100644
index 0000000..de0a261
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/fileBrowserHandler.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.file_browser_handler intro:intros.fileBrowserHandler}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/fontSettings.html b/chrome/common/extensions/docs/templates/public/extensions/fontSettings.html
new file mode 100644
index 0000000..a09fcd9
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/fontSettings.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.font_settings intro:intros.fontSettings}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/getstarted.html b/chrome/common/extensions/docs/templates/public/extensions/getstarted.html
new file mode 100644
index 0000000..2e4c001
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/getstarted.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.getstarted}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/history.html b/chrome/common/extensions/docs/templates/public/extensions/history.html
new file mode 100644
index 0000000..33d199c
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/history.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.history intro:intros.history}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/hosting.html b/chrome/common/extensions/docs/templates/public/extensions/hosting.html
new file mode 100644
index 0000000..3f3730c
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/hosting.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.hosting}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/i18n-messages.html b/chrome/common/extensions/docs/templates/public/extensions/i18n-messages.html
new file mode 100644
index 0000000..9a698e3
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/i18n-messages.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.i18n-messages}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/i18n.html b/chrome/common/extensions/docs/templates/public/extensions/i18n.html
new file mode 100644
index 0000000..3fac07c
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/i18n.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.i18n intro:intros.i18n}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/idle.html b/chrome/common/extensions/docs/templates/public/extensions/idle.html
new file mode 100644
index 0000000..19a7e96
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/idle.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.idle intro:intros.idle}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/index.html b/chrome/common/extensions/docs/templates/public/extensions/index.html
new file mode 100644
index 0000000..75cc3fa
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/index.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    {{+partials.header_head}}
+    <link href="{{static}}/css/index.css" rel="stylesheet" type="text/css">
+    <title>Google Chrome Extensions</title>
+  </head>
+  <body>
+    {{+partials.header_body title:extensions_title}}
+    <div id="gc-container">
+      <div id="gc-pagecontent">
+        <table id="index" class="simple">
+          <tr>
+            <td>
+              <h3>What are extensions?</h3>
+              <p>
+                Extensions are small software programs that
+                can modify and enhance the functionality of the Chrome browser.
+                You write them using web technologies such as
+                HTML, JavaScript, and CSS.
+              </p>
+              <p>
+                <img src="{{static}}/images/index/gmail-small.png" width="91" height="35" align="right" style="margin-top:0px; margin-left:0.5em" alt="A screenshot of an extension's icon in the browser bar">
+                Extensions have little to no user interface.
+                For example, the image to the right shows the icon
+                that provides the UI for the
+                <a href="samples.html#gmail">Gmail extension</a>.
+              </p>
+
+              <p>
+                Extensions bundle all their files
+                into a single file that the user downloads and installs.
+                This bundling means that, unlike ordinary web apps,
+                extensions don't need to depend
+                on content from the web.
+              </p>
+              <p>
+                You can distribute your extension
+                using the
+                <a href="https://chrome.google.com/webstore/developer/dashboard">Chrome Developer Dashboard</a>
+                to publish to the
+                <a href="http://chrome.google.com/webstore">Chrome Web Store</a>.
+                For more information, see the
+                <a href="http://code.google.com/chrome/webstore">store developer documentation</a>.
+              </p>
+            </td>
+            <td id="howDoIStart">
+              <h3>How do I start?</h3>
+              <p></p>
+              <ol>
+                <li>
+                  Follow the <a href="getstarted.html">Getting Started tutorial</a>
+                </li>
+                <li>
+                  Read the
+                  <a href="overview.html">Overview</a>
+                </li>
+                <li>
+                  Keep up-to-date by reading the
+                  <a href="http://blog.chromium.org/">Chromium blog</a>
+                </li>
+                <li>
+                  Subscribe to the
+                  <a href="http://groups.google.com/a/chromium.org/group/chromium-extensions">chromium-extensions group</a>
+                </li>
+              </ol>
+              <p></p>
+              <h3>Featured videos</h3>
+              <p>
+                <a href="http://www.youtube.com/view_play_list?p=CA101D6A85FE9D4B">Technical videos</a> <br>
+                <a href="http://www.youtube.com/view_play_list?p=38DF05697DE372B1">Developer snapshots</a> (below)
+              </p>
+              <p>
+                <iframe title="YouTube video player" width="300" height="199" src="http://www.youtube.com/embed/wRDPTnY3yO8?rel=0" frameborder="0" allowfullscreen=""></iframe>
+              </p>
+            </td>
+          </tr>
+        </table>
+      </div>
+    </div>
+  </body>
+  {{+partials.extensions_footer}}
+</html>
diff --git a/chrome/common/extensions/docs/templates/public/extensions/input_ime.html b/chrome/common/extensions/docs/templates/public/extensions/input_ime.html
new file mode 100644
index 0000000..1f5622d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/input_ime.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.input_ime intro:intros.input_ime}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/management.html b/chrome/common/extensions/docs/templates/public/extensions/management.html
new file mode 100644
index 0000000..8d9b334
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/management.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.management intro:intros.management}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/manifest.html b/chrome/common/extensions/docs/templates/public/extensions/manifest.html
new file mode 100644
index 0000000..7f40b8d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/manifest.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.manifest}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/manifestVersion.html b/chrome/common/extensions/docs/templates/public/extensions/manifestVersion.html
new file mode 100644
index 0000000..61f21a5
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/manifestVersion.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.manifestVersion}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/match_patterns.html b/chrome/common/extensions/docs/templates/public/extensions/match_patterns.html
new file mode 100644
index 0000000..6fb437c
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/match_patterns.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.match_patterns}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/messaging.html b/chrome/common/extensions/docs/templates/public/extensions/messaging.html
new file mode 100644
index 0000000..bd9d8bf
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/messaging.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.messaging}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/notifications.html b/chrome/common/extensions/docs/templates/public/extensions/notifications.html
new file mode 100644
index 0000000..72a57bc
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/notifications.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.notifications}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/npapi.html b/chrome/common/extensions/docs/templates/public/extensions/npapi.html
new file mode 100644
index 0000000..81dd45a
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/npapi.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.npapi}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/omnibox.html b/chrome/common/extensions/docs/templates/public/extensions/omnibox.html
new file mode 100644
index 0000000..565382b
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/omnibox.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.omnibox intro:intros.omnibox}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/options.html b/chrome/common/extensions/docs/templates/public/extensions/options.html
new file mode 100644
index 0000000..3825d01
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/options.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.options}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/override.html b/chrome/common/extensions/docs/templates/public/extensions/override.html
new file mode 100644
index 0000000..bcc9123
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/override.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.override}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/overview.html b/chrome/common/extensions/docs/templates/public/extensions/overview.html
new file mode 100644
index 0000000..b1511c8
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/overview.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.overview}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/packaging.html b/chrome/common/extensions/docs/templates/public/extensions/packaging.html
new file mode 100644
index 0000000..8483d40
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/packaging.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.packaging}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/pageAction.html b/chrome/common/extensions/docs/templates/public/extensions/pageAction.html
new file mode 100644
index 0000000..2889fcb
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/pageAction.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.page_action intro:intros.pageAction}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/pageCapture.html b/chrome/common/extensions/docs/templates/public/extensions/pageCapture.html
new file mode 100644
index 0000000..7a43702
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/pageCapture.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.page_capture intro:intros.pageCapture}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/permission_warnings.html b/chrome/common/extensions/docs/templates/public/extensions/permission_warnings.html
new file mode 100644
index 0000000..d89cc33
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/permission_warnings.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.permission_warnings}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/permissions.html b/chrome/common/extensions/docs/templates/public/extensions/permissions.html
new file mode 100644
index 0000000..83b21d3
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/permissions.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.permissions intro:intros.permissions}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/privacy.html b/chrome/common/extensions/docs/templates/public/extensions/privacy.html
new file mode 100644
index 0000000..08152f9
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/privacy.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.privacy intro:intros.privacy}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/proxy.html b/chrome/common/extensions/docs/templates/public/extensions/proxy.html
new file mode 100644
index 0000000..8c240e8
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/proxy.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.proxy intro:intros.proxy}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/pushMessaging.html b/chrome/common/extensions/docs/templates/public/extensions/pushMessaging.html
new file mode 100644
index 0000000..77ff2e4
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/pushMessaging.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.pushMessaging}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/runtime.html b/chrome/common/extensions/docs/templates/public/extensions/runtime.html
new file mode 100644
index 0000000..298cc9c
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/runtime.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.runtime}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/samples.html b/chrome/common/extensions/docs/templates/public/extensions/samples.html
new file mode 100644
index 0000000..feb5e71
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/samples.html
@@ -0,0 +1,5 @@
+{{+partials.samples sidenav:partials.extensions_sidenav
+                    filter_list:api_list.extensions.chrome
+                    samples_list:samples.extensions
+                    footer:partials.extensions_footer
+                    title:extensions_title}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/sandboxingEval.html b/chrome/common/extensions/docs/templates/public/extensions/sandboxingEval.html
new file mode 100644
index 0000000..019a38d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/sandboxingEval.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.sandboxingEval}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/scriptBadge.html b/chrome/common/extensions/docs/templates/public/extensions/scriptBadge.html
new file mode 100644
index 0000000..43a090a
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/scriptBadge.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.script_badge}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/search_results.html b/chrome/common/extensions/docs/templates/public/extensions/search_results.html
new file mode 100644
index 0000000..fadc624
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/search_results.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.search_results}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/storage.html b/chrome/common/extensions/docs/templates/public/extensions/storage.html
new file mode 100644
index 0000000..b0b0834
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/storage.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.storage intro:intros.storage}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/tabs.html b/chrome/common/extensions/docs/templates/public/extensions/tabs.html
new file mode 100644
index 0000000..04938b8
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/tabs.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.tabs intro:intros.tabs}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/themes.html b/chrome/common/extensions/docs/templates/public/extensions/themes.html
new file mode 100644
index 0000000..5704ff5
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/themes.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.themes}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/topSites.html b/chrome/common/extensions/docs/templates/public/extensions/topSites.html
new file mode 100644
index 0000000..478945d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/topSites.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.top_sites intro:intros.topSites}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/tts.html b/chrome/common/extensions/docs/templates/public/extensions/tts.html
new file mode 100644
index 0000000..08077a5
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/tts.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.tts intro:intros.tts}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/ttsEngine.html b/chrome/common/extensions/docs/templates/public/extensions/ttsEngine.html
new file mode 100644
index 0000000..a8928cd
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/ttsEngine.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.tts_engine intro:intros.ttsEngine}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/tut_analytics.html b/chrome/common/extensions/docs/templates/public/extensions/tut_analytics.html
new file mode 100644
index 0000000..3f967e1
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/tut_analytics.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.tut_analytics}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/tut_debugging.html b/chrome/common/extensions/docs/templates/public/extensions/tut_debugging.html
new file mode 100644
index 0000000..0605ef5
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/tut_debugging.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.tut_debugging}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/tut_migration_to_manifest_v2.html b/chrome/common/extensions/docs/templates/public/extensions/tut_migration_to_manifest_v2.html
new file mode 100644
index 0000000..a05bfca
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/tut_migration_to_manifest_v2.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.tut_migration_to_manifest_v2}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/tut_oauth.html b/chrome/common/extensions/docs/templates/public/extensions/tut_oauth.html
new file mode 100644
index 0000000..d7ba70a
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/tut_oauth.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.tut_oauth}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/tutorials.html b/chrome/common/extensions/docs/templates/public/extensions/tutorials.html
new file mode 100644
index 0000000..50bcb7c
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/tutorials.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.tutorials}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/types.html b/chrome/common/extensions/docs/templates/public/extensions/types.html
new file mode 100644
index 0000000..1044dff
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/types.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.types intro:intros.types}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/webNavigation.html b/chrome/common/extensions/docs/templates/public/extensions/webNavigation.html
new file mode 100644
index 0000000..11d8ac3
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/webNavigation.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.web_navigation intro:intros.webNavigation}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/webRequest.html b/chrome/common/extensions/docs/templates/public/extensions/webRequest.html
new file mode 100644
index 0000000..d282267
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/webRequest.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.web_request intro:intros.webRequest}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/webstore.html b/chrome/common/extensions/docs/templates/public/extensions/webstore.html
new file mode 100644
index 0000000..728616d
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/webstore.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.webstore}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/whats_new.html b/chrome/common/extensions/docs/templates/public/extensions/whats_new.html
new file mode 100644
index 0000000..af165a2
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/whats_new.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.whats_new}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/windows.html b/chrome/common/extensions/docs/templates/public/extensions/windows.html
new file mode 100644
index 0000000..8f32cf4
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/windows.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_api api:apis.windows intro:intros.windows}}
diff --git a/chrome/common/extensions/docs/templates/public/extensions/xhr.html b/chrome/common/extensions/docs/templates/public/extensions/xhr.html
new file mode 100644
index 0000000..fe158ef
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/extensions/xhr.html
@@ -0,0 +1 @@
+{{+partials.standard_extensions_article article:intros.xhr}}
diff --git a/chrome/common/extensions/draggable_region.cc b/chrome/common/extensions/draggable_region.cc
new file mode 100644
index 0000000..0556e72
--- /dev/null
+++ b/chrome/common/extensions/draggable_region.cc
@@ -0,0 +1,13 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/draggable_region.h"
+
+namespace extensions {
+
+DraggableRegion::DraggableRegion()
+    : draggable(false) {
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/draggable_region.h b/chrome/common/extensions/draggable_region.h
new file mode 100644
index 0000000..e5a3c5f
--- /dev/null
+++ b/chrome/common/extensions/draggable_region.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_DRAGGABLE_REGION_H_
+#define CHROME_COMMON_EXTENSIONS_DRAGGABLE_REGION_H_
+
+#include "ui/gfx/rect.h"
+
+namespace extensions {
+
+struct DraggableRegion {
+  bool draggable;
+  gfx::Rect bounds;
+
+  DraggableRegion();
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_DRAGGABLE_REGION_H_
diff --git a/chrome/common/extensions/event_filter.cc b/chrome/common/extensions/event_filter.cc
new file mode 100644
index 0000000..ca23eee
--- /dev/null
+++ b/chrome/common/extensions/event_filter.cc
@@ -0,0 +1,172 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/event_filter.h"
+
+#include "chrome/common/extensions/matcher/url_matcher_factory.h"
+
+namespace extensions {
+
+EventFilter::EventMatcherEntry::EventMatcherEntry(
+    scoped_ptr<EventMatcher> event_matcher,
+    URLMatcher* url_matcher,
+    const URLMatcherConditionSet::Vector& condition_sets)
+    : event_matcher_(event_matcher.Pass()),
+      url_matcher_(url_matcher) {
+  for (URLMatcherConditionSet::Vector::const_iterator it =
+       condition_sets.begin(); it != condition_sets.end(); it++)
+    condition_set_ids_.push_back((*it)->id());
+  url_matcher_->AddConditionSets(condition_sets);
+}
+
+EventFilter::EventMatcherEntry::~EventMatcherEntry() {
+  url_matcher_->RemoveConditionSets(condition_set_ids_);
+}
+
+void EventFilter::EventMatcherEntry::DontRemoveConditionSetsInDestructor() {
+  condition_set_ids_.clear();
+}
+
+EventFilter::EventFilter()
+    : next_id_(0),
+      next_condition_set_id_(0) {
+}
+
+EventFilter::~EventFilter() {
+  // Normally when an event matcher entry is removed from event_matchers_ it
+  // will remove its condition sets from url_matcher_, but as url_matcher_ is
+  // being destroyed anyway there is no need to do that step here.
+  for (EventMatcherMultiMap::iterator it = event_matchers_.begin();
+       it != event_matchers_.end(); it++) {
+    for (EventMatcherMap::iterator it2 = it->second.begin();
+         it2 != it->second.end(); it2++) {
+      it2->second->DontRemoveConditionSetsInDestructor();
+    }
+  }
+}
+
+EventFilter::MatcherID
+EventFilter::AddEventMatcher(const std::string& event_name,
+                             scoped_ptr<EventMatcher> matcher) {
+  MatcherID id = next_id_++;
+  URLMatcherConditionSet::Vector condition_sets;
+  if (!CreateConditionSets(id, matcher.get(), &condition_sets))
+    return -1;
+
+  for (URLMatcherConditionSet::Vector::iterator it = condition_sets.begin();
+       it != condition_sets.end(); it++) {
+    condition_set_id_to_event_matcher_id_.insert(
+        std::make_pair((*it)->id(), id));
+  }
+  id_to_event_name_[id] = event_name;
+  event_matchers_[event_name][id] = linked_ptr<EventMatcherEntry>(
+      new EventMatcherEntry(matcher.Pass(), &url_matcher_, condition_sets));
+  return id;
+}
+
+EventMatcher* EventFilter::GetEventMatcher(MatcherID id) {
+  DCHECK(id_to_event_name_.find(id) != id_to_event_name_.end());
+  const std::string& event_name = id_to_event_name_[id];
+  return event_matchers_[event_name][id]->event_matcher();
+}
+
+const std::string& EventFilter::GetEventName(MatcherID id) {
+  DCHECK(id_to_event_name_.find(id) != id_to_event_name_.end());
+  return id_to_event_name_[id];
+}
+
+bool EventFilter::CreateConditionSets(
+    MatcherID id,
+    EventMatcher* matcher,
+    URLMatcherConditionSet::Vector* condition_sets) {
+  if (matcher->GetURLFilterCount() == 0) {
+    // If there are no URL filters then we want to match all events, so create a
+    // URLFilter from an empty dictionary.
+    base::DictionaryValue empty_dict;
+    return AddDictionaryAsConditionSet(&empty_dict, condition_sets);
+  }
+  for (int i = 0; i < matcher->GetURLFilterCount(); i++) {
+    base::DictionaryValue* url_filter;
+    if (!matcher->GetURLFilter(i, &url_filter))
+      return false;
+    if (!AddDictionaryAsConditionSet(url_filter, condition_sets))
+      return false;
+  }
+  return true;
+}
+
+bool EventFilter::AddDictionaryAsConditionSet(
+    base::DictionaryValue* url_filter,
+    URLMatcherConditionSet::Vector* condition_sets) {
+  std::string error;
+  URLMatcherConditionSet::ID condition_set_id = next_condition_set_id_++;
+  condition_sets->push_back(URLMatcherFactory::CreateFromURLFilterDictionary(
+      url_matcher_.condition_factory(),
+      url_filter,
+      condition_set_id,
+      &error));
+  if (!error.empty()) {
+    LOG(ERROR) << "CreateFromURLFilterDictionary failed: " << error;
+    url_matcher_.ClearUnusedConditionSets();
+    condition_sets->clear();
+    return false;
+  }
+  return true;
+}
+
+std::string EventFilter::RemoveEventMatcher(MatcherID id) {
+  std::map<MatcherID, std::string>::iterator it = id_to_event_name_.find(id);
+  std::string event_name = it->second;
+  // EventMatcherEntry's destructor causes the condition set ids to be removed
+  // from url_matcher_.
+  event_matchers_[event_name].erase(id);
+  id_to_event_name_.erase(it);
+  return event_name;
+}
+
+std::set<EventFilter::MatcherID> EventFilter::MatchEvent(
+    const std::string& event_name, const EventFilteringInfo& event_info) {
+  std::set<MatcherID> matchers;
+  EventMatcherMultiMap::iterator it = event_matchers_.find(event_name);
+  if (it == event_matchers_.end())
+    return matchers;
+
+  EventMatcherMap& matcher_map = it->second;
+  GURL url_to_match_against = event_info.has_url() ? event_info.url() : GURL();
+  std::set<URLMatcherConditionSet::ID> matching_condition_set_ids =
+      url_matcher_.MatchURL(url_to_match_against);
+  for (std::set<URLMatcherConditionSet::ID>::iterator it =
+       matching_condition_set_ids.begin();
+       it != matching_condition_set_ids.end(); it++) {
+    std::map<URLMatcherConditionSet::ID, MatcherID>::iterator matcher_id =
+        condition_set_id_to_event_matcher_id_.find(*it);
+    if (matcher_id == condition_set_id_to_event_matcher_id_.end()) {
+      NOTREACHED() << "id not found in condition set map (" << (*it) << ")";
+      continue;
+    }
+    MatcherID id = matcher_id->second;
+    EventMatcherMap::iterator matcher_entry = matcher_map.find(id);
+    if (matcher_entry == matcher_map.end()) {
+      // Matcher must be for a different event.
+      continue;
+    }
+    const EventMatcher* event_matcher = matcher_entry->second->event_matcher();
+    if (event_matcher->MatchNonURLCriteria(event_info)) {
+      CHECK(!event_matcher->HasURLFilters() || event_info.has_url());
+      matchers.insert(id);
+    }
+  }
+
+  return matchers;
+}
+
+int EventFilter::GetMatcherCountForEvent(const std::string& name) {
+  EventMatcherMultiMap::const_iterator it = event_matchers_.find(name);
+  if (it == event_matchers_.end())
+    return 0;
+
+  return it->second.size();
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/event_filter.h b/chrome/common/extensions/event_filter.h
new file mode 100644
index 0000000..59a6b3c
--- /dev/null
+++ b/chrome/common/extensions/event_filter.h
@@ -0,0 +1,126 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EVENT_FILTER_H_
+#define CHROME_COMMON_EXTENSIONS_EVENT_FILTER_H_
+
+#include "base/memory/linked_ptr.h"
+#include "chrome/common/extensions/event_matcher.h"
+#include "chrome/common/extensions/event_filtering_info.h"
+#include "chrome/common/extensions/matcher/url_matcher.h"
+
+#include <map>
+#include <set>
+
+namespace extensions {
+
+// Matches incoming events against a collection of EventMatchers. Each added
+// EventMatcher is given an id which is returned by MatchEvent() when it is
+// passed a matching event.
+class EventFilter {
+ public:
+  typedef int MatcherID;
+  EventFilter();
+  ~EventFilter();
+
+  // Adds an event matcher that will be used in calls to MatchEvent(). Returns
+  // the id of the matcher, or -1 if there was an error.
+  MatcherID AddEventMatcher(const std::string& event_name,
+                            scoped_ptr<EventMatcher> matcher);
+
+  // Retrieve the EventMatcher with the given id.
+  EventMatcher* GetEventMatcher(MatcherID id);
+
+  // Retrieve the name of the event that the EventMatcher specified by |id| is
+  // referring to.
+  const std::string& GetEventName(MatcherID id);
+
+  // Removes an event matcher, returning the name of the event that it was for.
+  std::string RemoveEventMatcher(MatcherID id);
+
+  // Match an event named |event_name| with filtering info |event_info| against
+  // our set of event matchers. Returns a set of ids that correspond to the
+  // event matchers that matched the event.
+  // TODO(koz): Add a std::string* parameter for retrieving error messages.
+  std::set<MatcherID> MatchEvent(const std::string& event_name,
+                                 const EventFilteringInfo& event_info);
+
+  int GetMatcherCountForEvent(const std::string& event_name);
+
+  // For testing.
+  bool IsURLMatcherEmpty() const {
+    return url_matcher_.IsEmpty();
+  }
+
+ private:
+  class EventMatcherEntry {
+   public:
+    // Adds |condition_sets| to |url_matcher| on construction and removes them
+    // again on destruction. |condition_sets| should be the
+    // URLMatcherConditionSets that match the URL constraints specified by
+    // |event_matcher|.
+    EventMatcherEntry(scoped_ptr<EventMatcher> event_matcher,
+                      URLMatcher* url_matcher,
+                      const URLMatcherConditionSet::Vector& condition_sets);
+    ~EventMatcherEntry();
+
+    // Prevents the removal of condition sets when this class is destroyed. We
+    // call this in EventFilter's destructor so that we don't do the costly
+    // removal of condition sets when the URLMatcher is going to be destroyed
+    // and clean them up anyway.
+    void DontRemoveConditionSetsInDestructor();
+
+    EventMatcher* event_matcher() {
+      return event_matcher_.get();
+    }
+
+   private:
+    scoped_ptr<EventMatcher> event_matcher_;
+    // The id sets in url_matcher_ that this EventMatcher owns.
+    std::vector<URLMatcherConditionSet::ID> condition_set_ids_;
+    URLMatcher* url_matcher_;
+
+    DISALLOW_COPY_AND_ASSIGN(EventMatcherEntry);
+  };
+
+  // Maps from a matcher id to an event matcher entry.
+  typedef std::map<MatcherID, linked_ptr<EventMatcherEntry> > EventMatcherMap;
+
+  // Maps from event name to the map of matchers that are registered for it.
+  typedef std::map<std::string, EventMatcherMap> EventMatcherMultiMap;
+
+  // Adds the list of URL filters in |matcher| to the URL matcher, having
+  // matches for those URLs map to |id|.
+  bool CreateConditionSets(MatcherID id,
+                           EventMatcher* matcher,
+                           URLMatcherConditionSet::Vector* condition_sets);
+
+  bool AddDictionaryAsConditionSet(
+      base::DictionaryValue* url_filter,
+      URLMatcherConditionSet::Vector* condition_sets);
+
+  URLMatcher url_matcher_;
+  EventMatcherMultiMap event_matchers_;
+
+  // The next id to assign to an EventMatcher.
+  MatcherID next_id_;
+
+  // The next id to assign to a condition set passed to URLMatcher.
+  URLMatcherConditionSet::ID next_condition_set_id_;
+
+  // Maps condition set ids, which URLMatcher operates in, to event matcher
+  // ids, which the interface to this class operates in. As each EventFilter
+  // can specify many condition sets this is a many to one relationship.
+  std::map<URLMatcherConditionSet::ID, MatcherID>
+      condition_set_id_to_event_matcher_id_;
+
+  // Maps from event matcher ids to the name of the event they match on.
+  std::map<MatcherID, std::string> id_to_event_name_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventFilter);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_EVENT_FILTER_H_
diff --git a/chrome/common/extensions/event_filter_unittest.cc b/chrome/common/extensions/event_filter_unittest.cc
new file mode 100644
index 0000000..a4ac8ee
--- /dev/null
+++ b/chrome/common/extensions/event_filter_unittest.cc
@@ -0,0 +1,244 @@
+// Copyright (c) 2012 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.
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "chrome/common/extensions/event_filter.h"
+#include "chrome/common/extensions/event_filtering_info.h"
+#include "chrome/common/extensions/event_matcher.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+class EventFilterUnittest : public testing::Test {
+ public:
+  EventFilterUnittest() {
+    google_event_.SetURL(GURL("http://google.com"));
+    yahoo_event_.SetURL(GURL("http://yahoo.com"));
+    random_url_event_.SetURL(GURL("http://www.something-else.com"));
+    empty_url_event_.SetURL(GURL());
+  }
+
+ protected:
+  scoped_ptr<base::Value> HostSuffixDict(const std::string& host_suffix) {
+    scoped_ptr<base::DictionaryValue> dict(new DictionaryValue());
+    dict->Set("hostSuffix", base::Value::CreateStringValue(host_suffix));
+    return scoped_ptr<base::Value>(dict.release());
+  }
+
+  scoped_ptr<base::ListValue> ValueAsList(scoped_ptr<base::Value> value) {
+    scoped_ptr<base::ListValue> result(new base::ListValue());
+    result->Append(value.release());
+    return result.Pass();
+  }
+
+  scoped_ptr<EventMatcher> AllURLs() {
+    return scoped_ptr<EventMatcher>(new EventMatcher(
+        scoped_ptr<DictionaryValue>(new DictionaryValue)));
+  }
+
+  scoped_ptr<EventMatcher> HostSuffixMatcher(const std::string& host_suffix) {
+    return MatcherFromURLFilterList(ValueAsList(HostSuffixDict(host_suffix)));
+  }
+
+  scoped_ptr<EventMatcher> MatcherFromURLFilterList(
+      scoped_ptr<ListValue> url_filter_list) {
+    scoped_ptr<DictionaryValue> filter_dict(new DictionaryValue);
+    filter_dict->Set("url", url_filter_list.release());
+    return scoped_ptr<EventMatcher>(new EventMatcher(filter_dict.Pass()));
+  }
+
+  EventFilter event_filter_;
+  EventFilteringInfo empty_event_;
+  EventFilteringInfo google_event_;
+  EventFilteringInfo yahoo_event_;
+  EventFilteringInfo random_url_event_;
+  EventFilteringInfo empty_url_event_;
+};
+
+TEST_F(EventFilterUnittest, NoMatchersMatchIfEmpty) {
+  std::set<int> matches = event_filter_.MatchEvent("some-event",
+                                                   empty_event_);
+  ASSERT_EQ(0u, matches.size());
+}
+
+TEST_F(EventFilterUnittest, AddingEventMatcherDoesntCrash) {
+  event_filter_.AddEventMatcher("event1", AllURLs());
+}
+
+TEST_F(EventFilterUnittest,
+    DontMatchAgainstMatchersForDifferentEvents) {
+  event_filter_.AddEventMatcher("event1", AllURLs());
+  std::set<int> matches = event_filter_.MatchEvent("event2",
+                                                   empty_event_);
+  ASSERT_EQ(0u, matches.size());
+}
+
+TEST_F(EventFilterUnittest, DoMatchAgainstMatchersForSameEvent) {
+  int id = event_filter_.AddEventMatcher("event1", AllURLs());
+  std::set<int> matches = event_filter_.MatchEvent("event1",
+      google_event_);
+  ASSERT_EQ(1u, matches.size());
+  ASSERT_EQ(1u, matches.count(id));
+}
+
+TEST_F(EventFilterUnittest, DontMatchUnlessMatcherMatches) {
+  EventFilteringInfo info;
+  info.SetURL(GURL("http://www.yahoo.com"));
+  event_filter_.AddEventMatcher("event1", HostSuffixMatcher("google.com"));
+  std::set<int> matches = event_filter_.MatchEvent("event1", info);
+  ASSERT_TRUE(matches.empty());
+}
+
+TEST_F(EventFilterUnittest, RemovingAnEventMatcherStopsItMatching) {
+  int id = event_filter_.AddEventMatcher("event1", AllURLs());
+  event_filter_.RemoveEventMatcher(id);
+  std::set<int> matches = event_filter_.MatchEvent("event1",
+                                                   empty_event_);
+  ASSERT_TRUE(matches.empty());
+}
+
+TEST_F(EventFilterUnittest, MultipleEventMatches) {
+  int id1 = event_filter_.AddEventMatcher("event1", AllURLs());
+  int id2 = event_filter_.AddEventMatcher("event1", AllURLs());
+  std::set<int> matches = event_filter_.MatchEvent("event1",
+      google_event_);
+  ASSERT_EQ(2u, matches.size());
+  ASSERT_EQ(1u, matches.count(id1));
+  ASSERT_EQ(1u, matches.count(id2));
+}
+
+TEST_F(EventFilterUnittest, TestURLMatching) {
+  EventFilteringInfo info;
+  info.SetURL(GURL("http://www.google.com"));
+  int id = event_filter_.AddEventMatcher("event1",
+                                         HostSuffixMatcher("google.com"));
+  std::set<int> matches = event_filter_.MatchEvent("event1", info);
+  ASSERT_EQ(1u, matches.size());
+  ASSERT_EQ(1u, matches.count(id));
+}
+
+TEST_F(EventFilterUnittest, TestMultipleURLFiltersMatchOnAny) {
+  scoped_ptr<base::ListValue> filters(new base::ListValue());
+  filters->Append(HostSuffixDict("google.com").release());
+  filters->Append(HostSuffixDict("yahoo.com").release());
+
+  scoped_ptr<EventMatcher> matcher(MatcherFromURLFilterList(filters.Pass()));
+  int id = event_filter_.AddEventMatcher("event1", matcher.Pass());
+
+  {
+    std::set<int> matches = event_filter_.MatchEvent("event1",
+        google_event_);
+    ASSERT_EQ(1u, matches.size());
+    ASSERT_EQ(1u, matches.count(id));
+  }
+  {
+    std::set<int> matches = event_filter_.MatchEvent("event1",
+        yahoo_event_);
+    ASSERT_EQ(1u, matches.size());
+    ASSERT_EQ(1u, matches.count(id));
+  }
+  {
+    std::set<int> matches = event_filter_.MatchEvent("event1",
+        random_url_event_);
+    ASSERT_EQ(0u, matches.size());
+  }
+}
+
+TEST_F(EventFilterUnittest, TestStillMatchesAfterRemoval) {
+  int id1 = event_filter_.AddEventMatcher("event1", AllURLs());
+  int id2 = event_filter_.AddEventMatcher("event1", AllURLs());
+
+  event_filter_.RemoveEventMatcher(id1);
+  {
+    std::set<int> matches = event_filter_.MatchEvent("event1",
+        google_event_);
+    ASSERT_EQ(1u, matches.size());
+    ASSERT_EQ(1u, matches.count(id2));
+  }
+}
+
+TEST_F(EventFilterUnittest, TestMatchesOnlyAgainstPatternsForCorrectEvent) {
+  int id1 = event_filter_.AddEventMatcher("event1", AllURLs());
+  event_filter_.AddEventMatcher("event2", AllURLs());
+
+  {
+    std::set<int> matches = event_filter_.MatchEvent("event1",
+        google_event_);
+    ASSERT_EQ(1u, matches.size());
+    ASSERT_EQ(1u, matches.count(id1));
+  }
+}
+
+TEST_F(EventFilterUnittest, TestGetMatcherCountForEvent) {
+  ASSERT_EQ(0, event_filter_.GetMatcherCountForEvent("event1"));
+  int id1 = event_filter_.AddEventMatcher("event1", AllURLs());
+  ASSERT_EQ(1, event_filter_.GetMatcherCountForEvent("event1"));
+  int id2 = event_filter_.AddEventMatcher("event1", AllURLs());
+  ASSERT_EQ(2, event_filter_.GetMatcherCountForEvent("event1"));
+  event_filter_.RemoveEventMatcher(id1);
+  ASSERT_EQ(1, event_filter_.GetMatcherCountForEvent("event1"));
+  event_filter_.RemoveEventMatcher(id2);
+  ASSERT_EQ(0, event_filter_.GetMatcherCountForEvent("event1"));
+}
+
+TEST_F(EventFilterUnittest, RemoveEventMatcherReturnsEventName) {
+  int id1 = event_filter_.AddEventMatcher("event1", AllURLs());
+  int id2 = event_filter_.AddEventMatcher("event1", AllURLs());
+  int id3 = event_filter_.AddEventMatcher("event2", AllURLs());
+
+  ASSERT_EQ("event1", event_filter_.RemoveEventMatcher(id1));
+  ASSERT_EQ("event1", event_filter_.RemoveEventMatcher(id2));
+  ASSERT_EQ("event2", event_filter_.RemoveEventMatcher(id3));
+}
+
+TEST_F(EventFilterUnittest, InvalidURLFilterCantBeAdded) {
+  scoped_ptr<base::ListValue> filter_list(new base::ListValue());
+  filter_list->Append(new base::ListValue());  // Should be a dict.
+  scoped_ptr<EventMatcher> matcher(MatcherFromURLFilterList(
+      filter_list.Pass()));
+  int id1 = event_filter_.AddEventMatcher("event1", matcher.Pass());
+  EXPECT_TRUE(event_filter_.IsURLMatcherEmpty());
+  ASSERT_EQ(-1, id1);
+}
+
+TEST_F(EventFilterUnittest, EmptyListOfURLFiltersMatchesAllURLs) {
+  scoped_ptr<base::ListValue> filter_list(new base::ListValue());
+  scoped_ptr<EventMatcher> matcher(MatcherFromURLFilterList(
+      scoped_ptr<ListValue>(new ListValue)));
+  int id = event_filter_.AddEventMatcher("event1", matcher.Pass());
+  std::set<int> matches = event_filter_.MatchEvent("event1",
+      google_event_);
+  ASSERT_EQ(1u, matches.size());
+  ASSERT_EQ(1u, matches.count(id));
+}
+
+TEST_F(EventFilterUnittest,
+    InternalURLMatcherShouldBeEmptyWhenThereAreNoEventMatchers) {
+  ASSERT_TRUE(event_filter_.IsURLMatcherEmpty());
+  int id = event_filter_.AddEventMatcher("event1",
+                                         HostSuffixMatcher("google.com"));
+  ASSERT_FALSE(event_filter_.IsURLMatcherEmpty());
+  event_filter_.RemoveEventMatcher(id);
+  ASSERT_TRUE(event_filter_.IsURLMatcherEmpty());
+}
+
+TEST_F(EventFilterUnittest, EmptyURLsShouldBeMatchedByEmptyURLFilters) {
+  int id = event_filter_.AddEventMatcher("event1", AllURLs());
+  std::set<int> matches = event_filter_.MatchEvent("event1", empty_url_event_);
+  ASSERT_EQ(1u, matches.size());
+  ASSERT_EQ(1u, matches.count(id));
+}
+
+TEST_F(EventFilterUnittest,
+    EmptyURLsShouldBeMatchedByEmptyURLFiltersWithAnEmptyItem) {
+  scoped_ptr<EventMatcher> matcher(MatcherFromURLFilterList(ValueAsList(
+      scoped_ptr<Value>(new DictionaryValue()))));
+  int id = event_filter_.AddEventMatcher("event1", matcher.Pass());
+  std::set<int> matches = event_filter_.MatchEvent("event1", empty_url_event_);
+  ASSERT_EQ(1u, matches.size());
+  ASSERT_EQ(1u, matches.count(id));
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/event_filtering_info.cc b/chrome/common/extensions/event_filtering_info.cc
new file mode 100644
index 0000000..1e30283
--- /dev/null
+++ b/chrome/common/extensions/event_filtering_info.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/event_filtering_info.h"
+
+#include "base/values.h"
+#include "base/json/json_writer.h"
+
+namespace extensions {
+
+EventFilteringInfo::EventFilteringInfo()
+    : has_url_(false) {
+}
+
+EventFilteringInfo::~EventFilteringInfo() {
+}
+
+void EventFilteringInfo::SetURL(const GURL& url) {
+  url_ = url;
+  has_url_ = true;
+}
+
+std::string EventFilteringInfo::AsJSONString() const {
+  std::string result;
+  base::DictionaryValue value;
+  if (has_url_)
+    value.SetString("url", url_.spec());
+
+  base::JSONWriter::Write(&value, &result);
+  return result;
+}
+
+scoped_ptr<base::Value> EventFilteringInfo::AsValue() const {
+  if (IsEmpty())
+    return scoped_ptr<base::Value>(base::Value::CreateNullValue());
+
+  scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue);
+  if (has_url_)
+    result->SetString("url", url_.spec());
+  return result.PassAs<base::Value>();
+}
+
+bool EventFilteringInfo::IsEmpty() const {
+  return !has_url_;
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/event_filtering_info.h b/chrome/common/extensions/event_filtering_info.h
new file mode 100644
index 0000000..8525241
--- /dev/null
+++ b/chrome/common/extensions/event_filtering_info.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EVENT_FILTERING_INFO_H_
+#define CHROME_COMMON_EXTENSIONS_EVENT_FILTERING_INFO_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "googleurl/src/gurl.h"
+#include "v8/include/v8.h"
+
+namespace base {
+class Value;
+}
+
+namespace extensions {
+
+// Extra information about an event that is used in event filtering.
+//
+// This is the information that is matched against criteria specified in JS
+// extension event listeners. Eg:
+//
+// chrome.someApi.onSomeEvent.addListener(cb,
+//                                        {url: [{hostSuffix: 'google.com'}],
+//                                         tabId: 1});
+class EventFilteringInfo {
+ public:
+  EventFilteringInfo();
+  ~EventFilteringInfo();
+  void SetURL(const GURL& url);
+
+  bool has_url() const { return has_url_; }
+  const GURL& url() const { return url_; }
+
+  std::string AsJSONString() const;
+  scoped_ptr<base::Value> AsValue() const;
+  bool IsEmpty() const;
+
+ private:
+  bool has_url_;
+  GURL url_;
+
+  // Allow implicit copy and assignment.
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_EVENT_FILTERING_INFO_H_
diff --git a/chrome/common/extensions/event_matcher.cc b/chrome/common/extensions/event_matcher.cc
new file mode 100644
index 0000000..8a64f78
--- /dev/null
+++ b/chrome/common/extensions/event_matcher.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/event_matcher.h"
+#include "chrome/common/extensions/event_filtering_info.h"
+
+namespace {
+const char kUrlFiltersKey[] = "url";
+}
+
+namespace extensions {
+
+EventMatcher::EventMatcher(scoped_ptr<base::DictionaryValue> filter)
+    : filter_(filter.Pass()) {
+}
+
+EventMatcher::~EventMatcher() {
+}
+
+bool EventMatcher::MatchNonURLCriteria(
+    const EventFilteringInfo& event_info) const {
+  // There is currently no criteria apart from URL criteria.
+  return true;
+}
+
+int EventMatcher::GetURLFilterCount() const {
+  base::ListValue* url_filters = NULL;
+  if (filter_->GetList(kUrlFiltersKey, &url_filters))
+    return url_filters->GetSize();
+  return 0;
+}
+
+bool EventMatcher::GetURLFilter(int i, base::DictionaryValue** url_filter_out) {
+  base::ListValue* url_filters = NULL;
+  if (filter_->GetList(kUrlFiltersKey, &url_filters)) {
+    return url_filters->GetDictionary(i, url_filter_out);
+  }
+  return false;
+}
+
+int EventMatcher::HasURLFilters() const {
+  return GetURLFilterCount() != 0;
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/event_matcher.h b/chrome/common/extensions/event_matcher.h
new file mode 100644
index 0000000..24acc6e
--- /dev/null
+++ b/chrome/common/extensions/event_matcher.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EVENT_MATCHER_H_
+#define CHROME_COMMON_EXTENSIONS_EVENT_MATCHER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+
+namespace extensions {
+
+class EventFilteringInfo;
+
+// Matches EventFilteringInfos against a set of criteria. This is intended to
+// be used by EventFilter which performs efficient URL matching across
+// potentially many EventMatchers itself. This is why this class only exposes
+// MatchNonURLCriteria() - URL matching is handled by EventFilter.
+class EventMatcher {
+ public:
+  explicit EventMatcher(scoped_ptr<base::DictionaryValue> filter);
+  ~EventMatcher();
+
+  // Returns true if |event_info| satisfies this matcher's criteria, not taking
+  // into consideration any URL criteria.
+  bool MatchNonURLCriteria(const EventFilteringInfo& event_info) const;
+
+  int GetURLFilterCount() const;
+  bool GetURLFilter(int i, base::DictionaryValue** url_filter_out);
+
+  int HasURLFilters() const;
+
+  base::DictionaryValue* value() const {
+    return filter_.get();
+  }
+
+ private:
+  // Contains a dictionary that corresponds to a single event filter, eg:
+  //
+  // {url: [{hostSuffix: 'google.com'}]}
+  //
+  // The valid filter keys are event-specific.
+  scoped_ptr<base::DictionaryValue> filter_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventMatcher);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_EVENT_MATCHER_H_
diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc
new file mode 100644
index 0000000..dc27118
--- /dev/null
+++ b/chrome/common/extensions/extension.cc
@@ -0,0 +1,4102 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension.h"
+
+#include <ostream>
+
+#include "base/base64.h"
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/i18n/rtl.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/stl_util.h"
+#include "base/string_number_conversions.h"
+#include "base/string_piece.h"
+#include "base/string_util.h"
+#include "base/string16.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/chrome_version_info.h"
+#include "chrome/common/extensions/csp_validator.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/extension_resource.h"
+#include "chrome/common/extensions/feature_switch.h"
+#include "chrome/common/extensions/features/feature.h"
+#include "chrome/common/extensions/features/simple_feature_provider.h"
+#include "chrome/common/extensions/file_browser_handler.h"
+#include "chrome/common/extensions/manifest.h"
+#include "chrome/common/extensions/permissions/permission_set.h"
+#include "chrome/common/extensions/permissions/permissions_info.h"
+#include "chrome/common/extensions/url_pattern_set.h"
+#include "chrome/common/extensions/user_script.h"
+#include "chrome/common/url_constants.h"
+#include "crypto/sha2.h"
+#include "googleurl/src/url_util.h"
+#include "grit/chromium_strings.h"
+#include "grit/theme_resources.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "webkit/glue/image_decoder.h"
+#include "webkit/glue/web_intent_service_data.h"
+
+#if defined(OS_WIN)
+#include "base/win/metro.h"
+#include "grit/generated_resources.h"
+#endif
+
+namespace keys = extension_manifest_keys;
+namespace values = extension_manifest_values;
+namespace errors = extension_manifest_errors;
+namespace info_keys = extension_info_keys;
+
+using extensions::csp_validator::ContentSecurityPolicyIsLegal;
+using extensions::csp_validator::ContentSecurityPolicyIsSandboxed;
+using extensions::csp_validator::ContentSecurityPolicyIsSecure;
+
+namespace extensions {
+
+namespace {
+
+const int kModernManifestVersion = 2;
+const int kPEMOutputColumns = 65;
+
+const char kOverrideExtentUrlPatternFormat[] = "chrome://%s/*";
+
+// The maximum number of commands (including page action/browser actions) an
+// extension can have.
+const size_t kMaxCommandsPerExtension = 4;
+
+// KEY MARKERS
+const char kKeyBeginHeaderMarker[] = "-----BEGIN";
+const char kKeyBeginFooterMarker[] = "-----END";
+const char kKeyInfoEndMarker[] = "KEY-----";
+const char kPublic[] = "PUBLIC";
+const char kPrivate[] = "PRIVATE";
+
+const int kRSAKeySize = 1024;
+
+const char kDefaultContentSecurityPolicy[] =
+    "script-src 'self' chrome-extension-resource:; object-src 'self'";
+
+#define PLATFORM_APP_LOCAL_CSP_SOURCES \
+    "'self' data: chrome-extension-resource:"
+const char kDefaultPlatformAppContentSecurityPolicy[] =
+    // Platform apps can only use local resources by default.
+    "default-src 'self' chrome-extension-resource:;"
+    // For remote resources, they can fetch them via XMLHttpRequest.
+    "connect-src *;"
+    // And serve them via data: or same-origin (blob:, filesystem:) URLs
+    "style-src " PLATFORM_APP_LOCAL_CSP_SOURCES " 'unsafe-inline';"
+    "img-src " PLATFORM_APP_LOCAL_CSP_SOURCES ";"
+    "frame-src " PLATFORM_APP_LOCAL_CSP_SOURCES ";"
+    "font-src " PLATFORM_APP_LOCAL_CSP_SOURCES ";"
+    // Media can be loaded from remote resources since:
+    // 1. <video> and <audio> have good fallback behavior when offline or under
+    //    spotty connectivity.
+    // 2. Fetching via XHR and serving via blob: URLs currently does not allow
+    //    streaming or partial buffering.
+    "media-src *;";
+
+const char kDefaultSandboxedPageContentSecurityPolicy[] =
+    "sandbox allow-scripts allow-forms allow-popups";
+
+// Converts a normal hexadecimal string into the alphabet used by extensions.
+// We use the characters 'a'-'p' instead of '0'-'f' to avoid ever having a
+// completely numeric host, since some software interprets that as an IP
+// address.
+static void ConvertHexadecimalToIDAlphabet(std::string* id) {
+  for (size_t i = 0; i < id->size(); ++i) {
+    int val;
+    if (base::HexStringToInt(base::StringPiece(id->begin() + i,
+                                               id->begin() + i + 1),
+                             &val)) {
+      (*id)[i] = val + 'a';
+    } else {
+      (*id)[i] = 'a';
+    }
+  }
+}
+
+// Strips leading slashes from the file path. Returns true iff the final path is
+// non empty.
+bool NormalizeAndValidatePath(std::string* path) {
+  size_t first_non_slash = path->find_first_not_of('/');
+  if (first_non_slash == std::string::npos) {
+    *path = "";
+    return false;
+  }
+
+  *path = path->substr(first_non_slash);
+  return true;
+}
+
+// Loads icon paths defined in dictionary |icons_value| into ExtensionIconSet
+// |icons|. |icons_value| is a dictionary value {icon size -> icon path}. Icons
+// in |icons_value| whose size is not in |icon_sizes| will be ignored.
+// Returns success. If load fails, |error| will be set.
+bool LoadIconsFromDictionary(const DictionaryValue* icons_value,
+                             const int* icon_sizes,
+                             size_t num_icon_sizes,
+                             ExtensionIconSet* icons,
+                             string16* error) {
+  DCHECK(icons);
+  for (size_t i = 0; i < num_icon_sizes; ++i) {
+    std::string key = base::IntToString(icon_sizes[i]);
+    if (icons_value->HasKey(key)) {
+      std::string icon_path;
+      if (!icons_value->GetString(key, &icon_path)) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidIconPath, key);
+        return false;
+      }
+
+      if (!NormalizeAndValidatePath(&icon_path)) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidIconPath, key);
+        return false;
+      }
+
+      icons->Add(icon_sizes[i], icon_path);
+    }
+  }
+  return true;
+}
+
+// A singleton object containing global data needed by the extension objects.
+class ExtensionConfig {
+ public:
+  static ExtensionConfig* GetInstance() {
+    return Singleton<ExtensionConfig>::get();
+  }
+
+  Extension::ScriptingWhitelist* whitelist() { return &scripting_whitelist_; }
+
+ private:
+  friend struct DefaultSingletonTraits<ExtensionConfig>;
+
+  ExtensionConfig() {
+    // Whitelist ChromeVox, an accessibility extension from Google that needs
+    // the ability to script webui pages. This is temporary and is not
+    // meant to be a general solution.
+    // TODO(dmazzoni): remove this once we have an extension API that
+    // allows any extension to request read-only access to webui pages.
+    scripting_whitelist_.push_back("kgejglhpjiefppelpmljglcjbhoiplfn");
+  }
+  ~ExtensionConfig() { }
+
+  // A whitelist of extensions that can script anywhere. Do not add to this
+  // list (except in tests) without consulting the Extensions team first.
+  // Note: Component extensions have this right implicitly and do not need to be
+  // added to this list.
+  Extension::ScriptingWhitelist scripting_whitelist_;
+};
+
+// Rank extension locations in a way that allows
+// Extension::GetHigherPriorityLocation() to compare locations.
+// An extension installed from two locations will have the location
+// with the higher rank, as returned by this function. The actual
+// integer values may change, and should never be persisted.
+int GetLocationRank(Extension::Location location) {
+  const int kInvalidRank = -1;
+  int rank = kInvalidRank;  // Will CHECK that rank is not kInvalidRank.
+
+  switch (location) {
+    // Component extensions can not be overriden by any other type.
+    case Extension::COMPONENT:
+      rank = 6;
+      break;
+
+    // Policy controlled extensions may not be overridden by any type
+    // that is not part of chrome.
+    case Extension::EXTERNAL_POLICY_DOWNLOAD:
+      rank = 5;
+      break;
+
+    // A developer-loaded extension should override any installed type
+    // that a user can disable.
+    case Extension::LOAD:
+      rank = 4;
+      break;
+
+    // The relative priority of various external sources is not important,
+    // but having some order ensures deterministic behavior.
+    case Extension::EXTERNAL_REGISTRY:
+      rank = 3;
+      break;
+
+    case Extension::EXTERNAL_PREF:
+      rank = 2;
+      break;
+
+    case Extension::EXTERNAL_PREF_DOWNLOAD:
+      rank = 1;
+      break;
+
+    // User installed extensions are overridden by any external type.
+    case Extension::INTERNAL:
+      rank = 0;
+      break;
+
+    default:
+      NOTREACHED() << "Need to add new extension locaton " << location;
+  }
+
+  CHECK(rank != kInvalidRank);
+  return rank;
+}
+
+bool ReadLaunchDimension(const extensions::Manifest* manifest,
+                         const char* key,
+                         int* target,
+                         bool is_valid_container,
+                         string16* error) {
+  Value* temp = NULL;
+  if (manifest->Get(key, &temp)) {
+    if (!is_valid_container) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidLaunchValueContainer,
+          key);
+      return false;
+    }
+    if (!temp->GetAsInteger(target) || *target < 0) {
+      *target = 0;
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidLaunchValue,
+          key);
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace
+
+const FilePath::CharType Extension::kManifestFilename[] =
+    FILE_PATH_LITERAL("manifest.json");
+const FilePath::CharType Extension::kLocaleFolder[] =
+    FILE_PATH_LITERAL("_locales");
+const FilePath::CharType Extension::kMessagesFilename[] =
+    FILE_PATH_LITERAL("messages.json");
+
+#if defined(OS_WIN)
+const char Extension::kExtensionRegistryPath[] =
+    "Software\\Google\\Chrome\\Extensions";
+#endif
+
+// first 16 bytes of SHA256 hashed public key.
+const size_t Extension::kIdSize = 16;
+
+const char Extension::kMimeType[] = "application/x-chrome-extension";
+
+const int Extension::kPageActionIconMaxSize = 19;
+const int Extension::kBrowserActionIconMaxSize = 19;
+
+const int Extension::kValidWebExtentSchemes =
+    URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS;
+
+const int Extension::kValidHostPermissionSchemes =
+    UserScript::kValidUserScriptSchemes | URLPattern::SCHEME_CHROMEUI;
+
+Extension::Requirements::Requirements()
+    : webgl(false),
+      css3d(false),
+      npapi(false) {
+}
+
+Extension::Requirements::~Requirements() {}
+
+Extension::InputComponentInfo::InputComponentInfo()
+    : type(INPUT_COMPONENT_TYPE_NONE),
+      shortcut_alt(false),
+      shortcut_ctrl(false),
+      shortcut_shift(false) {
+}
+
+Extension::InputComponentInfo::~InputComponentInfo() {}
+
+Extension::TtsVoice::TtsVoice() {}
+Extension::TtsVoice::~TtsVoice() {}
+
+Extension::OAuth2Info::OAuth2Info() {}
+Extension::OAuth2Info::~OAuth2Info() {}
+
+Extension::ActionInfo::ActionInfo() {}
+Extension::ActionInfo::~ActionInfo() {}
+
+//
+// Extension
+//
+
+// static
+scoped_refptr<Extension> Extension::Create(const FilePath& path,
+                                           Location location,
+                                           const DictionaryValue& value,
+                                           int flags,
+                                           std::string* utf8_error) {
+  return Extension::Create(path,
+                           location,
+                           value,
+                           flags,
+                           std::string(),  // ID is ignored if empty.
+                           utf8_error);
+}
+
+scoped_refptr<Extension> Extension::Create(const FilePath& path,
+                                           Location location,
+                                           const DictionaryValue& value,
+                                           int flags,
+                                           const std::string& explicit_id,
+                                           std::string* utf8_error) {
+  DCHECK(utf8_error);
+  string16 error;
+  scoped_ptr<extensions::Manifest> manifest(
+      new extensions::Manifest(location,
+                               scoped_ptr<DictionaryValue>(value.DeepCopy())));
+
+  if (!InitExtensionID(manifest.get(), path, explicit_id, flags, &error)) {
+    *utf8_error = UTF16ToUTF8(error);
+    return NULL;
+  }
+
+  InstallWarningVector install_warnings;
+  manifest->ValidateManifest(utf8_error, &install_warnings);
+  if (!utf8_error->empty())
+    return NULL;
+
+  scoped_refptr<Extension> extension = new Extension(path, manifest.Pass());
+  extension->install_warnings_.swap(install_warnings);
+
+  if (!extension->InitFromValue(flags, &error)) {
+    *utf8_error = UTF16ToUTF8(error);
+    return NULL;
+  }
+
+  if (!extension->CheckPlatformAppFeatures(utf8_error) ||
+      !extension->CheckConflictingFeatures(utf8_error)) {
+    return NULL;
+  }
+
+  return extension;
+}
+
+// static
+Extension::Location Extension::GetHigherPriorityLocation(
+    Extension::Location loc1, Extension::Location loc2) {
+  if (loc1 == loc2)
+    return loc1;
+
+  int loc1_rank = GetLocationRank(loc1);
+  int loc2_rank = GetLocationRank(loc2);
+
+  // If two different locations have the same rank, then we can not
+  // deterministicly choose a location.
+  CHECK(loc1_rank != loc2_rank);
+
+  // Highest rank has highest priority.
+  return (loc1_rank > loc2_rank ? loc1 : loc2 );
+}
+
+void Extension::OverrideLaunchUrl(const GURL& override_url) {
+  GURL new_url(override_url);
+  if (!new_url.is_valid()) {
+    DLOG(WARNING) << "Invalid override url given for " << name();
+  } else {
+    if (new_url.has_port()) {
+      DLOG(WARNING) << "Override URL passed for " << name()
+                    << " should not contain a port.  Removing it.";
+
+      GURL::Replacements remove_port;
+      remove_port.ClearPort();
+      new_url = new_url.ReplaceComponents(remove_port);
+    }
+
+    launch_web_url_ = new_url.spec();
+
+    URLPattern pattern(kValidWebExtentSchemes);
+    URLPattern::ParseResult result = pattern.Parse(new_url.spec());
+    DCHECK_EQ(result, URLPattern::PARSE_SUCCESS);
+    pattern.SetPath(pattern.path() + '*');
+    extent_.AddPattern(pattern);
+  }
+}
+
+FilePath Extension::MaybeNormalizePath(const FilePath& path) {
+#if defined(OS_WIN)
+  // Normalize any drive letter to upper-case. We do this for consistency with
+  // net_utils::FilePathToFileURL(), which does the same thing, to make string
+  // comparisons simpler.
+  std::wstring path_str = path.value();
+  if (path_str.size() >= 2 && path_str[0] >= L'a' && path_str[0] <= L'z' &&
+      path_str[1] == ':')
+    path_str[0] += ('A' - 'a');
+
+  return FilePath(path_str);
+#else
+  return path;
+#endif
+}
+
+Extension::Location Extension::location() const {
+  return manifest_->location();
+}
+
+const std::string& Extension::id() const {
+  return manifest_->extension_id();
+}
+
+const std::string Extension::VersionString() const {
+  return version()->GetString();
+}
+
+void Extension::AddInstallWarnings(
+    const InstallWarningVector& new_warnings) {
+  install_warnings_.insert(install_warnings_.end(),
+                           new_warnings.begin(), new_warnings.end());
+}
+
+// static
+bool Extension::IsExtension(const FilePath& file_name) {
+  return file_name.MatchesExtension(chrome::kExtensionFileExtension);
+}
+
+// static
+bool Extension::IdIsValid(const std::string& id) {
+  // Verify that the id is legal.
+  if (id.size() != (kIdSize * 2))
+    return false;
+
+  // We only support lowercase IDs, because IDs can be used as URL components
+  // (where GURL will lowercase it).
+  std::string temp = StringToLowerASCII(id);
+  for (size_t i = 0; i < temp.size(); i++)
+    if (temp[i] < 'a' || temp[i] > 'p')
+      return false;
+
+  return true;
+}
+
+// static
+std::string Extension::GenerateIdForPath(const FilePath& path) {
+  FilePath new_path = Extension::MaybeNormalizePath(path);
+  std::string path_bytes =
+      std::string(reinterpret_cast<const char*>(new_path.value().data()),
+                  new_path.value().size() * sizeof(FilePath::CharType));
+  std::string id;
+  return GenerateId(path_bytes, &id) ? id : "";
+}
+
+void Extension::GetBasicInfo(bool enabled,
+                             DictionaryValue* info) const {
+  info->SetString(info_keys::kIdKey, id());
+  info->SetString(info_keys::kNameKey, name());
+  info->SetBoolean(info_keys::kEnabledKey, enabled);
+  info->SetBoolean(info_keys::kOfflineEnabledKey, offline_enabled());
+  info->SetString(info_keys::kVersionKey, VersionString());
+  info->SetString(info_keys::kDescriptionKey, description());
+  info->SetString(info_keys::kOptionsUrlKey,
+                  options_url().possibly_invalid_spec());
+  info->SetString(info_keys::kHomepageUrlKey,
+                  GetHomepageURL().possibly_invalid_spec());
+  info->SetString(info_keys::kDetailsUrlKey,
+                  details_url().possibly_invalid_spec());
+  info->SetBoolean(info_keys::kPackagedAppKey, is_platform_app());
+}
+
+Extension::Type Extension::GetType() const {
+  return converted_from_user_script() ? TYPE_USER_SCRIPT : manifest_->type();
+}
+
+// static
+GURL Extension::GetResourceURL(const GURL& extension_url,
+                               const std::string& relative_path) {
+  DCHECK(extension_url.SchemeIs(chrome::kExtensionScheme));
+  DCHECK_EQ("/", extension_url.path());
+
+  std::string path = relative_path;
+
+  // If the relative path starts with "/", it is "absolute" relative to the
+  // extension base directory, but extension_url is already specified to refer
+  // to that base directory, so strip the leading "/" if present.
+  if (relative_path.size() > 0 && relative_path[0] == '/')
+    path = relative_path.substr(1);
+
+  GURL ret_val = GURL(extension_url.spec() + path);
+  DCHECK(StartsWithASCII(ret_val.spec(), extension_url.spec(), false));
+
+  return ret_val;
+}
+
+bool Extension::is_platform_app() const {
+  return manifest_->is_platform_app();
+}
+
+bool Extension::is_hosted_app() const {
+  return manifest()->is_hosted_app();
+}
+
+bool Extension::is_legacy_packaged_app() const {
+  return manifest()->is_legacy_packaged_app();
+}
+
+bool Extension::is_theme() const {
+  return manifest()->is_theme();
+}
+
+GURL Extension::GetBackgroundURL() const {
+  if (background_scripts_.empty())
+    return background_url_;
+  return GetResourceURL(extension_filenames::kGeneratedBackgroundPageFilename);
+}
+
+bool Extension::ResourceMatches(const URLPatternSet& pattern_set,
+                                const std::string& resource) const {
+  return pattern_set.MatchesURL(extension_url_.Resolve(resource));
+}
+
+bool Extension::IsResourceWebAccessible(const std::string& relative_path)
+    const {
+  // For old manifest versions which do not specify web_accessible_resources
+  // we always allow resource loads.
+  if (manifest_version_ < 2 && !HasWebAccessibleResources())
+    return true;
+
+  return ResourceMatches(web_accessible_resources_, relative_path);
+}
+
+bool Extension::HasWebAccessibleResources() const {
+  return web_accessible_resources_.size() > 0;
+}
+
+bool Extension::IsSandboxedPage(const std::string& relative_path) const {
+  return ResourceMatches(sandboxed_pages_, relative_path);
+}
+
+
+std::string Extension::GetResourceContentSecurityPolicy(
+    const std::string& relative_path) const {
+  return IsSandboxedPage(relative_path) ?
+      sandboxed_pages_content_security_policy_ : content_security_policy_;
+}
+
+bool Extension::GenerateId(const std::string& input, std::string* output) {
+  DCHECK(output);
+  uint8 hash[Extension::kIdSize];
+  crypto::SHA256HashString(input, hash, sizeof(hash));
+  *output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash)));
+  ConvertHexadecimalToIDAlphabet(output);
+
+  return true;
+}
+
+// Helper method that loads a UserScript object from a dictionary in the
+// content_script list of the manifest.
+bool Extension::LoadUserScriptHelper(const DictionaryValue* content_script,
+                                     int definition_index,
+                                     string16* error,
+                                     UserScript* result) {
+  // run_at
+  if (content_script->HasKey(keys::kRunAt)) {
+    std::string run_location;
+    if (!content_script->GetString(keys::kRunAt, &run_location)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidRunAt,
+          base::IntToString(definition_index));
+      return false;
+    }
+
+    if (run_location == values::kRunAtDocumentStart) {
+      result->set_run_location(UserScript::DOCUMENT_START);
+    } else if (run_location == values::kRunAtDocumentEnd) {
+      result->set_run_location(UserScript::DOCUMENT_END);
+    } else if (run_location == values::kRunAtDocumentIdle) {
+      result->set_run_location(UserScript::DOCUMENT_IDLE);
+    } else {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidRunAt,
+          base::IntToString(definition_index));
+      return false;
+    }
+  }
+
+  // all frames
+  if (content_script->HasKey(keys::kAllFrames)) {
+    bool all_frames = false;
+    if (!content_script->GetBoolean(keys::kAllFrames, &all_frames)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidAllFrames, base::IntToString(definition_index));
+      return false;
+    }
+    result->set_match_all_frames(all_frames);
+  }
+
+  // matches (required)
+  const ListValue* matches = NULL;
+  if (!content_script->GetList(keys::kMatches, &matches)) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidMatches,
+        base::IntToString(definition_index));
+    return false;
+  }
+
+  if (matches->GetSize() == 0) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidMatchCount,
+        base::IntToString(definition_index));
+    return false;
+  }
+  for (size_t j = 0; j < matches->GetSize(); ++j) {
+    std::string match_str;
+    if (!matches->GetString(j, &match_str)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidMatch,
+          base::IntToString(definition_index),
+          base::IntToString(j),
+          errors::kExpectString);
+      return false;
+    }
+
+    URLPattern pattern(UserScript::kValidUserScriptSchemes);
+    if (CanExecuteScriptEverywhere())
+      pattern.SetValidSchemes(URLPattern::SCHEME_ALL);
+
+    URLPattern::ParseResult parse_result = pattern.Parse(match_str);
+    if (parse_result != URLPattern::PARSE_SUCCESS) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidMatch,
+          base::IntToString(definition_index),
+          base::IntToString(j),
+          URLPattern::GetParseResultString(parse_result));
+      return false;
+    }
+
+    if (pattern.MatchesScheme(chrome::kFileScheme) &&
+        !CanExecuteScriptEverywhere()) {
+      wants_file_access_ = true;
+      if (!(creation_flags_ & ALLOW_FILE_ACCESS)) {
+        pattern.SetValidSchemes(
+            pattern.valid_schemes() & ~URLPattern::SCHEME_FILE);
+      }
+    }
+
+    result->add_url_pattern(pattern);
+  }
+
+  // exclude_matches
+  if (content_script->HasKey(keys::kExcludeMatches)) {  // optional
+    const ListValue* exclude_matches = NULL;
+    if (!content_script->GetList(keys::kExcludeMatches, &exclude_matches)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidExcludeMatches,
+          base::IntToString(definition_index));
+      return false;
+    }
+
+    for (size_t j = 0; j < exclude_matches->GetSize(); ++j) {
+      std::string match_str;
+      if (!exclude_matches->GetString(j, &match_str)) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidExcludeMatch,
+            base::IntToString(definition_index),
+            base::IntToString(j),
+            errors::kExpectString);
+        return false;
+      }
+
+      URLPattern pattern(UserScript::kValidUserScriptSchemes);
+      if (CanExecuteScriptEverywhere())
+        pattern.SetValidSchemes(URLPattern::SCHEME_ALL);
+      URLPattern::ParseResult parse_result = pattern.Parse(match_str);
+      if (parse_result != URLPattern::PARSE_SUCCESS) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidExcludeMatch,
+            base::IntToString(definition_index), base::IntToString(j),
+            URLPattern::GetParseResultString(parse_result));
+        return false;
+      }
+
+      result->add_exclude_url_pattern(pattern);
+    }
+  }
+
+  // include/exclude globs (mostly for Greasemonkey compatibility)
+  if (!LoadGlobsHelper(content_script, definition_index, keys::kIncludeGlobs,
+                       error, &UserScript::add_glob, result)) {
+      return false;
+  }
+
+  if (!LoadGlobsHelper(content_script, definition_index, keys::kExcludeGlobs,
+                       error, &UserScript::add_exclude_glob, result)) {
+      return false;
+  }
+
+  // js and css keys
+  const ListValue* js = NULL;
+  if (content_script->HasKey(keys::kJs) &&
+      !content_script->GetList(keys::kJs, &js)) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidJsList,
+        base::IntToString(definition_index));
+    return false;
+  }
+
+  const ListValue* css = NULL;
+  if (content_script->HasKey(keys::kCss) &&
+      !content_script->GetList(keys::kCss, &css)) {
+    *error = ExtensionErrorUtils::
+        FormatErrorMessageUTF16(errors::kInvalidCssList,
+        base::IntToString(definition_index));
+    return false;
+  }
+
+  // The manifest needs to have at least one js or css user script definition.
+  if (((js ? js->GetSize() : 0) + (css ? css->GetSize() : 0)) == 0) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kMissingFile,
+        base::IntToString(definition_index));
+    return false;
+  }
+
+  if (js) {
+    for (size_t script_index = 0; script_index < js->GetSize();
+         ++script_index) {
+      const Value* value;
+      std::string relative;
+      if (!js->Get(script_index, &value) || !value->GetAsString(&relative)) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidJs,
+            base::IntToString(definition_index),
+            base::IntToString(script_index));
+        return false;
+      }
+      GURL url = GetResourceURL(relative);
+      ExtensionResource resource = GetResource(relative);
+      result->js_scripts().push_back(UserScript::File(
+          resource.extension_root(), resource.relative_path(), url));
+    }
+  }
+
+  if (css) {
+    for (size_t script_index = 0; script_index < css->GetSize();
+         ++script_index) {
+      const Value* value;
+      std::string relative;
+      if (!css->Get(script_index, &value) || !value->GetAsString(&relative)) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidCss,
+            base::IntToString(definition_index),
+            base::IntToString(script_index));
+        return false;
+      }
+      GURL url = GetResourceURL(relative);
+      ExtensionResource resource = GetResource(relative);
+      result->css_scripts().push_back(UserScript::File(
+          resource.extension_root(), resource.relative_path(), url));
+    }
+  }
+
+  return true;
+}
+
+bool Extension::LoadGlobsHelper(
+    const DictionaryValue* content_script,
+    int content_script_index,
+    const char* globs_property_name,
+    string16* error,
+    void(UserScript::*add_method)(const std::string& glob),
+    UserScript* instance) {
+  if (!content_script->HasKey(globs_property_name))
+    return true;  // they are optional
+
+  const ListValue* list = NULL;
+  if (!content_script->GetList(globs_property_name, &list)) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidGlobList,
+        base::IntToString(content_script_index),
+        globs_property_name);
+    return false;
+  }
+
+  for (size_t i = 0; i < list->GetSize(); ++i) {
+    std::string glob;
+    if (!list->GetString(i, &glob)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidGlob,
+          base::IntToString(content_script_index),
+          globs_property_name,
+          base::IntToString(i));
+      return false;
+    }
+
+    (instance->*add_method)(glob);
+  }
+
+  return true;
+}
+
+scoped_ptr<Extension::ActionInfo> Extension::LoadExtensionActionInfoHelper(
+    const DictionaryValue* extension_action,
+    ActionInfo::Type action_type,
+    string16* error) {
+  scoped_ptr<ActionInfo> result(new ActionInfo());
+
+  if (manifest_version_ == 1) {
+    // kPageActionIcons is obsolete, and used by very few extensions. Continue
+    // loading it, but only take the first icon as the default_icon path.
+    const ListValue* icons = NULL;
+    if (extension_action->HasKey(keys::kPageActionIcons) &&
+        extension_action->GetList(keys::kPageActionIcons, &icons)) {
+      for (ListValue::const_iterator iter = icons->begin();
+           iter != icons->end(); ++iter) {
+        std::string path;
+        if (!(*iter)->GetAsString(&path) || !NormalizeAndValidatePath(&path)) {
+          *error = ASCIIToUTF16(errors::kInvalidPageActionIconPath);
+          return scoped_ptr<ActionInfo>();
+        }
+
+        result->default_icon.Add(extension_misc::EXTENSION_ICON_ACTION, path);
+        break;
+      }
+    }
+
+    std::string id;
+    if (extension_action->HasKey(keys::kPageActionId)) {
+      if (!extension_action->GetString(keys::kPageActionId, &id)) {
+        *error = ASCIIToUTF16(errors::kInvalidPageActionId);
+        return scoped_ptr<ActionInfo>();
+      }
+      result->id = id;
+    }
+  }
+
+  // Read the page action |default_icon| (optional).
+  // The |default_icon| value can be either dictionary {icon size -> icon path}
+  // or non empty string value.
+  if (extension_action->HasKey(keys::kPageActionDefaultIcon)) {
+    const DictionaryValue* icons_value = NULL;
+    std::string default_icon;
+    if (extension_action->GetDictionary(keys::kPageActionDefaultIcon,
+                                        &icons_value)) {
+      if (!LoadIconsFromDictionary(icons_value,
+                                   extension_misc::kExtensionActionIconSizes,
+                                   extension_misc::kNumExtensionActionIconSizes,
+                                   &result->default_icon,
+                                   error)) {
+        return scoped_ptr<ActionInfo>();
+      }
+    } else if (extension_action->GetString(keys::kPageActionDefaultIcon,
+                                           &default_icon) &&
+               NormalizeAndValidatePath(&default_icon)) {
+      result->default_icon.Add(extension_misc::EXTENSION_ICON_ACTION,
+                               default_icon);
+    } else {
+      *error = ASCIIToUTF16(errors::kInvalidPageActionIconPath);
+      return scoped_ptr<ActionInfo>();
+    }
+  }
+
+  // Read the page action title from |default_title| if present, |name| if not
+  // (both optional).
+  if (extension_action->HasKey(keys::kPageActionDefaultTitle)) {
+    if (!extension_action->GetString(keys::kPageActionDefaultTitle,
+                                     &result->default_title)) {
+      *error = ASCIIToUTF16(errors::kInvalidPageActionDefaultTitle);
+      return scoped_ptr<ActionInfo>();
+    }
+  } else if (manifest_version_ == 1 && extension_action->HasKey(keys::kName)) {
+    if (!extension_action->GetString(keys::kName, &result->default_title)) {
+      *error = ASCIIToUTF16(errors::kInvalidPageActionName);
+      return scoped_ptr<ActionInfo>();
+    }
+  }
+
+  // Read the action's |popup| (optional).
+  const char* popup_key = NULL;
+  if (extension_action->HasKey(keys::kPageActionDefaultPopup))
+    popup_key = keys::kPageActionDefaultPopup;
+
+  if (manifest_version_ == 1 &&
+      extension_action->HasKey(keys::kPageActionPopup)) {
+    if (popup_key) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidPageActionOldAndNewKeys,
+          keys::kPageActionDefaultPopup,
+          keys::kPageActionPopup);
+      return scoped_ptr<ActionInfo>();
+    }
+    popup_key = keys::kPageActionPopup;
+  }
+
+  if (popup_key) {
+    const DictionaryValue* popup = NULL;
+    std::string url_str;
+
+    if (extension_action->GetString(popup_key, &url_str)) {
+      // On success, |url_str| is set.  Nothing else to do.
+    } else if (manifest_version_ == 1 &&
+               extension_action->GetDictionary(popup_key, &popup)) {
+      if (!popup->GetString(keys::kPageActionPopupPath, &url_str)) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidPageActionPopupPath, "<missing>");
+        return scoped_ptr<ActionInfo>();
+      }
+    } else {
+      *error = ASCIIToUTF16(errors::kInvalidPageActionPopup);
+      return scoped_ptr<ActionInfo>();
+    }
+
+    if (!url_str.empty()) {
+      // An empty string is treated as having no popup.
+      result->default_popup_url = GetResourceURL(url_str);
+      if (!result->default_popup_url.is_valid()) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidPageActionPopupPath, url_str);
+        return scoped_ptr<ActionInfo>();
+      }
+    } else {
+      DCHECK(result->default_popup_url.is_empty())
+          << "Shouldn't be possible for the popup to be set.";
+    }
+  }
+
+  return result.Pass();
+}
+
+// static
+bool Extension::InitExtensionID(extensions::Manifest* manifest,
+                                const FilePath& path,
+                                const std::string& explicit_id,
+                                int creation_flags,
+                                string16* error) {
+  if (!explicit_id.empty()) {
+    manifest->set_extension_id(explicit_id);
+    return true;
+  }
+
+  if (manifest->HasKey(keys::kPublicKey)) {
+    std::string public_key;
+    std::string public_key_bytes;
+    std::string extension_id;
+    if (!manifest->GetString(keys::kPublicKey, &public_key) ||
+        !ParsePEMKeyBytes(public_key, &public_key_bytes) ||
+        !GenerateId(public_key_bytes, &extension_id)) {
+      *error = ASCIIToUTF16(errors::kInvalidKey);
+      return false;
+    }
+    manifest->set_extension_id(extension_id);
+    return true;
+  }
+
+  if (creation_flags & REQUIRE_KEY) {
+    *error = ASCIIToUTF16(errors::kInvalidKey);
+    return false;
+  } else {
+    // If there is a path, we generate the ID from it. This is useful for
+    // development mode, because it keeps the ID stable across restarts and
+    // reloading the extension.
+    std::string extension_id = GenerateIdForPath(path);
+    if (extension_id.empty()) {
+      NOTREACHED() << "Could not create ID from path.";
+      return false;
+    }
+    manifest->set_extension_id(extension_id);
+    return true;
+  }
+}
+
+bool Extension::LoadRequiredFeatures(string16* error) {
+  if (!LoadName(error) ||
+      !LoadVersion(error))
+    return false;
+  return true;
+}
+
+bool Extension::LoadName(string16* error) {
+  string16 localized_name;
+  if (!manifest_->GetString(keys::kName, &localized_name)) {
+    *error = ASCIIToUTF16(errors::kInvalidName);
+    return false;
+  }
+  non_localized_name_ = UTF16ToUTF8(localized_name);
+  base::i18n::AdjustStringForLocaleDirection(&localized_name);
+  name_ = UTF16ToUTF8(localized_name);
+  return true;
+}
+
+bool Extension::LoadDescription(string16* error) {
+  if (manifest_->HasKey(keys::kDescription) &&
+      !manifest_->GetString(keys::kDescription, &description_)) {
+    *error = ASCIIToUTF16(errors::kInvalidDescription);
+    return false;
+  }
+  return true;
+}
+
+bool Extension::LoadAppFeatures(string16* error) {
+  if (!LoadExtent(keys::kWebURLs, &extent_,
+                  errors::kInvalidWebURLs, errors::kInvalidWebURL, error) ||
+      !LoadLaunchURL(error) ||
+      !LoadLaunchContainer(error)) {
+    return false;
+  }
+  if (manifest_->HasKey(keys::kDisplayInLauncher) &&
+      !manifest_->GetBoolean(keys::kDisplayInLauncher, &display_in_launcher_)) {
+    *error = ASCIIToUTF16(errors::kInvalidDisplayInLauncher);
+    return false;
+  }
+  if (manifest_->HasKey(keys::kDisplayInNewTabPage)) {
+    if (!manifest_->GetBoolean(keys::kDisplayInNewTabPage,
+                               &display_in_new_tab_page_)) {
+      *error = ASCIIToUTF16(errors::kInvalidDisplayInNewTabPage);
+      return false;
+    }
+  } else {
+    // Inherit default from display_in_launcher property.
+    display_in_new_tab_page_ = display_in_launcher_;
+  }
+  return true;
+}
+
+bool Extension::LoadOAuth2Info(string16* error) {
+  if (!manifest_->HasKey(keys::kOAuth2))
+    return true;
+
+  if (!manifest_->GetString(keys::kOAuth2ClientId, &oauth2_info_.client_id) ||
+      oauth2_info_.client_id.empty()) {
+    *error = ASCIIToUTF16(errors::kInvalidOAuth2ClientId);
+    return false;
+  }
+
+  ListValue* list = NULL;
+  if (!manifest_->GetList(keys::kOAuth2Scopes, &list)) {
+    *error = ASCIIToUTF16(errors::kInvalidOAuth2Scopes);
+    return false;
+  }
+
+  for (size_t i = 0; i < list->GetSize(); ++i) {
+    std::string scope;
+    if (!list->GetString(i, &scope)) {
+      *error = ASCIIToUTF16(errors::kInvalidOAuth2Scopes);
+      return false;
+    }
+    oauth2_info_.scopes.push_back(scope);
+  }
+
+  return true;
+}
+
+bool Extension::LoadExtent(const char* key,
+                           URLPatternSet* extent,
+                           const char* list_error,
+                           const char* value_error,
+                           string16* error) {
+  Value* temp_pattern_value = NULL;
+  if (!manifest_->Get(key, &temp_pattern_value))
+    return true;
+
+  if (temp_pattern_value->GetType() != Value::TYPE_LIST) {
+    *error = ASCIIToUTF16(list_error);
+    return false;
+  }
+
+  ListValue* pattern_list = static_cast<ListValue*>(temp_pattern_value);
+  for (size_t i = 0; i < pattern_list->GetSize(); ++i) {
+    std::string pattern_string;
+    if (!pattern_list->GetString(i, &pattern_string)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(value_error,
+                                                       base::UintToString(i),
+                                                       errors::kExpectString);
+      return false;
+    }
+
+    URLPattern pattern(kValidWebExtentSchemes);
+    URLPattern::ParseResult parse_result = pattern.Parse(pattern_string);
+    if (parse_result == URLPattern::PARSE_ERROR_EMPTY_PATH) {
+      pattern_string += "/";
+      parse_result = pattern.Parse(pattern_string);
+    }
+
+    if (parse_result != URLPattern::PARSE_SUCCESS) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          value_error,
+          base::UintToString(i),
+          URLPattern::GetParseResultString(parse_result));
+      return false;
+    }
+
+    // Do not allow authors to claim "<all_urls>".
+    if (pattern.match_all_urls()) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          value_error,
+          base::UintToString(i),
+          errors::kCannotClaimAllURLsInExtent);
+      return false;
+    }
+
+    // Do not allow authors to claim "*" for host.
+    if (pattern.host().empty()) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          value_error,
+          base::UintToString(i),
+          errors::kCannotClaimAllHostsInExtent);
+      return false;
+    }
+
+    // We do not allow authors to put wildcards in their paths. Instead, we
+    // imply one at the end.
+    if (pattern.path().find('*') != std::string::npos) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          value_error,
+          base::UintToString(i),
+          errors::kNoWildCardsInPaths);
+      return false;
+    }
+    pattern.SetPath(pattern.path() + '*');
+
+    extent->AddPattern(pattern);
+  }
+
+  return true;
+}
+
+bool Extension::LoadLaunchURL(string16* error) {
+  Value* temp = NULL;
+
+  // launch URL can be either local (to chrome-extension:// root) or an absolute
+  // web URL.
+  if (manifest_->Get(keys::kLaunchLocalPath, &temp)) {
+    if (manifest_->Get(keys::kLaunchWebURL, NULL)) {
+      *error = ASCIIToUTF16(errors::kLaunchPathAndURLAreExclusive);
+      return false;
+    }
+
+    if (manifest_->Get(keys::kWebURLs, NULL)) {
+      *error = ASCIIToUTF16(errors::kLaunchPathAndExtentAreExclusive);
+      return false;
+    }
+
+    std::string launch_path;
+    if (!temp->GetAsString(&launch_path)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidLaunchValue,
+          keys::kLaunchLocalPath);
+      return false;
+    }
+
+    // Ensure the launch path is a valid relative URL.
+    GURL resolved = url().Resolve(launch_path);
+    if (!resolved.is_valid() || resolved.GetOrigin() != url()) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidLaunchValue,
+          keys::kLaunchLocalPath);
+      return false;
+    }
+
+    launch_local_path_ = launch_path;
+  } else if (manifest_->Get(keys::kLaunchWebURL, &temp)) {
+    std::string launch_url;
+    if (!temp->GetAsString(&launch_url)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidLaunchValue,
+          keys::kLaunchWebURL);
+      return false;
+    }
+
+    // Ensure the launch URL is a valid absolute URL and web extent scheme.
+    GURL url(launch_url);
+    URLPattern pattern(kValidWebExtentSchemes);
+    if (!url.is_valid() || !pattern.SetScheme(url.scheme())) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidLaunchValue,
+          keys::kLaunchWebURL);
+      return false;
+    }
+
+    launch_web_url_ = launch_url;
+  } else if (is_legacy_packaged_app() || is_hosted_app()) {
+    *error = ASCIIToUTF16(errors::kLaunchURLRequired);
+    return false;
+  }
+
+  // If there is no extent, we default the extent based on the launch URL.
+  if (web_extent().is_empty() && !launch_web_url().empty()) {
+    GURL launch_url(launch_web_url());
+    URLPattern pattern(kValidWebExtentSchemes);
+    if (!pattern.SetScheme("*")) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidLaunchValue,
+          keys::kLaunchWebURL);
+      return false;
+    }
+    pattern.SetHost(launch_url.host());
+    pattern.SetPath("/*");
+    extent_.AddPattern(pattern);
+  }
+
+  // In order for the --apps-gallery-url switch to work with the gallery
+  // process isolation, we must insert any provided value into the component
+  // app's launch url and web extent.
+  if (id() == extension_misc::kWebStoreAppId) {
+    std::string gallery_url_str = CommandLine::ForCurrentProcess()->
+        GetSwitchValueASCII(switches::kAppsGalleryURL);
+
+    // Empty string means option was not used.
+    if (!gallery_url_str.empty()) {
+      GURL gallery_url(gallery_url_str);
+      OverrideLaunchUrl(gallery_url);
+    }
+  } else if (id() == extension_misc::kCloudPrintAppId) {
+    // In order for the --cloud-print-service switch to work, we must update
+    // the launch URL and web extent.
+    // TODO(sanjeevr): Ideally we want to use CloudPrintURL here but that is
+    // currently under chrome/browser.
+    const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+    GURL cloud_print_service_url = GURL(command_line.GetSwitchValueASCII(
+        switches::kCloudPrintServiceURL));
+    if (!cloud_print_service_url.is_empty()) {
+      std::string path(
+          cloud_print_service_url.path() + "/enable_chrome_connector");
+      GURL::Replacements replacements;
+      replacements.SetPathStr(path);
+      GURL cloud_print_enable_connector_url =
+          cloud_print_service_url.ReplaceComponents(replacements);
+      OverrideLaunchUrl(cloud_print_enable_connector_url);
+    }
+  } else if (id() == extension_misc::kChromeAppId) {
+    // Override launch url to new tab.
+    launch_web_url_ = chrome::kChromeUINewTabURL;
+    extent_.ClearPatterns();
+  }
+
+  return true;
+}
+
+bool Extension::LoadLaunchContainer(string16* error) {
+  Value* tmp_launcher_container = NULL;
+  if (!manifest_->Get(keys::kLaunchContainer, &tmp_launcher_container))
+    return true;
+
+  std::string launch_container_string;
+  if (!tmp_launcher_container->GetAsString(&launch_container_string)) {
+    *error = ASCIIToUTF16(errors::kInvalidLaunchContainer);
+    return false;
+  }
+
+  if (launch_container_string == values::kLaunchContainerPanel) {
+    launch_container_ = extension_misc::LAUNCH_PANEL;
+  } else if (launch_container_string == values::kLaunchContainerTab) {
+    launch_container_ = extension_misc::LAUNCH_TAB;
+  } else {
+    *error = ASCIIToUTF16(errors::kInvalidLaunchContainer);
+    return false;
+  }
+
+  bool can_specify_initial_size =
+      launch_container_ == extension_misc::LAUNCH_PANEL ||
+      launch_container_ == extension_misc::LAUNCH_WINDOW;
+
+  // Validate the container width if present.
+  if (!ReadLaunchDimension(manifest_.get(),
+                           keys::kLaunchWidth,
+                           &launch_width_,
+                           can_specify_initial_size,
+                           error)) {
+      return false;
+  }
+
+  // Validate container height if present.
+  if (!ReadLaunchDimension(manifest_.get(),
+                           keys::kLaunchHeight,
+                           &launch_height_,
+                           can_specify_initial_size,
+                           error)) {
+      return false;
+  }
+
+  return true;
+}
+
+bool Extension::LoadSharedFeatures(
+    const APIPermissionSet& api_permissions,
+    string16* error) {
+  if (!LoadDescription(error) ||
+      !LoadHomepageURL(error) ||
+      !LoadUpdateURL(error) ||
+      !LoadIcons(error) ||
+      !LoadCommands(error) ||
+      !LoadPlugins(error) ||
+      !LoadNaClModules(error) ||
+      !LoadWebAccessibleResources(error) ||
+      !LoadSandboxedPages(error) ||
+      !LoadRequirements(error) ||
+      !LoadDefaultLocale(error) ||
+      !LoadOfflineEnabled(error) ||
+      !LoadOptionsPage(error) ||
+      // LoadBackgroundScripts() must be called before LoadBackgroundPage().
+      !LoadBackgroundScripts(error) ||
+      !LoadBackgroundPage(api_permissions, error) ||
+      !LoadBackgroundPersistent(api_permissions, error) ||
+      !LoadBackgroundAllowJSAccess(api_permissions, error) ||
+      !LoadWebIntentServices(error) ||
+      !LoadOAuth2Info(error))
+    return false;
+
+  return true;
+}
+
+bool Extension::LoadVersion(string16* error) {
+  std::string version_str;
+  if (!manifest_->GetString(keys::kVersion, &version_str)) {
+    *error = ASCIIToUTF16(errors::kInvalidVersion);
+    return false;
+  }
+  version_.reset(new Version(version_str));
+  if (!version_->IsValid() || version_->components().size() > 4) {
+    *error = ASCIIToUTF16(errors::kInvalidVersion);
+    return false;
+  }
+  return true;
+}
+
+bool Extension::LoadManifestVersion(string16* error) {
+  // Get the original value out of the dictionary so that we can validate it
+  // more strictly.
+  if (manifest_->value()->HasKey(keys::kManifestVersion)) {
+    int manifest_version = 1;
+    if (!manifest_->GetInteger(keys::kManifestVersion, &manifest_version) ||
+        manifest_version < 1) {
+      *error = ASCIIToUTF16(errors::kInvalidManifestVersion);
+      return false;
+    }
+  }
+
+  manifest_version_ = manifest_->GetManifestVersion();
+  if (creation_flags_ & REQUIRE_MODERN_MANIFEST_VERSION &&
+      manifest_version_ < kModernManifestVersion &&
+      !CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kAllowLegacyExtensionManifests)) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidManifestVersionOld,
+        base::IntToString(kModernManifestVersion));
+    return false;
+  }
+
+  return true;
+}
+
+bool Extension::LoadHomepageURL(string16* error) {
+  if (!manifest_->HasKey(keys::kHomepageURL))
+    return true;
+  std::string tmp_homepage_url;
+  if (!manifest_->GetString(keys::kHomepageURL, &tmp_homepage_url)) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidHomepageURL, "");
+    return false;
+  }
+  homepage_url_ = GURL(tmp_homepage_url);
+  if (!homepage_url_.is_valid() ||
+      (!homepage_url_.SchemeIs("http") &&
+       !homepage_url_.SchemeIs("https"))) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidHomepageURL, tmp_homepage_url);
+    return false;
+  }
+  return true;
+}
+
+bool Extension::LoadUpdateURL(string16* error) {
+  if (!manifest_->HasKey(keys::kUpdateURL))
+    return true;
+  std::string tmp_update_url;
+  if (!manifest_->GetString(keys::kUpdateURL, &tmp_update_url)) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidUpdateURL, "");
+    return false;
+  }
+  update_url_ = GURL(tmp_update_url);
+  if (!update_url_.is_valid() ||
+      update_url_.has_ref()) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidUpdateURL, tmp_update_url);
+    return false;
+  }
+  return true;
+}
+
+bool Extension::LoadIcons(string16* error) {
+  if (!manifest_->HasKey(keys::kIcons))
+    return true;
+  DictionaryValue* icons_value = NULL;
+  if (!manifest_->GetDictionary(keys::kIcons, &icons_value)) {
+    *error = ASCIIToUTF16(errors::kInvalidIcons);
+    return false;
+  }
+
+  return LoadIconsFromDictionary(icons_value,
+                                 extension_misc::kExtensionIconSizes,
+                                 extension_misc::kNumExtensionIconSizes,
+                                 &icons_,
+                                 error);
+}
+
+bool Extension::LoadCommands(string16* error) {
+  if (manifest_->HasKey(keys::kCommands)) {
+    DictionaryValue* commands = NULL;
+    if (!manifest_->GetDictionary(keys::kCommands, &commands)) {
+      *error = ASCIIToUTF16(errors::kInvalidCommandsKey);
+      return false;
+    }
+
+    if (commands->size() > kMaxCommandsPerExtension) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidKeyBindingTooMany,
+          base::IntToString(kMaxCommandsPerExtension));
+      return false;
+    }
+
+    int command_index = 0;
+    for (DictionaryValue::key_iterator iter = commands->begin_keys();
+         iter != commands->end_keys(); ++iter) {
+      ++command_index;
+
+      DictionaryValue* command = NULL;
+      if (!commands->GetDictionary(*iter, &command)) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidKeyBindingDictionary,
+            base::IntToString(command_index));
+        return false;
+      }
+
+      scoped_ptr<extensions::Command> binding(new extensions::Command());
+      if (!binding->Parse(command, *iter, command_index, error))
+        return false;  // |error| already set.
+
+      std::string command_name = binding->command_name();
+      if (command_name == values::kPageActionCommandEvent) {
+        page_action_command_.reset(binding.release());
+      } else if (command_name == values::kBrowserActionCommandEvent) {
+        browser_action_command_.reset(binding.release());
+      } else if (command_name == values::kScriptBadgeCommandEvent) {
+        script_badge_command_.reset(binding.release());
+      } else {
+        if (command_name[0] != '_')  // All commands w/underscore are reserved.
+          named_commands_[command_name] = *binding.get();
+      }
+    }
+  }
+
+  if (manifest_->HasKey(keys::kBrowserAction) &&
+      !browser_action_command_.get()) {
+    // If the extension defines a browser action, but no command for it, then
+    // we synthesize a generic one, so the user can configure a shortcut for it.
+    // No keyboard shortcut will be assigned to it, until the user selects one.
+    browser_action_command_.reset(
+        new extensions::Command(
+            values::kBrowserActionCommandEvent, string16(), ""));
+  }
+
+  return true;
+}
+
+bool Extension::LoadPlugins(string16* error) {
+  if (!manifest_->HasKey(keys::kPlugins))
+    return true;
+
+  ListValue* list_value = NULL;
+  if (!manifest_->GetList(keys::kPlugins, &list_value)) {
+    *error = ASCIIToUTF16(errors::kInvalidPlugins);
+    return false;
+  }
+
+  for (size_t i = 0; i < list_value->GetSize(); ++i) {
+    DictionaryValue* plugin_value = NULL;
+    if (!list_value->GetDictionary(i, &plugin_value)) {
+      *error = ASCIIToUTF16(errors::kInvalidPlugins);
+      return false;
+    }
+    // Get plugins[i].path.
+    std::string path_str;
+    if (!plugin_value->GetString(keys::kPluginsPath, &path_str)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidPluginsPath, base::IntToString(i));
+      return false;
+    }
+
+    // Get plugins[i].content (optional).
+    bool is_public = false;
+    if (plugin_value->HasKey(keys::kPluginsPublic)) {
+      if (!plugin_value->GetBoolean(keys::kPluginsPublic, &is_public)) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidPluginsPublic, base::IntToString(i));
+        return false;
+      }
+    }
+
+    // We don't allow extensions to load NPAPI plugins on Chrome OS, or under
+    // Windows 8 Metro mode, but still parse the entries to display consistent
+    // error messages. If the extension actually requires the plugins then
+    // LoadRequirements will prevent it loading.
+#if defined(OS_CHROMEOS)
+    continue;
+#elif defined(OS_WIN)
+    if (base::win::IsMetroProcess()) {
+      continue;
+    }
+#endif  // defined(OS_WIN).
+    plugins_.push_back(PluginInfo());
+    plugins_.back().path = path().Append(FilePath::FromUTF8Unsafe(path_str));
+    plugins_.back().is_public = is_public;
+  }
+  return true;
+}
+
+bool Extension::LoadNaClModules(string16* error) {
+  if (!manifest_->HasKey(keys::kNaClModules))
+    return true;
+  ListValue* list_value = NULL;
+  if (!manifest_->GetList(keys::kNaClModules, &list_value)) {
+    *error = ASCIIToUTF16(errors::kInvalidNaClModules);
+    return false;
+  }
+
+  for (size_t i = 0; i < list_value->GetSize(); ++i) {
+    DictionaryValue* module_value = NULL;
+    if (!list_value->GetDictionary(i, &module_value)) {
+      *error = ASCIIToUTF16(errors::kInvalidNaClModules);
+      return false;
+    }
+
+    // Get nacl_modules[i].path.
+    std::string path_str;
+    if (!module_value->GetString(keys::kNaClModulesPath, &path_str)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidNaClModulesPath, base::IntToString(i));
+      return false;
+    }
+
+    // Get nacl_modules[i].mime_type.
+    std::string mime_type;
+    if (!module_value->GetString(keys::kNaClModulesMIMEType, &mime_type)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidNaClModulesMIMEType, base::IntToString(i));
+      return false;
+    }
+
+    nacl_modules_.push_back(NaClModuleInfo());
+    nacl_modules_.back().url = GetResourceURL(path_str);
+    nacl_modules_.back().mime_type = mime_type;
+  }
+
+  return true;
+}
+
+bool Extension::LoadWebAccessibleResources(string16* error) {
+  if (!manifest_->HasKey(keys::kWebAccessibleResources))
+    return true;
+  ListValue* list_value = NULL;
+  if (!manifest_->GetList(keys::kWebAccessibleResources, &list_value)) {
+    *error = ASCIIToUTF16(errors::kInvalidWebAccessibleResourcesList);
+    return false;
+  }
+  for (size_t i = 0; i < list_value->GetSize(); ++i) {
+    std::string relative_path;
+    if (!list_value->GetString(i, &relative_path)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidWebAccessibleResource, base::IntToString(i));
+      return false;
+    }
+    URLPattern pattern(URLPattern::SCHEME_EXTENSION);
+    if (pattern.Parse(extension_url_.spec()) != URLPattern::PARSE_SUCCESS) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidURLPatternError, extension_url_.spec());
+      return false;
+    }
+    while (relative_path[0] == '/')
+      relative_path = relative_path.substr(1, relative_path.length() - 1);
+    pattern.SetPath(pattern.path() + relative_path);
+    web_accessible_resources_.AddPattern(pattern);
+  }
+
+  return true;
+}
+
+bool Extension::LoadSandboxedPages(string16* error) {
+  if (!manifest_->HasPath(keys::kSandboxedPages))
+    return true;
+
+  ListValue* list_value = NULL;
+  if (!manifest_->GetList(keys::kSandboxedPages, &list_value)) {
+    *error = ASCIIToUTF16(errors::kInvalidSandboxedPagesList);
+    return false;
+  }
+  for (size_t i = 0; i < list_value->GetSize(); ++i) {
+    std::string relative_path;
+    if (!list_value->GetString(i, &relative_path)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidSandboxedPage, base::IntToString(i));
+      return false;
+    }
+    URLPattern pattern(URLPattern::SCHEME_EXTENSION);
+    if (pattern.Parse(extension_url_.spec()) != URLPattern::PARSE_SUCCESS) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidURLPatternError, extension_url_.spec());
+      return false;
+    }
+    while (relative_path[0] == '/')
+      relative_path = relative_path.substr(1, relative_path.length() - 1);
+    pattern.SetPath(pattern.path() + relative_path);
+    sandboxed_pages_.AddPattern(pattern);
+  }
+
+  if (manifest_->HasPath(keys::kSandboxedPagesCSP)) {
+    if (!manifest_->GetString(
+        keys::kSandboxedPagesCSP, &sandboxed_pages_content_security_policy_)) {
+      *error = ASCIIToUTF16(errors::kInvalidSandboxedPagesCSP);
+      return false;
+    }
+
+    if (!ContentSecurityPolicyIsLegal(
+            sandboxed_pages_content_security_policy_) ||
+        !ContentSecurityPolicyIsSandboxed(
+            sandboxed_pages_content_security_policy_, GetType())) {
+      *error = ASCIIToUTF16(errors::kInvalidSandboxedPagesCSP);
+      return false;
+    }
+  } else {
+    sandboxed_pages_content_security_policy_ =
+        kDefaultSandboxedPageContentSecurityPolicy;
+    CHECK(ContentSecurityPolicyIsSandboxed(
+        sandboxed_pages_content_security_policy_, GetType()));
+  }
+
+  return true;
+}
+
+bool Extension::LoadRequirements(string16* error) {
+  // Before parsing requirements from the manifest, automatically default the
+  // NPAPI plugin requirement based on whether it includes NPAPI plugins.
+  ListValue* list_value = NULL;
+  requirements_.npapi =
+    manifest_->GetList(keys::kPlugins, &list_value) && !list_value->empty();
+
+  if (!manifest_->HasKey(keys::kRequirements))
+    return true;
+
+  DictionaryValue* requirements_value = NULL;
+  if (!manifest_->GetDictionary(keys::kRequirements, &requirements_value)) {
+    *error = ASCIIToUTF16(errors::kInvalidRequirements);
+    return false;
+  }
+
+  for (DictionaryValue::key_iterator it = requirements_value->begin_keys();
+       it != requirements_value->end_keys(); ++it) {
+    DictionaryValue* requirement_value;
+    if (!requirements_value->GetDictionaryWithoutPathExpansion(
+        *it, &requirement_value)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidRequirement, *it);
+      return false;
+    }
+
+    if (*it == "plugins") {
+      for (DictionaryValue::key_iterator plugin_it =
+              requirement_value->begin_keys();
+           plugin_it != requirement_value->end_keys(); ++plugin_it) {
+        bool plugin_required = false;
+        if (!requirement_value->GetBoolean(*plugin_it, &plugin_required)) {
+          *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+              errors::kInvalidRequirement, *it);
+          return false;
+        }
+        if (*plugin_it == "npapi") {
+          requirements_.npapi = plugin_required;
+        } else {
+          *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+              errors::kInvalidRequirement, *it);
+          return false;
+        }
+      }
+    } else if (*it == "3D") {
+      ListValue* features = NULL;
+      if (!requirement_value->GetListWithoutPathExpansion("features",
+                                                          &features) ||
+          !features) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidRequirement, *it);
+        return false;
+      }
+
+      for (base::ListValue::iterator feature_it = features->begin();
+           feature_it != features->end();
+           ++feature_it) {
+        std::string feature;
+        if ((*feature_it)->GetAsString(&feature)) {
+          if (feature == "webgl") {
+            requirements_.webgl = true;
+          } else if (feature == "css3d") {
+            requirements_.css3d = true;
+          } else {
+            *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+                errors::kInvalidRequirement, *it);
+            return false;
+          }
+        }
+      }
+    } else {
+      *error = ASCIIToUTF16(errors::kInvalidRequirements);
+      return false;
+    }
+  }
+  return true;
+}
+
+bool Extension::LoadDefaultLocale(string16* error) {
+  if (!manifest_->HasKey(keys::kDefaultLocale))
+    return true;
+  if (!manifest_->GetString(keys::kDefaultLocale, &default_locale_) ||
+      !l10n_util::IsValidLocaleSyntax(default_locale_)) {
+    *error = ASCIIToUTF16(errors::kInvalidDefaultLocale);
+    return false;
+  }
+  return true;
+}
+
+bool Extension::LoadOfflineEnabled(string16* error) {
+  // Defaults to false, except for platform apps which are offline by default.
+  if (!manifest_->HasKey(keys::kOfflineEnabled)) {
+    offline_enabled_ = is_platform_app();
+    return true;
+  }
+  if (!manifest_->GetBoolean(keys::kOfflineEnabled, &offline_enabled_)) {
+    *error = ASCIIToUTF16(errors::kInvalidOfflineEnabled);
+    return false;
+  }
+  return true;
+}
+
+bool Extension::LoadOptionsPage(string16* error) {
+  if (!manifest_->HasKey(keys::kOptionsPage))
+    return true;
+  std::string options_str;
+  if (!manifest_->GetString(keys::kOptionsPage, &options_str)) {
+    *error = ASCIIToUTF16(errors::kInvalidOptionsPage);
+    return false;
+  }
+
+  if (is_hosted_app()) {
+    // hosted apps require an absolute URL.
+    GURL options_url(options_str);
+    if (!options_url.is_valid() ||
+        !(options_url.SchemeIs("http") || options_url.SchemeIs("https"))) {
+      *error = ASCIIToUTF16(errors::kInvalidOptionsPageInHostedApp);
+      return false;
+    }
+    options_url_ = options_url;
+  } else {
+    GURL absolute(options_str);
+    if (absolute.is_valid()) {
+      *error = ASCIIToUTF16(errors::kInvalidOptionsPageExpectUrlInPackage);
+      return false;
+    }
+    options_url_ = GetResourceURL(options_str);
+    if (!options_url_.is_valid()) {
+      *error = ASCIIToUTF16(errors::kInvalidOptionsPage);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool Extension::LoadBackgroundScripts(string16* error) {
+  const std::string& key = is_platform_app() ?
+      keys::kPlatformAppBackgroundScripts : keys::kBackgroundScripts;
+  return LoadBackgroundScripts(key, error);
+}
+
+bool Extension::LoadBackgroundScripts(const std::string& key, string16* error) {
+  Value* background_scripts_value = NULL;
+  if (!manifest_->Get(key, &background_scripts_value))
+    return true;
+
+  CHECK(background_scripts_value);
+  if (background_scripts_value->GetType() != Value::TYPE_LIST) {
+    *error = ASCIIToUTF16(errors::kInvalidBackgroundScripts);
+    return false;
+  }
+
+  ListValue* background_scripts =
+      static_cast<ListValue*>(background_scripts_value);
+  for (size_t i = 0; i < background_scripts->GetSize(); ++i) {
+    std::string script;
+    if (!background_scripts->GetString(i, &script)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidBackgroundScript, base::IntToString(i));
+      return false;
+    }
+    background_scripts_.push_back(script);
+  }
+
+  return true;
+}
+
+bool Extension::LoadBackgroundPage(
+    const APIPermissionSet& api_permissions,
+    string16* error) {
+  if (is_platform_app()) {
+    return LoadBackgroundPage(
+        keys::kPlatformAppBackgroundPage, api_permissions, error);
+  }
+
+  if (!LoadBackgroundPage(keys::kBackgroundPage, api_permissions, error))
+    return false;
+  if (background_url_.is_empty()) {
+    return LoadBackgroundPage(
+        keys::kBackgroundPageLegacy, api_permissions, error);
+  }
+  return true;
+}
+
+bool Extension::LoadBackgroundPage(
+    const std::string& key,
+    const APIPermissionSet& api_permissions,
+    string16* error) {
+  base::Value* background_page_value = NULL;
+  if (!manifest_->Get(key, &background_page_value))
+    return true;
+
+  if (!background_scripts_.empty()) {
+    *error = ASCIIToUTF16(errors::kInvalidBackgroundCombination);
+    return false;
+  }
+
+
+  std::string background_str;
+  if (!background_page_value->GetAsString(&background_str)) {
+    *error = ASCIIToUTF16(errors::kInvalidBackground);
+    return false;
+  }
+
+  if (is_hosted_app()) {
+    background_url_ = GURL(background_str);
+
+    // Make sure "background" permission is set.
+    if (!api_permissions.count(APIPermission::kBackground)) {
+      *error = ASCIIToUTF16(errors::kBackgroundPermissionNeeded);
+      return false;
+    }
+    // Hosted apps require an absolute URL.
+    if (!background_url_.is_valid()) {
+      *error = ASCIIToUTF16(errors::kInvalidBackgroundInHostedApp);
+      return false;
+    }
+
+    if (!(background_url_.SchemeIs("https") ||
+          (CommandLine::ForCurrentProcess()->HasSwitch(
+              switches::kAllowHTTPBackgroundPage) &&
+           background_url_.SchemeIs("http")))) {
+      *error = ASCIIToUTF16(errors::kInvalidBackgroundInHostedApp);
+      return false;
+    }
+  } else {
+    background_url_ = GetResourceURL(background_str);
+  }
+
+  return true;
+}
+
+bool Extension::LoadBackgroundPersistent(
+    const APIPermissionSet& api_permissions,
+    string16* error) {
+  if (is_platform_app()) {
+    background_page_is_persistent_ = false;
+    return true;
+  }
+
+  Value* background_persistent = NULL;
+  if (!manifest_->Get(keys::kBackgroundPersistent, &background_persistent))
+    return true;
+
+  if (!background_persistent->GetAsBoolean(&background_page_is_persistent_)) {
+    *error = ASCIIToUTF16(errors::kInvalidBackgroundPersistent);
+    return false;
+  }
+
+  if (!has_background_page()) {
+    *error = ASCIIToUTF16(errors::kInvalidBackgroundPersistentNoPage);
+    return false;
+  }
+
+  return true;
+}
+
+bool Extension::LoadBackgroundAllowJSAccess(
+    const APIPermissionSet& api_permissions,
+    string16* error) {
+  Value* allow_js_access = NULL;
+  if (!manifest_->Get(keys::kBackgroundAllowJsAccess, &allow_js_access))
+    return true;
+
+  if (!allow_js_access->IsType(Value::TYPE_BOOLEAN) ||
+      !allow_js_access->GetAsBoolean(&allow_background_js_access_)) {
+    *error = ASCIIToUTF16(errors::kInvalidBackgroundAllowJsAccess);
+    return false;
+  }
+
+  return true;
+}
+
+bool Extension::LoadWebIntentAction(const std::string& action_name,
+                                    const DictionaryValue& intent_service,
+                                    string16* error) {
+  DCHECK(error);
+  webkit_glue::WebIntentServiceData service;
+  std::string value;
+
+  service.action = UTF8ToUTF16(action_name);
+
+  const ListValue* mime_types = NULL;
+  if (!intent_service.HasKey(keys::kIntentType) ||
+      !intent_service.GetList(keys::kIntentType, &mime_types) ||
+      mime_types->GetSize() == 0) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidIntentType, action_name);
+    return false;
+  }
+
+  std::string href;
+  if (intent_service.HasKey(keys::kIntentPath)) {
+    if (!intent_service.GetString(keys::kIntentPath, &href)) {
+      *error = ASCIIToUTF16(errors::kInvalidIntentHref);
+      return false;
+    }
+  }
+
+  if (intent_service.HasKey(keys::kIntentHref)) {
+    if (!href.empty()) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidIntentHrefOldAndNewKey, action_name,
+          keys::kIntentPath, keys::kIntentHref);
+       return false;
+    }
+    if (!intent_service.GetString(keys::kIntentHref, &href)) {
+      *error = ASCIIToUTF16(errors::kInvalidIntentHref);
+      return false;
+    }
+  }
+
+  // For packaged/hosted apps, empty href implies the respective launch URLs.
+  if (href.empty()) {
+    if (is_hosted_app()) {
+      href = launch_web_url();
+    } else if (is_legacy_packaged_app()) {
+      href = launch_local_path();
+    }
+  }
+
+  // If there still is not an  href, the manifest is malformed, unless this is a
+  // platform app in which case the href should not be present.
+  if (href.empty() && !is_platform_app()) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidIntentHrefEmpty, action_name);
+    return false;
+  } else if (!href.empty() && is_platform_app()) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidIntentHrefInPlatformApp, action_name);
+    return false;
+  }
+
+  GURL service_url(href);
+  if (is_hosted_app()) {
+    // Hosted apps require an absolute URL for intents.
+    if (!service_url.is_valid() ||
+        !(web_extent().MatchesURL(service_url))) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidIntentPageInHostedApp, action_name);
+      return false;
+    }
+    service.service_url = service_url;
+  } else if (is_platform_app()) {
+    service.service_url = GetBackgroundURL();
+  } else {
+    // We do not allow absolute intent URLs in non-hosted apps.
+    if (service_url.is_valid()) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kCannotAccessPage, href);
+      return false;
+    }
+    service.service_url = GetResourceURL(href);
+  }
+
+  if (intent_service.HasKey(keys::kIntentTitle) &&
+      !intent_service.GetString(keys::kIntentTitle, &service.title)) {
+    *error = ASCIIToUTF16(errors::kInvalidIntentTitle);
+    return false;
+  }
+
+  if (intent_service.HasKey(keys::kIntentDisposition)) {
+    if (is_platform_app()) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidIntentDispositionInPlatformApp, action_name);
+      return false;
+    }
+    if (!intent_service.GetString(keys::kIntentDisposition, &value) ||
+        (value != values::kIntentDispositionWindow &&
+         value != values::kIntentDispositionInline)) {
+      *error = ASCIIToUTF16(errors::kInvalidIntentDisposition);
+      return false;
+    }
+    if (value == values::kIntentDispositionInline) {
+      service.disposition =
+          webkit_glue::WebIntentServiceData::DISPOSITION_INLINE;
+    } else {
+      service.disposition =
+          webkit_glue::WebIntentServiceData::DISPOSITION_WINDOW;
+    }
+  }
+
+  for (size_t i = 0; i < mime_types->GetSize(); ++i) {
+    if (!mime_types->GetString(i, &service.type)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidIntentTypeElement, action_name,
+          std::string(base::IntToString(i)));
+      return false;
+    }
+    intents_services_.push_back(service);
+  }
+  return true;
+}
+
+bool Extension::LoadWebIntentServices(string16* error) {
+  DCHECK(error);
+
+  if (!manifest_->HasKey(keys::kIntents))
+    return true;
+
+  DictionaryValue* all_services = NULL;
+  if (!manifest_->GetDictionary(keys::kIntents, &all_services)) {
+    *error = ASCIIToUTF16(errors::kInvalidIntents);
+    return false;
+  }
+
+  for (DictionaryValue::key_iterator iter(all_services->begin_keys());
+       iter != all_services->end_keys(); ++iter) {
+    // Any entry in the intents dictionary can either have a list of
+    // dictionaries, or just a single dictionary attached to that. Try
+    // lists first, fall back to single dictionary.
+    ListValue* service_list = NULL;
+    DictionaryValue* one_service = NULL;
+    if (all_services->GetListWithoutPathExpansion(*iter, &service_list)) {
+      for (size_t i = 0; i < service_list->GetSize(); ++i) {
+        if (!service_list->GetDictionary(i, &one_service)) {
+            *error = ASCIIToUTF16(errors::kInvalidIntent);
+            return false;
+        }
+        if (!LoadWebIntentAction(*iter, *one_service, error))
+          return false;
+      }
+    } else {
+      if (!all_services->GetDictionaryWithoutPathExpansion(*iter,
+                                                           &one_service)) {
+        *error = ASCIIToUTF16(errors::kInvalidIntent);
+        return false;
+      }
+      if (!LoadWebIntentAction(*iter, *one_service, error))
+        return false;
+    }
+  }
+  return true;
+}
+
+bool Extension::LoadFileHandler(const std::string& handler_id,
+                                const DictionaryValue& handler_info,
+                                string16* error) {
+  DCHECK(error);
+  DCHECK(is_platform_app());
+  webkit_glue::WebIntentServiceData service;
+
+  // TODO(jeremya): use a file-handler-specific data structure instead of web
+  // intents.
+  service.action = ASCIIToUTF16("http://webintents.org/view");
+
+  const ListValue* mime_types = NULL;
+  if (!handler_info.HasKey(keys::kFileHandlerTypes) ||
+      !handler_info.GetList(keys::kFileHandlerTypes, &mime_types) ||
+      mime_types->GetSize() == 0) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kInvalidFileHandlerType, handler_id);
+    return false;
+  }
+
+  service.service_url = GetBackgroundURL();
+
+  if (handler_info.HasKey(keys::kFileHandlerTitle) &&
+      !handler_info.GetString(keys::kFileHandlerTitle, &service.title)) {
+    *error = ASCIIToUTF16(errors::kInvalidFileHandlerTitle);
+    return false;
+  }
+
+  for (size_t i = 0; i < mime_types->GetSize(); ++i) {
+    if (!mime_types->GetString(i, &service.type)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidFileHandlerTypeElement, handler_id,
+          std::string(base::IntToString(i)));
+      return false;
+    }
+    intents_services_.push_back(service);
+  }
+  return true;
+}
+
+bool Extension::LoadFileHandlers(string16* error) {
+  DCHECK(error);
+
+  if (!manifest_->HasKey(keys::kFileHandlers))
+    return true;
+
+  DictionaryValue* all_handlers = NULL;
+  if (!manifest_->GetDictionary(keys::kFileHandlers, &all_handlers)) {
+    *error = ASCIIToUTF16(errors::kInvalidFileHandlers);
+    return false;
+  }
+
+  for (DictionaryValue::key_iterator iter(all_handlers->begin_keys());
+       iter != all_handlers->end_keys(); ++iter) {
+    // A file handler entry is a title and a list of MIME types to handle.
+    DictionaryValue* handler = NULL;
+    if (all_handlers->GetDictionaryWithoutPathExpansion(*iter, &handler)) {
+      if (!LoadFileHandler(*iter, *handler, error))
+        return false;
+    } else {
+      *error = ASCIIToUTF16(errors::kInvalidFileHandlers);
+      return false;
+    }
+  }
+  return true;
+}
+
+bool Extension::LoadExtensionFeatures(const APIPermissionSet& api_permissions,
+                                      string16* error) {
+  if (manifest_->HasKey(keys::kConvertedFromUserScript))
+    manifest_->GetBoolean(keys::kConvertedFromUserScript,
+                          &converted_from_user_script_);
+
+  if (!LoadDevToolsPage(error) ||
+      !LoadInputComponents(api_permissions, error) ||
+      !LoadContentScripts(error) ||
+      !LoadPageAction(error) ||
+      !LoadBrowserAction(error) ||
+      !LoadScriptBadge(error) ||
+      !LoadFileBrowserHandlers(error) ||
+      !LoadChromeURLOverrides(error) ||
+      !LoadOmnibox(error) ||
+      !LoadTextToSpeechVoices(error) ||
+      !LoadIncognitoMode(error) ||
+      !LoadFileHandlers(error) ||
+      !LoadContentSecurityPolicy(error))
+    return false;
+
+  return true;
+}
+
+bool Extension::LoadDevToolsPage(string16* error) {
+  if (!manifest_->HasKey(keys::kDevToolsPage))
+    return true;
+  std::string devtools_str;
+  if (!manifest_->GetString(keys::kDevToolsPage, &devtools_str)) {
+    *error = ASCIIToUTF16(errors::kInvalidDevToolsPage);
+    return false;
+  }
+  devtools_url_ = GetResourceURL(devtools_str);
+  return true;
+}
+
+bool Extension::LoadInputComponents(const APIPermissionSet& api_permissions,
+                                    string16* error) {
+  if (!manifest_->HasKey(keys::kInputComponents))
+    return true;
+  ListValue* list_value = NULL;
+  if (!manifest_->GetList(keys::kInputComponents, &list_value)) {
+    *error = ASCIIToUTF16(errors::kInvalidInputComponents);
+    return false;
+  }
+
+  for (size_t i = 0; i < list_value->GetSize(); ++i) {
+    DictionaryValue* module_value = NULL;
+    std::string name_str;
+    InputComponentType type;
+    std::string id_str;
+    std::string description_str;
+    std::string language_str;
+    std::set<std::string> layouts;
+    std::string shortcut_keycode_str;
+    bool shortcut_alt = false;
+    bool shortcut_ctrl = false;
+    bool shortcut_shift = false;
+
+    if (!list_value->GetDictionary(i, &module_value)) {
+      *error = ASCIIToUTF16(errors::kInvalidInputComponents);
+      return false;
+    }
+
+    // Get input_components[i].name.
+    if (!module_value->GetString(keys::kName, &name_str)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidInputComponentName, base::IntToString(i));
+      return false;
+    }
+
+    // Get input_components[i].type.
+    std::string type_str;
+    if (module_value->GetString(keys::kType, &type_str)) {
+      if (type_str == "ime") {
+        type = INPUT_COMPONENT_TYPE_IME;
+      } else {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidInputComponentType, base::IntToString(i));
+        return false;
+      }
+    } else {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidInputComponentType, base::IntToString(i));
+      return false;
+    }
+
+    // Get input_components[i].id.
+    if (!module_value->GetString(keys::kId, &id_str)) {
+      id_str = "";
+    }
+
+    // Get input_components[i].description.
+    if (!module_value->GetString(keys::kDescription, &description_str)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidInputComponentDescription, base::IntToString(i));
+      return false;
+    }
+    // Get input_components[i].language.
+    if (!module_value->GetString(keys::kLanguage, &language_str)) {
+      language_str = "";
+    }
+
+    // Get input_components[i].layouts.
+    ListValue* layouts_value = NULL;
+    if (!module_value->GetList(keys::kLayouts, &layouts_value)) {
+      *error = ASCIIToUTF16(errors::kInvalidInputComponentLayouts);
+      return false;
+    }
+
+    for (size_t j = 0; j < layouts_value->GetSize(); ++j) {
+      std::string layout_name_str;
+      if (!layouts_value->GetString(j, &layout_name_str)) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidInputComponentLayoutName, base::IntToString(i),
+            base::IntToString(j));
+        return false;
+      }
+      layouts.insert(layout_name_str);
+    }
+
+    if (module_value->HasKey(keys::kShortcutKey)) {
+      DictionaryValue* shortcut_value = NULL;
+      if (!module_value->GetDictionary(keys::kShortcutKey, &shortcut_value)) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidInputComponentShortcutKey, base::IntToString(i));
+        return false;
+      }
+
+      // Get input_components[i].shortcut_keycode.
+      if (!shortcut_value->GetString(keys::kKeycode, &shortcut_keycode_str)) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidInputComponentShortcutKeycode,
+            base::IntToString(i));
+        return false;
+      }
+
+      // Get input_components[i].shortcut_alt.
+      if (!shortcut_value->GetBoolean(keys::kAltKey, &shortcut_alt)) {
+        shortcut_alt = false;
+      }
+
+      // Get input_components[i].shortcut_ctrl.
+      if (!shortcut_value->GetBoolean(keys::kCtrlKey, &shortcut_ctrl)) {
+        shortcut_ctrl = false;
+      }
+
+      // Get input_components[i].shortcut_shift.
+      if (!shortcut_value->GetBoolean(keys::kShiftKey, &shortcut_shift)) {
+        shortcut_shift = false;
+      }
+    }
+
+    input_components_.push_back(InputComponentInfo());
+    input_components_.back().name = name_str;
+    input_components_.back().type = type;
+    input_components_.back().id = id_str;
+    input_components_.back().description = description_str;
+    input_components_.back().language = language_str;
+    input_components_.back().layouts.insert(layouts.begin(), layouts.end());
+    input_components_.back().shortcut_keycode = shortcut_keycode_str;
+    input_components_.back().shortcut_alt = shortcut_alt;
+    input_components_.back().shortcut_ctrl = shortcut_ctrl;
+    input_components_.back().shortcut_shift = shortcut_shift;
+  }
+
+  return true;
+}
+
+bool Extension::LoadContentScripts(string16* error) {
+  if (!manifest_->HasKey(keys::kContentScripts))
+    return true;
+  ListValue* list_value;
+  if (!manifest_->GetList(keys::kContentScripts, &list_value)) {
+    *error = ASCIIToUTF16(errors::kInvalidContentScriptsList);
+    return false;
+  }
+
+  for (size_t i = 0; i < list_value->GetSize(); ++i) {
+    DictionaryValue* content_script = NULL;
+    if (!list_value->GetDictionary(i, &content_script)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidContentScript, base::IntToString(i));
+      return false;
+    }
+
+    UserScript script;
+    if (!LoadUserScriptHelper(content_script, i, error, &script))
+      return false;  // Failed to parse script context definition.
+    script.set_extension_id(id());
+    if (converted_from_user_script_) {
+      script.set_emulate_greasemonkey(true);
+      script.set_match_all_frames(true);  // Greasemonkey matches all frames.
+    }
+    content_scripts_.push_back(script);
+  }
+  return true;
+}
+
+bool Extension::LoadPageAction(string16* error) {
+  DictionaryValue* page_action_value = NULL;
+
+  if (manifest_->HasKey(keys::kPageActions)) {
+    ListValue* list_value = NULL;
+    if (!manifest_->GetList(keys::kPageActions, &list_value)) {
+      *error = ASCIIToUTF16(errors::kInvalidPageActionsList);
+      return false;
+    }
+
+    size_t list_value_length = list_value->GetSize();
+
+    if (list_value_length == 0u) {
+      // A list with zero items is allowed, and is equivalent to not having
+      // a page_actions key in the manifest.  Don't set |page_action_value|.
+    } else if (list_value_length == 1u) {
+      if (!list_value->GetDictionary(0, &page_action_value)) {
+        *error = ASCIIToUTF16(errors::kInvalidPageAction);
+        return false;
+      }
+    } else {  // list_value_length > 1u.
+      *error = ASCIIToUTF16(errors::kInvalidPageActionsListSize);
+      return false;
+    }
+  } else if (manifest_->HasKey(keys::kPageAction)) {
+    if (!manifest_->GetDictionary(keys::kPageAction, &page_action_value)) {
+      *error = ASCIIToUTF16(errors::kInvalidPageAction);
+      return false;
+    }
+  }
+
+  // If page_action_value is not NULL, then there was a valid page action.
+  if (page_action_value) {
+    page_action_info_ = LoadExtensionActionInfoHelper(
+        page_action_value, Extension::ActionInfo::TYPE_PAGE, error);
+    if (!page_action_info_.get())
+      return false;  // Failed to parse page action definition.
+  }
+
+  return true;
+}
+
+bool Extension::LoadBrowserAction(string16* error) {
+  if (!manifest_->HasKey(keys::kBrowserAction))
+    return true;
+  DictionaryValue* browser_action_value = NULL;
+  if (!manifest_->GetDictionary(keys::kBrowserAction, &browser_action_value)) {
+    *error = ASCIIToUTF16(errors::kInvalidBrowserAction);
+    return false;
+  }
+
+  browser_action_info_ = LoadExtensionActionInfoHelper(
+      browser_action_value, Extension::ActionInfo::TYPE_BROWSER, error);
+  if (!browser_action_info_.get())
+    return false;  // Failed to parse browser action definition.
+  return true;
+}
+
+bool Extension::LoadScriptBadge(string16* error) {
+  if (manifest_->HasKey(keys::kScriptBadge)) {
+    if (!FeatureSwitch::script_badges()->IsEnabled()) {
+      // So as to not confuse developers if they specify a script badge section
+      // in the manifest, show a warning if the script badge declaration isn't
+      // going to have any effect.
+      install_warnings_.push_back(
+          InstallWarning(InstallWarning::FORMAT_TEXT,
+                         errors::kScriptBadgeRequiresFlag));
+    }
+
+    DictionaryValue* script_badge_value = NULL;
+    if (!manifest_->GetDictionary(keys::kScriptBadge, &script_badge_value)) {
+      *error = ASCIIToUTF16(errors::kInvalidScriptBadge);
+      return false;
+    }
+
+    script_badge_info_ = LoadExtensionActionInfoHelper(
+        script_badge_value, Extension::ActionInfo::TYPE_SCRIPT_BADGE, error);
+    if (!script_badge_info_.get())
+      return false;  // Failed to parse script badge definition.
+  } else {
+    script_badge_info_.reset(new ActionInfo());
+  }
+
+  // Script badges always use their extension's title and icon so users can rely
+  // on the visual appearance to know which extension is running.  This isn't
+  // bulletproof since an malicious extension could use a different 16x16 icon
+  // that matches the icon of a trusted extension, and users wouldn't be warned
+  // during installation.
+
+  if (!script_badge_info_->default_title.empty()) {
+    install_warnings_.push_back(
+        InstallWarning(InstallWarning::FORMAT_TEXT,
+                       errors::kScriptBadgeTitleIgnored));
+  }
+  script_badge_info_->default_title = name();
+
+  if (!script_badge_info_->default_icon.empty()) {
+    install_warnings_.push_back(
+        InstallWarning(InstallWarning::FORMAT_TEXT,
+                       errors::kScriptBadgeIconIgnored));
+  }
+
+  script_badge_info_->default_icon.Clear();
+  for (size_t i = 0; i < extension_misc::kNumScriptBadgeIconSizes; i++) {
+    std::string path = icons().Get(extension_misc::kScriptBadgeIconSizes[i],
+                                   ExtensionIconSet::MATCH_BIGGER);
+    if (!path.empty())
+      script_badge_info_->default_icon.Add(
+          extension_misc::kScriptBadgeIconSizes[i], path);
+  }
+
+  return true;
+}
+
+bool Extension::LoadFileBrowserHandlers(string16* error) {
+  if (!manifest_->HasKey(keys::kFileBrowserHandlers))
+    return true;
+  ListValue* file_browser_handlers_value = NULL;
+  if (!manifest_->GetList(keys::kFileBrowserHandlers,
+                         &file_browser_handlers_value)) {
+    *error = ASCIIToUTF16(errors::kInvalidFileBrowserHandler);
+    return false;
+  }
+  file_browser_handlers_.reset(
+      LoadFileBrowserHandlersHelper(file_browser_handlers_value, error));
+  if (!file_browser_handlers_.get())
+    return false;  // Failed to parse file browser actions definition.
+  return true;
+}
+
+Extension::FileBrowserHandlerList* Extension::LoadFileBrowserHandlersHelper(
+    const ListValue* extension_actions, string16* error) {
+  scoped_ptr<FileBrowserHandlerList> result(
+      new FileBrowserHandlerList());
+  for (ListValue::const_iterator iter = extension_actions->begin();
+       iter != extension_actions->end();
+       ++iter) {
+    if (!(*iter)->IsType(Value::TYPE_DICTIONARY)) {
+      *error = ASCIIToUTF16(errors::kInvalidFileBrowserHandler);
+      return NULL;
+    }
+    scoped_ptr<FileBrowserHandler> action(
+        LoadFileBrowserHandler(
+            reinterpret_cast<DictionaryValue*>(*iter), error));
+    if (!action.get())
+      return NULL;  // Failed to parse file browser action definition.
+    result->push_back(linked_ptr<FileBrowserHandler>(action.release()));
+  }
+  return result.release();
+}
+
+FileBrowserHandler* Extension::LoadFileBrowserHandler(
+    const DictionaryValue* file_browser_handler, string16* error) {
+  scoped_ptr<FileBrowserHandler> result(new FileBrowserHandler());
+  result->set_extension_id(id());
+
+  std::string id;
+  // Read the file action |id| (mandatory).
+  if (!file_browser_handler->HasKey(keys::kPageActionId) ||
+      !file_browser_handler->GetString(keys::kPageActionId, &id)) {
+    *error = ASCIIToUTF16(errors::kInvalidPageActionId);
+    return NULL;
+  }
+  result->set_id(id);
+
+  // Read the page action title from |default_title| (mandatory).
+  std::string title;
+  if (!file_browser_handler->HasKey(keys::kPageActionDefaultTitle) ||
+      !file_browser_handler->GetString(keys::kPageActionDefaultTitle, &title)) {
+    *error = ASCIIToUTF16(errors::kInvalidPageActionDefaultTitle);
+    return NULL;
+  }
+  result->set_title(title);
+
+  // Initialize access permissions (optional).
+  const ListValue* access_list_value = NULL;
+  if (file_browser_handler->HasKey(keys::kFileAccessList)) {
+    if (!file_browser_handler->GetList(keys::kFileAccessList,
+                                       &access_list_value) ||
+        access_list_value->empty()) {
+      *error = ASCIIToUTF16(errors::kInvalidFileAccessList);
+      return NULL;
+    }
+    for (size_t i = 0; i < access_list_value->GetSize(); ++i) {
+      std::string access;
+      if (!access_list_value->GetString(i, &access) ||
+          result->AddFileAccessPermission(access)) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidFileAccessValue, base::IntToString(i));
+        return NULL;
+      }
+    }
+  }
+  if (!result->ValidateFileAccessPermissions()) {
+    *error = ASCIIToUTF16(errors::kInvalidFileAccessList);
+    return NULL;
+  }
+
+  // Initialize file filters (mandatory, unless "create" access is specified,
+  // in which case is ignored).
+  if (!result->HasCreateAccessPermission()) {
+    const ListValue* list_value = NULL;
+    if (!file_browser_handler->HasKey(keys::kFileFilters) ||
+        !file_browser_handler->GetList(keys::kFileFilters, &list_value) ||
+        list_value->empty()) {
+      *error = ASCIIToUTF16(errors::kInvalidFileFiltersList);
+      return NULL;
+    }
+    for (size_t i = 0; i < list_value->GetSize(); ++i) {
+      std::string filter;
+      if (!list_value->GetString(i, &filter)) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidFileFilterValue, base::IntToString(i));
+        return NULL;
+      }
+      StringToLowerASCII(&filter);
+      if (!StartsWithASCII(filter,
+                           std::string(chrome::kFileSystemScheme) + ':',
+                           true)) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidURLPatternError, filter);
+        return NULL;
+      }
+      // The user inputs filesystem:*; we don't actually implement scheme
+      // wildcards in URLPattern, so transform to what will match correctly.
+      filter.replace(0, 11, "chrome-extension://*/");
+      URLPattern pattern(URLPattern::SCHEME_EXTENSION);
+      if (pattern.Parse(filter) != URLPattern::PARSE_SUCCESS) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidURLPatternError, filter);
+        return NULL;
+      }
+      std::string path = pattern.path();
+      bool allowed = path == "/*" || path == "/*.*" ||
+          (path.compare(0, 3, "/*.") == 0 &&
+           path.find_first_of('*', 3) == std::string::npos);
+      if (!allowed) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidURLPatternError, filter);
+        return NULL;
+      }
+      result->AddPattern(pattern);
+    }
+  }
+
+  std::string default_icon;
+  // Read the file browser action |default_icon| (optional).
+  if (file_browser_handler->HasKey(keys::kPageActionDefaultIcon)) {
+    if (!file_browser_handler->GetString(
+            keys::kPageActionDefaultIcon, &default_icon) ||
+        default_icon.empty()) {
+      *error = ASCIIToUTF16(errors::kInvalidPageActionIconPath);
+      return NULL;
+    }
+    result->set_icon_path(default_icon);
+  }
+
+  return result.release();
+}
+
+bool Extension::LoadChromeURLOverrides(string16* error) {
+  if (!manifest_->HasKey(keys::kChromeURLOverrides))
+    return true;
+  DictionaryValue* overrides = NULL;
+  if (!manifest_->GetDictionary(keys::kChromeURLOverrides, &overrides)) {
+    *error = ASCIIToUTF16(errors::kInvalidChromeURLOverrides);
+    return false;
+  }
+
+  // Validate that the overrides are all strings
+  for (DictionaryValue::key_iterator iter = overrides->begin_keys();
+       iter != overrides->end_keys(); ++iter) {
+    std::string page = *iter;
+    std::string val;
+    // Restrict override pages to a list of supported URLs.
+    bool is_override = (page != chrome::kChromeUINewTabHost &&
+                        page != chrome::kChromeUIBookmarksHost &&
+                        page != chrome::kChromeUIHistoryHost);
+#if defined(OS_CHROMEOS)
+    is_override = (is_override &&
+                   page != chrome::kChromeUIActivationMessageHost &&
+                   page != chrome::kChromeUIWallpaperHost);
+#endif
+#if defined(FILE_MANAGER_EXTENSION)
+    is_override = (is_override &&
+                   !(location() == COMPONENT &&
+                     page == chrome::kChromeUIFileManagerHost));
+#endif
+
+    if (is_override || !overrides->GetStringWithoutPathExpansion(*iter, &val)) {
+      *error = ASCIIToUTF16(errors::kInvalidChromeURLOverrides);
+      return false;
+    }
+    // Replace the entry with a fully qualified chrome-extension:// URL.
+    chrome_url_overrides_[page] = GetResourceURL(val);
+
+    // For component extensions, add override URL to extent patterns.
+    if (is_legacy_packaged_app() && location() == COMPONENT) {
+      URLPattern pattern(URLPattern::SCHEME_CHROMEUI);
+      std::string url = base::StringPrintf(kOverrideExtentUrlPatternFormat,
+                                           page.c_str());
+      if (pattern.Parse(url) != URLPattern::PARSE_SUCCESS) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidURLPatternError, url);
+        return false;
+      }
+      extent_.AddPattern(pattern);
+    }
+  }
+
+  // An extension may override at most one page.
+  if (overrides->size() > 1) {
+    *error = ASCIIToUTF16(errors::kMultipleOverrides);
+    return false;
+  }
+
+  return true;
+}
+
+bool Extension::LoadOmnibox(string16* error) {
+  if (!manifest_->HasKey(keys::kOmnibox))
+    return true;
+  if (!manifest_->GetString(keys::kOmniboxKeyword, &omnibox_keyword_) ||
+      omnibox_keyword_.empty()) {
+    *error = ASCIIToUTF16(errors::kInvalidOmniboxKeyword);
+    return false;
+  }
+  return true;
+}
+
+bool Extension::LoadTextToSpeechVoices(string16* error) {
+  if (!manifest_->HasKey(keys::kTtsEngine))
+    return true;
+  DictionaryValue* tts_dict = NULL;
+  if (!manifest_->GetDictionary(keys::kTtsEngine, &tts_dict)) {
+    *error = ASCIIToUTF16(errors::kInvalidTts);
+    return false;
+  }
+
+  if (tts_dict->HasKey(keys::kTtsVoices)) {
+    ListValue* tts_voices = NULL;
+    if (!tts_dict->GetList(keys::kTtsVoices, &tts_voices)) {
+      *error = ASCIIToUTF16(errors::kInvalidTtsVoices);
+      return false;
+    }
+
+    for (size_t i = 0; i < tts_voices->GetSize(); i++) {
+      DictionaryValue* one_tts_voice = NULL;
+      if (!tts_voices->GetDictionary(i, &one_tts_voice)) {
+        *error = ASCIIToUTF16(errors::kInvalidTtsVoices);
+        return false;
+      }
+
+      TtsVoice voice_data;
+      if (one_tts_voice->HasKey(keys::kTtsVoicesVoiceName)) {
+        if (!one_tts_voice->GetString(
+                keys::kTtsVoicesVoiceName, &voice_data.voice_name)) {
+          *error = ASCIIToUTF16(errors::kInvalidTtsVoicesVoiceName);
+          return false;
+        }
+      }
+      if (one_tts_voice->HasKey(keys::kTtsVoicesLang)) {
+        if (!one_tts_voice->GetString(
+                keys::kTtsVoicesLang, &voice_data.lang) ||
+            !l10n_util::IsValidLocaleSyntax(voice_data.lang)) {
+          *error = ASCIIToUTF16(errors::kInvalidTtsVoicesLang);
+          return false;
+        }
+      }
+      if (one_tts_voice->HasKey(keys::kTtsVoicesGender)) {
+        if (!one_tts_voice->GetString(
+                keys::kTtsVoicesGender, &voice_data.gender) ||
+            (voice_data.gender != keys::kTtsGenderMale &&
+             voice_data.gender != keys::kTtsGenderFemale)) {
+          *error = ASCIIToUTF16(errors::kInvalidTtsVoicesGender);
+          return false;
+        }
+      }
+      if (one_tts_voice->HasKey(keys::kTtsVoicesEventTypes)) {
+        ListValue* event_types_list;
+        if (!one_tts_voice->GetList(
+                keys::kTtsVoicesEventTypes, &event_types_list)) {
+          *error = ASCIIToUTF16(errors::kInvalidTtsVoicesEventTypes);
+          return false;
+        }
+        for (size_t i = 0; i < event_types_list->GetSize(); i++) {
+          std::string event_type;
+          if (!event_types_list->GetString(i, &event_type)) {
+            *error = ASCIIToUTF16(errors::kInvalidTtsVoicesEventTypes);
+            return false;
+          }
+          if (event_type != keys::kTtsVoicesEventTypeEnd &&
+              event_type != keys::kTtsVoicesEventTypeError &&
+              event_type != keys::kTtsVoicesEventTypeMarker &&
+              event_type != keys::kTtsVoicesEventTypeSentence &&
+              event_type != keys::kTtsVoicesEventTypeStart &&
+              event_type != keys::kTtsVoicesEventTypeWord) {
+            *error = ASCIIToUTF16(errors::kInvalidTtsVoicesEventTypes);
+            return false;
+          }
+          if (voice_data.event_types.find(event_type) !=
+              voice_data.event_types.end()) {
+            *error = ASCIIToUTF16(errors::kInvalidTtsVoicesEventTypes);
+            return false;
+          }
+          voice_data.event_types.insert(event_type);
+        }
+      }
+
+      tts_voices_.push_back(voice_data);
+    }
+  }
+  return true;
+}
+
+bool Extension::LoadIncognitoMode(string16* error) {
+  // Apps default to split mode, extensions default to spanning.
+  incognito_split_mode_ = is_app();
+  if (!manifest_->HasKey(keys::kIncognito))
+    return true;
+  std::string value;
+  if (!manifest_->GetString(keys::kIncognito, &value)) {
+    *error = ASCIIToUTF16(errors::kInvalidIncognitoBehavior);
+    return false;
+  }
+  if (value == values::kIncognitoSpanning) {
+    incognito_split_mode_ = false;
+  } else if (value == values::kIncognitoSplit) {
+    incognito_split_mode_ = true;
+  } else {
+    *error = ASCIIToUTF16(errors::kInvalidIncognitoBehavior);
+    return false;
+  }
+  return true;
+}
+
+bool Extension::LoadContentSecurityPolicy(string16* error) {
+  const std::string& key = is_platform_app() ?
+      keys::kPlatformAppContentSecurityPolicy : keys::kContentSecurityPolicy;
+
+  if (manifest_->HasPath(key)) {
+    std::string content_security_policy;
+    if (!manifest_->GetString(key, &content_security_policy)) {
+      *error = ASCIIToUTF16(errors::kInvalidContentSecurityPolicy);
+      return false;
+    }
+    if (!ContentSecurityPolicyIsLegal(content_security_policy)) {
+      *error = ASCIIToUTF16(errors::kInvalidContentSecurityPolicy);
+      return false;
+    }
+    if (manifest_version_ >= 2 &&
+        !ContentSecurityPolicyIsSecure(content_security_policy, GetType())) {
+      *error = ASCIIToUTF16(errors::kInsecureContentSecurityPolicy);
+      return false;
+    }
+
+    content_security_policy_ = content_security_policy;
+  } else if (manifest_version_ >= 2) {
+    // Manifest version 2 introduced a default Content-Security-Policy.
+    // TODO(abarth): Should we continue to let extensions override the
+    //               default Content-Security-Policy?
+    content_security_policy_ = is_platform_app() ?
+        kDefaultPlatformAppContentSecurityPolicy :
+        kDefaultContentSecurityPolicy;
+    CHECK(ContentSecurityPolicyIsSecure(content_security_policy_, GetType()));
+  }
+  return true;
+}
+
+bool Extension::LoadAppIsolation(const APIPermissionSet& api_permissions,
+                                 string16* error) {
+  // Platform apps always get isolated storage.
+  if (is_platform_app()) {
+    is_storage_isolated_ = true;
+    return true;
+  }
+
+  // Other apps only get it if it is requested _and_ experimental APIs are
+  // enabled.
+  if (!api_permissions.count(APIPermission::kExperimental) || !is_app())
+    return true;
+
+  Value* tmp_isolation = NULL;
+  if (!manifest_->Get(keys::kIsolation, &tmp_isolation))
+    return true;
+
+  if (tmp_isolation->GetType() != Value::TYPE_LIST) {
+    *error = ASCIIToUTF16(errors::kInvalidIsolation);
+    return false;
+  }
+
+  ListValue* isolation_list = static_cast<ListValue*>(tmp_isolation);
+  for (size_t i = 0; i < isolation_list->GetSize(); ++i) {
+    std::string isolation_string;
+    if (!isolation_list->GetString(i, &isolation_string)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidIsolationValue,
+          base::UintToString(i));
+      return false;
+    }
+
+    // Check for isolated storage.
+    if (isolation_string == values::kIsolatedStorage) {
+      is_storage_isolated_ = true;
+    } else {
+      DLOG(WARNING) << "Did not recognize isolation type: " << isolation_string;
+    }
+  }
+  return true;
+}
+
+bool Extension::LoadThemeFeatures(string16* error) {
+  if (!manifest_->HasKey(keys::kTheme))
+    return true;
+  DictionaryValue* theme_value = NULL;
+  if (!manifest_->GetDictionary(keys::kTheme, &theme_value)) {
+    *error = ASCIIToUTF16(errors::kInvalidTheme);
+    return false;
+  }
+  if (!LoadThemeImages(theme_value, error))
+    return false;
+  if (!LoadThemeColors(theme_value, error))
+    return false;
+  if (!LoadThemeTints(theme_value, error))
+    return false;
+  if (!LoadThemeDisplayProperties(theme_value, error))
+    return false;
+
+  return true;
+}
+
+bool Extension::LoadThemeImages(const DictionaryValue* theme_value,
+                                string16* error) {
+  const DictionaryValue* images_value = NULL;
+  if (theme_value->GetDictionary(keys::kThemeImages, &images_value)) {
+    // Validate that the images are all strings
+    for (DictionaryValue::key_iterator iter = images_value->begin_keys();
+         iter != images_value->end_keys(); ++iter) {
+      std::string val;
+      if (!images_value->GetString(*iter, &val)) {
+        *error = ASCIIToUTF16(errors::kInvalidThemeImages);
+        return false;
+      }
+    }
+    theme_images_.reset(images_value->DeepCopy());
+  }
+  return true;
+}
+
+bool Extension::LoadThemeColors(const DictionaryValue* theme_value,
+                                string16* error) {
+  const DictionaryValue* colors_value = NULL;
+  if (theme_value->GetDictionary(keys::kThemeColors, &colors_value)) {
+    // Validate that the colors are RGB or RGBA lists
+    for (DictionaryValue::key_iterator iter = colors_value->begin_keys();
+         iter != colors_value->end_keys(); ++iter) {
+      const ListValue* color_list = NULL;
+      double alpha = 0.0;
+      int color = 0;
+      // The color must be a list
+      if (!colors_value->GetListWithoutPathExpansion(*iter, &color_list) ||
+          // And either 3 items (RGB) or 4 (RGBA)
+          ((color_list->GetSize() != 3) &&
+           ((color_list->GetSize() != 4) ||
+            // For RGBA, the fourth item must be a real or int alpha value.
+            // Note that GetDouble() can get an integer value.
+            !color_list->GetDouble(3, &alpha))) ||
+          // For both RGB and RGBA, the first three items must be ints (R,G,B)
+          !color_list->GetInteger(0, &color) ||
+          !color_list->GetInteger(1, &color) ||
+          !color_list->GetInteger(2, &color)) {
+        *error = ASCIIToUTF16(errors::kInvalidThemeColors);
+        return false;
+      }
+    }
+    theme_colors_.reset(colors_value->DeepCopy());
+  }
+  return true;
+}
+
+bool Extension::LoadThemeTints(const DictionaryValue* theme_value,
+                               string16* error) {
+  const DictionaryValue* tints_value = NULL;
+  if (!theme_value->GetDictionary(keys::kThemeTints, &tints_value))
+    return true;
+
+  // Validate that the tints are all reals.
+  for (DictionaryValue::key_iterator iter = tints_value->begin_keys();
+       iter != tints_value->end_keys(); ++iter) {
+    const ListValue* tint_list = NULL;
+    double v = 0.0;
+    if (!tints_value->GetListWithoutPathExpansion(*iter, &tint_list) ||
+        tint_list->GetSize() != 3 ||
+        !tint_list->GetDouble(0, &v) ||
+        !tint_list->GetDouble(1, &v) ||
+        !tint_list->GetDouble(2, &v)) {
+      *error = ASCIIToUTF16(errors::kInvalidThemeTints);
+      return false;
+    }
+  }
+  theme_tints_.reset(tints_value->DeepCopy());
+  return true;
+}
+
+bool Extension::LoadThemeDisplayProperties(const DictionaryValue* theme_value,
+                                           string16* error) {
+  const DictionaryValue* display_properties_value = NULL;
+  if (theme_value->GetDictionary(keys::kThemeDisplayProperties,
+                                 &display_properties_value)) {
+    theme_display_properties_.reset(
+        display_properties_value->DeepCopy());
+  }
+  return true;
+}
+
+// static
+bool Extension::IsTrustedId(const std::string& id) {
+  // See http://b/4946060 for more details.
+  return id == std::string("nckgahadagoaajjgafhacjanaoiihapd");
+}
+
+Extension::Extension(const FilePath& path,
+                     scoped_ptr<extensions::Manifest> manifest)
+    : manifest_version_(0),
+      incognito_split_mode_(false),
+      offline_enabled_(false),
+      converted_from_user_script_(false),
+      background_page_is_persistent_(true),
+      allow_background_js_access_(true),
+      manifest_(manifest.release()),
+      is_storage_isolated_(false),
+      launch_container_(extension_misc::LAUNCH_TAB),
+      launch_width_(0),
+      launch_height_(0),
+      display_in_launcher_(true),
+      display_in_new_tab_page_(true),
+      wants_file_access_(false),
+      creation_flags_(0) {
+  DCHECK(path.empty() || path.IsAbsolute());
+  path_ = MaybeNormalizePath(path);
+}
+
+Extension::~Extension() {
+}
+
+ExtensionResource Extension::GetResource(
+    const std::string& relative_path) const {
+  std::string new_path = relative_path;
+  // We have some legacy data where resources have leading slashes.
+  // See: http://crbug.com/121164
+  if (!new_path.empty() && new_path.at(0) == '/')
+    new_path.erase(0, 1);
+#if defined(OS_POSIX)
+  FilePath relative_file_path(new_path);
+#elif defined(OS_WIN)
+  FilePath relative_file_path(UTF8ToWide(new_path));
+#endif
+  ExtensionResource r(id(), path(), relative_file_path);
+  if ((creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE)) {
+    r.set_follow_symlinks_anywhere();
+  }
+  return r;
+}
+
+ExtensionResource Extension::GetResource(
+    const FilePath& relative_file_path) const {
+  ExtensionResource r(id(), path(), relative_file_path);
+  if ((creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE)) {
+    r.set_follow_symlinks_anywhere();
+  }
+  return r;
+}
+
+// TODO(rafaelw): Move ParsePEMKeyBytes, ProducePEM & FormatPEMForOutput to a
+// util class in base:
+// http://code.google.com/p/chromium/issues/detail?id=13572
+bool Extension::ParsePEMKeyBytes(const std::string& input,
+                                 std::string* output) {
+  DCHECK(output);
+  if (!output)
+    return false;
+  if (input.length() == 0)
+    return false;
+
+  std::string working = input;
+  if (StartsWithASCII(working, kKeyBeginHeaderMarker, true)) {
+    working = CollapseWhitespaceASCII(working, true);
+    size_t header_pos = working.find(kKeyInfoEndMarker,
+      sizeof(kKeyBeginHeaderMarker) - 1);
+    if (header_pos == std::string::npos)
+      return false;
+    size_t start_pos = header_pos + sizeof(kKeyInfoEndMarker) - 1;
+    size_t end_pos = working.rfind(kKeyBeginFooterMarker);
+    if (end_pos == std::string::npos)
+      return false;
+    if (start_pos >= end_pos)
+      return false;
+
+    working = working.substr(start_pos, end_pos - start_pos);
+    if (working.length() == 0)
+      return false;
+  }
+
+  return base::Base64Decode(working, output);
+}
+
+bool Extension::ProducePEM(const std::string& input, std::string* output) {
+  DCHECK(output);
+  return (input.length() == 0) ? false : base::Base64Encode(input, output);
+}
+
+bool Extension::FormatPEMForFileOutput(const std::string& input,
+                                       std::string* output,
+                                       bool is_public) {
+  DCHECK(output);
+  if (input.length() == 0)
+    return false;
+  *output = "";
+  output->append(kKeyBeginHeaderMarker);
+  output->append(" ");
+  output->append(is_public ? kPublic : kPrivate);
+  output->append(" ");
+  output->append(kKeyInfoEndMarker);
+  output->append("\n");
+  for (size_t i = 0; i < input.length(); ) {
+    int slice = std::min<int>(input.length() - i, kPEMOutputColumns);
+    output->append(input.substr(i, slice));
+    output->append("\n");
+    i += slice;
+  }
+  output->append(kKeyBeginFooterMarker);
+  output->append(" ");
+  output->append(is_public ? kPublic : kPrivate);
+  output->append(" ");
+  output->append(kKeyInfoEndMarker);
+  output->append("\n");
+
+  return true;
+}
+
+// static
+void Extension::DecodeIcon(const Extension* extension,
+                           int preferred_icon_size,
+                           ExtensionIconSet::MatchType match_type,
+                           scoped_ptr<SkBitmap>* result) {
+  std::string path = extension->icons().Get(preferred_icon_size, match_type);
+  int size = extension->icons().GetIconSizeFromPath(path);
+  ExtensionResource icon_resource = extension->GetResource(path);
+  DecodeIconFromPath(icon_resource.GetFilePath(), size, result);
+}
+
+// static
+void Extension::DecodeIcon(const Extension* extension,
+                           int icon_size,
+                           scoped_ptr<SkBitmap>* result) {
+  DecodeIcon(extension, icon_size, ExtensionIconSet::MATCH_EXACTLY, result);
+}
+
+// static
+void Extension::DecodeIconFromPath(const FilePath& icon_path,
+                                   int icon_size,
+                                   scoped_ptr<SkBitmap>* result) {
+  if (icon_path.empty())
+    return;
+
+  std::string file_contents;
+  if (!file_util::ReadFileToString(icon_path, &file_contents)) {
+    DLOG(ERROR) << "Could not read icon file: " << icon_path.LossyDisplayName();
+    return;
+  }
+
+  // Decode the image using WebKit's image decoder.
+  const unsigned char* data =
+    reinterpret_cast<const unsigned char*>(file_contents.data());
+  webkit_glue::ImageDecoder decoder;
+  scoped_ptr<SkBitmap> decoded(new SkBitmap());
+  *decoded = decoder.Decode(data, file_contents.length());
+  if (decoded->empty()) {
+    DLOG(ERROR) << "Could not decode icon file: "
+                << icon_path.LossyDisplayName();
+    return;
+  }
+
+  if (decoded->width() != icon_size || decoded->height() != icon_size) {
+    DLOG(ERROR) << "Icon file has unexpected size: "
+                << base::IntToString(decoded->width()) << "x"
+                << base::IntToString(decoded->height());
+    return;
+  }
+
+  result->swap(decoded);
+}
+
+// static
+const gfx::ImageSkia& Extension::GetDefaultIcon(bool is_app) {
+  int id = is_app ? IDR_APP_DEFAULT_ICON : IDR_EXTENSION_DEFAULT_ICON;
+  return *ResourceBundle::GetSharedInstance().GetImageSkiaNamed(id);
+}
+
+// static
+GURL Extension::GetBaseURLFromExtensionId(const std::string& extension_id) {
+  return GURL(std::string(chrome::kExtensionScheme) +
+              content::kStandardSchemeSeparator + extension_id + "/");
+}
+
+bool Extension::InitFromValue(int flags, string16* error) {
+  DCHECK(error);
+
+  base::AutoLock auto_lock(runtime_data_lock_);
+
+  // Initialize permissions with an empty, default permission set.
+  runtime_data_.SetActivePermissions(new PermissionSet());
+  optional_permission_set_ = new PermissionSet();
+  required_permission_set_ = new PermissionSet();
+
+  creation_flags_ = flags;
+
+  // Important to load manifest version first because many other features
+  // depend on its value.
+  if (!LoadManifestVersion(error))
+    return false;
+
+  // Validate minimum Chrome version. We don't need to store this, since the
+  // extension is not valid if it is incorrect
+  if (!CheckMinimumChromeVersion(error))
+    return false;
+
+  if (!LoadRequiredFeatures(error))
+    return false;
+
+  // We don't need to validate because InitExtensionID already did that.
+  manifest_->GetString(keys::kPublicKey, &public_key_);
+
+  extension_url_ = Extension::GetBaseURLFromExtensionId(id());
+
+  // Load App settings. LoadExtent at least has to be done before
+  // ParsePermissions(), because the valid permissions depend on what type of
+  // package this is.
+  if (is_app() && !LoadAppFeatures(error))
+    return false;
+
+  APIPermissionSet api_permissions;
+  URLPatternSet host_permissions;
+  if (!ParsePermissions(keys::kPermissions,
+                        error,
+                        &api_permissions,
+                        &host_permissions)) {
+    return false;
+  }
+
+  // TODO(jeremya/kalman) do this via the features system by exposing the
+  // app.window API to platform apps, with no dependency on any permissions.
+  // See http://crbug.com/120069.
+  if (is_platform_app()) {
+    api_permissions.insert(APIPermission::kAppCurrentWindowInternal);
+    api_permissions.insert(APIPermission::kAppRuntime);
+    api_permissions.insert(APIPermission::kAppWindow);
+  }
+
+  if (from_webstore()) {
+    details_url_ =
+        GURL(extension_urls::GetWebstoreItemDetailURLPrefix() + id());
+  }
+
+  APIPermissionSet optional_api_permissions;
+  URLPatternSet optional_host_permissions;
+  if (!ParsePermissions(keys::kOptionalPermissions,
+                        error,
+                        &optional_api_permissions,
+                        &optional_host_permissions)) {
+    return false;
+  }
+
+  if (!LoadAppIsolation(api_permissions, error))
+    return false;
+
+  if (!LoadSharedFeatures(api_permissions, error))
+    return false;
+
+  if (!LoadExtensionFeatures(api_permissions, error))
+    return false;
+
+  if (!LoadThemeFeatures(error))
+    return false;
+
+  if (HasMultipleUISurfaces()) {
+    *error = ASCIIToUTF16(errors::kOneUISurfaceOnly);
+    return false;
+  }
+
+  runtime_data_.SetActivePermissions(new PermissionSet(
+      this, api_permissions, host_permissions));
+  required_permission_set_ = new PermissionSet(
+      this, api_permissions, host_permissions);
+  optional_permission_set_ = new PermissionSet(
+      optional_api_permissions, optional_host_permissions, URLPatternSet());
+
+  return true;
+}
+
+GURL Extension::GetHomepageURL() const {
+  if (homepage_url_.is_valid())
+    return homepage_url_;
+
+  return UpdatesFromGallery() ?
+      GURL(extension_urls::GetWebstoreItemDetailURLPrefix() + id()) : GURL();
+}
+
+std::set<FilePath> Extension::GetBrowserImages() const {
+  std::set<FilePath> image_paths;
+  // TODO(viettrungluu): These |FilePath::FromWStringHack(UTF8ToWide())|
+  // indicate that we're doing something wrong.
+
+  // Extension icons.
+  for (ExtensionIconSet::IconMap::const_iterator iter = icons().map().begin();
+       iter != icons().map().end(); ++iter) {
+    image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(iter->second)));
+  }
+
+  // Theme images.
+  DictionaryValue* theme_images = GetThemeImages();
+  if (theme_images) {
+    for (DictionaryValue::key_iterator it = theme_images->begin_keys();
+         it != theme_images->end_keys(); ++it) {
+      std::string val;
+      if (theme_images->GetStringWithoutPathExpansion(*it, &val))
+        image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(val)));
+    }
+  }
+
+  if (page_action_info() && !page_action_info()->default_icon.empty()) {
+    for (ExtensionIconSet::IconMap::const_iterator iter =
+             page_action_info()->default_icon.map().begin();
+         iter != page_action_info()->default_icon.map().end();
+         ++iter) {
+       image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(iter->second)));
+    }
+  }
+
+  if (browser_action_info() && !browser_action_info()->default_icon.empty()) {
+    for (ExtensionIconSet::IconMap::const_iterator iter =
+             browser_action_info()->default_icon.map().begin();
+         iter != browser_action_info()->default_icon.map().end();
+         ++iter) {
+       image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(iter->second)));
+    }
+  }
+
+  return image_paths;
+}
+
+GURL Extension::GetFullLaunchURL() const {
+  return launch_local_path().empty() ? GURL(launch_web_url()) :
+                                       url().Resolve(launch_local_path());
+}
+
+static std::string SizeToString(const gfx::Size& max_size) {
+  return base::IntToString(max_size.width()) + "x" +
+         base::IntToString(max_size.height());
+}
+
+// static
+void Extension::SetScriptingWhitelist(
+    const Extension::ScriptingWhitelist& whitelist) {
+  ScriptingWhitelist* current_whitelist =
+      ExtensionConfig::GetInstance()->whitelist();
+  current_whitelist->clear();
+  for (ScriptingWhitelist::const_iterator it = whitelist.begin();
+       it != whitelist.end(); ++it) {
+    current_whitelist->push_back(*it);
+  }
+}
+
+// static
+const Extension::ScriptingWhitelist* Extension::GetScriptingWhitelist() {
+  return ExtensionConfig::GetInstance()->whitelist();
+}
+
+void Extension::SetCachedImage(const ExtensionResource& source,
+                               const SkBitmap& image,
+                               const gfx::Size& original_size) const {
+  DCHECK(source.extension_root() == path());  // The resource must come from
+                                              // this extension.
+  const FilePath& path = source.relative_path();
+  gfx::Size actual_size(image.width(), image.height());
+  std::string location;
+  if (actual_size != original_size)
+    location = SizeToString(actual_size);
+  image_cache_[ImageCacheKey(path, location)] = image;
+}
+
+bool Extension::HasCachedImage(const ExtensionResource& source,
+                               const gfx::Size& max_size) const {
+  DCHECK(source.extension_root() == path());  // The resource must come from
+                                              // this extension.
+  return GetCachedImageImpl(source, max_size) != NULL;
+}
+
+SkBitmap Extension::GetCachedImage(const ExtensionResource& source,
+                                   const gfx::Size& max_size) const {
+  DCHECK(source.extension_root() == path());  // The resource must come from
+                                              // this extension.
+  SkBitmap* image = GetCachedImageImpl(source, max_size);
+  return image ? *image : SkBitmap();
+}
+
+SkBitmap* Extension::GetCachedImageImpl(const ExtensionResource& source,
+                                        const gfx::Size& max_size) const {
+  const FilePath& path = source.relative_path();
+
+  // Look for exact size match.
+  ImageCache::iterator i = image_cache_.find(
+      ImageCacheKey(path, SizeToString(max_size)));
+  if (i != image_cache_.end())
+    return &(i->second);
+
+  // If we have the original size version cached, return that if it's small
+  // enough.
+  i = image_cache_.find(ImageCacheKey(path, std::string()));
+  if (i != image_cache_.end()) {
+    const SkBitmap& image = i->second;
+    if (image.width() <= max_size.width() &&
+        image.height() <= max_size.height()) {
+      return &(i->second);
+    }
+  }
+
+  return NULL;
+}
+
+ExtensionResource Extension::GetIconResource(
+    int size, ExtensionIconSet::MatchType match_type) const {
+  std::string path = icons().Get(size, match_type);
+  return path.empty() ? ExtensionResource() : GetResource(path);
+}
+
+GURL Extension::GetIconURL(int size,
+                           ExtensionIconSet::MatchType match_type) const {
+  std::string path = icons().Get(size, match_type);
+  return path.empty() ? GURL() : GetResourceURL(path);
+}
+
+bool Extension::ParsePermissions(const char* key,
+                                 string16* error,
+                                 APIPermissionSet* api_permissions,
+                                 URLPatternSet* host_permissions) {
+  if (manifest_->HasKey(key)) {
+    ListValue* permissions = NULL;
+    if (!manifest_->GetList(key, &permissions)) {
+      *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+          errors::kInvalidPermissions, "");
+      return false;
+    }
+
+    // NOTE: We need to get the APIPermission before we check if features
+    // associated with them are available because the feature system does not
+    // know about aliases.
+
+    std::vector<std::string> host_data;
+    if (!APIPermissionSet::ParseFromJSON(permissions, api_permissions,
+                                         error, &host_data))
+      return false;
+
+    // Verify feature availability of permissions.
+    std::vector<APIPermission::ID> to_remove;
+    SimpleFeatureProvider* permission_features =
+        SimpleFeatureProvider::GetPermissionFeatures();
+    for (APIPermissionSet::const_iterator it = api_permissions->begin();
+         it != api_permissions->end(); ++it) {
+      extensions::Feature* feature =
+          permission_features->GetFeature(it->name());
+
+      // The feature should exist since we just got an APIPermission
+      // for it. The two systems should be updated together whenever a
+      // permission is added.
+      CHECK(feature);
+
+      Feature::Availability availability =
+          feature->IsAvailableToManifest(
+              id(),
+              GetType(),
+              Feature::ConvertLocation(location()),
+              manifest_version());
+      if (!availability.is_available()) {
+        // Don't fail, but warn the developer that the manifest contains
+        // unrecognized permissions. This may happen legitimately if the
+        // extensions requests platform- or channel-specific permissions.
+        install_warnings_.push_back(InstallWarning(InstallWarning::FORMAT_TEXT,
+                                                   availability.message()));
+        to_remove.push_back(it->id());
+        continue;
+      }
+
+      if (it->id() == APIPermission::kExperimental) {
+        if (!CanSpecifyExperimentalPermission()) {
+          *error = ASCIIToUTF16(errors::kExperimentalFlagRequired);
+          return false;
+        }
+      }
+    }
+
+    // Remove permissions that are not available to this extension.
+    for (std::vector<APIPermission::ID>::const_iterator it = to_remove.begin();
+         it != to_remove.end(); ++it) {
+      api_permissions->erase(*it);
+    }
+
+    // Parse host pattern permissions.
+    const int kAllowedSchemes = CanExecuteScriptEverywhere() ?
+        URLPattern::SCHEME_ALL : kValidHostPermissionSchemes;
+
+    for (std::vector<std::string>::const_iterator it = host_data.begin();
+         it != host_data.end(); ++it) {
+      const std::string& permission_str = *it;
+
+      // Check if it's a host pattern permission.
+      URLPattern pattern = URLPattern(kAllowedSchemes);
+      URLPattern::ParseResult parse_result = pattern.Parse(permission_str);
+      if (parse_result == URLPattern::PARSE_SUCCESS) {
+        if (!CanSpecifyHostPermission(pattern, *api_permissions)) {
+          *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+              errors::kInvalidPermissionScheme, permission_str);
+          return false;
+        }
+
+        // The path component is not used for host permissions, so we force it
+        // to match all paths.
+        pattern.SetPath("/*");
+
+        if (pattern.MatchesScheme(chrome::kFileScheme) &&
+            !CanExecuteScriptEverywhere()) {
+          wants_file_access_ = true;
+          if (!(creation_flags_ & ALLOW_FILE_ACCESS)) {
+            pattern.SetValidSchemes(
+                pattern.valid_schemes() & ~URLPattern::SCHEME_FILE);
+          }
+        }
+
+        host_permissions->AddPattern(pattern);
+        continue;
+      }
+
+      // It's probably an unknown API permission. Do not throw an error so
+      // extensions can retain backwards compatability (http://crbug.com/42742).
+      install_warnings_.push_back(InstallWarning(
+          InstallWarning::FORMAT_TEXT,
+          base::StringPrintf(
+              "Permission '%s' is unknown or URL pattern is malformed.",
+              permission_str.c_str())));
+    }
+  }
+  return true;
+}
+
+bool Extension::CanSilentlyIncreasePermissions() const {
+  return location() != INTERNAL;
+}
+
+bool Extension::CanSpecifyHostPermission(const URLPattern& pattern,
+    const APIPermissionSet& permissions) const {
+  if (!pattern.match_all_urls() &&
+      pattern.MatchesScheme(chrome::kChromeUIScheme)) {
+    // Regular extensions are only allowed access to chrome://favicon.
+    if (pattern.host() == chrome::kChromeUIFaviconHost)
+      return true;
+
+    // Experimental extensions are also allowed chrome://thumb.
+    if (pattern.host() == chrome::kChromeUIThumbnailHost) {
+      return permissions.find(APIPermission::kExperimental) !=
+          permissions.end();
+    }
+
+    // Component extensions can have access to all of chrome://*.
+    if (CanExecuteScriptEverywhere())
+      return true;
+
+    return false;
+  }
+
+  // Otherwise, the valid schemes were handled by URLPattern.
+  return true;
+}
+
+bool Extension::HasAPIPermission(APIPermission::ID permission) const {
+  base::AutoLock auto_lock(runtime_data_lock_);
+  return runtime_data_.GetActivePermissions()->HasAPIPermission(permission);
+}
+
+bool Extension::HasAPIPermission(const std::string& function_name) const {
+  base::AutoLock auto_lock(runtime_data_lock_);
+  return runtime_data_.GetActivePermissions()->
+      HasAccessToFunction(function_name);
+}
+
+bool Extension::HasAPIPermissionForTab(int tab_id,
+                                       APIPermission::ID permission) const {
+  base::AutoLock auto_lock(runtime_data_lock_);
+  if (runtime_data_.GetActivePermissions()->HasAPIPermission(permission))
+    return true;
+  scoped_refptr<const PermissionSet> tab_specific_permissions =
+      runtime_data_.GetTabSpecificPermissions(tab_id);
+  return tab_specific_permissions.get() &&
+         tab_specific_permissions->HasAPIPermission(permission);
+}
+
+bool Extension::CheckAPIPermissionWithParam(APIPermission::ID permission,
+    const APIPermission::CheckParam* param) const {
+  base::AutoLock auto_lock(runtime_data_lock_);
+  return runtime_data_.GetActivePermissions()->
+      CheckAPIPermissionWithParam(permission, param);
+}
+
+const URLPatternSet& Extension::GetEffectiveHostPermissions() const {
+  base::AutoLock auto_lock(runtime_data_lock_);
+  return runtime_data_.GetActivePermissions()->effective_hosts();
+}
+
+bool Extension::HasHostPermission(const GURL& url) const {
+  if (url.SchemeIs(chrome::kChromeUIScheme) &&
+      url.host() != chrome::kChromeUIFaviconHost &&
+      url.host() != chrome::kChromeUIThumbnailHost &&
+      location() != Extension::COMPONENT) {
+    return false;
+  }
+
+  base::AutoLock auto_lock(runtime_data_lock_);
+  return runtime_data_.GetActivePermissions()->
+      HasExplicitAccessToOrigin(url);
+}
+
+bool Extension::HasEffectiveAccessToAllHosts() const {
+  base::AutoLock auto_lock(runtime_data_lock_);
+  return runtime_data_.GetActivePermissions()->HasEffectiveAccessToAllHosts();
+}
+
+bool Extension::HasFullPermissions() const {
+  base::AutoLock auto_lock(runtime_data_lock_);
+  return runtime_data_.GetActivePermissions()->HasEffectiveFullAccess();
+}
+
+PermissionMessages Extension::GetPermissionMessages() const {
+  base::AutoLock auto_lock(runtime_data_lock_);
+  if (IsTrustedId(id())) {
+    return PermissionMessages();
+  } else {
+    return runtime_data_.GetActivePermissions()->GetPermissionMessages(
+        GetType());
+  }
+}
+
+std::vector<string16> Extension::GetPermissionMessageStrings() const {
+  base::AutoLock auto_lock(runtime_data_lock_);
+  if (IsTrustedId(id()))
+    return std::vector<string16>();
+  else
+    return runtime_data_.GetActivePermissions()->GetWarningMessages(GetType());
+}
+
+bool Extension::ShouldSkipPermissionWarnings() const {
+  return IsTrustedId(id());
+}
+
+void Extension::SetActivePermissions(
+    const PermissionSet* permissions) const {
+  base::AutoLock auto_lock(runtime_data_lock_);
+  runtime_data_.SetActivePermissions(permissions);
+}
+
+scoped_refptr<const PermissionSet>
+    Extension::GetActivePermissions() const {
+  base::AutoLock auto_lock(runtime_data_lock_);
+  return runtime_data_.GetActivePermissions();
+}
+
+bool Extension::HasMultipleUISurfaces() const {
+  int num_surfaces = 0;
+
+  if (page_action_info())
+    ++num_surfaces;
+
+  if (browser_action_info())
+    ++num_surfaces;
+
+  if (is_app())
+    ++num_surfaces;
+
+  return num_surfaces > 1;
+}
+
+bool Extension::CanExecuteScriptOnPage(const GURL& document_url,
+                                       const GURL& top_frame_url,
+                                       int tab_id,
+                                       const UserScript* script,
+                                       std::string* error) const {
+  base::AutoLock auto_lock(runtime_data_lock_);
+  // The gallery is special-cased as a restricted URL for scripting to prevent
+  // access to special JS bindings we expose to the gallery (and avoid things
+  // like extensions removing the "report abuse" link).
+  // TODO(erikkay): This seems like the wrong test.  Shouldn't we we testing
+  // against the store app extent?
+  GURL store_url(extension_urls::GetWebstoreLaunchURL());
+  if ((document_url.host() == store_url.host()) &&
+      !CanExecuteScriptEverywhere() &&
+      !CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kAllowScriptingGallery)) {
+    if (error)
+      *error = errors::kCannotScriptGallery;
+    return false;
+  }
+
+  if (document_url.SchemeIs(chrome::kChromeUIScheme) &&
+      !CanExecuteScriptEverywhere()) {
+    return false;
+  }
+
+  if (top_frame_url.SchemeIs(chrome::kExtensionScheme) &&
+      top_frame_url.GetOrigin() !=
+          GetBaseURLFromExtensionId(id()).GetOrigin() &&
+      !CanExecuteScriptEverywhere()) {
+    return false;
+  }
+
+  // If a tab ID is specified, try the tab-specific permissions.
+  if (tab_id >= 0) {
+    scoped_refptr<const PermissionSet> tab_permissions =
+        runtime_data_.GetTabSpecificPermissions(tab_id);
+    if (tab_permissions.get() &&
+        tab_permissions->explicit_hosts().MatchesSecurityOrigin(document_url)) {
+      return true;
+    }
+  }
+
+  // If a script is specified, use its matches.
+  if (script)
+    return script->MatchesURL(document_url);
+
+  // Otherwise, see if this extension has permission to execute script
+  // programmatically on pages.
+  if (runtime_data_.GetActivePermissions()->HasExplicitAccessToOrigin(
+          document_url)) {
+    return true;
+  }
+
+  if (error) {
+    *error = ExtensionErrorUtils::FormatErrorMessage(errors::kCannotAccessPage,
+                                                     document_url.spec());
+  }
+
+  return false;
+}
+
+bool Extension::ShowConfigureContextMenus() const {
+  // Don't show context menu for component extensions. We might want to show
+  // options for component extension button but now there is no component
+  // extension with options. All other menu items like uninstall have
+  // no sense for component extensions.
+  return location() != Extension::COMPONENT;
+}
+
+bool Extension::CanSpecifyExperimentalPermission() const {
+  if (location() == Extension::COMPONENT)
+    return true;
+
+  if (CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kEnableExperimentalExtensionApis)) {
+    return true;
+  }
+
+  // We rely on the webstore to check access to experimental. This way we can
+  // whitelist extensions to have access to experimental in just the store, and
+  // not have to push a new version of the client.
+  if (from_webstore())
+    return true;
+
+  return false;
+}
+
+bool Extension::CanExecuteScriptEverywhere() const {
+  if (location() == Extension::COMPONENT)
+    return true;
+
+  ScriptingWhitelist* whitelist = ExtensionConfig::GetInstance()->whitelist();
+
+  for (ScriptingWhitelist::const_iterator it = whitelist->begin();
+       it != whitelist->end(); ++it) {
+    if (id() == *it) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool Extension::CanCaptureVisiblePage(const GURL& page_url,
+                                      int tab_id,
+                                      std::string* error) const {
+  if (tab_id >= 0) {
+    scoped_refptr<const PermissionSet> tab_permissions =
+        GetTabSpecificPermissions(tab_id);
+    if (tab_permissions.get() &&
+        tab_permissions->explicit_hosts().MatchesSecurityOrigin(page_url)) {
+      return true;
+    }
+  }
+
+  if (HasHostPermission(page_url) || page_url.GetOrigin() == url())
+    return true;
+
+  if (error) {
+    *error = ExtensionErrorUtils::FormatErrorMessage(errors::kCannotAccessPage,
+                                                     page_url.spec());
+  }
+  return false;
+}
+
+bool Extension::UpdatesFromGallery() const {
+  return extension_urls::IsWebstoreUpdateUrl(update_url());
+}
+
+bool Extension::OverlapsWithOrigin(const GURL& origin) const {
+  if (url() == origin)
+    return true;
+
+  if (web_extent().is_empty())
+    return false;
+
+  // Note: patterns and extents ignore port numbers.
+  URLPattern origin_only_pattern(kValidWebExtentSchemes);
+  if (!origin_only_pattern.SetScheme(origin.scheme()))
+    return false;
+  origin_only_pattern.SetHost(origin.host());
+  origin_only_pattern.SetPath("/*");
+
+  URLPatternSet origin_only_pattern_list;
+  origin_only_pattern_list.AddPattern(origin_only_pattern);
+
+  return web_extent().OverlapsWith(origin_only_pattern_list);
+}
+
+Extension::SyncType Extension::GetSyncType() const {
+  if (!IsSyncable()) {
+    // We have a non-standard location.
+    return SYNC_TYPE_NONE;
+  }
+
+  // Disallow extensions with non-gallery auto-update URLs for now.
+  //
+  // TODO(akalin): Relax this restriction once we've put in UI to
+  // approve synced extensions.
+  if (!update_url().is_empty() && !UpdatesFromGallery())
+    return SYNC_TYPE_NONE;
+
+  // Disallow extensions with native code plugins.
+  //
+  // TODO(akalin): Relax this restriction once we've put in UI to
+  // approve synced extensions.
+  if (!plugins().empty()) {
+    return SYNC_TYPE_NONE;
+  }
+
+  switch (GetType()) {
+    case Extension::TYPE_EXTENSION:
+      return SYNC_TYPE_EXTENSION;
+
+    case Extension::TYPE_USER_SCRIPT:
+      // We only want to sync user scripts with gallery update URLs.
+      if (UpdatesFromGallery())
+        return SYNC_TYPE_EXTENSION;
+      else
+        return SYNC_TYPE_NONE;
+
+    case Extension::TYPE_HOSTED_APP:
+    case Extension::TYPE_LEGACY_PACKAGED_APP:
+        return SYNC_TYPE_APP;
+
+    default:
+      return SYNC_TYPE_NONE;
+  }
+}
+
+bool Extension::IsSyncable() const {
+  // TODO(akalin): Figure out if we need to allow some other types.
+
+  // Default apps are not synced because otherwise they will pollute profiles
+  // that don't already have them. Specially, if a user doesn't have default
+  // apps, creates a new profile (which get default apps) and then enables sync
+  // for it, then their profile everywhere gets the default apps.
+  bool is_syncable = (location() == Extension::INTERNAL &&
+      !was_installed_by_default());
+  // Sync the chrome web store to maintain its position on the new tab page.
+  is_syncable |= (id() ==  extension_misc::kWebStoreAppId);
+  return is_syncable;
+}
+
+bool Extension::RequiresSortOrdinal() const {
+  return is_app() && (display_in_launcher_ || display_in_new_tab_page_);
+}
+
+bool Extension::ShouldDisplayInAppLauncher() const {
+  // Only apps should be displayed in the launcher.
+  return is_app() && display_in_launcher_;
+}
+
+bool Extension::ShouldDisplayInNewTabPage() const {
+  // Only apps should be displayed on the NTP.
+  return is_app() && display_in_new_tab_page_;
+}
+
+bool Extension::InstallWarning::operator==(const InstallWarning& other) const {
+  return format == other.format && message == other.message;
+}
+
+void PrintTo(const Extension::InstallWarning& warning, ::std::ostream* os) {
+  *os << "InstallWarning(";
+  switch (warning.format) {
+    case Extension::InstallWarning::FORMAT_TEXT:
+      *os << "FORMAT_TEXT, \"";
+      break;
+    case Extension::InstallWarning::FORMAT_HTML:
+      *os << "FORMAT_HTML, \"";
+      break;
+  }
+  // This is just for test error messages, so no need to escape '"'
+  // characters inside the message.
+  *os << warning.message << "\")";
+}
+
+ExtensionInfo::ExtensionInfo(const DictionaryValue* manifest,
+                             const std::string& id,
+                             const FilePath& path,
+                             Extension::Location location)
+    : extension_id(id),
+      extension_path(path),
+      extension_location(location) {
+  if (manifest)
+    extension_manifest.reset(manifest->DeepCopy());
+}
+
+bool Extension::ShouldDisplayInExtensionSettings() const {
+  // Don't show for themes since the settings UI isn't really useful for them.
+  if (is_theme())
+    return false;
+
+  // Don't show component extensions because they are only extensions as an
+  // implementation detail of Chrome.
+  if (location() == Extension::COMPONENT &&
+      !CommandLine::ForCurrentProcess()->HasSwitch(
+        switches::kShowComponentExtensionOptions)) {
+    return false;
+  }
+
+  // Always show unpacked extensions and apps.
+  if (location() == Extension::LOAD)
+    return true;
+
+  // Unless they are unpacked, never show hosted apps. Note: We intentionally
+  // show packaged apps and platform apps because there are some pieces of
+  // functionality that are only available in chrome://extensions/ but which
+  // are needed for packaged and platform apps. For example, inspecting
+  // background pages. See http://crbug.com/116134.
+  if (is_hosted_app())
+    return false;
+
+  return true;
+}
+
+bool Extension::HasContentScriptAtURL(const GURL& url) const {
+  for (UserScriptList::const_iterator it = content_scripts_.begin();
+      it != content_scripts_.end(); ++it) {
+    if (it->MatchesURL(url))
+      return true;
+  }
+  return false;
+}
+
+scoped_refptr<const PermissionSet> Extension::GetTabSpecificPermissions(
+    int tab_id) const {
+  base::AutoLock auto_lock(runtime_data_lock_);
+  return runtime_data_.GetTabSpecificPermissions(tab_id);
+}
+
+void Extension::UpdateTabSpecificPermissions(
+    int tab_id,
+    scoped_refptr<const PermissionSet> permissions) const {
+  base::AutoLock auto_lock(runtime_data_lock_);
+  runtime_data_.UpdateTabSpecificPermissions(tab_id, permissions);
+}
+
+void Extension::ClearTabSpecificPermissions(int tab_id) const {
+  base::AutoLock auto_lock(runtime_data_lock_);
+  runtime_data_.ClearTabSpecificPermissions(tab_id);
+}
+
+bool Extension::CheckMinimumChromeVersion(string16* error) const {
+  if (!manifest_->HasKey(keys::kMinimumChromeVersion))
+    return true;
+  std::string minimum_version_string;
+  if (!manifest_->GetString(keys::kMinimumChromeVersion,
+                            &minimum_version_string)) {
+    *error = ASCIIToUTF16(errors::kInvalidMinimumChromeVersion);
+    return false;
+  }
+
+  Version minimum_version(minimum_version_string);
+  if (!minimum_version.IsValid()) {
+    *error = ASCIIToUTF16(errors::kInvalidMinimumChromeVersion);
+    return false;
+  }
+
+  chrome::VersionInfo current_version_info;
+  if (!current_version_info.is_valid()) {
+    NOTREACHED();
+    return false;
+  }
+
+  Version current_version(current_version_info.Version());
+  if (!current_version.IsValid()) {
+    DCHECK(false);
+    return false;
+  }
+
+  if (current_version.CompareTo(minimum_version) < 0) {
+    *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+        errors::kChromeVersionTooLow,
+        l10n_util::GetStringUTF8(IDS_PRODUCT_NAME),
+        minimum_version_string);
+    return false;
+  }
+  return true;
+}
+
+bool Extension::CheckPlatformAppFeatures(std::string* utf8_error) const {
+  if (!is_platform_app())
+    return true;
+
+  if (!has_background_page()) {
+    *utf8_error = errors::kBackgroundRequiredForPlatformApps;
+    return false;
+  }
+
+  if (!incognito_split_mode_) {
+    *utf8_error = errors::kInvalidIncognitoModeForPlatformApp;
+    return false;
+  }
+
+  return true;
+}
+
+bool Extension::CheckConflictingFeatures(std::string* utf8_error) const {
+  if (has_lazy_background_page() &&
+      HasAPIPermission(APIPermission::kWebRequest)) {
+    *utf8_error = errors::kWebRequestConflictsWithLazyBackground;
+    return false;
+  }
+
+  return true;
+}
+
+ExtensionInfo::~ExtensionInfo() {}
+
+Extension::RuntimeData::RuntimeData() {}
+Extension::RuntimeData::RuntimeData(const PermissionSet* active)
+    : active_permissions_(active) {}
+Extension::RuntimeData::~RuntimeData() {}
+
+scoped_refptr<const PermissionSet>
+    Extension::RuntimeData::GetActivePermissions() const {
+  return active_permissions_;
+}
+
+void Extension::RuntimeData::SetActivePermissions(
+    const PermissionSet* active) {
+  active_permissions_ = active;
+}
+
+scoped_refptr<const PermissionSet>
+    Extension::RuntimeData::GetTabSpecificPermissions(int tab_id) const {
+  CHECK_GE(tab_id, 0);
+  TabPermissionsMap::const_iterator it = tab_specific_permissions_.find(tab_id);
+  return (it != tab_specific_permissions_.end()) ? it->second : NULL;
+}
+
+void Extension::RuntimeData::UpdateTabSpecificPermissions(
+    int tab_id,
+    scoped_refptr<const PermissionSet> permissions) {
+  CHECK_GE(tab_id, 0);
+  if (tab_specific_permissions_.count(tab_id)) {
+    tab_specific_permissions_[tab_id] = PermissionSet::CreateUnion(
+        tab_specific_permissions_[tab_id],
+        permissions.get());
+  } else {
+    tab_specific_permissions_[tab_id] = permissions;
+  }
+}
+
+void Extension::RuntimeData::ClearTabSpecificPermissions(int tab_id) {
+  CHECK_GE(tab_id, 0);
+  tab_specific_permissions_.erase(tab_id);
+}
+
+UnloadedExtensionInfo::UnloadedExtensionInfo(
+    const Extension* extension,
+    extension_misc::UnloadedExtensionReason reason)
+  : reason(reason),
+    already_disabled(false),
+    extension(extension) {}
+
+UpdatedExtensionPermissionsInfo::UpdatedExtensionPermissionsInfo(
+    const Extension* extension,
+    const PermissionSet* permissions,
+    Reason reason)
+    : reason(reason),
+      extension(extension),
+      permissions(permissions) {}
+
+}   // namespace extensions
diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h
new file mode 100644
index 0000000..b640663
--- /dev/null
+++ b/chrome/common/extensions/extension.h
@@ -0,0 +1,1292 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_H_
+
+#include <algorithm>
+#include <iosfwd>
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/hash_tables.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "chrome/common/extensions/command.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/extensions/extension_icon_set.h"
+#include "chrome/common/extensions/permissions/api_permission.h"
+#include "chrome/common/extensions/permissions/api_permission_set.h"
+#include "chrome/common/extensions/permissions/permission_message.h"
+#include "chrome/common/extensions/user_script.h"
+#include "chrome/common/extensions/url_pattern.h"
+#include "chrome/common/extensions/url_pattern_set.h"
+#include "googleurl/src/gurl.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/gfx/size.h"
+
+class ExtensionAction;
+class ExtensionResource;
+class FileBrowserHandler;
+class SkBitmap;
+class Version;
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+}
+
+namespace gfx {
+class ImageSkia;
+}
+
+namespace webkit_glue {
+struct WebIntentServiceData;
+}
+
+FORWARD_DECLARE_TEST(TabStripModelTest, Apps);
+
+namespace extensions {
+
+class Manifest;
+class PermissionSet;
+
+typedef std::set<std::string> OAuth2Scopes;
+
+// Represents a Chrome extension.
+class Extension : public base::RefCountedThreadSafe<Extension> {
+ public:
+  struct InstallWarning;
+
+  typedef std::map<const std::string, GURL> URLOverrideMap;
+  typedef std::vector<std::string> ScriptingWhitelist;
+  typedef std::vector<linked_ptr<FileBrowserHandler> > FileBrowserHandlerList;
+  typedef std::vector<InstallWarning> InstallWarningVector;
+
+  // What an extension was loaded from.
+  // NOTE: These values are stored as integers in the preferences and used
+  // in histograms so don't remove or reorder existing items.  Just append
+  // to the end.
+  enum Location {
+    INVALID,
+    INTERNAL,           // A crx file from the internal Extensions directory.
+    EXTERNAL_PREF,      // A crx file from an external directory (via prefs).
+    EXTERNAL_REGISTRY,  // A crx file from an external directory (via eg the
+                        // registry on Windows).
+    LOAD,               // --load-extension.
+    COMPONENT,          // An integral component of Chrome itself, which
+                        // happens to be implemented as an extension. We don't
+                        // show these in the management UI.
+    EXTERNAL_PREF_DOWNLOAD,    // A crx file from an external directory (via
+                               // prefs), installed from an update URL.
+    EXTERNAL_POLICY_DOWNLOAD,  // A crx file from an external directory (via
+                               // admin policies), installed from an update URL.
+
+    NUM_LOCATIONS
+  };
+
+  enum State {
+    DISABLED = 0,
+    ENABLED,
+    // An external extension that the user uninstalled. We should not reinstall
+    // such extensions on startup.
+    EXTERNAL_EXTENSION_UNINSTALLED,
+    NUM_STATES
+  };
+
+  // Used to record the reason an extension was disabled.
+  enum DeprecatedDisableReason {
+    DEPRECATED_DISABLE_UNKNOWN,
+    DEPRECATED_DISABLE_USER_ACTION,
+    DEPRECATED_DISABLE_PERMISSIONS_INCREASE,
+    DEPRECATED_DISABLE_RELOAD,
+    DEPRECATED_DISABLE_LAST,  // Not used.
+  };
+
+  enum DisableReason {
+    DISABLE_NONE = 0,
+    DISABLE_USER_ACTION = 1 << 0,
+    DISABLE_PERMISSIONS_INCREASE = 1 << 1,
+    DISABLE_RELOAD = 1 << 2,
+    DISABLE_UNSUPPORTED_REQUIREMENT = 1 << 3,
+    DISABLE_SIDELOAD_WIPEOUT = 1 << 4,
+  };
+
+  enum InstallType {
+    INSTALL_ERROR,
+    DOWNGRADE,
+    REINSTALL,
+    UPGRADE,
+    NEW_INSTALL
+  };
+
+  // Do not change the order of entries or remove entries in this list
+  // as this is used in UMA_HISTOGRAM_ENUMERATIONs about extensions.
+  enum Type {
+    TYPE_UNKNOWN = 0,
+    TYPE_EXTENSION,
+    TYPE_THEME,
+    TYPE_USER_SCRIPT,
+    TYPE_HOSTED_APP,
+    // This is marked legacy because platform apps are preferred. For
+    // backwards compatibility, we can't remove support for packaged apps
+    TYPE_LEGACY_PACKAGED_APP,
+    TYPE_PLATFORM_APP
+  };
+
+  enum SyncType {
+    SYNC_TYPE_NONE = 0,
+    SYNC_TYPE_EXTENSION,
+    SYNC_TYPE_APP
+  };
+
+  // Declared requirements for the extension.
+  struct Requirements {
+    Requirements();
+    ~Requirements();
+
+    bool webgl;
+    bool css3d;
+    bool npapi;
+  };
+
+  // An NPAPI plugin included in the extension.
+  struct PluginInfo {
+    FilePath path;  // Path to the plugin.
+    bool is_public;  // False if only this extension can load this plugin.
+  };
+
+  // An NaCl module included in the extension.
+  struct NaClModuleInfo {
+    GURL url;
+    std::string mime_type;
+  };
+
+  enum InputComponentType {
+    INPUT_COMPONENT_TYPE_NONE = -1,
+    INPUT_COMPONENT_TYPE_IME,
+    INPUT_COMPONENT_TYPE_COUNT
+  };
+
+  struct InputComponentInfo {
+    // Define out of line constructor/destructor to please Clang.
+    InputComponentInfo();
+    ~InputComponentInfo();
+
+    std::string name;
+    InputComponentType type;
+    std::string id;
+    std::string description;
+    std::string language;
+    std::set<std::string> layouts;
+    std::string shortcut_keycode;
+    bool shortcut_alt;
+    bool shortcut_ctrl;
+    bool shortcut_shift;
+  };
+
+  struct TtsVoice {
+    // Define out of line constructor/destructor to please Clang.
+    TtsVoice();
+    ~TtsVoice();
+
+    std::string voice_name;
+    std::string lang;
+    std::string gender;
+    std::set<std::string> event_types;
+  };
+
+  // OAuth2 info included in the extension.
+  struct OAuth2Info {
+    OAuth2Info();
+    ~OAuth2Info();
+
+    OAuth2Scopes GetScopesAsSet();
+
+    std::string client_id;
+    std::vector<std::string> scopes;
+  };
+
+  struct ActionInfo {
+    explicit ActionInfo();
+    ~ActionInfo();
+
+    // The types of extension actions.
+    enum Type {
+      TYPE_BROWSER,
+      TYPE_PAGE,
+      TYPE_SCRIPT_BADGE,
+    };
+
+    // Empty implies the key wasn't present.
+    ExtensionIconSet default_icon;
+    std::string default_title;
+    GURL default_popup_url;
+    // action id -- only used with legacy page actions API.
+    std::string id;
+  };
+
+  struct InstallWarning {
+    enum Format {
+      // IMPORTANT: Do not build HTML strings from user or developer-supplied
+      // input.
+      FORMAT_TEXT,
+      FORMAT_HTML,
+    };
+    InstallWarning(Format format, const std::string& message)
+        : format(format), message(message) {
+    }
+    bool operator==(const InstallWarning& other) const;
+    Format format;
+    std::string message;
+  };
+
+  enum InitFromValueFlags {
+    NO_FLAGS = 0,
+
+    // Usually, the id of an extension is generated by the "key" property of
+    // its manifest, but if |REQUIRE_KEY| is not set, a temporary ID will be
+    // generated based on the path.
+    REQUIRE_KEY = 1 << 0,
+
+    // Requires the extension to have an up-to-date manifest version.
+    // Typically, we'll support multiple manifest versions during a version
+    // transition. This flag signals that we want to require the most modern
+    // manifest version that Chrome understands.
+    REQUIRE_MODERN_MANIFEST_VERSION = 1 << 1,
+
+    // |ALLOW_FILE_ACCESS| indicates that the user is allowing this extension
+    // to have file access. If it's not present, then permissions and content
+    // scripts that match file:/// URLs will be filtered out.
+    ALLOW_FILE_ACCESS = 1 << 2,
+
+    // |FROM_WEBSTORE| indicates that the extension was installed from the
+    // Chrome Web Store.
+    FROM_WEBSTORE = 1 << 3,
+
+    // |FROM_BOOKMARK| indicates the extension was created using a mock App
+    // created from a bookmark.
+    FROM_BOOKMARK = 1 << 4,
+
+    // |FOLLOW_SYMLINKS_ANYWHERE| means that resources can be symlinks to
+    // anywhere in the filesystem, rather than being restricted to the
+    // extension directory.
+    FOLLOW_SYMLINKS_ANYWHERE = 1 << 5,
+
+    // |ERROR_ON_PRIVATE_KEY| means that private keys inside an
+    // extension should be errors rather than warnings.
+    ERROR_ON_PRIVATE_KEY = 1 << 6,
+
+    // |WAS_INSTALLED_BY_DEFAULT| installed by default when the profile was
+    // created.
+    WAS_INSTALLED_BY_DEFAULT = 1 << 7,
+  };
+
+  static scoped_refptr<Extension> Create(const FilePath& path,
+                                         Location location,
+                                         const base::DictionaryValue& value,
+                                         int flags,
+                                         std::string* error);
+
+  // In a few special circumstances, we want to create an Extension and give it
+  // an explicit id. Most consumers should just use the other Create() method.
+  static scoped_refptr<Extension> Create(const FilePath& path,
+      Location location,
+      const base::DictionaryValue& value,
+      int flags,
+      const std::string& explicit_id,
+      std::string* error);
+
+  // Given two install sources, return the one which should take priority
+  // over the other. If an extension is installed from two sources A and B,
+  // its install source should be set to GetHigherPriorityLocation(A, B).
+  static Location GetHigherPriorityLocation(Location loc1, Location loc2);
+
+  // Max size (both dimensions) for browser and page actions.
+  static const int kPageActionIconMaxSize;
+  static const int kBrowserActionIconMaxSize;
+
+  // Valid schemes for web extent URLPatterns.
+  static const int kValidWebExtentSchemes;
+
+  // Valid schemes for host permission URLPatterns.
+  static const int kValidHostPermissionSchemes;
+
+  // The name of the manifest inside an extension.
+  static const FilePath::CharType kManifestFilename[];
+
+  // The name of locale folder inside an extension.
+  static const FilePath::CharType kLocaleFolder[];
+
+  // The name of the messages file inside an extension.
+  static const FilePath::CharType kMessagesFilename[];
+
+#if defined(OS_WIN)
+  static const char kExtensionRegistryPath[];
+#endif
+
+  // The number of bytes in a legal id.
+  static const size_t kIdSize;
+
+  // The mimetype used for extensions.
+  static const char kMimeType[];
+
+  // Checks to see if the extension has a valid ID.
+  static bool IdIsValid(const std::string& id);
+
+  // Generate an ID for an extension in the given path.
+  // Used while developing extensions, before they have a key.
+  static std::string GenerateIdForPath(const FilePath& file_name);
+
+  // Returns true if the specified file is an extension.
+  static bool IsExtension(const FilePath& file_name);
+
+  // Whether the |location| is external or not.
+  static inline bool IsExternalLocation(Location location) {
+    return location == Extension::EXTERNAL_PREF ||
+           location == Extension::EXTERNAL_REGISTRY ||
+           location == Extension::EXTERNAL_PREF_DOWNLOAD ||
+           location == Extension::EXTERNAL_POLICY_DOWNLOAD;
+  }
+
+  // Whether extensions with |location| are auto-updatable or not.
+  static inline bool IsAutoUpdateableLocation(Location location) {
+    // Only internal and external extensions can be autoupdated.
+    return location == Extension::INTERNAL ||
+           IsExternalLocation(location);
+  }
+
+  // Policy-required extensions are silently auto-installed and updated, and
+  // cannot be disabled or modified by the user in any way. The same applies
+  // to internal components.
+  // This method is not generally called directly; instead, it is accessed
+  // through the ManagementPolicy held by the ExtensionSystem.
+  static inline bool IsRequired(Location location) {
+    return location == Extension::EXTERNAL_POLICY_DOWNLOAD ||
+           location == Extension::COMPONENT;
+  }
+
+  // Unpacked extensions start off with file access since they are a developer
+  // feature.
+  static inline bool ShouldAlwaysAllowFileAccess(Location location) {
+    return location == Extension::LOAD;
+  }
+
+  // Fills the |info| dictionary with basic information about the extension.
+  // |enabled| is injected for easier testing.
+  void GetBasicInfo(bool enabled, base::DictionaryValue* info) const;
+
+  // See Type definition above.
+  Type GetType() const;
+
+  // Returns an absolute url to a resource inside of an extension. The
+  // |extension_url| argument should be the url() from an Extension object. The
+  // |relative_path| can be untrusted user input. The returned URL will either
+  // be invalid() or a child of |extension_url|.
+  // NOTE: Static so that it can be used from multiple threads.
+  static GURL GetResourceURL(const GURL& extension_url,
+                             const std::string& relative_path);
+  GURL GetResourceURL(const std::string& relative_path) const {
+    return GetResourceURL(url(), relative_path);
+  }
+
+  // Returns true if the resource matches a pattern in the pattern_set.
+  bool ResourceMatches(const URLPatternSet& pattern_set,
+                       const std::string& resource) const;
+
+  // Returns true if the specified resource is web accessible.
+  bool IsResourceWebAccessible(const std::string& relative_path) const;
+
+  // Returns true if the specified page is sandboxed (served in a unique
+  // origin).
+  bool IsSandboxedPage(const std::string& relative_path) const;
+
+  // Returns the Content Security Policy that the specified resource should be
+  // served with.
+  std::string GetResourceContentSecurityPolicy(const std::string& relative_path)
+      const;
+
+  // Returns true when 'web_accessible_resources' are defined for the extension.
+  bool HasWebAccessibleResources() const;
+
+  // Returns an extension resource object. |relative_path| should be UTF8
+  // encoded.
+  ExtensionResource GetResource(const std::string& relative_path) const;
+
+  // As above, but with |relative_path| following the file system's encoding.
+  ExtensionResource GetResource(const FilePath& relative_path) const;
+
+  // |input| is expected to be the text of an rsa public or private key. It
+  // tolerates the presence or absence of bracking header/footer like this:
+  //     -----(BEGIN|END) [RSA PUBLIC/PRIVATE] KEY-----
+  // and may contain newlines.
+  static bool ParsePEMKeyBytes(const std::string& input, std::string* output);
+
+  // Does a simple base64 encoding of |input| into |output|.
+  static bool ProducePEM(const std::string& input, std::string* output);
+
+  // Generates an extension ID from arbitrary input. The same input string will
+  // always generate the same output ID.
+  static bool GenerateId(const std::string& input,
+                         std::string* output) WARN_UNUSED_RESULT;
+
+  // Expects base64 encoded |input| and formats into |output| including
+  // the appropriate header & footer.
+  static bool FormatPEMForFileOutput(const std::string& input,
+                                     std::string* output,
+                                     bool is_public);
+
+  // Given an extension, icon size, and match type, read a valid icon if present
+  // and decode it into result. In the browser process, this will DCHECK if not
+  // called on the file thread. To easily load extension images on the UI
+  // thread, see ImageLoadingTracker.
+  static void DecodeIcon(const Extension* extension,
+                         int icon_size,
+                         ExtensionIconSet::MatchType match_type,
+                         scoped_ptr<SkBitmap>* result);
+
+  // Given an extension and icon size, read it if present and decode it into
+  // result. In the browser process, this will DCHECK if not called on the
+  // file thread. To easily load extension images on the UI thread, see
+  // ImageLoadingTracker.
+  static void DecodeIcon(const Extension* extension,
+                         int icon_size,
+                         scoped_ptr<SkBitmap>* result);
+
+  // Given an icon_path and icon size, read it if present and decode it into
+  // result. In the browser process, this will DCHECK if not called on the
+  // file thread. To easily load extension images on the UI thread, see
+  // ImageLoadingTracker.
+  static void DecodeIconFromPath(const FilePath& icon_path,
+                                 int icon_size,
+                                 scoped_ptr<SkBitmap>* result);
+
+  // Returns the default extension/app icon (for extensions or apps that don't
+  // have one).
+  static const gfx::ImageSkia& GetDefaultIcon(bool is_app);
+
+  // Returns the base extension url for a given |extension_id|.
+  static GURL GetBaseURLFromExtensionId(const std::string& extension_id);
+
+  // Adds an extension to the scripting whitelist. Used for testing only.
+  static void SetScriptingWhitelist(const ScriptingWhitelist& whitelist);
+  static const ScriptingWhitelist* GetScriptingWhitelist();
+
+  // Parses the host and api permissions from the specified permission |key|
+  // from |manifest_|.
+  bool ParsePermissions(const char* key,
+                        string16* error,
+                        APIPermissionSet* api_permissions,
+                        URLPatternSet* host_permissions);
+
+  bool HasAPIPermission(APIPermission::ID permission) const;
+  bool HasAPIPermission(const std::string& function_name) const;
+  bool HasAPIPermissionForTab(int tab_id, APIPermission::ID permission) const;
+
+  bool CheckAPIPermissionWithParam(APIPermission::ID permission,
+      const APIPermission::CheckParam* param) const;
+
+  const URLPatternSet& GetEffectiveHostPermissions() const;
+
+  // Returns true if the extension can silently increase its permission level.
+  // Users must approve permissions for unpacked and packed extensions in the
+  // following situations:
+  //  - when installing or upgrading packed extensions
+  //  - when installing unpacked extensions that have NPAPI plugins
+  //  - when either type of extension requests optional permissions
+  bool CanSilentlyIncreasePermissions() const;
+
+  // Whether the extension has access to the given URL.
+  bool HasHostPermission(const GURL& url) const;
+
+  // Whether the extension has effective access to all hosts. This is true if
+  // there is a content script that matches all hosts, if there is a host
+  // permission grants access to all hosts (like <all_urls>) or an api
+  // permission that effectively grants access to all hosts (e.g. proxy,
+  // network, etc.)
+  bool HasEffectiveAccessToAllHosts() const;
+
+  // Whether the extension effectively has all permissions (for example, by
+  // having an NPAPI plugin).
+  bool HasFullPermissions() const;
+
+  // Returns the full list of permission messages that this extension
+  // should display at install time.
+  PermissionMessages GetPermissionMessages() const;
+
+  // Returns the full list of permission messages that this extension
+  // should display at install time. The messages are returned as strings
+  // for convenience.
+  std::vector<string16> GetPermissionMessageStrings() const;
+
+  // Returns true if the extension does not require permission warnings
+  // to be displayed at install time.
+  bool ShouldSkipPermissionWarnings() const;
+
+  // Sets the active |permissions|.
+  void SetActivePermissions(const PermissionSet* permissions) const;
+
+  // Gets the extension's active permission set.
+  scoped_refptr<const PermissionSet> GetActivePermissions() const;
+
+  // Whether context menu should be shown for page and browser actions.
+  bool ShowConfigureContextMenus() const;
+
+  // Returns the Homepage URL for this extension. If homepage_url was not
+  // specified in the manifest, this returns the Google Gallery URL. For
+  // third-party extensions, this returns a blank GURL.
+  GURL GetHomepageURL() const;
+
+  // Returns a list of paths (relative to the extension dir) for images that
+  // the browser might load (like themes and page action icons).
+  std::set<FilePath> GetBrowserImages() const;
+
+  // Get an extension icon as a resource or URL.
+  ExtensionResource GetIconResource(
+      int size, ExtensionIconSet::MatchType match_type) const;
+  GURL GetIconURL(int size, ExtensionIconSet::MatchType match_type) const;
+
+  // Gets the fully resolved absolute launch URL.
+  GURL GetFullLaunchURL() const;
+
+  // Image cache related methods. These are only valid on the UI thread and
+  // not maintained by this class. See ImageLoadingTracker for usage. The
+  // |original_size| parameter should be the size of the image at |source|
+  // before any scaling may have been done to produce the pixels in |image|.
+  void SetCachedImage(const ExtensionResource& source,
+                      const SkBitmap& image,
+                      const gfx::Size& original_size) const;
+  bool HasCachedImage(const ExtensionResource& source,
+                      const gfx::Size& max_size) const;
+  SkBitmap GetCachedImage(const ExtensionResource& source,
+                          const gfx::Size& max_size) const;
+
+  // Returns true if this extension can execute script on a page. If a
+  // UserScript object is passed, permission to run that specific script is
+  // checked (using its matches list). Otherwise, permission to execute script
+  // programmatically is checked (using the extension's host permission).
+  //
+  // This method is also aware of certain special pages that extensions are
+  // usually not allowed to run script on.
+  bool CanExecuteScriptOnPage(const GURL& document_url,
+                              const GURL& top_document_url,
+                              int tab_id,
+                              const UserScript* script,
+                              std::string* error) const;
+
+  // Returns true if this extension is a COMPONENT extension, or if it is
+  // on the whitelist of extensions that can script all pages.
+  bool CanExecuteScriptEverywhere() const;
+
+  // Returns true if this extension is allowed to obtain the contents of a
+  // page as an image.  Since a page may contain sensitive information, this
+  // is restricted to the extension's host permissions as well as the
+  // extension page itself.
+  bool CanCaptureVisiblePage(const GURL& page_url,
+                             int tab_id,
+                             std::string* error) const;
+
+  // Returns true if this extension updates itself using the extension
+  // gallery.
+  bool UpdatesFromGallery() const;
+
+  // Returns true if this extension or app includes areas within |origin|.
+  bool OverlapsWithOrigin(const GURL& origin) const;
+
+  // Returns the sync bucket to use for this extension.
+  SyncType GetSyncType() const;
+
+  // Returns true if the extension should be synced.
+  bool IsSyncable() const;
+
+  // Returns true if the extension requires a valid ordinal for sorting, e.g.,
+  // for displaying in a launcher or new tab page.
+  bool RequiresSortOrdinal() const;
+
+  // Returns true if the extension should be displayed in the app launcher.
+  bool ShouldDisplayInAppLauncher() const;
+
+  // Returns true if the extension should be displayed in the browser NTP.
+  bool ShouldDisplayInNewTabPage() const;
+
+  // Returns true if the extension should be displayed in the extension
+  // settings page (i.e. chrome://extensions).
+  bool ShouldDisplayInExtensionSettings() const;
+
+  // Returns true if the extension has a content script declared at |url|.
+  bool HasContentScriptAtURL(const GURL& url) const;
+
+  // Gets the tab-specific host permissions of |tab_id|, or NULL if there
+  // aren't any.
+  scoped_refptr<const PermissionSet> GetTabSpecificPermissions(int tab_id)
+      const;
+
+  // Updates the tab-specific permissions of |tab_id| to include those from
+  // |permissions|.
+  void UpdateTabSpecificPermissions(
+      int tab_id,
+      scoped_refptr<const PermissionSet> permissions) const;
+
+  // Clears the tab-specific permissions of |tab_id|.
+  void ClearTabSpecificPermissions(int tab_id) const;
+
+  // Accessors:
+
+  const Requirements& requirements() const { return requirements_; }
+  const FilePath& path() const { return path_; }
+  const GURL& url() const { return extension_url_; }
+  Location location() const;
+  const std::string& id() const;
+  const Version* version() const { return version_.get(); }
+  const std::string VersionString() const;
+  const std::string& name() const { return name_; }
+  const std::string& non_localized_name() const { return non_localized_name_; }
+  // Base64-encoded version of the key used to sign this extension.
+  // In pseudocode, returns
+  // base::Base64Encode(RSAPrivateKey(pem_file).ExportPublicKey()).
+  const std::string& public_key() const { return public_key_; }
+  const std::string& description() const { return description_; }
+  int manifest_version() const { return manifest_version_; }
+  bool converted_from_user_script() const {
+    return converted_from_user_script_;
+  }
+  const UserScriptList& content_scripts() const { return content_scripts_; }
+  const ActionInfo* script_badge_info() const {
+    return script_badge_info_.get();
+  }
+  const ActionInfo* page_action_info() const { return page_action_info_.get(); }
+  const ActionInfo* browser_action_info() const {
+    return browser_action_info_.get();
+  }
+  bool is_verbose_install_message() const {
+    return !omnibox_keyword().empty() ||
+           browser_action_info() ||
+           (page_action_info() &&
+            (page_action_command() ||
+             !page_action_info()->default_icon.empty()));
+  }
+  const FileBrowserHandlerList* file_browser_handlers() const {
+    return file_browser_handlers_.get();
+  }
+  const std::vector<PluginInfo>& plugins() const { return plugins_; }
+  const std::vector<NaClModuleInfo>& nacl_modules() const {
+    return nacl_modules_;
+  }
+  const std::vector<InputComponentInfo>& input_components() const {
+    return input_components_;
+  }
+  // The browser action command that the extension wants to use, which is not
+  // necessarily the one it can use, as it might be inactive (see also
+  // GetBrowserActionCommand in CommandService).
+  const extensions::Command* browser_action_command() const {
+    return browser_action_command_.get();
+  }
+  // The page action command that the extension wants to use, which is not
+  // necessarily the one it can use, as it might be inactive (see also
+  // GetPageActionCommand in CommandService).
+  const extensions::Command* page_action_command() const {
+    return page_action_command_.get();
+  }
+  // The script badge command that the extension wants to use, which is not
+  // necessarily the one it can use, as it might be inactive (see also
+  // GetScriptBadgeCommand in CommandService).
+  const extensions::Command* script_badge_command() const {
+    return script_badge_command_.get();
+  }
+  // The map (of command names to commands) that the extension wants to use,
+  // which is not necessarily the one it can use, as they might be inactive
+  // (see also GetNamedCommands in CommandService).
+  const extensions::CommandMap& named_commands() const {
+    return named_commands_;
+  }
+  bool has_background_page() const {
+    return background_url_.is_valid() || !background_scripts_.empty();
+  }
+  bool allow_background_js_access() const {
+    return allow_background_js_access_;
+  }
+  const std::vector<std::string>& background_scripts() const {
+    return background_scripts_;
+  }
+  bool has_persistent_background_page() const {
+    return has_background_page() && background_page_is_persistent_;
+  }
+  bool has_lazy_background_page() const {
+    return has_background_page() && !background_page_is_persistent_;
+  }
+  const GURL& options_url() const { return options_url_; }
+  const GURL& devtools_url() const { return devtools_url_; }
+  const GURL& details_url() const { return details_url_;}
+  const PermissionSet* optional_permission_set() const {
+    return optional_permission_set_.get();
+  }
+  const PermissionSet* required_permission_set() const {
+    return required_permission_set_.get();
+  }
+  // Appends |new_warnings| to install_warnings().
+  void AddInstallWarnings(const InstallWarningVector& new_warnings);
+  const InstallWarningVector& install_warnings() const {
+    return install_warnings_;
+  }
+  const GURL& update_url() const { return update_url_; }
+  const ExtensionIconSet& icons() const { return icons_; }
+  const extensions::Manifest* manifest() const {
+    return manifest_.get();
+  }
+  const std::string default_locale() const { return default_locale_; }
+  const URLOverrideMap& GetChromeURLOverrides() const {
+    return chrome_url_overrides_;
+  }
+  const std::string omnibox_keyword() const { return omnibox_keyword_; }
+  bool incognito_split_mode() const { return incognito_split_mode_; }
+  bool offline_enabled() const { return offline_enabled_; }
+  const std::vector<TtsVoice>& tts_voices() const { return tts_voices_; }
+  const OAuth2Info& oauth2_info() const { return oauth2_info_; }
+  const std::vector<webkit_glue::WebIntentServiceData>&
+      intents_services() const {
+    return intents_services_;
+  }
+
+  bool wants_file_access() const { return wants_file_access_; }
+  int creation_flags() const { return creation_flags_; }
+  bool from_webstore() const { return (creation_flags_ & FROM_WEBSTORE) != 0; }
+  bool from_bookmark() const { return (creation_flags_ & FROM_BOOKMARK) != 0; }
+  bool was_installed_by_default() const {
+    return (creation_flags_ & WAS_INSTALLED_BY_DEFAULT) != 0;
+  }
+
+  // App-related.
+  bool is_app() const {
+    return is_legacy_packaged_app() || is_hosted_app() || is_platform_app();
+  }
+  bool is_platform_app() const;
+  bool is_hosted_app() const;
+  bool is_legacy_packaged_app() const;
+  bool is_storage_isolated() const { return is_storage_isolated_; }
+  const URLPatternSet& web_extent() const { return extent_; }
+  const std::string& launch_local_path() const { return launch_local_path_; }
+  const std::string& launch_web_url() const { return launch_web_url_; }
+  extension_misc::LaunchContainer launch_container() const {
+    return launch_container_;
+  }
+  int launch_width() const { return launch_width_; }
+  int launch_height() const { return launch_height_; }
+
+  // Theme-related.
+  bool is_theme() const;
+  base::DictionaryValue* GetThemeImages() const { return theme_images_.get(); }
+  base::DictionaryValue* GetThemeColors() const {return theme_colors_.get(); }
+  base::DictionaryValue* GetThemeTints() const { return theme_tints_.get(); }
+  base::DictionaryValue* GetThemeDisplayProperties() const {
+    return theme_display_properties_.get();
+  }
+
+  GURL GetBackgroundURL() const;
+
+ private:
+  friend class base::RefCountedThreadSafe<Extension>;
+
+  // We keep a cache of images loaded from extension resources based on their
+  // path and a string representation of a size that may have been used to
+  // scale it (or the empty string if the image is at its original size).
+  typedef std::pair<FilePath, std::string> ImageCacheKey;
+  typedef std::map<ImageCacheKey, SkBitmap> ImageCache;
+
+  class RuntimeData {
+   public:
+    RuntimeData();
+    explicit RuntimeData(const PermissionSet* active);
+    ~RuntimeData();
+
+    void SetActivePermissions(const PermissionSet* active);
+    scoped_refptr<const PermissionSet> GetActivePermissions() const;
+
+    scoped_refptr<const PermissionSet> GetTabSpecificPermissions(int tab_id)
+        const;
+    void UpdateTabSpecificPermissions(
+        int tab_id,
+        scoped_refptr<const PermissionSet> permissions);
+    void ClearTabSpecificPermissions(int tab_id);
+
+   private:
+    friend class base::RefCountedThreadSafe<RuntimeData>;
+
+    scoped_refptr<const PermissionSet> active_permissions_;
+
+    typedef std::map<int, scoped_refptr<const PermissionSet> >
+        TabPermissionsMap;
+    TabPermissionsMap tab_specific_permissions_;
+  };
+
+  // Chooses the extension ID for an extension based on a variety of criteria.
+  // The chosen ID will be set in |manifest|.
+  static bool InitExtensionID(extensions::Manifest* manifest,
+                              const FilePath& path,
+                              const std::string& explicit_id,
+                              int creation_flags,
+                              string16* error);
+
+  // Normalize the path for use by the extension. On Windows, this will make
+  // sure the drive letter is uppercase.
+  static FilePath MaybeNormalizePath(const FilePath& path);
+
+  // Returns true if this extension id is from a trusted provider.
+  static bool IsTrustedId(const std::string& id);
+
+  Extension(const FilePath& path, scoped_ptr<extensions::Manifest> manifest);
+  ~Extension();
+
+  // Initialize the extension from a parsed manifest.
+  // TODO(aa): Rename to just Init()? There's no Value here anymore.
+  // TODO(aa): It is really weird the way this class essentially contains a copy
+  // of the underlying DictionaryValue in its members. We should decide to
+  // either wrap the DictionaryValue and go with that only, or we should parse
+  // into strong types and discard the value. But doing both is bad.
+  bool InitFromValue(int flags, string16* error);
+
+  // The following are helpers for InitFromValue to load various features of the
+  // extension from the manifest.
+
+  bool LoadAppIsolation(const APIPermissionSet& api_permissions,
+                        string16* error);
+
+  bool LoadRequiredFeatures(string16* error);
+  bool LoadName(string16* error);
+  bool LoadVersion(string16* error);
+
+  bool LoadAppFeatures(string16* error);
+  bool LoadExtent(const char* key,
+                  URLPatternSet* extent,
+                  const char* list_error,
+                  const char* value_error,
+                  string16* error);
+  bool LoadLaunchContainer(string16* error);
+  bool LoadLaunchURL(string16* error);
+
+  bool LoadSharedFeatures(const APIPermissionSet& api_permissions,
+                          string16* error);
+  bool LoadDescription(string16* error);
+  bool LoadManifestVersion(string16* error);
+  bool LoadHomepageURL(string16* error);
+  bool LoadUpdateURL(string16* error);
+  bool LoadIcons(string16* error);
+  bool LoadCommands(string16* error);
+  bool LoadPlugins(string16* error);
+  bool LoadNaClModules(string16* error);
+  bool LoadWebAccessibleResources(string16* error);
+  bool LoadSandboxedPages(string16* error);
+  // Must be called after LoadPlugins().
+  bool LoadRequirements(string16* error);
+  bool LoadDefaultLocale(string16* error);
+  bool LoadOfflineEnabled(string16* error);
+  bool LoadOptionsPage(string16* error);
+  bool LoadBackgroundScripts(string16* error);
+  bool LoadBackgroundScripts(const std::string& key, string16* error);
+  bool LoadBackgroundPage(const APIPermissionSet& api_permissions,
+                          string16* error);
+  bool LoadBackgroundPage(const std::string& key,
+                          const APIPermissionSet& api_permissions,
+                          string16* error);
+  bool LoadBackgroundPersistent(
+      const APIPermissionSet& api_permissions,
+      string16* error);
+  bool LoadBackgroundAllowJSAccess(
+      const APIPermissionSet& api_permissions,
+      string16* error);
+  // Parses a single action in the manifest.
+  bool LoadWebIntentAction(const std::string& action_name,
+                           const base::DictionaryValue& intent_service,
+                           string16* error);
+  bool LoadWebIntentServices(string16* error);
+  bool LoadFileHandler(const std::string& handler_id,
+                       const base::DictionaryValue& handler_info,
+                       string16* error);
+  bool LoadFileHandlers(string16* error);
+  bool LoadExtensionFeatures(const APIPermissionSet& api_permissions,
+                             string16* error);
+  bool LoadDevToolsPage(string16* error);
+  bool LoadInputComponents(const APIPermissionSet& api_permissions,
+                           string16* error);
+  bool LoadContentScripts(string16* error);
+  bool LoadPageAction(string16* error);
+  bool LoadBrowserAction(string16* error);
+  bool LoadScriptBadge(string16* error);
+  bool LoadFileBrowserHandlers(string16* error);
+  // Helper method to load a FileBrowserHandlerList from the manifest.
+  FileBrowserHandlerList* LoadFileBrowserHandlersHelper(
+      const base::ListValue* extension_actions, string16* error);
+  // Helper method to load an FileBrowserHandler from manifest.
+  FileBrowserHandler* LoadFileBrowserHandler(
+      const base::DictionaryValue* file_browser_handlers, string16* error);
+  bool LoadChromeURLOverrides(string16* error);
+  bool LoadOmnibox(string16* error);
+  bool LoadTextToSpeechVoices(string16* error);
+  bool LoadIncognitoMode(string16* error);
+  bool LoadContentSecurityPolicy(string16* error);
+
+  bool LoadThemeFeatures(string16* error);
+  bool LoadThemeImages(const base::DictionaryValue* theme_value,
+                       string16* error);
+  bool LoadThemeColors(const base::DictionaryValue* theme_value,
+                       string16* error);
+  bool LoadThemeTints(const base::DictionaryValue* theme_value,
+                      string16* error);
+  bool LoadThemeDisplayProperties(const base::DictionaryValue* theme_value,
+                                  string16* error);
+
+  // Helper function for implementing HasCachedImage/GetCachedImage. A return
+  // value of NULL means there is no matching image cached (we allow caching an
+  // empty SkBitmap).
+  SkBitmap* GetCachedImageImpl(const ExtensionResource& source,
+                               const gfx::Size& max_size) const;
+
+  // Helper method that loads a UserScript object from a
+  // dictionary in the content_script list of the manifest.
+  bool LoadUserScriptHelper(const base::DictionaryValue* content_script,
+                            int definition_index,
+                            string16* error,
+                            UserScript* result);
+
+  // Helper method that loads either the include_globs or exclude_globs list
+  // from an entry in the content_script lists of the manifest.
+  bool LoadGlobsHelper(const base::DictionaryValue* content_script,
+                       int content_script_index,
+                       const char* globs_property_name,
+                       string16* error,
+                       void(UserScript::*add_method)(const std::string& glob),
+                       UserScript* instance);
+
+  // Helper method to load an ExtensionAction from the page_action or
+  // browser_action entries in the manifest.
+  scoped_ptr<ActionInfo> LoadExtensionActionInfoHelper(
+      const base::DictionaryValue* manifest_section,
+      ActionInfo::Type action_type,
+      string16* error);
+
+  // Helper method that loads the OAuth2 info from the 'oauth2' manifest key.
+  bool LoadOAuth2Info(string16* error);
+
+  // Returns true if the extension has more than one "UI surface". For example,
+  // an extension that has a browser action and a page action.
+  bool HasMultipleUISurfaces() const;
+
+  // Updates the launch URL and extents for the extension using the given
+  // |override_url|.
+  void OverrideLaunchUrl(const GURL& override_url);
+
+  // Custom checks for the experimental permission that can't be expressed in
+  // _permission_features.json.
+  bool CanSpecifyExperimentalPermission() const;
+
+  // Checks whether the host |pattern| is allowed for this extension, given API
+  // permissions |permissions|.
+  bool CanSpecifyHostPermission(const URLPattern& pattern,
+      const APIPermissionSet& permissions) const;
+
+  bool CheckMinimumChromeVersion(string16* error) const;
+
+  // Check that platform app features are valid. Called after InitFromValue.
+  bool CheckPlatformAppFeatures(std::string* utf8_error) const;
+
+  // Check that features don't conflict. Called after InitFromValue.
+  bool CheckConflictingFeatures(std::string* utf8_error) const;
+
+  // Cached images for this extension. This should only be touched on the UI
+  // thread.
+  mutable ImageCache image_cache_;
+
+  // The extension's human-readable name. Name is used for display purpose. It
+  // might be wrapped with unicode bidi control characters so that it is
+  // displayed correctly in RTL context.
+  // NOTE: Name is UTF-8 and may contain non-ascii characters.
+  std::string name_;
+
+  // A non-localized version of the extension's name. This is useful for
+  // debug output.
+  std::string non_localized_name_;
+
+  // The version of this extension's manifest. We increase the manifest
+  // version when making breaking changes to the extension system.
+  // Version 1 was the first manifest version (implied by a lack of a
+  // manifest_version attribute in the extension's manifest). We initialize
+  // this member variable to 0 to distinguish the "uninitialized" case from
+  // the case when we know the manifest version actually is 1.
+  int manifest_version_;
+
+  // The requirements declared in the manifest.
+  Requirements requirements_;
+
+  // The absolute path to the directory the extension is stored in.
+  FilePath path_;
+
+  // Default locale for fall back. Can be empty if extension is not localized.
+  std::string default_locale_;
+
+  // If true, a separate process will be used for the extension in incognito
+  // mode.
+  bool incognito_split_mode_;
+
+  // Whether the extension or app should be enabled when offline.
+  bool offline_enabled_;
+
+  // Defines the set of URLs in the extension's web content.
+  URLPatternSet extent_;
+
+  // The extension runtime data.
+  mutable base::Lock runtime_data_lock_;
+  mutable RuntimeData runtime_data_;
+
+  // The set of permissions the extension can request at runtime.
+  scoped_refptr<const PermissionSet> optional_permission_set_;
+
+  // The extension's required / default set of permissions.
+  scoped_refptr<const PermissionSet> required_permission_set_;
+
+  // Any warnings that occurred when trying to create/parse the extension.
+  InstallWarningVector install_warnings_;
+
+  // The icons for the extension.
+  ExtensionIconSet icons_;
+
+  // The base extension url for the extension.
+  GURL extension_url_;
+
+  // The extension's version.
+  scoped_ptr<Version> version_;
+
+  // An optional longer description of the extension.
+  std::string description_;
+
+  // True if the extension was generated from a user script. (We show slightly
+  // different UI if so).
+  bool converted_from_user_script_;
+
+  // Paths to the content scripts the extension contains.
+  UserScriptList content_scripts_;
+
+  // The extension's page action, if any.
+  scoped_ptr<ActionInfo> page_action_info_;
+
+  // The extension's browser action, if any.
+  scoped_ptr<ActionInfo> browser_action_info_;
+
+  // The extension's script badge.  Never NULL.
+  scoped_ptr<ActionInfo> script_badge_info_;
+
+  // The extension's file browser actions, if any.
+  scoped_ptr<FileBrowserHandlerList> file_browser_handlers_;
+
+  // Optional list of NPAPI plugins and associated properties.
+  std::vector<PluginInfo> plugins_;
+
+  // Optional list of NaCl modules and associated properties.
+  std::vector<NaClModuleInfo> nacl_modules_;
+
+  // Optional list of input components and associated properties.
+  std::vector<InputComponentInfo> input_components_;
+
+  // Optional list of commands (keyboard shortcuts).
+  scoped_ptr<extensions::Command> browser_action_command_;
+  scoped_ptr<extensions::Command> page_action_command_;
+  scoped_ptr<extensions::Command> script_badge_command_;
+  extensions::CommandMap named_commands_;
+
+  // Optional list of web accessible extension resources.
+  URLPatternSet web_accessible_resources_;
+
+  // Optional list of extension pages that are sandboxed (served from a unique
+  // origin with a different Content Security Policy).
+  URLPatternSet sandboxed_pages_;
+
+  // Content Security Policy that should be used to enforce the sandbox used
+  // by sandboxed pages (guaranteed to have the "sandbox" directive without the
+  // "allow-same-origin" token).
+  std::string sandboxed_pages_content_security_policy_;
+
+  // Optional URL to a master page of which a single instance should be always
+  // loaded in the background.
+  GURL background_url_;
+
+  // Optional list of scripts to use to generate a background page. If this is
+  // present, background_url_ will be empty and generated by GetBackgroundURL().
+  std::vector<std::string> background_scripts_;
+
+  // True if the background page should stay loaded forever; false if it should
+  // load on-demand (when it needs to handle an event). Defaults to true.
+  bool background_page_is_persistent_;
+
+  // True if the background page can be scripted by pages of the app or
+  // extension, in which case all such pages must run in the same process.
+  // False if such pages are not permitted to script the background page,
+  // allowing them to run in different processes.
+  bool allow_background_js_access_;
+
+  // Optional URL to a page for setting options/preferences.
+  GURL options_url_;
+
+  // Optional URL to a devtools extension page.
+  GURL devtools_url_;
+
+  // URL to the webstore page of the extension.
+  GURL details_url_;
+
+  // The public key used to sign the contents of the crx package.
+  std::string public_key_;
+
+  // A map of resource id's to relative file paths.
+  scoped_ptr<base::DictionaryValue> theme_images_;
+
+  // A map of color names to colors.
+  scoped_ptr<base::DictionaryValue> theme_colors_;
+
+  // A map of color names to colors.
+  scoped_ptr<base::DictionaryValue> theme_tints_;
+
+  // A map of display properties.
+  scoped_ptr<base::DictionaryValue> theme_display_properties_;
+
+  // The homepage for this extension. Useful if it is not hosted by Google and
+  // therefore does not have a Gallery URL.
+  GURL homepage_url_;
+
+  // URL for fetching an update manifest
+  GURL update_url_;
+
+  // The manifest from which this extension was created.
+  scoped_ptr<Manifest> manifest_;
+
+  // A map of chrome:// hostnames (newtab, downloads, etc.) to Extension URLs
+  // which override the handling of those URLs. (see ExtensionOverrideUI).
+  URLOverrideMap chrome_url_overrides_;
+
+  // Whether this extension requests isolated storage.
+  bool is_storage_isolated_;
+
+  // The local path inside the extension to use with the launcher.
+  std::string launch_local_path_;
+
+  // A web url to use with the launcher. Note that this might be relative or
+  // absolute. If relative, it is relative to web_origin.
+  std::string launch_web_url_;
+
+  // The window type that an app's manifest specifies to launch into.
+  // This is not always the window type an app will open into, because
+  // users can override the way each app launches.  See
+  // ExtensionPrefs::GetLaunchContainer(), which looks at a per-app pref
+  // to decide what container an app will launch in.
+  extension_misc::LaunchContainer launch_container_;
+
+  // The default size of the container when launching. Only respected for
+  // containers like panels and windows.
+  int launch_width_;
+  int launch_height_;
+
+  // Should this app be shown in the app launcher.
+  bool display_in_launcher_;
+
+  // Should this app be shown in the browser New Tab Page.
+  bool display_in_new_tab_page_;
+
+  // The Omnibox keyword for this extension, or empty if there is none.
+  std::string omnibox_keyword_;
+
+  // List of text-to-speech voices that this extension provides, if any.
+  std::vector<TtsVoice> tts_voices_;
+
+  // The OAuth2 client id and scopes, if specified by the extension.
+  OAuth2Info oauth2_info_;
+
+  // List of intent services that this extension provides, if any.
+  std::vector<webkit_glue::WebIntentServiceData> intents_services_;
+
+  // Whether the extension has host permissions or user script patterns that
+  // imply access to file:/// scheme URLs (the user may not have actually
+  // granted it that access).
+  bool wants_file_access_;
+
+  // The flags that were passed to InitFromValue.
+  int creation_flags_;
+
+  // The Content-Security-Policy for this extension.  Extensions can use
+  // Content-Security-Policies to mitigate cross-site scripting and other
+  // vulnerabilities.
+  std::string content_security_policy_;
+
+  FRIEND_TEST_ALL_PREFIXES(ExtensionTest, LoadPageActionHelper);
+  FRIEND_TEST_ALL_PREFIXES(::TabStripModelTest, Apps);
+
+  DISALLOW_COPY_AND_ASSIGN(Extension);
+};
+
+typedef std::vector< scoped_refptr<const Extension> > ExtensionList;
+typedef std::set<std::string> ExtensionIdSet;
+typedef std::vector<std::string> ExtensionIdList;
+
+// Let gtest print InstallWarnings.
+void PrintTo(const Extension::InstallWarning&, ::std::ostream* os);
+
+// Handy struct to pass core extension info around.
+struct ExtensionInfo {
+  ExtensionInfo(const base::DictionaryValue* manifest,
+                const std::string& id,
+                const FilePath& path,
+                Extension::Location location);
+  ~ExtensionInfo();
+
+  scoped_ptr<base::DictionaryValue> extension_manifest;
+  std::string extension_id;
+  FilePath extension_path;
+  Extension::Location extension_location;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ExtensionInfo);
+};
+
+struct UnloadedExtensionInfo {
+  extension_misc::UnloadedExtensionReason reason;
+
+  // Was the extension already disabled?
+  bool already_disabled;
+
+  // The extension being unloaded - this should always be non-NULL.
+  const Extension* extension;
+
+  UnloadedExtensionInfo(
+      const Extension* extension,
+      extension_misc::UnloadedExtensionReason reason);
+};
+
+// The details sent for EXTENSION_PERMISSIONS_UPDATED notifications.
+struct UpdatedExtensionPermissionsInfo {
+  enum Reason {
+    ADDED,    // The permissions were added to the extension.
+    REMOVED,  // The permissions were removed from the extension.
+  };
+
+  Reason reason;
+
+  // The extension who's permissions have changed.
+  const Extension* extension;
+
+  // The permissions that have changed. For Reason::ADDED, this would contain
+  // only the permissions that have added, and for Reason::REMOVED, this would
+  // only contain the removed permissions.
+  const PermissionSet* permissions;
+
+  UpdatedExtensionPermissionsInfo(
+      const Extension* extension,
+      const PermissionSet* permissions,
+      Reason reason);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_H_
diff --git a/chrome/common/extensions/extension_builder.cc b/chrome/common/extensions/extension_builder.cc
new file mode 100644
index 0000000..2a44607
--- /dev/null
+++ b/chrome/common/extensions/extension_builder.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_builder.h"
+
+#include "chrome/common/extensions/extension.h"
+
+namespace extensions {
+
+ExtensionBuilder::ExtensionBuilder()
+    : location_(Extension::LOAD),
+      flags_(Extension::NO_FLAGS) {
+}
+ExtensionBuilder::~ExtensionBuilder() {}
+
+scoped_refptr<Extension> ExtensionBuilder::Build() {
+  std::string error;
+  scoped_refptr<Extension> extension = Extension::Create(
+      path_,
+      location_,
+      *manifest_,
+      flags_,
+      id_,
+      &error);
+  CHECK_EQ("", error);
+  return extension;
+}
+
+ExtensionBuilder& ExtensionBuilder::SetPath(const FilePath& path) {
+  path_ = path;
+  return *this;
+}
+
+ExtensionBuilder& ExtensionBuilder::SetLocation(Extension::Location location) {
+  location_ = location;
+  return *this;
+}
+
+ExtensionBuilder& ExtensionBuilder::SetManifest(
+    scoped_ptr<DictionaryValue> manifest) {
+  manifest_ = manifest.Pass();
+  return *this;
+}
+
+ExtensionBuilder& ExtensionBuilder::AddFlags(int init_from_value_flags) {
+  flags_ |= init_from_value_flags;
+  return *this;
+}
+
+ExtensionBuilder& ExtensionBuilder::SetID(const std::string& id) {
+  id_ = id;
+  return *this;
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/extension_builder.h b/chrome/common/extensions/extension_builder.h
new file mode 100644
index 0000000..209b704
--- /dev/null
+++ b/chrome/common/extensions/extension_builder.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_BUILDER_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_BUILDER_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/value_builder.h"
+
+namespace extensions {
+
+// An easier way to create extensions than Extension::Create.  The
+// constructor sets up some defaults which are customized using the
+// methods.  The only method that must be called is SetManifest().
+class ExtensionBuilder {
+ public:
+  ExtensionBuilder();
+  ~ExtensionBuilder();
+
+  // Can only be called once, after which it's invalid to use the builder.
+  // CHECKs that the extension was created successfully.
+  scoped_refptr<Extension> Build();
+
+  // Defaults to FilePath().
+  ExtensionBuilder& SetPath(const FilePath& path);
+
+  // Defaults to Extension::LOAD.
+  ExtensionBuilder& SetLocation(Extension::Location location);
+
+  ExtensionBuilder& SetManifest(scoped_ptr<base::DictionaryValue> manifest);
+  ExtensionBuilder& SetManifest(DictionaryBuilder& manifest_builder) {
+    return SetManifest(manifest_builder.Build());
+  }
+
+  ExtensionBuilder& AddFlags(int init_from_value_flags);
+
+  // Defaults to the default extension ID created in Extension::Create.
+  ExtensionBuilder& SetID(const std::string& id);
+
+ private:
+  FilePath path_;
+  Extension::Location location_;
+  scoped_ptr<base::DictionaryValue> manifest_;
+  int flags_;
+  std::string id_;
+};
+
+} // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_BUILDER_H_
diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc
new file mode 100644
index 0000000..48baffe
--- /dev/null
+++ b/chrome/common/extensions/extension_constants.cc
@@ -0,0 +1,184 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/string_util.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/net/url_util.h"
+
+namespace extension_urls {
+
+std::string GetWebstoreLaunchURL() {
+  std::string gallery_prefix = kGalleryBrowsePrefix;
+  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAppsGalleryURL))
+    gallery_prefix = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+        switches::kAppsGalleryURL);
+  if (EndsWith(gallery_prefix, "/", true))
+    gallery_prefix = gallery_prefix.substr(0, gallery_prefix.length() - 1);
+  return gallery_prefix;
+}
+
+std::string GetExtensionGalleryURL() {
+  return GetWebstoreLaunchURL() + "/category/extensions";
+}
+
+std::string GetWebstoreItemDetailURLPrefix() {
+  return GetWebstoreLaunchURL() + "/detail/";
+}
+
+GURL GetWebstoreIntentQueryURL(const std::string& action,
+                               const std::string& type) {
+  const char kIntentsCategoryPath[] = "category/collection/webintent_apps";
+
+  GURL url(std::string(kGalleryBrowsePrefix) + "/");
+  url = url.Resolve(kIntentsCategoryPath);
+  url = chrome_common_net::AppendQueryParameter(url, "_wi", action);
+  url = chrome_common_net::AppendQueryParameter(url, "_mt", type);
+
+  return url;
+}
+
+GURL GetWebstoreItemJsonDataURL(const std::string& extension_id) {
+  return GURL(GetWebstoreLaunchURL() + "/inlineinstall/detail/" + extension_id);
+}
+
+const char kGalleryUpdateHttpsUrl[] =
+    "https://clients2.google.com/service/update2/crx";
+// TODO(battre): Delete the HTTP URL once the blacklist is downloaded via HTTPS.
+const char kExtensionBlocklistUrlPrefix[] =
+    "http://www.gstatic.com/chrome/extensions/blacklist";
+const char kExtensionBlocklistHttpsUrlPrefix[] =
+    "https://www.gstatic.com/chrome/extensions/blacklist";
+
+GURL GetWebstoreUpdateUrl() {
+  CommandLine* cmdline = CommandLine::ForCurrentProcess();
+  if (cmdline->HasSwitch(switches::kAppsGalleryUpdateURL))
+    return GURL(cmdline->GetSwitchValueASCII(switches::kAppsGalleryUpdateURL));
+  else
+    return GURL(kGalleryUpdateHttpsUrl);
+}
+
+bool IsWebstoreUpdateUrl(const GURL& update_url) {
+  GURL store_url = GetWebstoreUpdateUrl();
+  if (update_url == store_url) {
+    return true;
+  } else {
+    return (update_url.host() == store_url.host() &&
+            update_url.path() == store_url.path());
+  }
+}
+
+bool IsBlacklistUpdateUrl(const GURL& url) {
+  // The extension blacklist URL is returned from the update service and
+  // therefore not determined by Chromium. If the location of the blacklist file
+  // ever changes, we need to update this function. A DCHECK in the
+  // ExtensionUpdater ensures that we notice a change. This is the full URL
+  // of a blacklist:
+  // http://www.gstatic.com/chrome/extensions/blacklist/l_0_0_0_7.txt
+  return StartsWithASCII(url.spec(), kExtensionBlocklistUrlPrefix, true) ||
+      StartsWithASCII(url.spec(), kExtensionBlocklistHttpsUrlPrefix, true);
+}
+
+const char kGalleryBrowsePrefix[] = "https://chrome.google.com/webstore";
+
+}  // namespace extension_urls
+
+namespace extension_filenames {
+
+const char kTempExtensionName[] = "CRX_INSTALL";
+
+// The file to write our decoded images to, relative to the extension_path.
+const char kDecodedImagesFilename[] = "DECODED_IMAGES";
+
+// The file to write our decoded message catalogs to, relative to the
+// extension_path.
+const char kDecodedMessageCatalogsFilename[] = "DECODED_MESSAGE_CATALOGS";
+
+const char kGeneratedBackgroundPageFilename[] =
+    "_generated_background_page.html";
+}
+
+// These must match the values expected by the chrome.management extension API.
+namespace extension_info_keys {
+  const char kDescriptionKey[] = "description";
+  const char kEnabledKey[] = "enabled";
+  const char kHomepageUrlKey[] = "homepageUrl";
+  const char kIdKey[] = "id";
+  const char kNameKey[] = "name";
+  const char kOfflineEnabledKey[] = "offlineEnabled";
+  const char kOptionsUrlKey[] = "optionsUrl";
+  const char kDetailsUrlKey[] = "detailsUrl";
+  const char kVersionKey[] = "version";
+  const char kPackagedAppKey[] = "packagedApp";
+
+}  // namespace extension_filenames
+
+namespace extension_misc {
+const char kBookmarkManagerId[] = "eemcgdkfndhakfknompkggombfjjjeno";
+const char kCitrixReceiverAppId[] = "haiffjcadagjlijoggckpgfnoeiflnem";
+const char kCitrixReceiverAppBetaId[] = "gnedhmakppccajfpfiihfcdlnpgomkcf";
+const char kCitrixReceiverAppDevId[] = "fjcibdnjlbfnbfdjneajpipnlcppleek";
+const char kEnterpriseWebStoreAppId[] = "afchcafgojfnemjkcbhfekplkmjaldaa";
+const char kHTermAppId[] = "pnhechapfaindjhompbnflcldabbghjo";
+const char kHTermDevAppId[] = "okddffdblfhhnmhodogpojmfkjmhinfp";
+const char kCroshBuiltinAppId[] = "nkoccljplnhpfnfiajclkommnmllphnl";
+const char kWebStoreAppId[] = "ahfgeienlihckogmohjhadlkjgocpleb";
+const char kCloudPrintAppId[] = "mfehgcgbbipciphmccgaenjidiccnmng";
+const char kChromeAppId[] = "mgndgikekgjfcpckkfioiadnlibdjbkf";
+const char kAppLaunchHistogram[] = "Extensions.AppLaunch";
+#if defined(OS_CHROMEOS)
+const char kAccessExtensionPath[] =
+    "/usr/share/chromeos-assets/accessibility/extensions";
+const char kChromeVoxDirectoryName[] = "access_chromevox";
+const char kWallpaperManagerId[] = "obklkkbkpaoaejdabbfldmcfplpdgolj";
+#endif
+
+const char kAppStateNotInstalled[] = "not_installed";
+const char kAppStateInstalled[] = "installed";
+const char kAppStateDisabled[] = "disabled";
+const char kAppStateRunning[] = "running";
+const char kAppStateCannotRun[] = "cannot_run";
+const char kAppStateReadyToRun[] = "ready_to_run";
+
+const char kMediaFileSystemPathPart[] = "_";
+
+const char kAppNotificationsIncognitoError[] =
+    "This API is not accessible by 'split' mode "
+    "extensions in incognito windows.";
+
+const int kExtensionIconSizes[] = {
+  EXTENSION_ICON_GIGANTOR,  // 512
+  EXTENSION_ICON_EXTRA_LARGE,  // 256
+  EXTENSION_ICON_LARGE,  // 128
+  EXTENSION_ICON_MEDIUM,  // 48
+  EXTENSION_ICON_SMALL,  // 32
+  EXTENSION_ICON_SMALLISH,  // 24
+  EXTENSION_ICON_BITTY  // 16
+};
+
+const size_t kNumExtensionIconSizes =
+    arraysize(kExtensionIconSizes);
+
+const int kExtensionActionIconSizes[] = {
+  EXTENSION_ICON_ACTION,  // 19,
+  2 * EXTENSION_ICON_ACTION  // 38
+};
+
+const size_t kNumExtensionActionIconSizes =
+    arraysize(kExtensionActionIconSizes);
+
+const int kScriptBadgeIconSizes[] = {
+  EXTENSION_ICON_BITTY,  // 16
+  2 * EXTENSION_ICON_BITTY  // 32
+};
+
+const size_t kNumScriptBadgeIconSizes =
+    arraysize(kScriptBadgeIconSizes);
+
+}  // namespace extension_misc
diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h
new file mode 100644
index 0000000..ef23a77
--- /dev/null
+++ b/chrome/common/extensions/extension_constants.h
@@ -0,0 +1,284 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_CONSTANTS_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_CONSTANTS_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "googleurl/src/gurl.h"
+
+namespace extension_urls {
+  // Returns the URL prefix for the extension/apps gallery. Can be set via the
+  // --apps-gallery-url switch. The URL returned will not contain a trailing
+  // slash. Do not use this as a prefix/extent for the store.
+  std::string GetWebstoreLaunchURL();
+
+  // Returns the URL to the extensions category on the Web Store. This is
+  // derived from GetWebstoreLaunchURL().
+  std::string GetExtensionGalleryURL();
+
+  // Returns the URL prefix for an item in the extension/app gallery. This URL
+  // will contain a trailing slash and should be concatenated with an item ID
+  // to get the item detail URL.
+  std::string GetWebstoreItemDetailURLPrefix();
+
+  // Returns the URL leading to a search page for Web Intents. The search is
+  // specific to intents with the given |action| and |type|.
+  GURL GetWebstoreIntentQueryURL(const std::string& action,
+                                 const std::string& type);
+
+  // Returns the URL used to get webstore data (ratings, manifest, icon URL,
+  // etc.) about an extension from the webstore as JSON.
+  GURL GetWebstoreItemJsonDataURL(const std::string& extension_id);
+
+  // Return the update URL used by gallery/webstore extensions/apps.
+  GURL GetWebstoreUpdateUrl();
+
+  // Returns whether the URL is the webstore update URL (just considering host
+  // and path, not scheme, query, etc.)
+  bool IsWebstoreUpdateUrl(const GURL& update_url);
+
+  // Returns true if the URL points to an extension blacklist.
+  bool IsBlacklistUpdateUrl(const GURL& url);
+
+  // The greatest common prefixes of the main extensions gallery's browse and
+  // download URLs.
+  extern const char kGalleryBrowsePrefix[];
+}  // namespace extension_urls
+
+namespace extension_filenames {
+  // The name of a temporary directory to install an extension into for
+  // validation before finalizing install.
+  extern const char kTempExtensionName[];
+
+  // The file to write our decoded images to, relative to the extension_path.
+  extern const char kDecodedImagesFilename[];
+
+  // The file to write our decoded message catalogs to, relative to the
+  // extension_path.
+  extern const char kDecodedMessageCatalogsFilename[];
+
+  // The filename to use for a background page generated from
+  // background.scripts.
+  extern const char kGeneratedBackgroundPageFilename[];
+}
+
+// Keys in the dictionary returned by Extension::GetBasicInfo().
+namespace extension_info_keys {
+  extern const char kDescriptionKey[];
+  extern const char kEnabledKey[];
+  extern const char kHomepageUrlKey[];
+  extern const char kIdKey[];
+  extern const char kNameKey[];
+  extern const char kOfflineEnabledKey[];
+  extern const char kOptionsUrlKey[];
+  extern const char kDetailsUrlKey[];
+  extern const char kVersionKey[];
+  extern const char kPackagedAppKey[];
+}
+
+namespace extension_misc {
+  // Matches chrome.windows.WINDOW_ID_NONE.
+  const int kUnknownWindowId = -1;
+
+  // Matches chrome.windows.WINDOW_ID_CURRENT.
+  const int kCurrentWindowId = -2;
+
+  // The extension id of the bookmark manager.
+  extern const char kBookmarkManagerId[];
+
+  // The extension id of the Citrix Receiver application.
+  extern const char kCitrixReceiverAppId[];
+
+  // The extension id of the beta Citrix Receiver application.
+  extern const char kCitrixReceiverAppBetaId[];
+
+  // The extension id of the dev Citrix Receiver application.
+  extern const char kCitrixReceiverAppDevId[];
+
+  // The extension id of the Enterprise Web Store component application.
+  extern const char kEnterpriseWebStoreAppId[];
+
+  // The extension id of the HTerm app for ChromeOS.
+  extern const char kHTermAppId[];
+
+  // The extension id of the HTerm dev app for ChromeOS.
+  extern const char kHTermDevAppId[];
+
+  // The extension id of the Crosh component app for ChromeOS.
+  extern const char kCroshBuiltinAppId[];
+
+  // The extension id of the Web Store component application.
+  extern const char kWebStoreAppId[];
+
+  // The extension id of the Cloud Print component application.
+  extern const char kCloudPrintAppId[];
+
+  // The extension id of the Chrome component application.
+  extern const char kChromeAppId[];
+
+  // Note: this structure is an ASN.1 which encodes the algorithm used
+  // with its parameters. This is defined in PKCS #1 v2.1 (RFC 3447).
+  // It is encoding: { OID sha1WithRSAEncryption      PARAMETERS NULL }
+  const uint8 kSignatureAlgorithm[15] = {
+    0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+    0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00
+  };
+
+  // Don't remove items or change the order of this enum.  It's used in
+  // histograms and preferences.
+  enum LaunchContainer {
+    LAUNCH_WINDOW,
+    LAUNCH_PANEL,
+    LAUNCH_TAB,
+    // For platform apps, which don't actually have a container (they just get a
+    // "onLaunched" event).
+    LAUNCH_NONE
+  };
+
+  // The name of the app launch histogram.
+  extern const char kAppLaunchHistogram[];
+
+  // The buckets used for app launches.
+  enum AppLaunchBucket {
+    // Launch from NTP apps section while maximized.
+    APP_LAUNCH_NTP_APPS_MAXIMIZED,
+
+    // Launch from NTP apps section while collapsed.
+    APP_LAUNCH_NTP_APPS_COLLAPSED,
+
+    // Launch from NTP apps section while in menu mode.
+    APP_LAUNCH_NTP_APPS_MENU,
+
+    // Launch from NTP most visited section in any mode.
+    APP_LAUNCH_NTP_MOST_VISITED,
+
+    // Launch from NTP recently closed section in any mode.
+    APP_LAUNCH_NTP_RECENTLY_CLOSED,
+
+    // App link clicked from bookmark bar.
+    APP_LAUNCH_BOOKMARK_BAR,
+
+    // Nvigated to an app from within a web page (like by clicking a link).
+    APP_LAUNCH_CONTENT_NAVIGATION,
+
+    // Launch from session restore.
+    APP_LAUNCH_SESSION_RESTORE,
+
+    // Autolaunched at startup, like for pinned tabs.
+    APP_LAUNCH_AUTOLAUNCH,
+
+    // Launched from omnibox app links.
+    APP_LAUNCH_OMNIBOX_APP,
+
+    // App URL typed directly into the omnibox (w/ instant turned off).
+    APP_LAUNCH_OMNIBOX_LOCATION,
+
+    // Navigate to an app URL via instant.
+    APP_LAUNCH_OMNIBOX_INSTANT,
+
+    // Launch via chrome.management.launchApp.
+    APP_LAUNCH_EXTENSION_API,
+
+    // Launch using the --app or --app-id cmd line options.
+    APP_LAUNCH_CMD_LINE_APP,
+
+    // App launch by passing the URL on the cmd line (not using app switches).
+    APP_LAUNCH_CMD_LINE_URL,
+
+    // User clicked web store launcher on NTP.
+    APP_LAUNCH_NTP_WEBSTORE,
+
+    // App launched after the user re-enabled it on the NTP.
+    APP_LAUNCH_NTP_APP_RE_ENABLE,
+
+    // URL launched using the --app cmd line option, but the URL does not
+    // correspond to an installed app. These launches are left over from a
+    // feature that let you make desktop shortcuts from the file menu.
+    APP_LAUNCH_CMD_LINE_APP_LEGACY,
+
+    // User clicked web store link on the NTP footer.
+    APP_LAUNCH_NTP_WEBSTORE_FOOTER,
+
+    // User clicked [+] icon in apps page.
+    APP_LAUNCH_NTP_WEBSTORE_PLUS_ICON,
+
+    APP_LAUNCH_BUCKET_BOUNDARY,
+    APP_LAUNCH_BUCKET_INVALID
+  };
+
+#if defined(OS_CHROMEOS)
+  // The directory path on a ChromeOS device where accessibility extensions are
+  // stored.
+  extern const char kAccessExtensionPath[];
+  extern const char kChromeVoxDirectoryName[];
+  // The extension id of the wallpaper manager application.
+  extern const char kWallpaperManagerId[];
+#endif
+
+  // What causes an extension to be installed? Used in histograms, so don't
+  // change existing values.
+  enum CrxInstallCause {
+    INSTALL_CAUSE_UNSET = 0,
+    INSTALL_CAUSE_USER_DOWNLOAD,
+    INSTALL_CAUSE_UPDATE,
+    INSTALL_CAUSE_EXTERNAL_FILE,
+    INSTALL_CAUSE_AUTOMATION,
+    NUM_INSTALL_CAUSES
+  };
+
+  enum UnloadedExtensionReason {
+    UNLOAD_REASON_DISABLE,    // Extension is being disabled.
+    UNLOAD_REASON_UPDATE,     // Extension is being updated to a newer version.
+    UNLOAD_REASON_UNINSTALL,  // Extension is being uninstalled.
+    UNLOAD_REASON_TERMINATE,  // Extension has terminated.
+  };
+
+  // The states that an app can be in, as reported by chrome.app.installState
+  // and chrome.app.runningState.
+  extern const char kAppStateNotInstalled[];
+  extern const char kAppStateInstalled[];
+  extern const char kAppStateDisabled[];
+  extern const char kAppStateRunning[];
+  extern const char kAppStateCannotRun[];
+  extern const char kAppStateReadyToRun[];
+
+  // The path part of the file system url used for media file systems.
+  extern const char kMediaFileSystemPathPart[];
+
+  // Error indicating that the app notifications API is not accessible by split
+  // mode extensions in incognito windows.
+  extern const char kAppNotificationsIncognitoError[];
+
+  // NOTE: If you change this list, you should also change kExtensionIconSizes
+  // in cc file.
+  enum ExtensionIcons {
+    EXTENSION_ICON_GIGANTOR = 512,
+    EXTENSION_ICON_EXTRA_LARGE = 256,
+    EXTENSION_ICON_LARGE = 128,
+    EXTENSION_ICON_MEDIUM = 48,
+    EXTENSION_ICON_SMALL = 32,
+    EXTENSION_ICON_SMALLISH = 24,
+    EXTENSION_ICON_ACTION = 19,
+    EXTENSION_ICON_BITTY = 16,
+    EXTENSION_ICON_INVALID = 0,
+  };
+
+  // List of sizes for extension icons that can be defined in the manifest.
+  extern const int kExtensionIconSizes[];
+  extern const size_t kNumExtensionIconSizes;
+
+  // List of sizes for extension icons that can be defined in the manifest.
+  extern const int kExtensionActionIconSizes[];
+  extern const size_t kNumExtensionActionIconSizes;
+
+  // List of sizes for extension icons that can be defined in the manifest.
+  extern const int kScriptBadgeIconSizes[];
+  extern const size_t kNumScriptBadgeIconSizes;
+
+}  // extension_misc
+
+#endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_CONSTANTS_H_
diff --git a/chrome/common/extensions/extension_constants_unittest.cc b/chrome/common/extensions/extension_constants_unittest.cc
new file mode 100644
index 0000000..1be5c08
--- /dev/null
+++ b/chrome/common/extensions/extension_constants_unittest.cc
@@ -0,0 +1,17 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_constants.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(ExtensionConstantsTest, IntentSearchQuery) {
+  const char expected_url[] = "https://chrome.google.com/webstore/"
+      "category/collection/webintent_apps"
+      "?_wi=http%3A%2F%2Fwebintents.org%2Fedit&_mt=image%2Fjpeg";
+
+  GURL result = extension_urls::GetWebstoreIntentQueryURL(
+      "http://webintents.org/edit", "image/jpeg");
+   EXPECT_EQ(expected_url, result.spec());
+}
diff --git a/chrome/common/extensions/extension_error_utils.cc b/chrome/common/extensions/extension_error_utils.cc
new file mode 100644
index 0000000..42da01e
--- /dev/null
+++ b/chrome/common/extensions/extension_error_utils.cc
@@ -0,0 +1,69 @@
+// 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.
+
+#include "chrome/common/extensions/extension_error_utils.h"
+
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+
+std::string ExtensionErrorUtils::FormatErrorMessage(
+    const std::string& format,
+    const std::string& s1) {
+  std::string ret_val = format;
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
+  return ret_val;
+}
+
+std::string ExtensionErrorUtils::FormatErrorMessage(
+    const std::string& format,
+    const std::string& s1,
+    const std::string& s2) {
+  std::string ret_val = format;
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2);
+  return ret_val;
+}
+
+std::string ExtensionErrorUtils::FormatErrorMessage(
+    const std::string& format,
+    const std::string& s1,
+    const std::string& s2,
+    const std::string& s3) {
+  std::string ret_val = format;
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2);
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s3);
+  return ret_val;
+}
+
+string16 ExtensionErrorUtils::FormatErrorMessageUTF16(
+    const std::string& format,
+    const std::string& s1) {
+  std::string ret_val = format;
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
+  return UTF8ToUTF16(ret_val);
+}
+
+string16 ExtensionErrorUtils::FormatErrorMessageUTF16(
+    const std::string& format,
+    const std::string& s1,
+    const std::string& s2) {
+  std::string ret_val = format;
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2);
+  return UTF8ToUTF16(ret_val);
+}
+
+string16 ExtensionErrorUtils::FormatErrorMessageUTF16(
+    const std::string& format,
+    const std::string& s1,
+    const std::string& s2,
+    const std::string& s3) {
+  std::string ret_val = format;
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2);
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s3);
+  return UTF8ToUTF16(ret_val);
+}
+
diff --git a/chrome/common/extensions/extension_error_utils.h b/chrome/common/extensions/extension_error_utils.h
new file mode 100644
index 0000000..4745ca0
--- /dev/null
+++ b/chrome/common/extensions/extension_error_utils.h
@@ -0,0 +1,40 @@
+// 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_ERROR_UTILS_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_ERROR_UTILS_H_
+
+#include <string>
+
+#include "base/string16.h"
+
+class ExtensionErrorUtils {
+ public:
+  // Creates an error messages from a pattern.
+  static std::string FormatErrorMessage(const std::string& format,
+                                        const std::string& s1);
+
+  static std::string FormatErrorMessage(const std::string& format,
+                                        const std::string& s1,
+                                        const std::string& s2);
+
+  static std::string FormatErrorMessage(const std::string& format,
+                                        const std::string& s1,
+                                        const std::string& s2,
+                                        const std::string& s3);
+
+  static string16 FormatErrorMessageUTF16(const std::string& format,
+                                          const std::string& s1);
+
+  static string16 FormatErrorMessageUTF16(const std::string& format,
+                                          const std::string& s1,
+                                          const std::string& s2);
+
+  static string16 FormatErrorMessageUTF16(const std::string& format,
+                                          const std::string& s1,
+                                          const std::string& s2,
+                                          const std::string& s3);
+};
+
+#endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_ERROR_UTILS_H_
diff --git a/chrome/common/extensions/extension_file_util.cc b/chrome/common/extensions/extension_file_util.cc
new file mode 100644
index 0000000..7d857b5
--- /dev/null
+++ b/chrome/common/extensions/extension_file_util.cc
@@ -0,0 +1,762 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_file_util.h"
+
+#include <map>
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/path_service.h"
+#include "base/scoped_temp_dir.h"
+#include "base/stringprintf.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_l10n_util.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/extension_messages.h"
+#include "chrome/common/extensions/extension_resource.h"
+#include "chrome/common/extensions/message_bundle.h"
+#include "grit/generated_resources.h"
+#include "net/base/escape.h"
+#include "net/base/file_stream.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using extensions::Extension;
+
+namespace errors = extension_manifest_errors;
+
+namespace {
+
+const FilePath::CharType kTempDirectoryName[] = FILE_PATH_LITERAL("Temp");
+
+bool ValidateExtensionIconSet(const ExtensionIconSet& icon_set,
+                              const Extension* extension,
+                              int error_message_id,
+                              std::string* error) {
+  for (ExtensionIconSet::IconMap::const_iterator iter = icon_set.map().begin();
+       iter != icon_set.map().end();
+       ++iter) {
+    const FilePath path = extension->GetResource(iter->second).GetFilePath();
+    if (!extension_file_util::ValidateFilePath(path)) {
+      *error = l10n_util::GetStringFUTF8(error_message_id,
+                                         UTF8ToUTF16(iter->second));
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace
+
+namespace extension_file_util {
+
+// Validates locale info. Doesn't check if messages.json files are valid.
+static bool ValidateLocaleInfo(const Extension& extension,
+                               std::string* error);
+
+// Returns false and sets the error if script file can't be loaded,
+// or if it's not UTF-8 encoded.
+static bool IsScriptValid(const FilePath& path, const FilePath& relative_path,
+                          int message_id, std::string* error);
+
+FilePath InstallExtension(const FilePath& unpacked_source_dir,
+                          const std::string& id,
+                          const std::string& version,
+                          const FilePath& extensions_dir) {
+  FilePath extension_dir = extensions_dir.AppendASCII(id);
+  FilePath version_dir;
+
+  // Create the extension directory if it doesn't exist already.
+  if (!file_util::PathExists(extension_dir)) {
+    if (!file_util::CreateDirectory(extension_dir))
+      return FilePath();
+  }
+
+  // Get a temp directory on the same file system as the profile.
+  FilePath install_temp_dir = GetInstallTempDir(extensions_dir);
+  ScopedTempDir extension_temp_dir;
+  if (install_temp_dir.empty() ||
+      !extension_temp_dir.CreateUniqueTempDirUnderPath(install_temp_dir)) {
+    LOG(ERROR) << "Creating of temp dir under in the profile failed.";
+    return FilePath();
+  }
+  FilePath crx_temp_source =
+      extension_temp_dir.path().Append(unpacked_source_dir.BaseName());
+  if (!file_util::Move(unpacked_source_dir, crx_temp_source)) {
+    LOG(ERROR) << "Moving extension from : " << unpacked_source_dir.value()
+               << " to : " << crx_temp_source.value() << " failed.";
+    return FilePath();
+  }
+
+  // Try to find a free directory. There can be legitimate conflicts in the case
+  // of overinstallation of the same version.
+  const int kMaxAttempts = 100;
+  for (int i = 0; i < kMaxAttempts; ++i) {
+    FilePath candidate = extension_dir.AppendASCII(
+        base::StringPrintf("%s_%u", version.c_str(), i));
+    if (!file_util::PathExists(candidate)) {
+      version_dir = candidate;
+      break;
+    }
+  }
+
+  if (version_dir.empty()) {
+    LOG(ERROR) << "Could not find a home for extension " << id << " with "
+               << "version " << version << ".";
+    return FilePath();
+  }
+
+  if (!file_util::Move(crx_temp_source, version_dir)) {
+    LOG(ERROR) << "Installing extension from : " << crx_temp_source.value()
+               << " into : " << version_dir.value() << " failed.";
+    return FilePath();
+  }
+
+  return version_dir;
+}
+
+void UninstallExtension(const FilePath& extensions_dir,
+                        const std::string& id) {
+  // We don't care about the return value. If this fails (and it can, due to
+  // plugins that aren't unloaded yet, it will get cleaned up by
+  // ExtensionService::GarbageCollectExtensions).
+  file_util::Delete(extensions_dir.AppendASCII(id), true);  // recursive.
+}
+
+scoped_refptr<Extension> LoadExtension(const FilePath& extension_path,
+                                       Extension::Location location,
+                                       int flags,
+                                       std::string* error) {
+  return LoadExtension(extension_path, std::string(), location, flags, error);
+}
+
+scoped_refptr<Extension> LoadExtension(const FilePath& extension_path,
+                                       const std::string& extension_id,
+                                       Extension::Location location,
+                                       int flags,
+                                       std::string* error) {
+  scoped_ptr<DictionaryValue> manifest(LoadManifest(extension_path, error));
+  if (!manifest.get())
+    return NULL;
+  if (!extension_l10n_util::LocalizeExtension(extension_path, manifest.get(),
+                                              error)) {
+    return NULL;
+  }
+
+  scoped_refptr<Extension> extension(Extension::Create(extension_path,
+                                                       location,
+                                                       *manifest,
+                                                       flags,
+                                                       extension_id,
+                                                       error));
+  if (!extension.get())
+    return NULL;
+
+  Extension::InstallWarningVector warnings;
+  if (!ValidateExtension(extension.get(), error, &warnings))
+    return NULL;
+  extension->AddInstallWarnings(warnings);
+
+  return extension;
+}
+
+DictionaryValue* LoadManifest(const FilePath& extension_path,
+                              std::string* error) {
+  FilePath manifest_path =
+      extension_path.Append(Extension::kManifestFilename);
+  if (!file_util::PathExists(manifest_path)) {
+    *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE);
+    return NULL;
+  }
+
+  JSONFileValueSerializer serializer(manifest_path);
+  scoped_ptr<Value> root(serializer.Deserialize(NULL, error));
+  if (!root.get()) {
+    if (error->empty()) {
+      // If |error| is empty, than the file could not be read.
+      // It would be cleaner to have the JSON reader give a specific error
+      // in this case, but other code tests for a file error with
+      // error->empty().  For now, be consistent.
+      *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE);
+    } else {
+      *error = base::StringPrintf("%s  %s",
+                                  errors::kManifestParseError,
+                                  error->c_str());
+    }
+    return NULL;
+  }
+
+  if (!root->IsType(Value::TYPE_DICTIONARY)) {
+    *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_INVALID);
+    return NULL;
+  }
+
+  return static_cast<DictionaryValue*>(root.release());
+}
+
+std::vector<FilePath> FindPrivateKeyFiles(const FilePath& extension_dir) {
+  std::vector<FilePath> result;
+  // Pattern matching only works at the root level, so filter manually.
+  file_util::FileEnumerator traversal(extension_dir, /*recursive=*/true,
+                                      file_util::FileEnumerator::FILES);
+  for (FilePath current = traversal.Next(); !current.empty();
+       current = traversal.Next()) {
+    if (!current.MatchesExtension(chrome::kExtensionKeyFileExtension))
+      continue;
+
+    std::string key_contents;
+    if (!file_util::ReadFileToString(current, &key_contents)) {
+      // If we can't read the file, assume it's not a private key.
+      continue;
+    }
+    std::string key_bytes;
+    if (!Extension::ParsePEMKeyBytes(key_contents, &key_bytes)) {
+      // If we can't parse the key, assume it's ok too.
+      continue;
+    }
+
+    result.push_back(current);
+  }
+  return result;
+}
+
+bool ValidateFilePath(const FilePath& path) {
+  int64 size = 0;
+  if (!file_util::PathExists(path) ||
+      !file_util::GetFileSize(path, &size) ||
+      size == 0) {
+    return false;
+  }
+
+  return true;
+}
+
+bool ValidateExtension(const Extension* extension,
+                       std::string* error,
+                       Extension::InstallWarningVector* warnings) {
+  // Validate icons exist.
+  for (ExtensionIconSet::IconMap::const_iterator iter =
+           extension->icons().map().begin();
+       iter != extension->icons().map().end();
+       ++iter) {
+    const FilePath path = extension->GetResource(iter->second).GetFilePath();
+    if (!ValidateFilePath(path)) {
+      *error =
+          l10n_util::GetStringFUTF8(IDS_EXTENSION_LOAD_ICON_FAILED,
+                                    UTF8ToUTF16(iter->second));
+      return false;
+    }
+  }
+
+  // Theme resource validation.
+  if (extension->is_theme()) {
+    DictionaryValue* images_value = extension->GetThemeImages();
+    if (images_value) {
+      for (DictionaryValue::key_iterator iter = images_value->begin_keys();
+           iter != images_value->end_keys(); ++iter) {
+        std::string val;
+        if (images_value->GetStringWithoutPathExpansion(*iter, &val)) {
+          FilePath image_path = extension->path().Append(
+              FilePath::FromUTF8Unsafe(val));
+          if (!file_util::PathExists(image_path)) {
+            *error =
+                l10n_util::GetStringFUTF8(IDS_EXTENSION_INVALID_IMAGE_PATH,
+                                          image_path.LossyDisplayName());
+            return false;
+          }
+        }
+      }
+    }
+
+    // Themes cannot contain other extension types.
+    return true;
+  }
+
+  // Validate that claimed script resources actually exist,
+  // and are UTF-8 encoded.
+  ExtensionResource::SymlinkPolicy symlink_policy;
+  if ((extension->creation_flags() &
+       Extension::FOLLOW_SYMLINKS_ANYWHERE) != 0) {
+    symlink_policy = ExtensionResource::FOLLOW_SYMLINKS_ANYWHERE;
+  } else {
+    symlink_policy = ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT;
+  }
+
+  for (size_t i = 0; i < extension->content_scripts().size(); ++i) {
+    const extensions::UserScript& script = extension->content_scripts()[i];
+
+    for (size_t j = 0; j < script.js_scripts().size(); j++) {
+      const extensions::UserScript::File& js_script = script.js_scripts()[j];
+      const FilePath& path = ExtensionResource::GetFilePath(
+          js_script.extension_root(), js_script.relative_path(),
+          symlink_policy);
+      if (!IsScriptValid(path, js_script.relative_path(),
+                         IDS_EXTENSION_LOAD_JAVASCRIPT_FAILED, error))
+        return false;
+    }
+
+    for (size_t j = 0; j < script.css_scripts().size(); j++) {
+      const extensions::UserScript::File& css_script = script.css_scripts()[j];
+      const FilePath& path = ExtensionResource::GetFilePath(
+          css_script.extension_root(), css_script.relative_path(),
+          symlink_policy);
+      if (!IsScriptValid(path, css_script.relative_path(),
+                         IDS_EXTENSION_LOAD_CSS_FAILED, error))
+        return false;
+    }
+  }
+
+  // Validate claimed plugin paths.
+  for (size_t i = 0; i < extension->plugins().size(); ++i) {
+    const Extension::PluginInfo& plugin = extension->plugins()[i];
+    if (!file_util::PathExists(plugin.path)) {
+      *error =
+          l10n_util::GetStringFUTF8(
+              IDS_EXTENSION_LOAD_PLUGIN_PATH_FAILED,
+              plugin.path.LossyDisplayName());
+      return false;
+    }
+  }
+
+  const Extension::ActionInfo* action = extension->page_action_info();
+  if (action && !action->default_icon.empty() &&
+      !ValidateExtensionIconSet(action->default_icon, extension,
+          IDS_EXTENSION_LOAD_ICON_FOR_PAGE_ACTION_FAILED, error)) {
+    return false;
+  }
+
+  action = extension->browser_action_info();
+  if (action && !action->default_icon.empty() &&
+      !ValidateExtensionIconSet(action->default_icon, extension,
+          IDS_EXTENSION_LOAD_ICON_FOR_BROWSER_ACTION_FAILED, error)) {
+    return false;
+  }
+
+  // Validate that background scripts exist.
+  for (size_t i = 0; i < extension->background_scripts().size(); ++i) {
+    if (!file_util::PathExists(
+            extension->GetResource(
+                extension->background_scripts()[i]).GetFilePath())) {
+      *error = l10n_util::GetStringFUTF8(
+          IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED,
+          UTF8ToUTF16(extension->background_scripts()[i]));
+      return false;
+    }
+  }
+
+  // Validate background page location, except for hosted apps, which should use
+  // an external URL. Background page for hosted apps are verified when the
+  // extension is created (in Extension::InitFromValue)
+  if (extension->has_background_page() &&
+      !extension->is_hosted_app() &&
+      extension->background_scripts().empty()) {
+    FilePath page_path = ExtensionURLToRelativeFilePath(
+        extension->GetBackgroundURL());
+    const FilePath path = extension->GetResource(page_path).GetFilePath();
+    if (path.empty() || !file_util::PathExists(path)) {
+      *error =
+          l10n_util::GetStringFUTF8(
+              IDS_EXTENSION_LOAD_BACKGROUND_PAGE_FAILED,
+              page_path.LossyDisplayName());
+      return false;
+    }
+  }
+
+  // Validate path to the options page.  Don't check the URL for hosted apps,
+  // because they are expected to refer to an external URL.
+  if (!extension->options_url().is_empty() && !extension->is_hosted_app()) {
+    const FilePath options_path = ExtensionURLToRelativeFilePath(
+        extension->options_url());
+    const FilePath path = extension->GetResource(options_path).GetFilePath();
+    if (path.empty() || !file_util::PathExists(path)) {
+      *error =
+          l10n_util::GetStringFUTF8(
+              IDS_EXTENSION_LOAD_OPTIONS_PAGE_FAILED,
+              options_path.LossyDisplayName());
+      return false;
+    }
+  }
+
+  // Validate locale info.
+  if (!ValidateLocaleInfo(*extension, error))
+    return false;
+
+  // Check children of extension root to see if any of them start with _ and is
+  // not on the reserved list.
+  if (!CheckForIllegalFilenames(extension->path(), error)) {
+    return false;
+  }
+
+  // Check that extensions don't include private key files.
+  std::vector<FilePath> private_keys = FindPrivateKeyFiles(extension->path());
+  if (extension->creation_flags() & Extension::ERROR_ON_PRIVATE_KEY) {
+    if (!private_keys.empty()) {
+      // Only print one of the private keys because l10n_util doesn't have a way
+      // to translate a list of strings.
+      *error = l10n_util::GetStringFUTF8(
+          IDS_EXTENSION_CONTAINS_PRIVATE_KEY,
+          private_keys.front().LossyDisplayName());
+      return false;
+    }
+  } else {
+    for (size_t i = 0; i < private_keys.size(); ++i) {
+      warnings->push_back(Extension::InstallWarning(
+          Extension::InstallWarning::FORMAT_TEXT,
+          l10n_util::GetStringFUTF8(
+              IDS_EXTENSION_CONTAINS_PRIVATE_KEY,
+              private_keys[i].LossyDisplayName())));
+    }
+    // Only warn; don't block loading the extension.
+  }
+  return true;
+}
+
+void GarbageCollectExtensions(
+    const FilePath& install_directory,
+    const std::multimap<std::string, FilePath>& extension_paths) {
+  // Nothing to clean up if it doesn't exist.
+  if (!file_util::DirectoryExists(install_directory))
+    return;
+
+  DVLOG(1) << "Garbage collecting extensions...";
+  file_util::FileEnumerator enumerator(install_directory,
+                                       false,  // Not recursive.
+                                       file_util::FileEnumerator::DIRECTORIES);
+  FilePath extension_path;
+  for (extension_path = enumerator.Next(); !extension_path.value().empty();
+       extension_path = enumerator.Next()) {
+    std::string extension_id;
+
+    FilePath basename = extension_path.BaseName();
+    // Clean up temporary files left if Chrome crashed or quit in the middle
+    // of an extension install.
+    if (basename.value() == kTempDirectoryName) {
+      file_util::Delete(extension_path, true);  // Recursive
+      continue;
+    }
+
+    // Parse directory name as a potential extension ID.
+    if (IsStringASCII(basename.value())) {
+      extension_id = UTF16ToASCII(basename.LossyDisplayName());
+      if (!Extension::IdIsValid(extension_id))
+        extension_id.clear();
+    }
+
+    // Delete directories that aren't valid IDs.
+    if (extension_id.empty()) {
+      DLOG(WARNING) << "Invalid extension ID encountered in extensions "
+                       "directory: " << basename.value();
+      DVLOG(1) << "Deleting invalid extension directory "
+               << extension_path.value() << ".";
+      file_util::Delete(extension_path, true);  // Recursive.
+      continue;
+    }
+
+    typedef std::multimap<std::string, FilePath>::const_iterator Iter;
+    std::pair<Iter, Iter> iter_pair = extension_paths.equal_range(extension_id);
+
+    // If there is no entry in the prefs file, just delete the directory and
+    // move on. This can legitimately happen when an uninstall does not
+    // complete, for example, when a plugin is in use at uninstall time.
+    if (iter_pair.first == iter_pair.second) {
+      DVLOG(1) << "Deleting unreferenced install for directory "
+               << extension_path.LossyDisplayName() << ".";
+      file_util::Delete(extension_path, true);  // Recursive.
+      continue;
+    }
+
+    // Clean up old version directories.
+    file_util::FileEnumerator versions_enumerator(
+        extension_path,
+        false,  // Not recursive.
+        file_util::FileEnumerator::DIRECTORIES);
+    for (FilePath version_dir = versions_enumerator.Next();
+         !version_dir.value().empty();
+         version_dir = versions_enumerator.Next()) {
+      bool knownVersion = false;
+      for (Iter it = iter_pair.first; it != iter_pair.second; ++it)
+        if (version_dir.BaseName() == it->second.BaseName()) {
+          knownVersion = true;
+          break;
+        }
+      if (!knownVersion) {
+        DVLOG(1) << "Deleting old version for directory "
+                 << version_dir.LossyDisplayName() << ".";
+        file_util::Delete(version_dir, true);  // Recursive.
+      }
+    }
+  }
+}
+
+extensions::MessageBundle* LoadMessageBundle(
+    const FilePath& extension_path,
+    const std::string& default_locale,
+    std::string* error) {
+  error->clear();
+  // Load locale information if available.
+  FilePath locale_path = extension_path.Append(
+      Extension::kLocaleFolder);
+  if (!file_util::PathExists(locale_path))
+    return NULL;
+
+  std::set<std::string> locales;
+  if (!extension_l10n_util::GetValidLocales(locale_path, &locales, error))
+    return NULL;
+
+  if (default_locale.empty() ||
+      locales.find(default_locale) == locales.end()) {
+    *error = l10n_util::GetStringUTF8(
+        IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED);
+    return NULL;
+  }
+
+  extensions::MessageBundle* message_bundle =
+      extension_l10n_util::LoadMessageCatalogs(
+          locale_path,
+          default_locale,
+          extension_l10n_util::CurrentLocaleOrDefault(),
+          locales,
+          error);
+
+  return message_bundle;
+}
+
+SubstitutionMap* LoadMessageBundleSubstitutionMap(
+    const FilePath& extension_path,
+    const std::string& extension_id,
+    const std::string& default_locale) {
+  SubstitutionMap* returnValue = new SubstitutionMap();
+  if (!default_locale.empty()) {
+    // Touch disk only if extension is localized.
+    std::string error;
+    scoped_ptr<extensions::MessageBundle> bundle(
+        LoadMessageBundle(extension_path, default_locale, &error));
+
+    if (bundle.get())
+      *returnValue = *bundle->dictionary();
+  }
+
+  // Add @@extension_id reserved message here, so it's available to
+  // non-localized extensions too.
+  returnValue->insert(
+      std::make_pair(extensions::MessageBundle::kExtensionIdKey, extension_id));
+
+  return returnValue;
+}
+
+static bool ValidateLocaleInfo(const Extension& extension,
+                               std::string* error) {
+  // default_locale and _locales have to be both present or both missing.
+  const FilePath path = extension.path().Append(
+      Extension::kLocaleFolder);
+  bool path_exists = file_util::PathExists(path);
+  std::string default_locale = extension.default_locale();
+
+  // If both default locale and _locales folder are empty, skip verification.
+  if (default_locale.empty() && !path_exists)
+    return true;
+
+  if (default_locale.empty() && path_exists) {
+    *error = l10n_util::GetStringUTF8(
+        IDS_EXTENSION_LOCALES_NO_DEFAULT_LOCALE_SPECIFIED);
+    return false;
+  } else if (!default_locale.empty() && !path_exists) {
+    *error = errors::kLocalesTreeMissing;
+    return false;
+  }
+
+  // Treat all folders under _locales as valid locales.
+  file_util::FileEnumerator locales(path,
+                                    false,
+                                    file_util::FileEnumerator::DIRECTORIES);
+
+  std::set<std::string> all_locales;
+  extension_l10n_util::GetAllLocales(&all_locales);
+  const FilePath default_locale_path = path.AppendASCII(default_locale);
+  bool has_default_locale_message_file = false;
+
+  FilePath locale_path;
+  while (!(locale_path = locales.Next()).empty()) {
+    if (extension_l10n_util::ShouldSkipValidation(path, locale_path,
+                                                  all_locales))
+      continue;
+
+    FilePath messages_path =
+        locale_path.Append(Extension::kMessagesFilename);
+
+    if (!file_util::PathExists(messages_path)) {
+      *error = base::StringPrintf(
+          "%s %s", errors::kLocalesMessagesFileMissing,
+          UTF16ToUTF8(messages_path.LossyDisplayName()).c_str());
+      return false;
+    }
+
+    if (locale_path == default_locale_path)
+      has_default_locale_message_file = true;
+  }
+
+  // Only message file for default locale has to exist.
+  if (!has_default_locale_message_file) {
+    *error = errors::kLocalesNoDefaultMessages;
+    return false;
+  }
+
+  return true;
+}
+
+static bool IsScriptValid(const FilePath& path,
+                          const FilePath& relative_path,
+                          int message_id,
+                          std::string* error) {
+  std::string content;
+  if (!file_util::PathExists(path) ||
+      !file_util::ReadFileToString(path, &content)) {
+    *error = l10n_util::GetStringFUTF8(
+        message_id,
+        relative_path.LossyDisplayName());
+    return false;
+  }
+
+  if (!IsStringUTF8(content)) {
+    *error = l10n_util::GetStringFUTF8(
+        IDS_EXTENSION_BAD_FILE_ENCODING,
+        relative_path.LossyDisplayName());
+    return false;
+  }
+
+  return true;
+}
+
+bool CheckForIllegalFilenames(const FilePath& extension_path,
+                              std::string* error) {
+  // Reserved underscore names.
+  static const FilePath::CharType* reserved_names[] = {
+    Extension::kLocaleFolder,
+    FILE_PATH_LITERAL("__MACOSX"),
+  };
+  CR_DEFINE_STATIC_LOCAL(
+      std::set<FilePath::StringType>, reserved_underscore_names,
+      (reserved_names, reserved_names + arraysize(reserved_names)));
+
+  // Enumerate all files and directories in the extension root.
+  // There is a problem when using pattern "_*" with FileEnumerator, so we have
+  // to cheat with find_first_of and match all.
+  const int kFilesAndDirectories =
+      file_util::FileEnumerator::DIRECTORIES | file_util::FileEnumerator::FILES;
+  file_util::FileEnumerator all_files(
+      extension_path, false, kFilesAndDirectories);
+
+  FilePath file;
+  while (!(file = all_files.Next()).empty()) {
+    FilePath::StringType filename = file.BaseName().value();
+    // Skip all that don't start with "_".
+    if (filename.find_first_of(FILE_PATH_LITERAL("_")) != 0) continue;
+    if (reserved_underscore_names.find(filename) ==
+        reserved_underscore_names.end()) {
+      *error = base::StringPrintf(
+          "Cannot load extension with file or directory name %s. "
+          "Filenames starting with \"_\" are reserved for use by the system.",
+          filename.c_str());
+      return false;
+    }
+  }
+
+  return true;
+}
+
+FilePath ExtensionURLToRelativeFilePath(const GURL& url) {
+  std::string url_path = url.path();
+  if (url_path.empty() || url_path[0] != '/')
+    return FilePath();
+
+  // Drop the leading slashes and convert %-encoded UTF8 to regular UTF8.
+  std::string file_path = net::UnescapeURLComponent(url_path,
+      net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS);
+  size_t skip = file_path.find_first_not_of("/\\");
+  if (skip != file_path.npos)
+    file_path = file_path.substr(skip);
+
+  FilePath path =
+#if defined(OS_POSIX)
+    FilePath(file_path);
+#elif defined(OS_WIN)
+    FilePath(UTF8ToWide(file_path));
+#else
+    FilePath();
+    NOTIMPLEMENTED();
+#endif
+
+  // It's still possible for someone to construct an annoying URL whose path
+  // would still wind up not being considered relative at this point.
+  // For example: chrome-extension://id/c:////foo.html
+  if (path.IsAbsolute())
+    return FilePath();
+
+  return path;
+}
+
+FilePath ExtensionResourceURLToFilePath(const GURL& url, const FilePath& root) {
+  std::string host = net::UnescapeURLComponent(url.host(),
+      net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS);
+  if (host.empty())
+    return FilePath();
+
+  FilePath relative_path = ExtensionURLToRelativeFilePath(url);
+  if (relative_path.empty())
+    return FilePath();
+
+  FilePath path = root.AppendASCII(host).Append(relative_path);
+  if (!file_util::PathExists(path) ||
+      !file_util::AbsolutePath(&path) ||
+      !root.IsParent(path)) {
+    return FilePath();
+  }
+  return path;
+}
+
+FilePath GetInstallTempDir(const FilePath& extensions_dir) {
+  // We do file IO in this function, but only when the current profile's
+  // Temp directory has never been used before, or in a rare error case.
+  // Developers are not likely to see these situations often, so do an
+  // explicit thread check.
+  base::ThreadRestrictions::AssertIOAllowed();
+
+  // Create the temp directory as a sub-directory of the Extensions directory.
+  // This guarantees it is on the same file system as the extension's eventual
+  // install target.
+  FilePath temp_path = extensions_dir.Append(kTempDirectoryName);
+  if (file_util::PathExists(temp_path)) {
+    if (!file_util::DirectoryExists(temp_path)) {
+      DLOG(WARNING) << "Not a directory: " << temp_path.value();
+      return FilePath();
+    }
+    if (!file_util::PathIsWritable(temp_path)) {
+      DLOG(WARNING) << "Can't write to path: " << temp_path.value();
+      return FilePath();
+    }
+    // This is a directory we can write to.
+    return temp_path;
+  }
+
+  // Directory doesn't exist, so create it.
+  if (!file_util::CreateDirectory(temp_path)) {
+    DLOG(WARNING) << "Couldn't create directory: " << temp_path.value();
+    return FilePath();
+  }
+  return temp_path;
+}
+
+void DeleteFile(const FilePath& path, bool recursive) {
+  file_util::Delete(path, recursive);
+}
+
+}  // namespace extension_file_util
diff --git a/chrome/common/extensions/extension_file_util.h b/chrome/common/extensions/extension_file_util.h
new file mode 100644
index 0000000..fd1354d
--- /dev/null
+++ b/chrome/common/extensions/extension_file_util.h
@@ -0,0 +1,132 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_FILE_UTIL_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_FILE_UTIL_H_
+
+#include <string>
+#include <map>
+
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/message_bundle.h"
+
+class FilePath;
+class GURL;
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+class MessageBundle;
+}
+
+// Utilities for manipulating the on-disk storage of extensions.
+namespace extension_file_util {
+
+// Copies |unpacked_source_dir| into the right location under |extensions_dir|.
+// The destination directory is returned on success, or empty path is returned
+// on failure.
+FilePath InstallExtension(const FilePath& unpacked_source_dir,
+                          const std::string& id,
+                          const std::string& version,
+                          const FilePath& extensions_dir);
+
+// Removes all versions of the extension with |id| from |extensions_dir|.
+void UninstallExtension(const FilePath& extensions_dir,
+                        const std::string& id);
+
+// Loads and validates an extension from the specified directory. Returns NULL
+// on failure, with a description of the error in |error|.
+scoped_refptr<extensions::Extension> LoadExtension(
+    const FilePath& extension_root,
+    extensions::Extension::Location location,
+    int flags,
+    std::string* error);
+
+// The same as LoadExtension except use the provided |extension_id|.
+scoped_refptr<extensions::Extension> LoadExtension(
+    const FilePath& extension_root,
+    const std::string& extension_id,
+    extensions::Extension::Location location,
+    int flags,
+    std::string* error);
+
+// Loads an extension manifest from the specified directory. Returns NULL
+// on failure, with a description of the error in |error|.
+base::DictionaryValue* LoadManifest(const FilePath& extension_root,
+                                    std::string* error);
+
+// Returns true if the given file path exists and is not zero-length.
+bool ValidateFilePath(const FilePath& path);
+
+// Returns true if the given extension object is valid and consistent.
+// May also append a series of warning messages to |warnings|, but they
+// should not prevent the extension from running.
+//
+// Otherwise, returns false, and a description of the error is
+// returned in |error|.
+bool ValidateExtension(const extensions::Extension* extension,
+                       std::string* error,
+                       extensions::Extension::InstallWarningVector* warnings);
+
+// Returns a list of files that contain private keys inside |extension_dir|.
+std::vector<FilePath> FindPrivateKeyFiles(const FilePath& extension_dir);
+
+// Cleans up the extension install directory. It can end up with garbage in it
+// if extensions can't initially be removed when they are uninstalled (eg if a
+// file is in use).
+//
+// |extensions_dir| is the install directory to look in. |extension_paths| is a
+// map from extension id to full installation path.
+//
+// Obsolete version directories are removed, as are directories that aren't
+// found in |extension_paths|.
+void GarbageCollectExtensions(
+    const FilePath& extensions_dir,
+    const std::multimap<std::string, FilePath>& extension_paths);
+
+// Loads extension message catalogs and returns message bundle.
+// Returns NULL on error, or if extension is not localized.
+extensions::MessageBundle* LoadMessageBundle(
+    const FilePath& extension_path,
+    const std::string& default_locale,
+    std::string* error);
+
+// Loads the extension message bundle substitution map. Contains at least
+// extension_id item.
+extensions::MessageBundle::SubstitutionMap* LoadMessageBundleSubstitutionMap(
+    const FilePath& extension_path,
+    const std::string& extension_id,
+    const std::string& default_locale);
+
+// We need to reserve the namespace of entries that start with "_" for future
+// use by Chrome.
+// If any files or directories are found using "_" prefix and are not on
+// reserved list we return false, and set error message.
+bool CheckForIllegalFilenames(const FilePath& extension_path,
+                              std::string* error);
+
+// Get a relative file path from a chrome-extension:// URL.
+FilePath ExtensionURLToRelativeFilePath(const GURL& url);
+
+// Get a full file path from a chrome-extension-resource:// URL, If the URL
+// points a file outside of root, this function will return empty FilePath.
+FilePath ExtensionResourceURLToFilePath(const GURL& url, const FilePath& root);
+
+// Returns a path to a temporary directory for unpacking an extension that will
+// be installed into |extensions_dir|. Creates the directory if necessary.
+// The directory will be on the same file system as |extensions_dir| so
+// that the extension directory can be efficiently renamed into place. Returns
+// an empty file path on failure.
+FilePath GetInstallTempDir(const FilePath& extensions_dir);
+
+// Helper function to delete files. This is used to avoid ugly casts which
+// would be necessary with PostMessage since file_util::Delete is overloaded.
+// TODO(skerner): Make a version of Delete that is not overloaded in file_util.
+void DeleteFile(const FilePath& path, bool recursive);
+
+}  // namespace extension_file_util
+
+#endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_FILE_UTIL_H_
diff --git a/chrome/common/extensions/extension_file_util_unittest.cc b/chrome/common/extensions/extension_file_util_unittest.cc
new file mode 100644
index 0000000..4e7f9ab
--- /dev/null
+++ b/chrome/common/extensions/extension_file_util_unittest.cc
@@ -0,0 +1,593 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_file_util.h"
+
+#include "base/file_util.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/path_service.h"
+#include "base/scoped_temp_dir.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "grit/generated_resources.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using extensions::Extension;
+
+namespace keys = extension_manifest_keys;
+
+#if defined(OS_WIN)
+// http://crbug.com/106381
+#define InstallUninstallGarbageCollect DISABLED_InstallUninstallGarbageCollect
+#endif
+TEST(ExtensionFileUtil, InstallUninstallGarbageCollect) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  // Create a source extension.
+  std::string extension_id("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+  std::string version("1.0");
+  FilePath src = temp.path().AppendASCII(extension_id);
+  ASSERT_TRUE(file_util::CreateDirectory(src));
+
+  // Create a extensions tree.
+  FilePath all_extensions = temp.path().AppendASCII("extensions");
+  ASSERT_TRUE(file_util::CreateDirectory(all_extensions));
+
+  // Install in empty directory. Should create parent directories as needed.
+  FilePath version_1 = extension_file_util::InstallExtension(src,
+                                                             extension_id,
+                                                             version,
+                                                             all_extensions);
+  ASSERT_EQ(version_1.value(),
+            all_extensions.AppendASCII(extension_id).AppendASCII("1.0_0")
+                .value());
+  ASSERT_TRUE(file_util::DirectoryExists(version_1));
+
+  // Should have moved the source.
+  ASSERT_FALSE(file_util::DirectoryExists(src));
+
+  // Install again. Should create a new one with different name.
+  ASSERT_TRUE(file_util::CreateDirectory(src));
+  FilePath version_2 = extension_file_util::InstallExtension(src,
+                                                             extension_id,
+                                                             version,
+                                                             all_extensions);
+  ASSERT_EQ(version_2.value(),
+            all_extensions.AppendASCII(extension_id).AppendASCII("1.0_1")
+                .value());
+  ASSERT_TRUE(file_util::DirectoryExists(version_2));
+
+  // Should have moved the source.
+  ASSERT_FALSE(file_util::DirectoryExists(src));
+
+  // Install yet again. Should create a new one with a different name.
+  ASSERT_TRUE(file_util::CreateDirectory(src));
+  FilePath version_3 = extension_file_util::InstallExtension(src,
+                                                             extension_id,
+                                                             version,
+                                                             all_extensions);
+  ASSERT_EQ(version_3.value(),
+            all_extensions.AppendASCII(extension_id).AppendASCII("1.0_2")
+                .value());
+  ASSERT_TRUE(file_util::DirectoryExists(version_3));
+
+  // Collect garbage. Should remove first one.
+  std::multimap<std::string, FilePath> extension_paths;
+  extension_paths.insert(std::make_pair(extension_id,
+      FilePath().AppendASCII(extension_id).Append(version_2.BaseName())));
+  extension_paths.insert(std::make_pair(extension_id,
+      FilePath().AppendASCII(extension_id).Append(version_3.BaseName())));
+  extension_file_util::GarbageCollectExtensions(all_extensions,
+                                                extension_paths);
+  ASSERT_FALSE(file_util::DirectoryExists(version_1));
+  ASSERT_TRUE(file_util::DirectoryExists(version_2));
+  ASSERT_TRUE(file_util::DirectoryExists(version_3));
+
+  // Uninstall. Should remove entire extension subtree.
+  extension_file_util::UninstallExtension(all_extensions, extension_id);
+  ASSERT_FALSE(file_util::DirectoryExists(version_2.DirName()));
+  ASSERT_FALSE(file_util::DirectoryExists(version_3.DirName()));
+  ASSERT_TRUE(file_util::DirectoryExists(all_extensions));
+}
+
+TEST(ExtensionFileUtil, LoadExtensionWithValidLocales) {
+  FilePath install_dir;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
+  install_dir = install_dir.AppendASCII("extensions")
+      .AppendASCII("good")
+      .AppendASCII("Extensions")
+      .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
+      .AppendASCII("1.0.0.0");
+
+  std::string error;
+  scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
+      install_dir, Extension::LOAD, Extension::NO_FLAGS, &error));
+  ASSERT_TRUE(extension != NULL);
+  EXPECT_EQ("The first extension that I made.", extension->description());
+}
+
+TEST(ExtensionFileUtil, LoadExtensionWithoutLocalesFolder) {
+  FilePath install_dir;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
+  install_dir = install_dir.AppendASCII("extensions")
+      .AppendASCII("good")
+      .AppendASCII("Extensions")
+      .AppendASCII("bjafgdebaacbbbecmhlhpofkepfkgcpa")
+      .AppendASCII("1.0");
+
+  std::string error;
+  scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
+      install_dir, Extension::LOAD, Extension::NO_FLAGS, &error));
+  ASSERT_FALSE(extension == NULL);
+  EXPECT_TRUE(error.empty());
+}
+
+#if defined(OS_WIN)
+// http://crbug.com/106381
+#define CheckIllegalFilenamesNoUnderscores \
+    DISABLED_CheckIllegalFilenamesNoUnderscores
+#endif
+TEST(ExtensionFileUtil, CheckIllegalFilenamesNoUnderscores) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  FilePath src_path = temp.path().AppendASCII("some_dir");
+  ASSERT_TRUE(file_util::CreateDirectory(src_path));
+
+  std::string data = "{ \"name\": { \"message\": \"foobar\" } }";
+  ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("some_file.txt"),
+                                   data.c_str(), data.length()));
+  std::string error;
+  EXPECT_TRUE(extension_file_util::CheckForIllegalFilenames(temp.path(),
+                                                            &error));
+}
+
+#if defined(OS_WIN)
+// http://crbug.com/106381
+#define CheckIllegalFilenamesOnlyReserved \
+    DISABLED_CheckIllegalFilenamesOnlyReserved
+#endif
+TEST(ExtensionFileUtil, CheckIllegalFilenamesOnlyReserved) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  FilePath src_path = temp.path().Append(Extension::kLocaleFolder);
+  ASSERT_TRUE(file_util::CreateDirectory(src_path));
+
+  std::string error;
+  EXPECT_TRUE(extension_file_util::CheckForIllegalFilenames(temp.path(),
+                                                            &error));
+}
+
+#if defined(OS_WIN)
+// http://crbug.com/106381
+#define CheckIllegalFilenamesReservedAndIllegal \
+    DISABLED_CheckIllegalFilenamesReservedAndIllegal
+#endif
+TEST(ExtensionFileUtil, CheckIllegalFilenamesReservedAndIllegal) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  FilePath src_path = temp.path().Append(Extension::kLocaleFolder);
+  ASSERT_TRUE(file_util::CreateDirectory(src_path));
+
+  src_path = temp.path().AppendASCII("_some_dir");
+  ASSERT_TRUE(file_util::CreateDirectory(src_path));
+
+  std::string error;
+  EXPECT_FALSE(extension_file_util::CheckForIllegalFilenames(temp.path(),
+                                                             &error));
+}
+
+TEST(ExtensionFileUtil, LoadExtensionGivesHelpfullErrorOnMissingManifest) {
+  FilePath install_dir;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
+  install_dir = install_dir.AppendASCII("extensions")
+      .AppendASCII("bad")
+      .AppendASCII("Extensions")
+      .AppendASCII("dddddddddddddddddddddddddddddddd")
+      .AppendASCII("1.0");
+
+  std::string error;
+  scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
+      install_dir, Extension::LOAD, Extension::NO_FLAGS, &error));
+  ASSERT_TRUE(extension == NULL);
+  ASSERT_FALSE(error.empty());
+  ASSERT_STREQ("Manifest file is missing or unreadable.", error.c_str());
+}
+
+TEST(ExtensionFileUtil, LoadExtensionGivesHelpfullErrorOnBadManifest) {
+  FilePath install_dir;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
+  install_dir = install_dir.AppendASCII("extensions")
+      .AppendASCII("bad")
+      .AppendASCII("Extensions")
+      .AppendASCII("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")
+      .AppendASCII("1.0");
+
+  std::string error;
+  scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
+      install_dir, Extension::LOAD, Extension::NO_FLAGS, &error));
+  ASSERT_TRUE(extension == NULL);
+  ASSERT_FALSE(error.empty());
+  ASSERT_STREQ("Manifest is not valid JSON.  "
+               "Line: 2, column: 16, Syntax error.", error.c_str());
+}
+
+TEST(ExtensionFileUtil, FailLoadingNonUTF8Scripts) {
+  FilePath install_dir;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
+  install_dir = install_dir.AppendASCII("extensions")
+      .AppendASCII("bad")
+      .AppendASCII("bad_encoding");
+
+  std::string error;
+  scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
+      install_dir, Extension::LOAD, Extension::NO_FLAGS, &error));
+  ASSERT_TRUE(extension == NULL);
+  ASSERT_STREQ("Could not load file 'bad_encoding.js' for content script. "
+               "It isn't UTF-8 encoded.", error.c_str());
+}
+
+TEST(ExtensionFileUtil, ExtensionURLToRelativeFilePath) {
+#define URL_PREFIX "chrome-extension://extension-id/"
+  struct TestCase {
+    const char* url;
+    const char* expected_relative_path;
+  } test_cases[] = {
+    { URL_PREFIX "simple.html",
+      "simple.html" },
+    { URL_PREFIX "directory/to/file.html",
+      "directory/to/file.html" },
+    { URL_PREFIX "escape%20spaces.html",
+      "escape spaces.html" },
+    { URL_PREFIX "%C3%9Cber.html",
+      "\xC3\x9C" "ber.html" },
+#if defined(OS_WIN)
+    { URL_PREFIX "C%3A/simple.html",
+      "" },
+#endif
+    { URL_PREFIX "////simple.html",
+      "simple.html" },
+    { URL_PREFIX "/simple.html",
+      "simple.html" },
+    { URL_PREFIX "\\simple.html",
+      "simple.html" },
+    { URL_PREFIX "\\\\foo\\simple.html",
+      "foo/simple.html" },
+  };
+#undef URL_PREFIX
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+    GURL url(test_cases[i].url);
+#if defined(OS_POSIX)
+    FilePath expected_path(test_cases[i].expected_relative_path);
+#elif defined(OS_WIN)
+    FilePath expected_path(UTF8ToWide(test_cases[i].expected_relative_path));
+#endif
+
+    FilePath actual_path =
+        extension_file_util::ExtensionURLToRelativeFilePath(url);
+    EXPECT_FALSE(actual_path.IsAbsolute()) <<
+      " For the path " << actual_path.value();
+    EXPECT_EQ(expected_path.value(), actual_path.value()) <<
+      " For the path " << url;
+  }
+}
+
+TEST(ExtensionFileUtil, ExtensionResourceURLToFilePath) {
+  // Setup filesystem for testing.
+  FilePath root_path;
+  ASSERT_TRUE(file_util::CreateNewTempDirectory(
+        FILE_PATH_LITERAL(""), &root_path));
+  ASSERT_TRUE(file_util::AbsolutePath(&root_path));
+
+  FilePath api_path = root_path.Append(FILE_PATH_LITERAL("apiname"));
+  ASSERT_TRUE(file_util::CreateDirectory(api_path));
+
+  const char data[] = "Test Data";
+  FilePath resource_path = api_path.Append(FILE_PATH_LITERAL("test.js"));
+  ASSERT_TRUE(file_util::WriteFile(resource_path, data, sizeof(data)));
+  resource_path = api_path.Append(FILE_PATH_LITERAL("escape spaces.js"));
+  ASSERT_TRUE(file_util::WriteFile(resource_path, data, sizeof(data)));
+
+#ifdef FILE_PATH_USES_WIN_SEPARATORS
+#define SEP "\\"
+#else
+#define SEP "/"
+#endif
+#define URL_PREFIX "chrome-extension-resource://"
+  struct TestCase {
+    const char* url;
+    const FilePath::CharType* expected_path;
+  } test_cases[] = {
+    { URL_PREFIX "apiname/test.js",
+      FILE_PATH_LITERAL("test.js") },
+    { URL_PREFIX "/apiname/test.js",
+      FILE_PATH_LITERAL("test.js") },
+    // Test % escape
+    { URL_PREFIX "apiname/%74%65st.js",
+      FILE_PATH_LITERAL("test.js") },
+    { URL_PREFIX "apiname/escape%20spaces.js",
+      FILE_PATH_LITERAL("escape spaces.js") },
+    // Test file does not exist.
+    { URL_PREFIX "apiname/directory/to/file.js",
+      NULL },
+    // Test apiname/../../test.js
+    { URL_PREFIX "apiname/../../test.js",
+      FILE_PATH_LITERAL("test.js") },
+    { URL_PREFIX "apiname/..%2F../test.js",
+      NULL },
+    { URL_PREFIX "apiname/f/../../../test.js",
+      FILE_PATH_LITERAL("test.js") },
+    { URL_PREFIX "apiname/f%2F..%2F..%2F../test.js",
+      NULL },
+  };
+#undef SEP
+#undef URL_PREFIX
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+    GURL url(test_cases[i].url);
+    FilePath expected_path;
+    if (test_cases[i].expected_path)
+      expected_path = root_path.Append(FILE_PATH_LITERAL("apiname")).Append(
+          test_cases[i].expected_path);
+    FilePath actual_path =
+        extension_file_util::ExtensionResourceURLToFilePath(url, root_path);
+    EXPECT_EQ(expected_path.value(), actual_path.value()) <<
+      " For the path " << url;
+  }
+  // Remove temp files.
+  ASSERT_TRUE(file_util::Delete(root_path, true));
+}
+
+static scoped_refptr<Extension> LoadExtensionManifest(
+    DictionaryValue* manifest,
+    const FilePath& manifest_dir,
+    Extension::Location location,
+    int extra_flags,
+    std::string* error) {
+  scoped_refptr<Extension> extension = Extension::Create(
+      manifest_dir, location, *manifest, extra_flags, error);
+  return extension;
+}
+
+static scoped_refptr<Extension> LoadExtensionManifest(
+    const std::string& manifest_value,
+    const FilePath& manifest_dir,
+    Extension::Location location,
+    int extra_flags,
+    std::string* error) {
+  JSONStringValueSerializer serializer(manifest_value);
+  scoped_ptr<Value> result(serializer.Deserialize(NULL, error));
+  if (!result.get())
+    return NULL;
+  CHECK_EQ(Value::TYPE_DICTIONARY, result->GetType());
+  return LoadExtensionManifest(static_cast<DictionaryValue*>(result.get()),
+                               manifest_dir,
+                               location,
+                               extra_flags,
+                               error);
+}
+
+#if defined(OS_WIN)
+// http://crbug.com/108279
+#define ValidateThemeUTF8 DISABLED_ValidateThemeUTF8
+#endif
+TEST(ExtensionFileUtil, ValidateThemeUTF8) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  // "aeo" with accents. Use http://0xcc.net/jsescape/ to decode them.
+  std::string non_ascii_file = "\xC3\xA0\xC3\xA8\xC3\xB2.png";
+  FilePath non_ascii_path = temp.path().Append(FilePath::FromUTF8Unsafe(
+      non_ascii_file));
+  file_util::WriteFile(non_ascii_path, "", 0);
+
+  std::string kManifest =
+      base::StringPrintf(
+          "{ \"name\": \"Test\", \"version\": \"1.0\", "
+          "  \"theme\": { \"images\": { \"theme_frame\": \"%s\" } }"
+          "}", non_ascii_file.c_str());
+  std::string error;
+  scoped_refptr<Extension> extension = LoadExtensionManifest(
+      kManifest, temp.path(), Extension::LOAD, 0, &error);
+  ASSERT_TRUE(extension.get()) << error;
+
+  Extension::InstallWarningVector warnings;
+  EXPECT_TRUE(extension_file_util::ValidateExtension(extension,
+                                                     &error, &warnings)) <<
+      error;
+  EXPECT_EQ(0U, warnings.size());
+}
+
+#if defined(OS_WIN)
+// This test hangs on Windows sometimes. http://crbug.com/110279
+#define MAYBE_BackgroundScriptsMustExist DISABLED_BackgroundScriptsMustExist
+#else
+#define MAYBE_BackgroundScriptsMustExist BackgroundScriptsMustExist
+#endif
+TEST(ExtensionFileUtil, MAYBE_BackgroundScriptsMustExist) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  scoped_ptr<DictionaryValue> value(new DictionaryValue());
+  value->SetString("name", "test");
+  value->SetString("version", "1");
+  value->SetInteger("manifest_version", 1);
+
+  ListValue* scripts = new ListValue();
+  scripts->Append(Value::CreateStringValue("foo.js"));
+  value->Set("background.scripts", scripts);
+
+  std::string error;
+  Extension::InstallWarningVector warnings;
+  scoped_refptr<Extension> extension = LoadExtensionManifest(
+      value.get(), temp.path(), Extension::LOAD, 0, &error);
+  ASSERT_TRUE(extension.get()) << error;
+
+  EXPECT_FALSE(extension_file_util::ValidateExtension(extension,
+                                                      &error, &warnings));
+  EXPECT_EQ(l10n_util::GetStringFUTF8(
+      IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED, ASCIIToUTF16("foo.js")),
+           error);
+  EXPECT_EQ(0U, warnings.size());
+
+  scripts->Clear();
+  scripts->Append(Value::CreateStringValue("http://google.com/foo.js"));
+
+  extension = LoadExtensionManifest(value.get(), temp.path(), Extension::LOAD,
+                                    0, &error);
+  ASSERT_TRUE(extension.get()) << error;
+
+  warnings.clear();
+  EXPECT_FALSE(extension_file_util::ValidateExtension(extension,
+                                                      &error, &warnings));
+  EXPECT_EQ(l10n_util::GetStringFUTF8(
+      IDS_EXTENSION_LOAD_BACKGROUND_SCRIPT_FAILED,
+      ASCIIToUTF16("http://google.com/foo.js")),
+           error);
+  EXPECT_EQ(0U, warnings.size());
+}
+
+// Private key, generated by Chrome specifically for this test, and
+// never used elsewhere.
+const char private_key[] =
+    "-----BEGIN PRIVATE KEY-----\n"
+    "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKt02SR0FYaYy6fpW\n"
+    "MAA+kU1BgK3d+OmmWfdr+JATIjhRkyeSF4lTd/71JQsyKqPzYkQPi3EeROWM+goTv\n"
+    "EhJqq07q63BolpsFmlV+S4ny+sBA2B4aWwRYXlBWikdrQSA0mJMzvEHc6nKzBgXik\n"
+    "QSVbyyBNAsxlDB9WaCxRVOpK3AgMBAAECgYBGvSPlrVtAOAQ2V8j9FqorKZA8SLPX\n"
+    "IeJC/yzU3RB2nPMjI17aMOvrUHxJUhzMeh4jwabVvSzzDtKFozPGupW3xaI8sQdi2\n"
+    "WWMTQIk/Q9HHDWoQ9qA6SwX2qWCc5SyjCKqVp78ye+000kqTJYjBsDgXeAlzKcx2B\n"
+    "4GAAeWonDdkQJBANNb8wrqNWFn7DqyQTfELzcRTRnqQ/r1pdeJo6obzbnwGnlqe3t\n"
+    "KhLjtJNIGrQg5iC0OVLWFuvPJs0t3z62A1ckCQQDPq2JZuwTwu5Pl4DJ0r9O1FdqN\n"
+    "JgqPZyMptokCDQ3khLLGakIu+TqB9YtrzI69rJMSG2Egb+6McaDX+dh3XmR/AkB9t\n"
+    "xJf6qDnmA2td/tMtTc0NOk8Qdg/fD8xbZ/YfYMnVoYYs9pQoilBaWRePDRNURMLYZ\n"
+    "vHAI0Llmw7tj7jv17pAkEAz44uXRpjRKtllUIvi5pUENAHwDz+HvdpGH68jpU3hmb\n"
+    "uOwrmnQYxaMReFV68Z2w9DcLZn07f7/R9Wn72z89CxwJAFsDoNaDes4h48bX7plct\n"
+    "s9ACjmTwcCigZjN2K7AGv7ntCLF3DnV5dK0dTHNaAdD3SbY3jl29Rk2CwiURSX6Ee\n"
+    "g==\n"
+    "-----END PRIVATE KEY-----\n";
+
+TEST(ExtensionFileUtil, FindPrivateKeyFiles) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  FilePath src_path = temp.path().AppendASCII("some_dir");
+  ASSERT_TRUE(file_util::CreateDirectory(src_path));
+
+  ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("a_key.pem"),
+                                   private_key, arraysize(private_key)));
+  ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("second_key.pem"),
+                                   private_key, arraysize(private_key)));
+  // Shouldn't find a key with a different extension.
+  ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("key.diff_ext"),
+                                   private_key, arraysize(private_key)));
+  // Shouldn't find a key that isn't parsable.
+  ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("unparsable_key.pem"),
+                                   private_key, arraysize(private_key) - 30));
+  std::vector<FilePath> private_keys =
+      extension_file_util::FindPrivateKeyFiles(temp.path());
+  EXPECT_EQ(2U, private_keys.size());
+  EXPECT_THAT(private_keys,
+              testing::Contains(src_path.AppendASCII("a_key.pem")));
+  EXPECT_THAT(private_keys,
+              testing::Contains(src_path.AppendASCII("second_key.pem")));
+}
+
+TEST(ExtensionFileUtil, WarnOnPrivateKey) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  FilePath ext_path = temp.path().AppendASCII("ext_root");
+  ASSERT_TRUE(file_util::CreateDirectory(ext_path));
+
+  const char manifest[] =
+      "{\n"
+      "  \"name\": \"Test Extension\",\n"
+      "  \"version\": \"1.0\",\n"
+      "  \"manifest_version\": 2,\n"
+      "  \"description\": \"The first extension that I made.\"\n"
+      "}\n";
+  ASSERT_TRUE(file_util::WriteFile(ext_path.AppendASCII("manifest.json"),
+                                   manifest, strlen(manifest)));
+  ASSERT_TRUE(file_util::WriteFile(ext_path.AppendASCII("a_key.pem"),
+                                   private_key, strlen(private_key)));
+
+  std::string error;
+  scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
+      ext_path, "the_id", Extension::EXTERNAL_PREF,
+      Extension::NO_FLAGS, &error));
+  ASSERT_TRUE(extension.get()) << error;
+  ASSERT_EQ(1u, extension->install_warnings().size());
+  EXPECT_THAT(
+      extension->install_warnings(),
+      testing::ElementsAre(
+          testing::Field(
+              &Extension::InstallWarning::message,
+              testing::ContainsRegex(
+                  "extension includes the key file.*ext_root.a_key.pem"))));
+
+  // Turn the warning into an error with ERROR_ON_PRIVATE_KEY.
+  extension = extension_file_util::LoadExtension(
+      ext_path, "the_id", Extension::EXTERNAL_PREF,
+      Extension::ERROR_ON_PRIVATE_KEY, &error);
+  EXPECT_FALSE(extension.get());
+  EXPECT_THAT(error,
+              testing::ContainsRegex(
+                  "extension includes the key file.*ext_root.a_key.pem"));
+}
+
+TEST(ExtensionFileUtil, CheckZeroLengthImageFile) {
+  FilePath install_dir;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
+
+  // Try to install an extension with a zero-length icon file.
+  FilePath ext_dir = install_dir.AppendASCII("extensions")
+      .AppendASCII("bad")
+      .AppendASCII("Extensions")
+      .AppendASCII("ffffffffffffffffffffffffffffffff");
+
+  std::string error;
+  scoped_refptr<Extension> extension(extension_file_util::LoadExtension(
+      ext_dir, Extension::LOAD, Extension::NO_FLAGS, &error));
+  ASSERT_TRUE(extension == NULL);
+  ASSERT_STREQ("Could not load extension icon 'icon.png'.",
+      error.c_str());
+
+  // Try to install an extension with a zero-length browser action icon file.
+  ext_dir = install_dir.AppendASCII("extensions")
+      .AppendASCII("bad")
+      .AppendASCII("Extensions")
+      .AppendASCII("gggggggggggggggggggggggggggggggg");
+
+  scoped_refptr<Extension> extension2(extension_file_util::LoadExtension(
+      ext_dir, Extension::LOAD, Extension::NO_FLAGS, &error));
+  ASSERT_TRUE(extension2 == NULL);
+  ASSERT_STREQ("Could not load icon 'icon.png' for browser action.",
+      error.c_str());
+
+  // Try to install an extension with a zero-length page action icon file.
+  ext_dir = install_dir.AppendASCII("extensions")
+      .AppendASCII("bad")
+      .AppendASCII("Extensions")
+      .AppendASCII("hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh");
+
+  scoped_refptr<Extension> extension3(extension_file_util::LoadExtension(
+      ext_dir, Extension::LOAD, Extension::NO_FLAGS, &error));
+  ASSERT_TRUE(extension3 == NULL);
+  ASSERT_STREQ("Could not load icon 'icon.png' for page action.",
+      error.c_str());
+}
+
+// TODO(aa): More tests as motivation allows. Maybe steal some from
+// ExtensionService? Many of them could probably be tested here without the
+// MessageLoop shenanigans.
diff --git a/chrome/common/extensions/extension_icon_set.cc b/chrome/common/extensions/extension_icon_set.cc
new file mode 100644
index 0000000..bb8c763
--- /dev/null
+++ b/chrome/common/extensions/extension_icon_set.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_icon_set.h"
+
+#include "base/logging.h"
+
+ExtensionIconSet::ExtensionIconSet() {}
+
+ExtensionIconSet::~ExtensionIconSet() {}
+
+void ExtensionIconSet::Clear() {
+  map_.clear();
+}
+
+void ExtensionIconSet::Add(int size, const std::string& path) {
+  DCHECK(!path.empty() && path[0] != '/');
+  map_[size] = path;
+}
+
+std::string ExtensionIconSet::Get(int size, MatchType match_type) const {
+  // The searches for MATCH_BIGGER and MATCH_SMALLER below rely on the fact that
+  // std::map is sorted. This is per the spec, so it should be safe to rely on.
+  if (match_type == MATCH_EXACTLY) {
+    IconMap::const_iterator result = map_.find(size);
+    return result == map_.end() ? std::string() : result->second;
+  } else if (match_type == MATCH_SMALLER) {
+    IconMap::const_reverse_iterator result = map_.rend();
+    for (IconMap::const_reverse_iterator iter = map_.rbegin();
+         iter != map_.rend(); ++iter) {
+      if (iter->first <= size) {
+        result = iter;
+        break;
+      }
+    }
+    return result == map_.rend() ? std::string() : result->second;
+  } else {
+    DCHECK(match_type == MATCH_BIGGER);
+    IconMap::const_iterator result = map_.end();
+    for (IconMap::const_iterator iter = map_.begin(); iter != map_.end();
+         ++iter) {
+      if (iter->first >= size) {
+        result = iter;
+        break;
+      }
+    }
+    return result == map_.end() ? std::string() : result->second;
+  }
+}
+
+bool ExtensionIconSet::ContainsPath(const std::string& path) const {
+  return GetIconSizeFromPath(path) != 0;
+}
+
+int ExtensionIconSet::GetIconSizeFromPath(const std::string& path) const {
+  if (path.empty())
+    return 0;
+
+  DCHECK(path[0] != '/') <<
+      "ExtensionIconSet stores icon paths without leading slash.";
+
+  for (IconMap::const_iterator iter = map_.begin(); iter != map_.end();
+       ++iter) {
+    if (iter->second == path)
+      return iter->first;
+  }
+
+  return 0;
+}
diff --git a/chrome/common/extensions/extension_icon_set.h b/chrome/common/extensions/extension_icon_set.h
new file mode 100644
index 0000000..487099e
--- /dev/null
+++ b/chrome/common/extensions/extension_icon_set.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_ICON_SET_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_ICON_SET_H_
+
+#include <map>
+#include <string>
+
+// Represents the set of icons for an extension.
+class ExtensionIconSet {
+ public:
+  // Get an icon from the set, optionally falling back to a smaller or bigger
+  // size. MatchType is exclusive (do not OR them together).
+  enum MatchType {
+    MATCH_EXACTLY,
+    MATCH_BIGGER,
+    MATCH_SMALLER
+  };
+
+  // Access to the underlying map from icon size->{path, bitmap}.
+  typedef std::map<int, std::string> IconMap;
+
+  ExtensionIconSet();
+  ~ExtensionIconSet();
+
+  const IconMap& map() const { return map_; }
+  bool empty() const { return map_.empty(); }
+
+  // Remove all icons from the set.
+  void Clear();
+
+  // Add an icon path to the set. If a path for the specified size is already
+  // present, it is overwritten.
+  void Add(int size, const std::string& path);
+
+  // Gets path value of the icon found when searching for |size| using
+  // |mathc_type|.
+  std::string Get(int size, MatchType match_type) const;
+
+  // Returns true iff the set contains the specified path.
+  bool ContainsPath(const std::string& path) const;
+
+  // Returns icon size if the set contains the specified path or 0 if not found.
+  int GetIconSizeFromPath(const std::string& path) const;
+
+ private:
+  IconMap map_;
+};
+
+#endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_ICON_SET_H_
diff --git a/chrome/common/extensions/extension_icon_set_unittest.cc b/chrome/common/extensions/extension_icon_set_unittest.cc
new file mode 100644
index 0000000..1202cda
--- /dev/null
+++ b/chrome/common/extensions/extension_icon_set_unittest.cc
@@ -0,0 +1,93 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_icon_set.h"
+
+#include "chrome/common/extensions/extension_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+TEST(ExtensionIconSet, Basic) {
+  ExtensionIconSet icons;
+  EXPECT_EQ("", icons.Get(extension_misc::EXTENSION_ICON_LARGE,
+      ExtensionIconSet::MATCH_EXACTLY));
+  EXPECT_EQ("", icons.Get(extension_misc::EXTENSION_ICON_LARGE,
+      ExtensionIconSet::MATCH_BIGGER));
+  EXPECT_EQ("", icons.Get(extension_misc::EXTENSION_ICON_LARGE,
+      ExtensionIconSet::MATCH_SMALLER));
+  EXPECT_TRUE(icons.map().empty());
+
+  icons.Add(extension_misc::EXTENSION_ICON_LARGE, "large.png");
+  EXPECT_EQ("large.png", icons.Get(extension_misc::EXTENSION_ICON_LARGE,
+                                   ExtensionIconSet::MATCH_EXACTLY));
+  EXPECT_EQ("large.png", icons.Get(extension_misc::EXTENSION_ICON_LARGE,
+                                   ExtensionIconSet::MATCH_BIGGER));
+  EXPECT_EQ("large.png", icons.Get(extension_misc::EXTENSION_ICON_LARGE,
+                                   ExtensionIconSet::MATCH_SMALLER));
+  EXPECT_EQ("large.png", icons.Get(extension_misc::EXTENSION_ICON_MEDIUM,
+                                   ExtensionIconSet::MATCH_BIGGER));
+  EXPECT_EQ("large.png", icons.Get(extension_misc::EXTENSION_ICON_EXTRA_LARGE,
+                                   ExtensionIconSet::MATCH_SMALLER));
+  EXPECT_EQ("large.png", icons.Get(0, ExtensionIconSet::MATCH_BIGGER));
+  EXPECT_EQ("", icons.Get(extension_misc::EXTENSION_ICON_MEDIUM,
+                          ExtensionIconSet::MATCH_SMALLER));
+  EXPECT_EQ("", icons.Get(extension_misc::EXTENSION_ICON_EXTRA_LARGE,
+                          ExtensionIconSet::MATCH_BIGGER));
+
+  icons.Add(extension_misc::EXTENSION_ICON_SMALLISH, "smallish.png");
+  icons.Add(extension_misc::EXTENSION_ICON_SMALL, "small.png");
+  icons.Add(extension_misc::EXTENSION_ICON_EXTRA_LARGE, "extra_large.png");
+
+  EXPECT_EQ("", icons.Get(extension_misc::EXTENSION_ICON_MEDIUM,
+                          ExtensionIconSet::MATCH_EXACTLY));
+  EXPECT_EQ("small.png", icons.Get(extension_misc::EXTENSION_ICON_MEDIUM,
+                                   ExtensionIconSet::MATCH_SMALLER));
+  EXPECT_EQ("large.png", icons.Get(extension_misc::EXTENSION_ICON_MEDIUM,
+                                   ExtensionIconSet::MATCH_BIGGER));
+  EXPECT_EQ("", icons.Get(extension_misc::EXTENSION_ICON_BITTY,
+                          ExtensionIconSet::MATCH_SMALLER));
+  EXPECT_EQ("", icons.Get(extension_misc::EXTENSION_ICON_GIGANTOR,
+                          ExtensionIconSet::MATCH_BIGGER));
+}
+
+TEST(ExtensionIconSet, Values) {
+  ExtensionIconSet icons;
+  EXPECT_FALSE(icons.ContainsPath("foo"));
+
+  icons.Add(extension_misc::EXTENSION_ICON_BITTY, "foo");
+  icons.Add(extension_misc::EXTENSION_ICON_GIGANTOR, "bar");
+
+  EXPECT_TRUE(icons.ContainsPath("foo"));
+  EXPECT_TRUE(icons.ContainsPath("bar"));
+  EXPECT_FALSE(icons.ContainsPath("baz"));
+  EXPECT_FALSE(icons.ContainsPath(""));
+
+  icons.Clear();
+  EXPECT_FALSE(icons.ContainsPath("foo"));
+}
+
+TEST(ExtensionIconSet, FindSize) {
+  ExtensionIconSet icons;
+  EXPECT_EQ(extension_misc::EXTENSION_ICON_INVALID,
+            icons.GetIconSizeFromPath("foo"));
+
+  icons.Add(extension_misc::EXTENSION_ICON_BITTY, "foo");
+  icons.Add(extension_misc::EXTENSION_ICON_GIGANTOR, "bar");
+
+  EXPECT_EQ(extension_misc::EXTENSION_ICON_BITTY,
+            icons.GetIconSizeFromPath("foo"));
+  EXPECT_EQ(extension_misc::EXTENSION_ICON_GIGANTOR,
+            icons.GetIconSizeFromPath("bar"));
+  EXPECT_EQ(extension_misc::EXTENSION_ICON_INVALID,
+            icons.GetIconSizeFromPath("baz"));
+  EXPECT_EQ(extension_misc::EXTENSION_ICON_INVALID,
+            icons.GetIconSizeFromPath(""));
+
+  icons.Clear();
+  EXPECT_EQ(extension_misc::EXTENSION_ICON_INVALID,
+            icons.GetIconSizeFromPath("foo"));
+}
+
+}  // namespace
diff --git a/chrome/common/extensions/extension_l10n_util.cc b/chrome/common/extensions/extension_l10n_util.cc
new file mode 100644
index 0000000..22c6ddf
--- /dev/null
+++ b/chrome/common/extensions/extension_l10n_util.cc
@@ -0,0 +1,369 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_l10n_util.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/logging.h"
+#include "base/memory/linked_ptr.h"
+#include "base/stringprintf.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/extension_file_util.h"
+#include "chrome/common/extensions/message_bundle.h"
+#include "chrome/common/url_constants.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "unicode/uloc.h"
+
+namespace errors = extension_manifest_errors;
+namespace keys = extension_manifest_keys;
+
+static std::string& GetProcessLocale() {
+  CR_DEFINE_STATIC_LOCAL(std::string, locale, ());
+  return locale;
+}
+
+namespace extension_l10n_util {
+
+void SetProcessLocale(const std::string& locale) {
+  GetProcessLocale() = locale;
+}
+
+std::string GetDefaultLocaleFromManifest(const DictionaryValue& manifest,
+                                         std::string* error) {
+  std::string default_locale;
+  if (manifest.GetString(keys::kDefaultLocale, &default_locale))
+    return default_locale;
+
+  *error = errors::kInvalidDefaultLocale;
+  return "";
+
+}
+
+bool ShouldRelocalizeManifest(const extensions::ExtensionInfo& info) {
+  DictionaryValue* manifest = info.extension_manifest.get();
+  if (!manifest)
+    return false;
+
+  if (!manifest->HasKey(keys::kDefaultLocale))
+    return false;
+
+  std::string manifest_current_locale;
+  manifest->GetString(keys::kCurrentLocale, &manifest_current_locale);
+  return manifest_current_locale != CurrentLocaleOrDefault();
+}
+
+// Localizes manifest value for a given key.
+static bool LocalizeManifestValue(const std::string& key,
+                                  const extensions::MessageBundle& messages,
+                                  DictionaryValue* manifest,
+                                  std::string* error) {
+  std::string result;
+  if (!manifest->GetString(key, &result))
+    return true;
+
+  if (!messages.ReplaceMessages(&result, error))
+    return false;
+
+  manifest->SetString(key, result);
+  return true;
+}
+
+bool LocalizeManifest(const extensions::MessageBundle& messages,
+                      DictionaryValue* manifest,
+                      std::string* error) {
+  // Initialize name.
+  std::string result;
+  if (!manifest->GetString(keys::kName, &result)) {
+    *error = errors::kInvalidName;
+    return false;
+  }
+  if (!LocalizeManifestValue(keys::kName, messages, manifest, error)) {
+    return false;
+  }
+
+  // Initialize description.
+  if (!LocalizeManifestValue(keys::kDescription, messages, manifest, error))
+    return false;
+
+  // Initialize browser_action.default_title
+  std::string key(keys::kBrowserAction);
+  key.append(".");
+  key.append(keys::kPageActionDefaultTitle);
+  if (!LocalizeManifestValue(key, messages, manifest, error))
+    return false;
+
+  // Initialize page_action.default_title
+  key.assign(keys::kPageAction);
+  key.append(".");
+  key.append(keys::kPageActionDefaultTitle);
+  if (!LocalizeManifestValue(key, messages, manifest, error))
+    return false;
+
+  // Initialize omnibox.keyword.
+  if (!LocalizeManifestValue(keys::kOmniboxKeyword, messages, manifest, error))
+    return false;
+
+  ListValue* file_handlers = NULL;
+  if (manifest->GetList(keys::kFileBrowserHandlers, &file_handlers)) {
+    key.assign(keys::kFileBrowserHandlers);
+    for (size_t i = 0; i < file_handlers->GetSize(); i++) {
+      DictionaryValue* handler = NULL;
+      if (!file_handlers->GetDictionary(i, &handler)) {
+        *error = errors::kInvalidFileBrowserHandler;
+        return false;
+      }
+      if (!LocalizeManifestValue(keys::kPageActionDefaultTitle, messages,
+                                 handler, error))
+        return false;
+    }
+  }
+
+  // Initialize all intents.
+  DictionaryValue* intents = NULL;
+  if (manifest->GetDictionary(keys::kIntents, &intents)) {
+    DictionaryValue::key_iterator it = intents->begin_keys();
+    for ( ; it != intents->end_keys(); ++it) {
+      ListValue* actions = NULL;
+      DictionaryValue* action = NULL;
+
+      // Actions have either a dict or a list of dicts - handle both cases.
+      if (intents->GetListWithoutPathExpansion(*it, &actions)) {
+        for (size_t i = 0; i < actions->GetSize(); ++i) {
+          action = NULL;
+          if (actions->GetDictionary(i, &action)) {
+            if (!LocalizeManifestValue(keys::kIntentTitle, messages,
+                                       action, error))
+              return false;
+          }
+        }
+      } else if (intents->GetDictionaryWithoutPathExpansion(*it, &action)) {
+        if (!LocalizeManifestValue(keys::kIntentTitle, messages,
+                                   action, error))
+          return false;
+      }
+    }
+  }
+
+  // Initialize app.launch.local_path.
+  if (!LocalizeManifestValue(keys::kLaunchLocalPath, messages, manifest, error))
+    return false;
+
+  // Initialize app.launch.web_url.
+  if (!LocalizeManifestValue(keys::kLaunchWebURL, messages, manifest, error))
+    return false;
+
+  // Add current locale key to the manifest, so we can overwrite prefs
+  // with new manifest when chrome locale changes.
+  manifest->SetString(keys::kCurrentLocale, CurrentLocaleOrDefault());
+  return true;
+}
+
+bool LocalizeExtension(const FilePath& extension_path,
+                       DictionaryValue* manifest,
+                       std::string* error) {
+  DCHECK(manifest);
+
+  std::string default_locale = GetDefaultLocaleFromManifest(*manifest, error);
+
+  scoped_ptr<extensions::MessageBundle> message_bundle(
+      extension_file_util::LoadMessageBundle(
+          extension_path, default_locale, error));
+
+  if (!message_bundle.get() && !error->empty())
+    return false;
+
+  if (message_bundle.get() &&
+      !LocalizeManifest(*message_bundle, manifest, error))
+    return false;
+
+  return true;
+}
+
+bool AddLocale(const std::set<std::string>& chrome_locales,
+               const FilePath& locale_folder,
+               const std::string& locale_name,
+               std::set<std::string>* valid_locales,
+               std::string* error) {
+  // Accept name that starts with a . but don't add it to the list of supported
+  // locales.
+  if (locale_name.find(".") == 0)
+    return true;
+  if (chrome_locales.find(locale_name) == chrome_locales.end()) {
+    // Warn if there is an extension locale that's not in the Chrome list,
+    // but don't fail.
+    DLOG(WARNING) << base::StringPrintf("Supplied locale %s is not supported.",
+                                        locale_name.c_str());
+    return true;
+  }
+  // Check if messages file is actually present (but don't check content).
+  if (file_util::PathExists(
+      locale_folder.Append(extensions::Extension::kMessagesFilename))) {
+    valid_locales->insert(locale_name);
+  } else {
+    *error = base::StringPrintf("Catalog file is missing for locale %s.",
+                                locale_name.c_str());
+    return false;
+  }
+
+  return true;
+}
+
+std::string CurrentLocaleOrDefault() {
+  std::string current_locale = l10n_util::NormalizeLocale(GetProcessLocale());
+  if (current_locale.empty())
+    current_locale = "en";
+
+  return current_locale;
+}
+
+void GetAllLocales(std::set<std::string>* all_locales) {
+  const std::vector<std::string>& available_locales =
+      l10n_util::GetAvailableLocales();
+  // Add all parents of the current locale to the available locales set.
+  // I.e. for sr_Cyrl_RS we add sr_Cyrl_RS, sr_Cyrl and sr.
+  for (size_t i = 0; i < available_locales.size(); ++i) {
+    std::vector<std::string> result;
+    l10n_util::GetParentLocales(available_locales[i], &result);
+    all_locales->insert(result.begin(), result.end());
+  }
+}
+
+void GetAllFallbackLocales(const std::string& application_locale,
+                           const std::string& default_locale,
+                           std::vector<std::string>* all_fallback_locales) {
+  DCHECK(all_fallback_locales);
+  if (!application_locale.empty() && application_locale != default_locale)
+    l10n_util::GetParentLocales(application_locale, all_fallback_locales);
+  all_fallback_locales->push_back(default_locale);
+}
+
+bool GetValidLocales(const FilePath& locale_path,
+                     std::set<std::string>* valid_locales,
+                     std::string* error) {
+  std::set<std::string> chrome_locales;
+  GetAllLocales(&chrome_locales);
+
+  // Enumerate all supplied locales in the extension.
+  file_util::FileEnumerator locales(locale_path,
+                                    false,
+                                    file_util::FileEnumerator::DIRECTORIES);
+  FilePath locale_folder;
+  while (!(locale_folder = locales.Next()).empty()) {
+    std::string locale_name = locale_folder.BaseName().MaybeAsASCII();
+    if (locale_name.empty()) {
+      NOTREACHED();
+      continue;  // Not ASCII.
+    }
+    if (!AddLocale(chrome_locales,
+                   locale_folder,
+                   locale_name,
+                   valid_locales,
+                   error)) {
+      return false;
+    }
+  }
+
+  if (valid_locales->empty()) {
+    *error = extension_manifest_errors::kLocalesNoValidLocaleNamesListed;
+    return false;
+  }
+
+  return true;
+}
+
+// Loads contents of the messages file for given locale. If file is not found,
+// or there was parsing error we return NULL and set |error|.
+// Caller owns the returned object.
+static DictionaryValue* LoadMessageFile(const FilePath& locale_path,
+                                        const std::string& locale,
+                                        std::string* error) {
+  std::string extension_locale = locale;
+  FilePath file = locale_path.AppendASCII(extension_locale)
+      .Append(extensions::Extension::kMessagesFilename);
+  JSONFileValueSerializer messages_serializer(file);
+  Value *dictionary = messages_serializer.Deserialize(NULL, error);
+  if (!dictionary && error->empty()) {
+    // JSONFileValueSerializer just returns NULL if file cannot be found. It
+    // doesn't set the error, so we have to do it.
+    *error = base::StringPrintf("Catalog file is missing for locale %s.",
+                                extension_locale.c_str());
+  }
+
+  return static_cast<DictionaryValue*>(dictionary);
+}
+
+extensions::MessageBundle* LoadMessageCatalogs(
+    const FilePath& locale_path,
+    const std::string& default_locale,
+    const std::string& application_locale,
+    const std::set<std::string>& valid_locales,
+    std::string* error) {
+  std::vector<std::string> all_fallback_locales;
+  GetAllFallbackLocales(application_locale, default_locale,
+      &all_fallback_locales);
+
+  std::vector<linked_ptr<DictionaryValue> > catalogs;
+  for (size_t i = 0; i < all_fallback_locales.size(); ++i) {
+    // Skip all parent locales that are not supplied.
+    if (valid_locales.find(all_fallback_locales[i]) == valid_locales.end())
+      continue;
+    linked_ptr<DictionaryValue> catalog(
+      LoadMessageFile(locale_path, all_fallback_locales[i], error));
+    if (!catalog.get()) {
+      // If locale is valid, but messages.json is corrupted or missing, return
+      // an error.
+      return NULL;
+    } else {
+      catalogs.push_back(catalog);
+    }
+  }
+
+  return extensions::MessageBundle::Create(catalogs, error);
+}
+
+bool ShouldSkipValidation(const FilePath& locales_path,
+                          const FilePath& locale_path,
+                          const std::set<std::string>& all_locales) {
+  // Since we use this string as a key in a DictionaryValue, be paranoid about
+  // skipping any strings with '.'. This happens sometimes, for example with
+  // '.svn' directories.
+  FilePath relative_path;
+  if (!locales_path.AppendRelativePath(locale_path, &relative_path)) {
+    NOTREACHED();
+    return true;
+  }
+  std::string subdir = relative_path.MaybeAsASCII();
+  if (subdir.empty())
+    return true;  // Non-ASCII.
+
+  if (std::find(subdir.begin(), subdir.end(), '.') != subdir.end())
+    return true;
+
+  if (all_locales.find(subdir) == all_locales.end())
+    return true;
+
+  return false;
+}
+
+ScopedLocaleForTest::ScopedLocaleForTest()
+    : locale_(extension_l10n_util::CurrentLocaleOrDefault()) {}
+
+ScopedLocaleForTest::ScopedLocaleForTest(const std::string& locale)
+    : locale_(extension_l10n_util::CurrentLocaleOrDefault()) {
+  extension_l10n_util::SetProcessLocale(locale);
+}
+
+ScopedLocaleForTest::~ScopedLocaleForTest() {
+  extension_l10n_util::SetProcessLocale(locale_);
+}
+
+}  // namespace extension_l10n_util
diff --git a/chrome/common/extensions/extension_l10n_util.h b/chrome/common/extensions/extension_l10n_util.h
new file mode 100644
index 0000000..24178a0
--- /dev/null
+++ b/chrome/common/extensions/extension_l10n_util.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2012 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.
+//
+// This file declares extension specific l10n utils.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_L10N_UTIL_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_L10N_UTIL_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+class FilePath;
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+struct ExtensionInfo;
+class MessageBundle;
+}
+
+namespace extension_l10n_util {
+
+// Set the locale for this process to a fixed value, rather than using the
+// normal file-based lookup mechanisms. This is used to set the locale inside
+// the sandboxed utility process, where file reading is not allowed.
+void SetProcessLocale(const std::string& locale);
+
+// Returns default locale in form "en-US" or "sr" or empty string if
+// "default_locale" section was not defined in the manifest.json file.
+std::string GetDefaultLocaleFromManifest(const base::DictionaryValue& manifest,
+                                         std::string* error);
+
+// Returns true iff the extension was localized, and the current locale
+// doesn't match the locale written into info.extension_manifest.
+bool ShouldRelocalizeManifest(const extensions::ExtensionInfo& info);
+
+// Localize extension name, description, browser_action and other fields
+// in the manifest.
+bool LocalizeManifest(const extensions::MessageBundle& messages,
+                      base::DictionaryValue* manifest,
+                      std::string* error);
+
+// Load message catalogs, localize manifest and attach message bundle to the
+// extension.
+bool LocalizeExtension(const FilePath& extension_path,
+                       base::DictionaryValue* manifest,
+                       std::string* error);
+
+// Adds locale_name to the extension if it's in chrome_locales, and
+// if messages file is present (we don't check content of messages file here).
+// Returns false if locale_name was not found in chrome_locales, and sets
+// error with locale_name.
+// If file name starts with . return true (helps testing extensions under svn).
+bool AddLocale(const std::set<std::string>& chrome_locales,
+               const FilePath& locale_folder,
+               const std::string& locale_name,
+               std::set<std::string>* valid_locales,
+               std::string* error);
+
+// Returns normalized current locale, or default locale - en_US.
+std::string CurrentLocaleOrDefault();
+
+// Extends list of Chrome locales to them and their parents, so we can do
+// proper fallback.
+void GetAllLocales(std::set<std::string>* all_locales);
+
+// Provides a vector of all fallback locales for message localization.
+// The vector is ordered by priority of locale - |application_locale|,
+// first_parent, ..., |default_locale|.
+void GetAllFallbackLocales(const std::string& application_locale,
+                           const std::string& default_locale,
+                           std::vector<std::string>* all_fallback_locales);
+
+// Adds valid locales to the extension.
+// 1. Do nothing if _locales directory is missing (not an error).
+// 2. Get list of Chrome locales.
+// 3. Enumerate all subdirectories of _locales directory.
+// 4. Intersect both lists, and add intersection to the extension.
+// Returns false if any of supplied locales don't match chrome list of locales.
+// Fills out error with offending locale name.
+bool GetValidLocales(const FilePath& locale_path,
+                     std::set<std::string>* locales,
+                     std::string* error);
+
+// Loads messages file for default locale, and application locales (application
+// locales doesn't have to exist). Application locale is current locale and its
+// parents.
+// Returns message bundle if it can load default locale messages file, and all
+// messages are valid, else returns NULL and sets error.
+extensions::MessageBundle* LoadMessageCatalogs(
+    const FilePath& locale_path,
+    const std::string& default_locale,
+    const std::string& app_locale,
+    const std::set<std::string>& valid_locales,
+    std::string* error);
+
+// Returns true if directory has "." in the name (for .svn) or if it doesn't
+// belong to Chrome locales.
+// |locales_path| is extension_id/_locales
+// |locale_path| is extension_id/_locales/xx
+// |all_locales| is a set of all valid Chrome locales.
+bool ShouldSkipValidation(const FilePath& locales_path,
+                          const FilePath& locale_path,
+                          const std::set<std::string>& all_locales);
+
+// Sets the process locale for the duration of the current scope, then reverts
+// back to whatever the current locale was before constructing this.
+// For testing purposed only!
+class ScopedLocaleForTest {
+ public:
+  // Only revert back to current locale at end of scope, don't set locale.
+  ScopedLocaleForTest();
+
+  // Set temporary locale for the current scope
+  explicit ScopedLocaleForTest(const std::string& locale);
+
+  ~ScopedLocaleForTest();
+
+ private:
+  std::string locale_;  // The current locale at ctor time.
+};
+
+
+}  // namespace extension_l10n_util
+
+#endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_L10N_UTIL_H_
diff --git a/chrome/common/extensions/extension_l10n_util_unittest.cc b/chrome/common/extensions/extension_l10n_util_unittest.cc
new file mode 100644
index 0000000..bee0478
--- /dev/null
+++ b/chrome/common/extensions/extension_l10n_util_unittest.cc
@@ -0,0 +1,630 @@
+// Copyright (c) 2012 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.
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/scoped_temp_dir.h"
+#include "base/values.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/extension_l10n_util.h"
+#include "chrome/common/extensions/message_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using extensions::Extension;
+using extensions::ExtensionInfo;
+using extensions::MessageBundle;
+
+namespace errors = extension_manifest_errors;
+namespace keys = extension_manifest_keys;
+
+namespace {
+
+// crbug.com/108429: Crashing on Windows Vista bots.
+#if defined(OS_WIN)
+#define GetValidLocalesEmptyLocaleFolder \
+  DISABLED_GetValidLocalesEmptyLocaleFolder
+#define GetValidLocalesWithValidLocaleNoMessagesFile \
+  DISABLED_GetValidLocalesWithValidLocaleNoMessagesFile
+#define GetValidLocalesWithUnsupportedLocale \
+  DISABLED_GetValidLocalesWithUnsupportedLocale
+#define GetValidLocalesWithValidLocalesAndMessagesFile \
+  DISABLED_GetValidLocalesWithValidLocalesAndMessagesFile
+#define LoadMessageCatalogsValidFallback \
+  DISABLED_LoadMessageCatalogsValidFallback
+#define LoadMessageCatalogsMissingFiles DISABLED_LoadMessageCatalogsMissingFiles
+#define LoadMessageCatalogsBadJSONFormat \
+  DISABLED_LoadMessageCatalogsBadJSONFormat
+#define LoadMessageCatalogsDuplicateKeys \
+  DISABLED_LoadMessageCatalogsDuplicateKeys
+#define LocalizeEmptyManifest DISABLED_LocalizeEmptyManifest
+#define LocalizeManifestWithoutNameMsgAndEmptyDescription \
+  DISABLED_LocalizeManifestWithoutNameMsgAndEmptyDescription
+#define LocalizeManifestWithNameMsgAndEmptyDescription \
+  DISABLED_LocalizeManifestWithNameMsgAndEmptyDescription
+#define LocalizeManifestWithBadNameMsg DISABLED_LocalizeManifestWithBadNameMsg
+#define LocalizeManifestWithNameDescriptionDefaultTitleMsgs \
+  DISABLED_LocalizeManifestWithNameDescriptionDefaultTitleMsgs
+#define LocalizeManifestWithNameDescriptionOmniboxMsgs \
+  DISABLED_LocalizeManifestWithNameDescriptionOmniboxMsgs
+#define LocalizeManifestWithNameDescriptionFileHandlerTitle \
+  DISABLED_LocalizeManifestWithNameDescriptionFileHandlerTitle
+#define ShouldRelocalizeManifestWithNullManifest \
+  DISABLED_ShouldRelocalizeManifestWithNullManifest
+#define ShouldRelocalizeManifestEmptyManifest \
+  DISABLED_ShouldRelocalizeManifestEmptyManifest
+#define ShouldRelocalizeManifestWithDefaultLocale \
+  DISABLED_ShouldRelocalizeManifestWithDefaultLocale
+#define ShouldRelocalizeManifestWithCurrentLocale \
+  DISABLED_ShouldRelocalizeManifestWithCurrentLocale
+#define ShouldRelocalizeManifestSameCurrentLocale \
+  DISABLED_ShouldRelocalizeManifestSameCurrentLocale
+#define ShouldRelocalizeManifestDifferentCurrentLocale \
+  DISABLED_ShouldRelocalizeManifestDifferentCurrentLocale
+#endif
+
+TEST(ExtensionL10nUtil, GetValidLocalesEmptyLocaleFolder) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  FilePath src_path = temp.path().Append(Extension::kLocaleFolder);
+  ASSERT_TRUE(file_util::CreateDirectory(src_path));
+
+  std::string error;
+  std::set<std::string> locales;
+  EXPECT_FALSE(extension_l10n_util::GetValidLocales(src_path,
+                                                    &locales,
+                                                    &error));
+
+  EXPECT_TRUE(locales.empty());
+}
+
+TEST(ExtensionL10nUtil, GetValidLocalesWithValidLocaleNoMessagesFile) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  FilePath src_path = temp.path().Append(Extension::kLocaleFolder);
+  ASSERT_TRUE(file_util::CreateDirectory(src_path));
+  ASSERT_TRUE(file_util::CreateDirectory(src_path.AppendASCII("sr")));
+
+  std::string error;
+  std::set<std::string> locales;
+  EXPECT_FALSE(extension_l10n_util::GetValidLocales(src_path,
+                                                    &locales,
+                                                    &error));
+
+  EXPECT_TRUE(locales.empty());
+}
+
+TEST(ExtensionL10nUtil, GetValidLocalesWithUnsupportedLocale) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  FilePath src_path = temp.path().Append(Extension::kLocaleFolder);
+  ASSERT_TRUE(file_util::CreateDirectory(src_path));
+  // Supported locale.
+  FilePath locale_1 = src_path.AppendASCII("sr");
+  ASSERT_TRUE(file_util::CreateDirectory(locale_1));
+  std::string data("whatever");
+  ASSERT_TRUE(file_util::WriteFile(
+      locale_1.Append(Extension::kMessagesFilename),
+      data.c_str(), data.length()));
+  // Unsupported locale.
+  ASSERT_TRUE(file_util::CreateDirectory(src_path.AppendASCII("xxx_yyy")));
+
+  std::string error;
+  std::set<std::string> locales;
+  EXPECT_TRUE(extension_l10n_util::GetValidLocales(src_path,
+                                                   &locales,
+                                                   &error));
+
+  EXPECT_FALSE(locales.empty());
+  EXPECT_TRUE(locales.find("sr") != locales.end());
+  EXPECT_FALSE(locales.find("xxx_yyy") != locales.end());
+}
+
+TEST(ExtensionL10nUtil, GetValidLocalesWithValidLocalesAndMessagesFile) {
+  FilePath install_dir;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
+  install_dir = install_dir.AppendASCII("extensions")
+      .AppendASCII("good")
+      .AppendASCII("Extensions")
+      .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
+      .AppendASCII("1.0.0.0")
+      .Append(Extension::kLocaleFolder);
+
+  std::string error;
+  std::set<std::string> locales;
+  EXPECT_TRUE(extension_l10n_util::GetValidLocales(install_dir,
+                                                   &locales,
+                                                   &error));
+  EXPECT_EQ(3U, locales.size());
+  EXPECT_TRUE(locales.find("sr") != locales.end());
+  EXPECT_TRUE(locales.find("en") != locales.end());
+  EXPECT_TRUE(locales.find("en_US") != locales.end());
+}
+
+TEST(ExtensionL10nUtil, LoadMessageCatalogsValidFallback) {
+  FilePath install_dir;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
+  install_dir = install_dir.AppendASCII("extensions")
+      .AppendASCII("good")
+      .AppendASCII("Extensions")
+      .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
+      .AppendASCII("1.0.0.0")
+      .Append(Extension::kLocaleFolder);
+
+  std::string error;
+  std::set<std::string> locales;
+  EXPECT_TRUE(extension_l10n_util::GetValidLocales(install_dir,
+                                                   &locales,
+                                                   &error));
+
+  scoped_ptr<MessageBundle> bundle(extension_l10n_util::LoadMessageCatalogs(
+      install_dir, "sr", "en_US", locales, &error));
+  ASSERT_FALSE(NULL == bundle.get());
+  EXPECT_TRUE(error.empty());
+  EXPECT_EQ("Color", bundle->GetL10nMessage("color"));
+  EXPECT_EQ("Not in the US or GB.", bundle->GetL10nMessage("not_in_US_or_GB"));
+}
+
+TEST(ExtensionL10nUtil, LoadMessageCatalogsMissingFiles) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  FilePath src_path = temp.path().Append(Extension::kLocaleFolder);
+  ASSERT_TRUE(file_util::CreateDirectory(src_path));
+
+  std::set<std::string> valid_locales;
+  valid_locales.insert("sr");
+  valid_locales.insert("en");
+  std::string error;
+  EXPECT_TRUE(NULL == extension_l10n_util::LoadMessageCatalogs(src_path,
+                                                               "en",
+                                                               "sr",
+                                                               valid_locales,
+                                                               &error));
+  EXPECT_FALSE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LoadMessageCatalogsBadJSONFormat) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  FilePath src_path = temp.path().Append(Extension::kLocaleFolder);
+  ASSERT_TRUE(file_util::CreateDirectory(src_path));
+
+  FilePath locale = src_path.AppendASCII("sr");
+  ASSERT_TRUE(file_util::CreateDirectory(locale));
+
+  std::string data = "{ \"name\":";
+  ASSERT_TRUE(
+      file_util::WriteFile(locale.Append(Extension::kMessagesFilename),
+                           data.c_str(), data.length()));
+
+  std::set<std::string> valid_locales;
+  valid_locales.insert("sr");
+  valid_locales.insert("en_US");
+  std::string error;
+  EXPECT_TRUE(NULL == extension_l10n_util::LoadMessageCatalogs(src_path,
+                                                               "en_US",
+                                                               "sr",
+                                                               valid_locales,
+                                                               &error));
+  EXPECT_EQ("Line: 1, column: 10, Unexpected token.", error);
+}
+
+TEST(ExtensionL10nUtil, LoadMessageCatalogsDuplicateKeys) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  FilePath src_path = temp.path().Append(Extension::kLocaleFolder);
+  ASSERT_TRUE(file_util::CreateDirectory(src_path));
+
+  FilePath locale_1 = src_path.AppendASCII("en");
+  ASSERT_TRUE(file_util::CreateDirectory(locale_1));
+
+  std::string data =
+    "{ \"name\": { \"message\": \"something\" }, "
+    "\"name\": { \"message\": \"something else\" } }";
+  ASSERT_TRUE(
+      file_util::WriteFile(locale_1.Append(Extension::kMessagesFilename),
+                           data.c_str(), data.length()));
+
+  FilePath locale_2 = src_path.AppendASCII("sr");
+  ASSERT_TRUE(file_util::CreateDirectory(locale_2));
+
+  ASSERT_TRUE(
+      file_util::WriteFile(locale_2.Append(Extension::kMessagesFilename),
+                           data.c_str(), data.length()));
+
+  std::set<std::string> valid_locales;
+  valid_locales.insert("sr");
+  valid_locales.insert("en");
+  std::string error;
+  // JSON parser hides duplicates. We are going to get only one key/value
+  // pair at the end.
+  scoped_ptr<MessageBundle> message_bundle(
+      extension_l10n_util::LoadMessageCatalogs(src_path,
+                                               "en",
+                                               "sr",
+                                               valid_locales,
+                                               &error));
+  EXPECT_TRUE(NULL != message_bundle.get());
+  EXPECT_TRUE(error.empty());
+}
+
+// Caller owns the returned object.
+MessageBundle* CreateManifestBundle() {
+  linked_ptr<DictionaryValue> catalog(new DictionaryValue);
+
+  DictionaryValue* name_tree = new DictionaryValue();
+  name_tree->SetString("message", "name");
+  catalog->Set("name", name_tree);
+
+  DictionaryValue* description_tree = new DictionaryValue();
+  description_tree->SetString("message", "description");
+  catalog->Set("description", description_tree);
+
+  DictionaryValue* action_title_tree = new DictionaryValue();
+  action_title_tree->SetString("message", "action title");
+  catalog->Set("title", action_title_tree);
+
+  DictionaryValue* omnibox_keyword_tree = new DictionaryValue();
+  omnibox_keyword_tree->SetString("message", "omnibox keyword");
+  catalog->Set("omnibox_keyword", omnibox_keyword_tree);
+
+  DictionaryValue* file_handler_title_tree = new DictionaryValue();
+  file_handler_title_tree->SetString("message", "file handler title");
+  catalog->Set("file_handler_title", file_handler_title_tree);
+
+  DictionaryValue* launch_local_path_tree = new DictionaryValue();
+  launch_local_path_tree->SetString("message", "main.html");
+  catalog->Set("launch_local_path", launch_local_path_tree);
+
+  DictionaryValue* launch_web_url_tree = new DictionaryValue();
+  launch_web_url_tree->SetString("message", "http://www.google.com/");
+  catalog->Set("launch_web_url", launch_web_url_tree);
+
+  DictionaryValue* intent_title_tree = new DictionaryValue();
+  intent_title_tree->SetString("message", "intent title");
+  catalog->Set("intent_title", intent_title_tree);
+
+  DictionaryValue* intent_title_tree_2 = new DictionaryValue();
+  intent_title_tree_2->SetString("message", "intent title 2");
+  catalog->Set("intent_title2", intent_title_tree_2);
+
+  std::vector<linked_ptr<DictionaryValue> > catalogs;
+  catalogs.push_back(catalog);
+
+  std::string error;
+  MessageBundle* bundle = MessageBundle::Create(catalogs, &error);
+  EXPECT_TRUE(bundle);
+  EXPECT_TRUE(error.empty());
+
+  return bundle;
+}
+
+TEST(ExtensionL10nUtil, LocalizeEmptyManifest) {
+  DictionaryValue manifest;
+  std::string error;
+  scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+  EXPECT_FALSE(
+      extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+  EXPECT_EQ(std::string(errors::kInvalidName), error);
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithoutNameMsgAndEmptyDescription) {
+  DictionaryValue manifest;
+  manifest.SetString(keys::kName, "no __MSG");
+  std::string error;
+  scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+  EXPECT_TRUE(
+      extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+  std::string result;
+  ASSERT_TRUE(manifest.GetString(keys::kName, &result));
+  EXPECT_EQ("no __MSG", result);
+
+  EXPECT_FALSE(manifest.HasKey(keys::kDescription));
+
+  EXPECT_TRUE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithNameMsgAndEmptyDescription) {
+  DictionaryValue manifest;
+  manifest.SetString(keys::kName, "__MSG_name__");
+  std::string error;
+  scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+  EXPECT_TRUE(
+      extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+  std::string result;
+  ASSERT_TRUE(manifest.GetString(keys::kName, &result));
+  EXPECT_EQ("name", result);
+
+  EXPECT_FALSE(manifest.HasKey(keys::kDescription));
+
+  EXPECT_TRUE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithLocalLaunchURL) {
+  DictionaryValue manifest;
+  manifest.SetString(keys::kName, "name");
+  manifest.SetString(keys::kLaunchLocalPath, "__MSG_launch_local_path__");
+  std::string error;
+  scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+  EXPECT_TRUE(
+      extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+  std::string result;
+  ASSERT_TRUE(manifest.GetString(keys::kLaunchLocalPath, &result));
+  EXPECT_EQ("main.html", result);
+
+  EXPECT_TRUE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithHostedLaunchURL) {
+  DictionaryValue manifest;
+  manifest.SetString(keys::kName, "name");
+  manifest.SetString(keys::kLaunchWebURL, "__MSG_launch_web_url__");
+  std::string error;
+  scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+  EXPECT_TRUE(
+      extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+  std::string result;
+  ASSERT_TRUE(manifest.GetString(keys::kLaunchWebURL, &result));
+  EXPECT_EQ("http://www.google.com/", result);
+
+  EXPECT_TRUE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithIntents) {
+  DictionaryValue manifest;
+  DictionaryValue* intents = new DictionaryValue;
+  DictionaryValue* action = new DictionaryValue;
+
+  action->SetString(keys::kIntentTitle, "__MSG_intent_title__");
+  intents->Set("share", action);
+  manifest.SetString(keys::kName, "name");
+  manifest.Set(keys::kIntents, intents);
+
+  std::string error;
+  scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+  EXPECT_TRUE(
+      extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+  std::string result;
+  ASSERT_TRUE(manifest.GetString("intents.share.title", &result));
+  EXPECT_EQ("intent title", result);
+
+  EXPECT_TRUE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithIntentsList) {
+  DictionaryValue manifest;
+  DictionaryValue* intents = new DictionaryValue;
+  ListValue* actions = new ListValue;
+  DictionaryValue* action1 = new DictionaryValue;
+  DictionaryValue* action2 = new DictionaryValue;
+
+  action1->SetString(keys::kIntentTitle, "__MSG_intent_title__");
+  action2->SetString(keys::kIntentTitle, "__MSG_intent_title2__");
+  actions->Append(action1);
+  actions->Append(action2);
+  intents->Set("share", actions);
+  manifest.SetString(keys::kName, "name");
+  manifest.Set(keys::kIntents, intents);
+
+  std::string error;
+  scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+  EXPECT_TRUE(
+      extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+  std::string result;
+  ListValue* l10n_actions = NULL;
+  ASSERT_TRUE(manifest.GetList("intents.share", &l10n_actions));
+
+  DictionaryValue* l10n_action = NULL;
+  ASSERT_TRUE(l10n_actions->GetDictionary(0, &l10n_action));
+
+  ASSERT_TRUE(l10n_action->GetString(keys::kIntentTitle, &result));
+  EXPECT_EQ("intent title", result);
+
+  l10n_action = NULL;
+  ASSERT_TRUE(l10n_actions->GetDictionary(1, &l10n_action));
+
+  ASSERT_TRUE(l10n_action->GetString(keys::kIntentTitle, &result));
+  EXPECT_EQ("intent title 2", result);
+
+  EXPECT_TRUE(error.empty());
+}
+
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithBadNameMsg) {
+  DictionaryValue manifest;
+  manifest.SetString(keys::kName, "__MSG_name_is_bad__");
+  manifest.SetString(keys::kDescription, "__MSG_description__");
+  std::string error;
+  scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+  EXPECT_FALSE(
+      extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+  std::string result;
+  ASSERT_TRUE(manifest.GetString(keys::kName, &result));
+  EXPECT_EQ("__MSG_name_is_bad__", result);
+
+  ASSERT_TRUE(manifest.GetString(keys::kDescription, &result));
+  EXPECT_EQ("__MSG_description__", result);
+
+  EXPECT_EQ("Variable __MSG_name_is_bad__ used but not defined.", error);
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithNameDescriptionDefaultTitleMsgs) {
+  DictionaryValue manifest;
+  manifest.SetString(keys::kName, "__MSG_name__");
+  manifest.SetString(keys::kDescription, "__MSG_description__");
+  std::string action_title(keys::kBrowserAction);
+  action_title.append(".");
+  action_title.append(keys::kPageActionDefaultTitle);
+  manifest.SetString(action_title, "__MSG_title__");
+
+  std::string error;
+  scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+  EXPECT_TRUE(
+      extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+  std::string result;
+  ASSERT_TRUE(manifest.GetString(keys::kName, &result));
+  EXPECT_EQ("name", result);
+
+  ASSERT_TRUE(manifest.GetString(keys::kDescription, &result));
+  EXPECT_EQ("description", result);
+
+  ASSERT_TRUE(manifest.GetString(action_title, &result));
+  EXPECT_EQ("action title", result);
+
+  EXPECT_TRUE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithNameDescriptionOmniboxMsgs) {
+  DictionaryValue manifest;
+  manifest.SetString(keys::kName, "__MSG_name__");
+  manifest.SetString(keys::kDescription, "__MSG_description__");
+  manifest.SetString(keys::kOmniboxKeyword, "__MSG_omnibox_keyword__");
+
+  std::string error;
+  scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+  EXPECT_TRUE(
+      extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+  std::string result;
+  ASSERT_TRUE(manifest.GetString(keys::kName, &result));
+  EXPECT_EQ("name", result);
+
+  ASSERT_TRUE(manifest.GetString(keys::kDescription, &result));
+  EXPECT_EQ("description", result);
+
+  ASSERT_TRUE(manifest.GetString(keys::kOmniboxKeyword, &result));
+  EXPECT_EQ("omnibox keyword", result);
+
+  EXPECT_TRUE(error.empty());
+}
+
+TEST(ExtensionL10nUtil, LocalizeManifestWithNameDescriptionFileHandlerTitle) {
+  DictionaryValue manifest;
+  manifest.SetString(keys::kName, "__MSG_name__");
+  manifest.SetString(keys::kDescription, "__MSG_description__");
+  ListValue* handlers = new ListValue();
+  manifest.Set(keys::kFileBrowserHandlers, handlers);
+  DictionaryValue* handler = new DictionaryValue();
+  handlers->Append(handler);
+  handler->SetString(keys::kPageActionDefaultTitle,
+                     "__MSG_file_handler_title__");
+
+  std::string error;
+  scoped_ptr<MessageBundle> messages(CreateManifestBundle());
+
+  EXPECT_TRUE(
+      extension_l10n_util::LocalizeManifest(*messages, &manifest, &error));
+
+  std::string result;
+  ASSERT_TRUE(manifest.GetString(keys::kName, &result));
+  EXPECT_EQ("name", result);
+
+  ASSERT_TRUE(manifest.GetString(keys::kDescription, &result));
+  EXPECT_EQ("description", result);
+
+  ASSERT_TRUE(handler->GetString(keys::kPageActionDefaultTitle, &result));
+  EXPECT_EQ("file handler title", result);
+
+  EXPECT_TRUE(error.empty());
+}
+
+// Try with NULL manifest.
+TEST(ExtensionL10nUtil, ShouldRelocalizeManifestWithNullManifest) {
+  ExtensionInfo info(NULL, "", FilePath(), Extension::LOAD);
+
+  EXPECT_FALSE(extension_l10n_util::ShouldRelocalizeManifest(info));
+}
+
+// Try with default and current locales missing.
+TEST(ExtensionL10nUtil, ShouldRelocalizeManifestEmptyManifest) {
+  DictionaryValue manifest;
+  ExtensionInfo info(&manifest, "", FilePath(), Extension::LOAD);
+
+  EXPECT_FALSE(extension_l10n_util::ShouldRelocalizeManifest(info));
+}
+
+// Try with missing current_locale.
+TEST(ExtensionL10nUtil, ShouldRelocalizeManifestWithDefaultLocale) {
+  DictionaryValue manifest;
+  manifest.SetString(keys::kDefaultLocale, "en_US");
+
+  ExtensionInfo info(&manifest, "", FilePath(), Extension::LOAD);
+
+  EXPECT_TRUE(extension_l10n_util::ShouldRelocalizeManifest(info));
+}
+
+// Try with missing default_locale.
+TEST(ExtensionL10nUtil, ShouldRelocalizeManifestWithCurrentLocale) {
+  DictionaryValue manifest;
+  manifest.SetString(keys::kCurrentLocale,
+                     extension_l10n_util::CurrentLocaleOrDefault());
+
+  ExtensionInfo info(&manifest, "", FilePath(), Extension::LOAD);
+
+  EXPECT_FALSE(extension_l10n_util::ShouldRelocalizeManifest(info));
+}
+
+// Try with all data present, but with same current_locale as system locale.
+TEST(ExtensionL10nUtil, ShouldRelocalizeManifestSameCurrentLocale) {
+  DictionaryValue manifest;
+  manifest.SetString(keys::kDefaultLocale, "en_US");
+  manifest.SetString(keys::kCurrentLocale,
+                     extension_l10n_util::CurrentLocaleOrDefault());
+
+  ExtensionInfo info(&manifest, "", FilePath(), Extension::LOAD);
+
+  EXPECT_FALSE(extension_l10n_util::ShouldRelocalizeManifest(info));
+}
+
+// Try with all data present, but with different current_locale.
+TEST(ExtensionL10nUtil, ShouldRelocalizeManifestDifferentCurrentLocale) {
+  DictionaryValue manifest;
+  manifest.SetString(keys::kDefaultLocale, "en_US");
+  manifest.SetString(keys::kCurrentLocale, "sr");
+
+  ExtensionInfo info(&manifest, "", FilePath(), Extension::LOAD);
+
+  EXPECT_TRUE(extension_l10n_util::ShouldRelocalizeManifest(info));
+}
+
+TEST(ExtensionL10nUtil, GetAllFallbackLocales) {
+  std::vector<std::string> fallback_locales;
+  extension_l10n_util::GetAllFallbackLocales("en_US", "all", &fallback_locales);
+  ASSERT_EQ(3U, fallback_locales.size());
+
+  CHECK_EQ("en_US", fallback_locales[0]);
+  CHECK_EQ("en", fallback_locales[1]);
+  CHECK_EQ("all", fallback_locales[2]);
+}
+
+}  // namespace
diff --git a/chrome/common/extensions/extension_localization_peer.cc b/chrome/common/extensions/extension_localization_peer.cc
new file mode 100644
index 0000000..980d2a4
--- /dev/null
+++ b/chrome/common/extensions/extension_localization_peer.cc
@@ -0,0 +1,125 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_localization_peer.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/string_util.h"
+#include "chrome/common/extensions/extension_messages.h"
+#include "chrome/common/extensions/message_bundle.h"
+#include "chrome/common/url_constants.h"
+#include "grit/generated_resources.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_response_headers.h"
+#include "webkit/glue/webkit_glue.h"
+
+ExtensionLocalizationPeer::ExtensionLocalizationPeer(
+    webkit_glue::ResourceLoaderBridge::Peer* peer,
+    IPC::Sender* message_sender,
+    const GURL& request_url)
+    : original_peer_(peer),
+      message_sender_(message_sender),
+      request_url_(request_url) {
+}
+
+ExtensionLocalizationPeer::~ExtensionLocalizationPeer() {
+}
+
+// static
+ExtensionLocalizationPeer*
+ExtensionLocalizationPeer::CreateExtensionLocalizationPeer(
+    webkit_glue::ResourceLoaderBridge::Peer* peer,
+    IPC::Sender* message_sender,
+    const std::string& mime_type,
+    const GURL& request_url) {
+  // Return NULL if content is not text/css or it doesn't belong to extension
+  // scheme.
+  return (request_url.SchemeIs(chrome::kExtensionScheme) &&
+          StartsWithASCII(mime_type, "text/css", false)) ?
+      new ExtensionLocalizationPeer(peer, message_sender, request_url) : NULL;
+}
+
+void ExtensionLocalizationPeer::OnUploadProgress(
+    uint64 position, uint64 size) {
+  NOTREACHED();
+}
+
+bool ExtensionLocalizationPeer::OnReceivedRedirect(
+    const GURL& new_url,
+    const webkit_glue::ResourceResponseInfo& info,
+    bool* has_new_first_party_for_cookies,
+    GURL* new_first_party_for_cookies) {
+  NOTREACHED();
+  return false;
+}
+
+void ExtensionLocalizationPeer::OnReceivedResponse(
+    const webkit_glue::ResourceResponseInfo& info) {
+  response_info_ = info;
+}
+
+void ExtensionLocalizationPeer::OnReceivedData(const char* data,
+                                               int data_length,
+                                               int encoded_data_length) {
+  data_.append(data, data_length);
+}
+
+void ExtensionLocalizationPeer::OnCompletedRequest(
+    int error_code,
+    bool was_ignored_by_handler,
+    const std::string& security_info,
+    const base::TimeTicks& completion_time) {
+  // Make sure we delete ourselves at the end of this call.
+  scoped_ptr<ExtensionLocalizationPeer> this_deleter(this);
+
+  // Give sub-classes a chance at altering the data.
+  if (error_code != net::OK) {
+    // We failed to load the resource.
+    original_peer_->OnReceivedResponse(response_info_);
+    original_peer_->OnCompletedRequest(net::ERR_ABORTED, false, security_info,
+                                       completion_time);
+    return;
+  }
+
+  ReplaceMessages();
+
+  original_peer_->OnReceivedResponse(response_info_);
+  if (!data_.empty())
+    original_peer_->OnReceivedData(data_.data(),
+                                   static_cast<int>(data_.size()),
+                                   -1);
+  original_peer_->OnCompletedRequest(error_code, was_ignored_by_handler,
+                                     security_info, completion_time);
+}
+
+void ExtensionLocalizationPeer::ReplaceMessages() {
+  if (!message_sender_ || data_.empty())
+    return;
+
+  if (!request_url_.is_valid())
+    return;
+
+  std::string extension_id = request_url_.host();
+  extensions::L10nMessagesMap* l10n_messages =
+      extensions::GetL10nMessagesMap(extension_id);
+  if (!l10n_messages) {
+    extensions::L10nMessagesMap messages;
+    message_sender_->Send(new ExtensionHostMsg_GetMessageBundle(
+        extension_id, &messages));
+
+    // Save messages we got, so we don't have to ask again.
+    // Messages map is never empty, it contains at least @@extension_id value.
+    extensions::ExtensionToL10nMessagesMap& l10n_messages_map =
+        *extensions::GetExtensionToL10nMessagesMap();
+    l10n_messages_map[extension_id] = messages;
+
+    l10n_messages = extensions::GetL10nMessagesMap(extension_id);
+  }
+
+  std::string error;
+  if (extensions::MessageBundle::ReplaceMessagesWithExternalDictionary(
+          *l10n_messages, &data_, &error)) {
+    data_.resize(data_.size());
+  }
+}
diff --git a/chrome/common/extensions/extension_localization_peer.h b/chrome/common/extensions/extension_localization_peer.h
new file mode 100644
index 0000000..fd69f41
--- /dev/null
+++ b/chrome/common/extensions/extension_localization_peer.h
@@ -0,0 +1,83 @@
+// 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_LOCALIZATION_PEER_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_LOCALIZATION_PEER_H_
+
+#include <string>
+
+#include "ipc/ipc_sender.h"
+#include "webkit/glue/resource_loader_bridge.h"
+
+// The ExtensionLocalizationPeer is a proxy to a
+// webkit_glue::ResourceLoaderBridge::Peer instance.  It is used to pre-process
+// CSS files requested by extensions to replace localization templates with the
+// appropriate localized strings.
+//
+// Call the factory method CreateExtensionLocalizationPeer() to obtain an
+// instance of ExtensionLocalizationPeer based on the original Peer.
+class ExtensionLocalizationPeer
+    : public webkit_glue::ResourceLoaderBridge::Peer {
+ public:
+  virtual ~ExtensionLocalizationPeer();
+
+  static ExtensionLocalizationPeer* CreateExtensionLocalizationPeer(
+      webkit_glue::ResourceLoaderBridge::Peer* peer,
+      IPC::Sender* message_sender,
+      const std::string& mime_type,
+      const GURL& request_url);
+
+  // ResourceLoaderBridge::Peer methods.
+  virtual void OnUploadProgress(uint64 position, uint64 size) OVERRIDE;
+  virtual bool OnReceivedRedirect(
+      const GURL& new_url,
+      const webkit_glue::ResourceResponseInfo& info,
+      bool* has_new_first_party_for_cookies,
+      GURL* new_first_party_for_cookies) OVERRIDE;
+  virtual void OnReceivedResponse(
+      const webkit_glue::ResourceResponseInfo& info) OVERRIDE;
+  virtual void OnDownloadedData(int len) OVERRIDE {}
+  virtual void OnReceivedData(const char* data,
+                              int data_length,
+                              int encoded_data_length) OVERRIDE;
+  virtual void OnCompletedRequest(
+      int error_code,
+      bool was_ignored_by_handler,
+      const std::string& security_info,
+      const base::TimeTicks& completion_time) OVERRIDE;
+
+ private:
+  friend class ExtensionLocalizationPeerTest;
+
+  // Use CreateExtensionLocalizationPeer to create an instance.
+  ExtensionLocalizationPeer(
+      webkit_glue::ResourceLoaderBridge::Peer* peer,
+      IPC::Sender* message_sender,
+      const GURL& request_url);
+
+  // Loads message catalogs, and replaces all __MSG_some_name__ templates within
+  // loaded file.
+  void ReplaceMessages();
+
+  // Original peer that handles the request once we are done processing data_.
+  webkit_glue::ResourceLoaderBridge::Peer* original_peer_;
+
+  // We just pass though the response info. This holds the copy of the original.
+  webkit_glue::ResourceResponseInfo response_info_;
+
+  // Sends ExtensionHostMsg_GetMessageBundle message to the browser to fetch
+  // message catalog.
+  IPC::Sender* message_sender_;
+
+  // Buffer for incoming data. We wait until OnCompletedRequest before using it.
+  std::string data_;
+
+  // Original request URL.
+  GURL request_url_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ExtensionLocalizationPeer);
+};
+
+#endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_LOCALIZATION_PEER_H_
diff --git a/chrome/common/extensions/extension_localization_peer_unittest.cc b/chrome/common/extensions/extension_localization_peer_unittest.cc
new file mode 100644
index 0000000..c0ca38f
--- /dev/null
+++ b/chrome/common/extensions/extension_localization_peer_unittest.cc
@@ -0,0 +1,246 @@
+// Copyright (c) 2012 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.
+
+#include <map>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "chrome/common/extensions/extension_localization_peer.h"
+#include "chrome/common/extensions/message_bundle.h"
+#include "ipc/ipc_sender.h"
+#include "ipc/ipc_sync_message.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/glue/resource_loader_bridge.h"
+
+using testing::_;
+using testing::DoAll;
+using testing::Invoke;
+using testing::StrEq;
+using testing::Return;
+
+static const char* const kExtensionUrl_1 =
+    "chrome-extension://some_id/popup.css";
+
+static const char* const kExtensionUrl_2 =
+    "chrome-extension://some_id2/popup.css";
+
+static const char* const kExtensionUrl_3 =
+    "chrome-extension://some_id3/popup.css";
+
+void MessageDeleter(IPC::Message* message) {
+  delete static_cast<IPC::SyncMessage*>(message)->GetReplyDeserializer();
+  delete message;
+}
+
+class MockIpcMessageSender : public IPC::Sender {
+ public:
+  MockIpcMessageSender() {
+    ON_CALL(*this, Send(_))
+        .WillByDefault(DoAll(Invoke(MessageDeleter), Return(true)));
+  }
+
+  virtual ~MockIpcMessageSender() {}
+
+  MOCK_METHOD1(Send, bool(IPC::Message* message));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockIpcMessageSender);
+};
+
+class MockResourceLoaderBridgePeer
+    : public webkit_glue::ResourceLoaderBridge::Peer {
+ public:
+  MockResourceLoaderBridgePeer() {}
+  virtual ~MockResourceLoaderBridgePeer() {}
+
+  MOCK_METHOD2(OnUploadProgress, void(uint64 position, uint64 size));
+  MOCK_METHOD4(OnReceivedRedirect, bool(
+      const GURL& new_url,
+      const webkit_glue::ResourceResponseInfo& info,
+      bool* has_new_first_party_for_cookies,
+      GURL* new_first_party_for_cookies));
+  MOCK_METHOD1(OnReceivedResponse, void(
+      const webkit_glue::ResourceResponseInfo& info));
+  MOCK_METHOD1(OnDownloadedData, void(int len));
+  MOCK_METHOD3(OnReceivedData, void(const char* data,
+                                    int data_length,
+                                    int encoded_data_length));
+  MOCK_METHOD4(OnCompletedRequest, void(
+      int error_code,
+      bool was_ignored_by_handler,
+      const std::string& security_info,
+      const base::TimeTicks& completion_time));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockResourceLoaderBridgePeer);
+};
+
+class ExtensionLocalizationPeerTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    sender_.reset(new MockIpcMessageSender());
+    original_peer_.reset(new MockResourceLoaderBridgePeer());
+    filter_peer_.reset(
+        ExtensionLocalizationPeer::CreateExtensionLocalizationPeer(
+            original_peer_.get(), sender_.get(), "text/css",
+            GURL(kExtensionUrl_1)));
+  }
+
+  ExtensionLocalizationPeer* CreateExtensionLocalizationPeer(
+      const std::string& mime_type,
+      const GURL& request_url) {
+    return ExtensionLocalizationPeer::CreateExtensionLocalizationPeer(
+        original_peer_.get(), sender_.get(), mime_type, request_url);
+  }
+
+  std::string GetData(ExtensionLocalizationPeer* filter_peer) {
+    EXPECT_TRUE(NULL != filter_peer);
+    return filter_peer->data_;
+  }
+
+  void SetData(ExtensionLocalizationPeer* filter_peer,
+               const std::string& data) {
+    EXPECT_TRUE(NULL != filter_peer);
+    filter_peer->data_ = data;
+  }
+
+  scoped_ptr<MockIpcMessageSender> sender_;
+  scoped_ptr<MockResourceLoaderBridgePeer> original_peer_;
+  scoped_ptr<ExtensionLocalizationPeer> filter_peer_;
+};
+
+TEST_F(ExtensionLocalizationPeerTest, CreateWithWrongMimeType) {
+  filter_peer_.reset(
+      CreateExtensionLocalizationPeer("text/html", GURL(kExtensionUrl_1)));
+  EXPECT_TRUE(NULL == filter_peer_.get());
+}
+
+TEST_F(ExtensionLocalizationPeerTest, CreateWithValidInput) {
+  EXPECT_TRUE(NULL != filter_peer_.get());
+}
+
+TEST_F(ExtensionLocalizationPeerTest, OnReceivedData) {
+  EXPECT_TRUE(GetData(filter_peer_.get()).empty());
+
+  const std::string data_chunk("12345");
+  filter_peer_->OnReceivedData(data_chunk.c_str(), data_chunk.length(), -1);
+
+  EXPECT_EQ(data_chunk, GetData(filter_peer_.get()));
+
+  filter_peer_->OnReceivedData(data_chunk.c_str(), data_chunk.length(), -1);
+  EXPECT_EQ(data_chunk + data_chunk, GetData(filter_peer_.get()));
+}
+
+MATCHER_P(IsURLRequestEqual, status, "") { return arg.status() == status; }
+
+TEST_F(ExtensionLocalizationPeerTest, OnCompletedRequestBadURLRequestStatus) {
+  // It will self-delete once it exits OnCompletedRequest.
+  ExtensionLocalizationPeer* filter_peer = filter_peer_.release();
+
+  EXPECT_CALL(*original_peer_, OnReceivedResponse(_));
+  EXPECT_CALL(*original_peer_, OnCompletedRequest(
+    net::ERR_ABORTED, false, "", base::TimeTicks()));
+
+  filter_peer->OnCompletedRequest(net::ERR_FAILED, false, "",
+                                  base::TimeTicks());
+}
+
+TEST_F(ExtensionLocalizationPeerTest, OnCompletedRequestEmptyData) {
+  // It will self-delete once it exits OnCompletedRequest.
+  ExtensionLocalizationPeer* filter_peer = filter_peer_.release();
+
+  EXPECT_CALL(*original_peer_, OnReceivedData(_, _, _)).Times(0);
+  EXPECT_CALL(*sender_, Send(_)).Times(0);
+
+  EXPECT_CALL(*original_peer_, OnReceivedResponse(_));
+  EXPECT_CALL(*original_peer_, OnCompletedRequest(
+      net::OK, false, "", base::TimeTicks()));
+
+  filter_peer->OnCompletedRequest(net::OK, false, "", base::TimeTicks());
+}
+
+TEST_F(ExtensionLocalizationPeerTest, OnCompletedRequestNoCatalogs) {
+  // It will self-delete once it exits OnCompletedRequest.
+  ExtensionLocalizationPeer* filter_peer = filter_peer_.release();
+
+  SetData(filter_peer, "some text");
+
+  EXPECT_CALL(*sender_, Send(_));
+
+  std::string data = GetData(filter_peer);
+  EXPECT_CALL(*original_peer_,
+              OnReceivedData(StrEq(data.data()), data.length(), -1)).Times(2);
+
+  EXPECT_CALL(*original_peer_, OnReceivedResponse(_)).Times(2);
+  EXPECT_CALL(*original_peer_, OnCompletedRequest(
+          net::OK, false, "", base::TimeTicks())).Times(2);
+
+  filter_peer->OnCompletedRequest(net::OK, false, "", base::TimeTicks());
+
+  // Test if Send gets called again (it shouldn't be) when first call returned
+  // an empty dictionary.
+  filter_peer =
+      CreateExtensionLocalizationPeer("text/css", GURL(kExtensionUrl_1));
+  SetData(filter_peer, "some text");
+  filter_peer->OnCompletedRequest(net::OK, false, "", base::TimeTicks());
+}
+
+TEST_F(ExtensionLocalizationPeerTest, OnCompletedRequestWithCatalogs) {
+  // It will self-delete once it exits OnCompletedRequest.
+  ExtensionLocalizationPeer* filter_peer =
+      CreateExtensionLocalizationPeer("text/css", GURL(kExtensionUrl_2));
+
+  extensions::L10nMessagesMap messages;
+  messages.insert(std::make_pair("text", "new text"));
+  extensions::ExtensionToL10nMessagesMap& l10n_messages_map =
+      *extensions::GetExtensionToL10nMessagesMap();
+  l10n_messages_map["some_id2"] = messages;
+
+  SetData(filter_peer, "some __MSG_text__");
+
+  // We already have messages in memory, Send will be skipped.
+  EXPECT_CALL(*sender_, Send(_)).Times(0);
+
+  // __MSG_text__ gets replaced with "new text".
+  std::string data("some new text");
+  EXPECT_CALL(*original_peer_,
+              OnReceivedData(StrEq(data.data()), data.length(), -1));
+
+  EXPECT_CALL(*original_peer_, OnReceivedResponse(_));
+  EXPECT_CALL(*original_peer_, OnCompletedRequest(
+      net::OK, false, "", base::TimeTicks()));
+
+  filter_peer->OnCompletedRequest(net::OK, false, "", base::TimeTicks());
+}
+
+TEST_F(ExtensionLocalizationPeerTest, OnCompletedRequestReplaceMessagesFails) {
+  // It will self-delete once it exits OnCompletedRequest.
+  ExtensionLocalizationPeer* filter_peer =
+      CreateExtensionLocalizationPeer("text/css", GURL(kExtensionUrl_3));
+
+  extensions::L10nMessagesMap messages;
+  messages.insert(std::make_pair("text", "new text"));
+  extensions::ExtensionToL10nMessagesMap& l10n_messages_map =
+      *extensions::GetExtensionToL10nMessagesMap();
+  l10n_messages_map["some_id3"] = messages;
+
+  std::string message("some __MSG_missing_message__");
+  SetData(filter_peer, message);
+
+  // We already have messages in memory, Send will be skipped.
+  EXPECT_CALL(*sender_, Send(_)).Times(0);
+
+  // __MSG_missing_message__ is missing, so message stays the same.
+  EXPECT_CALL(*original_peer_,
+              OnReceivedData(StrEq(message.data()), message.length(), -1));
+
+  EXPECT_CALL(*original_peer_, OnReceivedResponse(_));
+  EXPECT_CALL(*original_peer_, OnCompletedRequest(
+      net::OK, false, "", base::TimeTicks()));
+
+  filter_peer->OnCompletedRequest(net::OK, false, "", base::TimeTicks());
+}
diff --git a/chrome/common/extensions/extension_manifest_constants.cc b/chrome/common/extensions/extension_manifest_constants.cc
new file mode 100644
index 0000000..e58ecdc
--- /dev/null
+++ b/chrome/common/extensions/extension_manifest_constants.cc
@@ -0,0 +1,537 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_manifest_constants.h"
+
+namespace extension_manifest_keys {
+
+const char kAllFrames[] = "all_frames";
+const char kAltKey[] = "altKey";
+const char kApp[] = "app";
+const char kBackgroundAllowJsAccess[] = "background.allow_js_access";
+const char kBackgroundPage[] = "background.page";
+const char kBackgroundPageLegacy[] = "background_page";
+const char kBackgroundPersistent[] = "background.persistent";
+const char kBackgroundScripts[] = "background.scripts";
+const char kBrowserAction[] = "browser_action";
+const char kChromeURLOverrides[] = "chrome_url_overrides";
+const char kCommands[] = "commands";
+const char kContentScripts[] = "content_scripts";
+const char kContentSecurityPolicy[] = "content_security_policy";
+const char kConvertedFromUserScript[] = "converted_from_user_script";
+const char kCss[] = "css";
+const char kCtrlKey[] = "ctrlKey";
+const char kCurrentLocale[] = "current_locale";
+const char kDefaultLocale[] = "default_locale";
+const char kDescription[] = "description";
+const char kDevToolsPage[] = "devtools_page";
+const char kDisplayInLauncher[] = "display_in_launcher";
+const char kDisplayInNewTabPage[] = "display_in_new_tab_page";
+const char kEventName[] = "event_name";
+const char kExcludeGlobs[] = "exclude_globs";
+const char kExcludeMatches[] = "exclude_matches";
+const char kFileAccessList[] = "file_access";
+const char kFileFilters[] = "file_filters";
+const char kFileBrowserHandlers[] = "file_browser_handlers";
+const char kFileHandlers[] = "file_handlers";
+const char kFileHandlerTitle[] = "title";
+const char kFileHandlerTypes[] = "types";
+const char kHomepageURL[] = "homepage_url";
+const char kIcons[] = "icons";
+const char kId[] = "id";
+const char kIncognito[] = "incognito";
+const char kIncludeGlobs[] = "include_globs";
+const char kInputComponents[] = "input_components";
+const char kIntentDisposition[] = "disposition";
+const char kIntentHref[] = "href";
+const char kIntentPath[] = "path";
+const char kIntents[] = "intents";
+const char kIntentTitle[] = "title";
+const char kIntentType[] = "type";
+const char kIsolation[] = "app.isolation";
+const char kJs[] = "js";
+const char kKey[] = "key";
+const char kKeycode[] = "keyCode";
+const char kLanguage[] = "language";
+const char kLaunch[] = "app.launch";
+const char kLaunchContainer[] = "app.launch.container";
+const char kLaunchHeight[] = "app.launch.height";
+const char kLaunchLocalPath[] = "app.launch.local_path";
+const char kLaunchWebURL[] = "app.launch.web_url";
+const char kLaunchMaxHeight[] = "app.launch.max_height";
+const char kLaunchMaxWidth[] = "app.launch.max_width";
+const char kLaunchMinHeight[] = "app.launch.min_height";
+const char kLaunchMinWidth[] = "app.launch.min_width";
+const char kLaunchWidth[] = "app.launch.width";
+const char kLayouts[] = "layouts";
+const char kManifestVersion[] = "manifest_version";
+const char kMatches[] = "matches";
+const char kMinimumChromeVersion[] = "minimum_chrome_version";
+const char kName[] = "name";
+const char kNaClModules[] = "nacl_modules";
+const char kNaClModulesMIMEType[] = "mime_type";
+const char kNaClModulesPath[] = "path";
+const char kOAuth2[] = "oauth2";
+const char kOAuth2ClientId[] = "oauth2.client_id";
+const char kOAuth2Scopes[] = "oauth2.scopes";
+const char kOfflineEnabled[] = "offline_enabled";
+const char kOmnibox[] = "omnibox";
+const char kOmniboxKeyword[] = "omnibox.keyword";
+const char kOptionalPermissions[] = "optional_permissions";
+const char kOptionsPage[] = "options_page";
+const char kPageAction[] = "page_action";
+const char kPageActionDefaultIcon[] = "default_icon";
+const char kPageActionDefaultPopup[] = "default_popup";
+const char kPageActionDefaultTitle[] = "default_title";
+const char kPageActionIcons[] = "icons";
+const char kPageActionId[] = "id";
+const char kPageActionPopup[] = "popup";
+const char kPageActionPopupPath[] = "path";
+const char kPageActions[] = "page_actions";
+const char kPermissions[] = "permissions";
+const char kPlatformAppBackground[] = "app.background";
+const char kPlatformAppBackgroundPage[] = "app.background.page";
+const char kPlatformAppBackgroundScripts[] = "app.background.scripts";
+const char kPlatformAppContentSecurityPolicy[] = "app.content_security_policy";
+const char kPlugins[] = "plugins";
+const char kPluginsPath[] = "path";
+const char kPluginsPublic[] = "public";
+const char kPublicKey[] = "key";
+const char kRequirements[] = "requirements";
+const char kRunAt[] = "run_at";
+const char kSandboxedPages[] = "sandbox.pages";
+const char kSandboxedPagesCSP[] = "sandbox.content_security_policy";
+const char kScriptBadge[] = "script_badge";
+const char kShiftKey[] = "shiftKey";
+const char kShortcutKey[] = "shortcutKey";
+const char kSignature[] = "signature";
+const char kSuggestedKey[] = "suggested_key";
+const char kTheme[] = "theme";
+const char kThemeColors[] = "colors";
+const char kThemeDisplayProperties[] = "properties";
+const char kThemeImages[] = "images";
+const char kThemeTints[] = "tints";
+const char kTtsEngine[] = "tts_engine";
+const char kTtsGenderFemale[] = "female";
+const char kTtsGenderMale[] = "male";
+const char kTtsVoices[] = "voices";
+const char kTtsVoicesEventTypeEnd[] = "end";
+const char kTtsVoicesEventTypeError[] = "error";
+const char kTtsVoicesEventTypeMarker[] = "marker";
+const char kTtsVoicesEventTypeSentence[] = "sentence";
+const char kTtsVoicesEventTypeStart[] = "start";
+const char kTtsVoicesEventTypeWord[] = "word";
+const char kTtsVoicesEventTypes[] = "event_types";
+const char kTtsVoicesGender[] = "gender";
+const char kTtsVoicesLang[] = "lang";
+const char kTtsVoicesVoiceName[] = "voice_name";
+const char kType[] = "type";
+const char kUpdateURL[] = "update_url";
+const char kVersion[] = "version";
+const char kWebAccessibleResources[] = "web_accessible_resources";
+const char kWebURLs[] = "app.urls";
+}  // namespace extension_manifest_keys
+
+namespace extension_manifest_values {
+const char kBrowserActionCommandEvent[] = "_execute_browser_action";
+const char kIncognitoSplit[] = "split";
+const char kIncognitoSpanning[] = "spanning";
+const char kIntentDispositionWindow[] = "window";
+const char kIntentDispositionInline[] = "inline";
+const char kIsolatedStorage[] = "storage";
+const char kKeybindingPlatformChromeOs[] = "chromeos";
+const char kKeybindingPlatformDefault[] = "default";
+const char kKeybindingPlatformLinux[] = "linux";
+const char kKeybindingPlatformMac[] = "mac";
+const char kKeybindingPlatformWin[] = "windows";
+const char kRunAtDocumentStart[] = "document_start";
+const char kRunAtDocumentEnd[] = "document_end";
+const char kRunAtDocumentIdle[] = "document_idle";
+const char kPageActionCommandEvent[] = "_execute_page_action";
+const char kPageActionTypeTab[] = "tab";
+const char kPageActionTypePermanent[] = "permanent";
+const char kScriptBadgeCommandEvent[] = "_execute_script_badge";
+const char kLaunchContainerPanel[] = "panel";
+const char kLaunchContainerTab[] = "tab";
+const char kLaunchContainerWindow[] = "window";
+}  // namespace extension_manifest_values
+
+// Extension-related error messages. Some of these are simple patterns, where a
+// '*' is replaced at runtime with a specific value. This is used instead of
+// printf because we want to unit test them and scanf is hard to make
+// cross-platform.
+namespace extension_manifest_errors {
+const char kAppsNotEnabled[] =
+    "Apps are not enabled.";
+const char kBackgroundPermissionNeeded[] =
+    "Hosted apps that use 'background_page' must have the 'background' "
+    "permission.";
+const char kBackgroundRequiredForPlatformApps[] =
+    "Packaged apps must have a background page or background scripts.";
+const char kCannotAccessPage[] =
+    "Cannot access contents of url \"*\". "
+    "Extension manifest must request permission to access this host.";
+const char kCannotChangeExtensionID[] =
+    "Installed extensions cannot change their IDs.";
+const char kCannotClaimAllHostsInExtent[] =
+    "Cannot claim all hosts ('*') in an extent.";
+const char kCannotClaimAllURLsInExtent[] =
+    "Cannot claim all URLs in an extent.";
+const char kCannotScriptGallery[] =
+    "The extensions gallery cannot be scripted.";
+const char kChromeVersionTooLow[] =
+    "This extension requires * version * or greater.";
+const char kDisabledByPolicy[] =
+    "This extension has been disabled by your administrator.";
+const char kExpectString[] = "Expect string value.";
+const char kExperimentalFlagRequired[] =
+    "Loading extensions with 'experimental' permission is turned off by "
+    "default. You can enable 'Experimental Extension APIs' "
+    "by visiting chrome://flags.";
+const char kInvalidAllFrames[] =
+    "Invalid value for 'content_scripts[*].all_frames'.";
+const char kInvalidBackground[] =
+    "Invalid value for 'background_page'.";
+const char kInvalidBackgroundAllowJsAccess[] =
+    "Invalid value for 'background.allow_js_access'.";
+const char kInvalidBackgroundCombination[] =
+    "The background.page and background.scripts properties cannot be used at "
+    "the same time.";
+const char kInvalidBackgroundScript[] =
+    "Invalid value for 'background.scripts[*]'.";
+const char kInvalidBackgroundScripts[] =
+    "Invalid value for 'background.scripts'.";
+const char kInvalidBackgroundInHostedApp[] =
+    "Invalid value for 'background_page'. Hosted apps must specify an "
+    "absolute HTTPS URL for the background page.";
+const char kInvalidBackgroundPersistent[] =
+    "Invalid value for 'background.persistent'.";
+const char kInvalidBackgroundPersistentNoPage[] =
+    "Must specify one of background.page or background.scripts to use"
+    " background.persistent.";
+const char kInvalidBrowserAction[] =
+    "Invalid value for 'browser_action'.";
+const char kInvalidChromeURLOverrides[] =
+    "Invalid value for 'chrome_url_overrides'.";
+const char kInvalidCommandsKey[] =
+    "Invalid value for 'commands'.";
+const char kInvalidContentScript[] =
+    "Invalid value for 'content_scripts[*]'.";
+const char kInvalidContentSecurityPolicy[] =
+    "Invalid value for 'content_security_policy'.";
+const char kInvalidContentScriptsList[] =
+    "Invalid value for 'content_scripts'.";
+const char kInvalidCss[] =
+    "Invalid value for 'content_scripts[*].css[*]'.";
+const char kInvalidCssList[] =
+    "Required value 'content_scripts[*].css' is invalid.";
+const char kInvalidDefaultLocale[] =
+    "Invalid value for default locale - locale name must be a string.";
+const char kInvalidDescription[] =
+    "Invalid value for 'description'.";
+const char kInvalidDevToolsPage[] =
+    "Invalid value for 'devtools_page'.";
+const char kInvalidDisplayInLauncher[] =
+    "Invalid value for 'display_in_launcher'.";
+const char kInvalidDisplayInNewTabPage[] =
+    "Invalid value for 'display_in_new_tab_page'.";
+const char kInvalidExcludeMatch[] =
+    "Invalid value for 'content_scripts[*].exclude_matches[*]': *";
+const char kInvalidExcludeMatches[] =
+    "Invalid value for 'content_scripts[*].exclude_matches'.";
+const char kInvalidFileAccessList[] =
+    "Invalid value for 'file_access'.";
+const char kInvalidFileAccessValue[] =
+    "Invalid value for 'file_access[*]'.";
+const char kInvalidFileBrowserHandler[] =
+    "Invalid value for 'file_browser_handers'.";
+const char kInvalidFileFiltersList[] =
+    "Invalid value for 'file_filters'.";
+const char kInvalidFileFilterValue[] =
+    "Invalid value for 'file_filters[*]'.";
+const char kInvalidFileHandlers[] =
+    "Invalid value for 'file_handlers'.";
+const char kInvalidFileHandlerTitle[] =
+    "Invalid value for 'file_handlers[*].title'.";
+const char kInvalidFileHandlerType[] =
+    "Invalid value for 'file_handlers[*].type'.";
+const char kInvalidFileHandlerTypeElement[] =
+    "Invalid value for 'file_handlers[*].type[*]'.";
+const char kInvalidGlob[] =
+    "Invalid value for 'content_scripts[*].*[*]'.";
+const char kInvalidGlobList[] =
+    "Invalid value for 'content_scripts[*].*'.";
+const char kInvalidHomepageURL[] =
+    "Invalid value for homepage url: '[*]'.";
+const char kInvalidIconPath[] =
+    "Invalid value for 'icons[\"*\"]'.";
+const char kInvalidIcons[] =
+    "Invalid value for 'icons'.";
+const char kInvalidIncognitoBehavior[] =
+    "Invalid value for 'incognito'.";
+const char kInvalidIncognitoModeForPlatformApp[] =
+    "Invalid value for 'incognito'. Packaged apps must use split incognito "
+    "mode.";
+const char kInvalidInputComponents[] =
+    "Invalid value for 'input_components'";
+const char kInvalidInputComponentDescription[] =
+    "Invalid value for 'input_components[*].description";
+const char kInvalidInputComponentLayoutName[] =
+    "Invalid value for 'input_components[*].layouts[*]";
+const char kInvalidInputComponentLayouts[] =
+    "Invalid value for 'input_components[*].layouts";
+const char kInvalidInputComponentName[] =
+    "Invalid value for 'input_components[*].name";
+const char kInvalidInputComponentShortcutKey[] =
+    "Invalid value for 'input_components[*].shortcutKey";
+const char kInvalidInputComponentShortcutKeycode[] =
+    "Invalid value for 'input_components[*].shortcutKey.keyCode";
+const char kInvalidInputComponentType[] =
+    "Invalid value for 'input_components[*].type";
+const char kInvalidIntent[] =
+    "Invalid value for intents[*]";
+const char kInvalidIntentDisposition[] =
+    "Invalid value for intents[*].disposition";
+const char kInvalidIntentDispositionInPlatformApp[] =
+    "Invalid value for intents[*].disposition. Packaged apps cannot specify "
+    "a disposition";
+const char kInvalidIntentHref[] =
+    "Invalid value for intents[*].href";
+const char kInvalidIntentHrefEmpty[] =
+    "Missing value for intents[*].href";
+const char kInvalidIntentHrefInPlatformApp[] =
+    "Invalid value for intents[*].href. Packaged apps cannot specify a "
+    "URL for intents";
+const char kInvalidIntentHrefOldAndNewKey[] =
+    "intents[*]: Key \"*\" is deprecated.  Key \"*\" has the same meaning. "
+    "You can not use both.";
+const char kInvalidIntentPageInHostedApp[] =
+    "Invalid value for intents[*].href. Hosted apps must specify an "
+    "absolute URL within app.urls[].";
+const char kInvalidIntents[] =
+    "Invalid value for intents";
+const char kInvalidIntentType[] =
+    "Invalid value for intents[*].type";
+const char kInvalidIntentTypeElement[] =
+    "Invalid value for intents[*].type[*]";
+const char kInvalidIntentTitle[] =
+    "Invalid value for intents[*].title";
+const char kInvalidIsolation[] =
+    "Invalid value for 'app.isolation'.";
+const char kInvalidIsolationValue[] =
+    "Invalid value for 'app.isolation[*]'.";
+const char kInvalidJs[] =
+    "Invalid value for 'content_scripts[*].js[*]'.";
+const char kInvalidJsList[] =
+    "Required value 'content_scripts[*].js' is invalid.";
+const char kInvalidKey[] =
+    "Value 'key' is missing or invalid.";
+const char kInvalidKeyBinding[] =
+     "Invalid value for 'commands[*].*': *.";
+const char kInvalidKeyBindingDescription[] =
+    "Invalid value for 'commands[*].description'.";
+const char kInvalidKeyBindingDictionary[] =
+    "Contents of 'commands[*]' invalid.";
+const char kInvalidKeyBindingMissingPlatform[] =
+    "Could not find key specification for 'command[*].*': Either specify a key "
+    "for '*', or specify a default key.";
+const char kInvalidKeyBindingTooMany[] =
+    "Too many commands specified for 'commands': The maximum is *.";
+const char kInvalidKeyBindingUnknownPlatform[] =
+    "Unknown platform for 'command[*]': *. Valid values are: 'windows', 'mac'"
+    " 'chromeos', 'linux' and 'default'.";
+const char kInvalidLaunchContainer[] =
+    "Invalid value for 'app.launch.container'.";
+const char kInvalidLaunchValue[] =
+    "Invalid value for '*'.";
+const char kInvalidLaunchValueContainer[] =
+    "Invalid container type for '*'.";
+const char kInvalidManifest[] =
+    "Manifest file is invalid.";
+const char kInvalidManifestVersion[] =
+    "Invalid value for 'manifest_version'. Must be an integer greater than "
+    "zero.";
+const char kInvalidManifestVersionOld[] =
+    "The 'manifest_version' key must be present and set to * (without quotes). "
+    "See developer.chrome.com/extensions/manifestVersion.html for details.";
+const char kInvalidMatch[] =
+    "Invalid value for 'content_scripts[*].matches[*]': *";
+const char kInvalidMatchCount[] =
+    "Invalid value for 'content_scripts[*].matches'. There must be at least "
+    "one match specified.";
+const char kInvalidMatches[] =
+    "Required value 'content_scripts[*].matches' is missing or invalid.";
+const char kInvalidMinimumChromeVersion[] =
+    "Invalid value for 'minimum_chrome_version'.";
+const char kInvalidName[] =
+    "Required value 'name' is missing or invalid.";
+const char kInvalidNaClModules[] =
+    "Invalid value for 'nacl_modules'.";
+const char kInvalidNaClModulesPath[] =
+    "Invalid value for 'nacl_modules[*].path'.";
+const char kInvalidNaClModulesMIMEType[] =
+    "Invalid value for 'nacl_modules[*].mime_type'.";
+const char kInvalidOAuth2ClientId[] =
+    "Invalid value for 'oauth2.client_id'.";
+const char kInvalidOAuth2Scopes[] =
+    "Invalid value for 'oauth2.scopes'.";
+const char kInvalidOfflineEnabled[] =
+    "Invalid value for 'offline_enabled'.";
+const char kInvalidOmniboxKeyword[] =
+    "Invalid value for 'omnibox.keyword'.";
+const char kInvalidOptionsPage[] =
+    "Invalid value for 'options_page'.";
+const char kInvalidOptionsPageExpectUrlInPackage[] =
+    "Invalid value for 'options_page'.  Value must be a relative path.";
+const char kInvalidOptionsPageInHostedApp[] =
+    "Invalid value for 'options_page'. Hosted apps must specify an "
+    "absolute URL.";
+const char kInvalidPageAction[] =
+    "Invalid value for 'page_action'.";
+const char kInvalidPageActionDefaultTitle[] =
+    "Invalid value for 'default_title'.";
+const char kInvalidPageActionIconPath[] =
+    "Invalid value for 'page_action.default_icon'.";
+const char kInvalidPageActionId[] =
+    "Required value 'id' is missing or invalid.";
+const char kInvalidPageActionName[] =
+    "Invalid value for 'page_action.name'.";
+const char kInvalidPageActionOldAndNewKeys[] =
+    "Key \"*\" is deprecated.  Key \"*\" has the same meaning.  You can not "
+    "use both.";
+const char kInvalidPageActionPopup[] =
+    "Invalid type for page action popup.";
+const char kInvalidPageActionPopupPath[] =
+    "Invalid value for page action popup path [*].";
+const char kInvalidPageActionsList[] =
+    "Invalid value for 'page_actions'.";
+const char kInvalidPageActionsListSize[] =
+    "Invalid value for 'page_actions'. There can be at most one page action.";
+const char kInvalidPageActionTypeValue[] =
+    "Invalid value for 'page_actions[*].type', expected 'tab' or 'permanent'.";
+const char kInvalidPermission[] =
+    "Invalid value for 'permissions[*]'.";
+const char kInvalidPermissions[] =
+    "Required value 'permissions' is missing or invalid.";
+const char kInvalidPermissionScheme[] =
+    "Invalid scheme for 'permissions[*]'.";
+const char kInvalidPlugins[] =
+    "Invalid value for 'plugins'.";
+const char kInvalidPluginsPath[] =
+    "Invalid value for 'plugins[*].path'.";
+const char kInvalidPluginsPublic[] =
+    "Invalid value for 'plugins[*].public'.";
+const char kInvalidRequirement[] =
+    "Invalid value for requirement \"*\"";
+const char kInvalidRequirements[] =
+    "Invalid value for 'requirements'";
+const char kInvalidRunAt[] =
+    "Invalid value for 'content_scripts[*].run_at'.";
+const char kInvalidSandboxedPagesList[] =
+    "Invalid value for 'sandbox.pages'.";
+const char kInvalidSandboxedPage[] =
+    "Invalid value for 'sandbox.pages[*]'.";
+const char kInvalidSandboxedPagesCSP[] =
+    "Invalid value for 'sandbox.content_security_policy'.";
+const char kInvalidScriptBadge[] =
+    "Invalid value for 'script_badge'.";
+const char kInvalidSignature[] =
+    "Value 'signature' is missing or invalid.";
+const char kInvalidTheme[] =
+    "Invalid value for 'theme'.";
+const char kInvalidThemeColors[] =
+    "Invalid value for theme colors - colors must be integers";
+const char kInvalidThemeImages[] =
+    "Invalid value for theme images - images must be strings.";
+const char kInvalidThemeImagesMissing[] =
+    "An image specified in the theme is missing.";
+const char kInvalidThemeTints[] =
+    "Invalid value for theme images - tints must be decimal numbers.";
+const char kInvalidTts[] =
+    "Invalid value for 'tts_engine'.";
+const char kInvalidTtsVoices[] =
+    "Invalid value for 'tts_engine.voices'.";
+const char kInvalidTtsVoicesEventTypes[] =
+    "Invalid value for 'tts_engine.voices[*].event_types'.";
+const char kInvalidTtsVoicesGender[] =
+    "Invalid value for 'tts_engine.voices[*].gender'.";
+const char kInvalidTtsVoicesLang[] =
+    "Invalid value for 'tts_engine.voices[*].lang'.";
+const char kInvalidTtsVoicesVoiceName[] =
+    "Invalid value for 'tts_engine.voices[*].voice_name'.";
+const char kInvalidUpdateURL[] =
+    "Invalid value for update url: '[*]'.";
+const char kInvalidURLPatternError[] =
+    "Invalid url pattern '*'";
+const char kInvalidVersion[] =
+    "Required value 'version' is missing or invalid. It must be between 1-4 "
+    "dot-separated integers each between 0 and 65536.";
+const char kInvalidWebAccessibleResourcesList[] =
+    "Invalid value for 'web_accessible_resources'.";
+const char kInvalidWebAccessibleResource[] =
+    "Invalid value for 'web_accessible_resources[*]'.";
+const char kInvalidWebURL[] =
+    "Invalid value for 'app.urls[*]': *";
+const char kInvalidWebURLs[] =
+    "Invalid value for 'app.urls'.";
+const char kInvalidZipHash[] =
+    "Required key 'zip_hash' is missing or invalid.";
+const char kInsecureContentSecurityPolicy[] =
+    "Invalid value for 'content_security_policy': Both 'script-src' and"
+    " 'object-src' directives must be specified (either explicitly, or"
+    " implicitly via 'default-src'), and both must whitelist only secure"
+    " resources. You may include any of the following sources: \"'self'\","
+    " \"'unsafe-eval'\", \"http://127.0.0.1\", \"http://localhost\", or any"
+    " \"https://\" or \"chrome-extension://\" origin. For more information,"
+    " see http://developer.chrome.com/extensions/contentSecurityPolicy.html";
+const char kLaunchPathAndExtentAreExclusive[] =
+    "The 'app.launch.local_path' and 'app.urls' keys cannot both be set.";
+const char kLaunchPathAndURLAreExclusive[] =
+    "The 'app.launch.local_path' and 'app.launch.web_url' keys cannot "
+    "both be set.";
+const char kLaunchURLRequired[] =
+    "Either 'app.launch.local_path' or 'app.launch.web_url' is required.";
+const char kLocalesMessagesFileMissing[] =
+    "Messages file is missing for locale.";
+const char kLocalesNoDefaultLocaleSpecified[] =
+    "Localization used, but default_locale wasn't specified in the manifest.";
+const char kLocalesNoDefaultMessages[] =
+    "Default locale is defined but default data couldn't be loaded.";
+const char kLocalesNoValidLocaleNamesListed[] =
+    "No valid locale name could be found in _locales directory.";
+const char kLocalesTreeMissing[] =
+    "Default locale was specified, but _locales subtree is missing.";
+const char kManifestParseError[] =
+    "Manifest is not valid JSON.";
+const char kManifestUnreadable[] =
+    "Manifest file is missing or unreadable.";
+const char kMissingFile[] =
+    "At least one js or css file is required for 'content_scripts[*]'.";
+const char kMultipleOverrides[] =
+    "An extension cannot override more than one page.";
+const char kNoWildCardsInPaths[] =
+  "Wildcards are not allowed in extent URL pattern paths.";
+const char kOneUISurfaceOnly[] =
+    "Only one of 'browser_action', 'page_action', and 'app' can be specified.";
+const char kPermissionNotAllowed[] =
+    "Access to permission '*' denied.";
+const char kPlatformAppNeedsManifestVersion2[] =
+    "Packaged apps need manifest_version set to >= 2";
+const char kReservedMessageFound[] =
+    "Reserved key * found in message catalog.";
+const char kScriptBadgeRequiresFlag[] =
+    "The script_badge manifest key is turned off by default. "
+    "You can enable it with the --enable-script-badges command-line flag.";
+const char kScriptBadgeIconIgnored[] =
+    "default_icon specified in script_badge manifest section will not be used.";
+const char kScriptBadgeTitleIgnored[] =
+    "default_title specified in script_badge manifest section will not be "
+    "used.";
+const char kWebRequestConflictsWithLazyBackground[] =
+    "The 'webRequest' API cannot be used with event pages.";
+#if defined(OS_CHROMEOS)
+const char kIllegalPlugins[] =
+    "Extensions cannot install plugins on Chrome OS";
+#endif
+}  // namespace extension_manifest_errors
diff --git a/chrome/common/extensions/extension_manifest_constants.h b/chrome/common/extensions/extension_manifest_constants.h
new file mode 100644
index 0000000..8612c1e
--- /dev/null
+++ b/chrome/common/extensions/extension_manifest_constants.h
@@ -0,0 +1,350 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_MANIFEST_CONSTANTS_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_MANIFEST_CONSTANTS_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "googleurl/src/gurl.h"
+
+// Keys used in JSON representation of extensions.
+namespace extension_manifest_keys {
+  extern const char kAllFrames[];
+  extern const char kAltKey[];
+  extern const char kApp[];
+  extern const char kBackgroundAllowJsAccess[];
+  extern const char kBackgroundPage[];
+  extern const char kBackgroundPageLegacy[];
+  extern const char kBackgroundPersistent[];
+  extern const char kBackgroundScripts[];
+  extern const char kBrowserAction[];
+  extern const char kBrowseURLs[];
+  extern const char kChromeURLOverrides[];
+  extern const char kCommands[];
+  extern const char kContentScripts[];
+  extern const char kContentSecurityPolicy[];
+  extern const char kConvertedFromUserScript[];
+  extern const char kCss[];
+  extern const char kCtrlKey[];
+  extern const char kCurrentLocale[];
+  extern const char kDefaultLocale[];
+  extern const char kDescription[];
+  extern const char kDevToolsPage[];
+  extern const char kDisplayInLauncher[];
+  extern const char kDisplayInNewTabPage[];
+  extern const char kEventName[];
+  extern const char kExcludeGlobs[];
+  extern const char kExcludeMatches[];
+  extern const char kFileAccessList[];
+  extern const char kFileHandlers[];
+  extern const char kFileHandlerTitle[];
+  extern const char kFileHandlerTypes[];
+  extern const char kFileFilters[];
+  extern const char kFileBrowserHandlers[];
+  extern const char kHomepageURL[];
+  extern const char kIcons[];
+  extern const char kId[];
+  extern const char kIncognito[];
+  extern const char kIncludeGlobs[];
+  extern const char kInputComponents[];
+  extern const char kIntentDisposition[];
+  extern const char kIntentHref[];
+  extern const char kIntentPath[];
+  extern const char kIntents[];
+  extern const char kIntentTitle[];
+  extern const char kIntentType[];
+  extern const char kIsolation[];
+  extern const char kJs[];
+  extern const char kKey[];
+  extern const char kKeycode[];
+  extern const char kLanguage[];
+  extern const char kLaunch[];
+  extern const char kLaunchContainer[];
+  extern const char kLaunchHeight[];
+  extern const char kLaunchLocalPath[];
+  extern const char kLaunchMaxHeight[];
+  extern const char kLaunchMaxWidth[];
+  extern const char kLaunchMinHeight[];
+  extern const char kLaunchMinWidth[];
+  extern const char kLaunchWebURL[];
+  extern const char kLaunchWidth[];
+  extern const char kLayouts[];
+  extern const char kManifestVersion[];
+  extern const char kMatches[];
+  extern const char kMinimumChromeVersion[];
+  extern const char kNaClModules[];
+  extern const char kNaClModulesMIMEType[];
+  extern const char kNaClModulesPath[];
+  extern const char kName[];
+  extern const char kOAuth2[];
+  extern const char kOAuth2ClientId[];
+  extern const char kOAuth2Scopes[];
+  extern const char kOfflineEnabled[];
+  extern const char kOmnibox[];
+  extern const char kOmniboxKeyword[];
+  extern const char kOptionalPermissions[];
+  extern const char kOptionsPage[];
+  extern const char kPageAction[];
+  extern const char kPageActionDefaultIcon[];
+  extern const char kPageActionDefaultPopup[];
+  extern const char kPageActionDefaultTitle[];
+  extern const char kPageActionIcons[];
+  extern const char kPageActionId[];
+  extern const char kPageActionPopup[];
+  extern const char kPageActionPopupPath[];
+  extern const char kPageActions[];
+  extern const char kPermissions[];
+  extern const char kPlatformAppBackground[];
+  extern const char kPlatformAppBackgroundPage[];
+  extern const char kPlatformAppBackgroundScripts[];
+  extern const char kPlatformAppContentSecurityPolicy[];
+  extern const char kPlugins[];
+  extern const char kPluginsPath[];
+  extern const char kPluginsPublic[];
+  extern const char kPublicKey[];
+  extern const char kRequirements[];
+  extern const char kRunAt[];
+  extern const char kSandboxedPages[];
+  extern const char kSandboxedPagesCSP[];
+  extern const char kScriptBadge[];
+  extern const char kShiftKey[];
+  extern const char kShortcutKey[];
+  extern const char kSignature[];
+  extern const char kSuggestedKey[];
+  extern const char kTheme[];
+  extern const char kThemeColors[];
+  extern const char kThemeDisplayProperties[];
+  extern const char kThemeImages[];
+  extern const char kThemeTints[];
+  extern const char kTtsEngine[];
+  extern const char kTtsGenderFemale[];
+  extern const char kTtsGenderMale[];
+  extern const char kTtsVoices[];
+  extern const char kTtsVoicesEventTypeEnd[];
+  extern const char kTtsVoicesEventTypeError[];
+  extern const char kTtsVoicesEventTypeMarker[];
+  extern const char kTtsVoicesEventTypeSentence[];
+  extern const char kTtsVoicesEventTypeStart[];
+  extern const char kTtsVoicesEventTypeWord[];
+  extern const char kTtsVoicesEventTypes[];
+  extern const char kTtsVoicesGender[];
+  extern const char kTtsVoicesLang[];
+  extern const char kTtsVoicesVoiceName[];
+  extern const char kType[];
+  extern const char kUpdateURL[];
+  extern const char kVersion[];
+  extern const char kWebAccessibleResources[];
+  extern const char kWebURLs[];
+}  // namespace extension_manifest_keys
+
+// Some values expected in manifests.
+namespace extension_manifest_values {
+  extern const char kBrowserActionCommandEvent[];
+  extern const char kIncognitoSplit[];
+  extern const char kIncognitoSpanning[];
+  extern const char kIntentDispositionWindow[];
+  extern const char kIntentDispositionInline[];
+  extern const char kIsolatedStorage[];
+  extern const char kKeybindingPlatformChromeOs[];
+  extern const char kKeybindingPlatformDefault[];
+  extern const char kKeybindingPlatformLinux[];
+  extern const char kKeybindingPlatformMac[];
+  extern const char kKeybindingPlatformWin[];
+  extern const char kLaunchContainerPanel[];
+  extern const char kLaunchContainerTab[];
+  extern const char kLaunchContainerWindow[];
+  extern const char kPageActionCommandEvent[];
+  extern const char kPageActionTypePermanent[];
+  extern const char kPageActionTypeTab[];
+  extern const char kScriptBadgeCommandEvent[];
+  extern const char kRunAtDocumentEnd[];
+  extern const char kRunAtDocumentIdle[];
+  extern const char kRunAtDocumentStart[];
+}  // namespace extension_manifest_values
+
+// Error messages returned from Extension::InitFromValue().
+namespace extension_manifest_errors {
+  extern const char kAppsNotEnabled[];
+  extern const char kBackgroundPermissionNeeded[];
+  extern const char kBackgroundRequiredForPlatformApps[];
+  extern const char kCannotAccessPage[];
+  extern const char kCannotChangeExtensionID[];
+  extern const char kCannotClaimAllHostsInExtent[];
+  extern const char kCannotClaimAllURLsInExtent[];
+  extern const char kCannotScriptGallery[];
+  extern const char kCannotUninstallManagedExtension[];
+  extern const char kChromeVersionTooLow[];
+  extern const char kDevToolsExperimental[];
+  extern const char kDisabledByPolicy[];
+  extern const char kExperimentalFlagRequired[];
+  extern const char kExpectString[];
+  extern const char kInvalidAllFrames[];
+  extern const char kInvalidBackground[];
+  extern const char kInvalidBackgroundAllowJsAccess[];
+  extern const char kInvalidBackgroundCombination[];
+  extern const char kInvalidBackgroundScript[];
+  extern const char kInvalidBackgroundScripts[];
+  extern const char kInvalidBackgroundInHostedApp[];
+  extern const char kInvalidBackgroundPersistent[];
+  extern const char kInvalidBackgroundPersistentNoPage[];
+  extern const char kInvalidBrowserAction[];
+  extern const char kInvalidBrowseURL[];
+  extern const char kInvalidBrowseURLs[];
+  extern const char kInvalidChromeURLOverrides[];
+  extern const char kInvalidCommandsKey[];
+  extern const char kInvalidContentScript[];
+  extern const char kInvalidContentScriptsList[];
+  extern const char kInvalidContentSecurityPolicy[];
+  extern const char kInvalidCss[];
+  extern const char kInvalidCssList[];
+  extern const char kInvalidDefaultLocale[];
+  extern const char kInvalidDescription[];
+  extern const char kInvalidDevToolsPage[];
+  extern const char kInvalidDisplayInLauncher[];
+  extern const char kInvalidDisplayInNewTabPage[];
+  extern const char kInvalidExcludeMatch[];
+  extern const char kInvalidExcludeMatches[];
+  extern const char kInvalidFileAccessList[];
+  extern const char kInvalidFileAccessValue[];
+  extern const char kInvalidFileBrowserHandler[];
+  extern const char kInvalidFileFiltersList[];
+  extern const char kInvalidFileFilterValue[];
+  extern const char kInvalidFileHandlers[];
+  extern const char kInvalidFileHandlerTitle[];
+  extern const char kInvalidFileHandlerType[];
+  extern const char kInvalidFileHandlerTypeElement[];
+  extern const char kInvalidGlob[];
+  extern const char kInvalidGlobList[];
+  extern const char kInvalidHomepageURL[];
+  extern const char kInvalidIconPath[];
+  extern const char kInvalidIcons[];
+  extern const char kInvalidIncognitoBehavior[];
+  extern const char kInvalidIncognitoModeForPlatformApp[];
+  extern const char kInvalidInputComponents[];
+  extern const char kInvalidInputComponentDescription[];
+  extern const char kInvalidInputComponentLayoutName[];
+  extern const char kInvalidInputComponentLayouts[];
+  extern const char kInvalidInputComponentName[];
+  extern const char kInvalidInputComponentShortcutKey[];
+  extern const char kInvalidInputComponentShortcutKeycode[];
+  extern const char kInvalidInputComponentType[];
+  extern const char kInvalidIntent[];
+  extern const char kInvalidIntentDisposition[];
+  extern const char kInvalidIntentDispositionInPlatformApp[];
+  extern const char kInvalidIntentHref[];
+  extern const char kInvalidIntentHrefEmpty[];
+  extern const char kInvalidIntentHrefInPlatformApp[];
+  extern const char kInvalidIntentHrefOldAndNewKey[];
+  extern const char kInvalidIntentPageInHostedApp[];
+  extern const char kInvalidIntents[];
+  extern const char kInvalidIntentType[];
+  extern const char kInvalidIntentTypeElement[];
+  extern const char kInvalidIntentTitle[];
+  extern const char kInvalidIsolation[];
+  extern const char kInvalidIsolationValue[];
+  extern const char kInvalidJs[];
+  extern const char kInvalidJsList[];
+  extern const char kInvalidKey[];
+  extern const char kInvalidKeyBinding[];
+  extern const char kInvalidKeyBindingDescription[];
+  extern const char kInvalidKeyBindingDictionary[];
+  extern const char kInvalidKeyBindingMissingPlatform[];
+  extern const char kInvalidKeyBindingTooMany[];
+  extern const char kInvalidKeyBindingUnknownPlatform[];
+  extern const char kInvalidLaunchContainer[];
+  extern const char kInvalidLaunchValue[];
+  extern const char kInvalidLaunchValueContainer[];
+  extern const char kInvalidManifest[];
+  extern const char kInvalidManifestVersion[];
+  extern const char kInvalidManifestVersionOld[];
+  extern const char kInvalidMatch[];
+  extern const char kInvalidMatchCount[];
+  extern const char kInvalidMatches[];
+  extern const char kInvalidMinimumChromeVersion[];
+  extern const char kInvalidNaClModules[];
+  extern const char kInvalidNaClModulesMIMEType[];
+  extern const char kInvalidNaClModulesPath[];
+  extern const char kInvalidName[];
+  extern const char kInvalidOAuth2ClientId[];
+  extern const char kInvalidOAuth2Scopes[];
+  extern const char kInvalidOfflineEnabled[];
+  extern const char kInvalidOmniboxKeyword[];
+  extern const char kInvalidOptionsPage[];
+  extern const char kInvalidOptionsPageExpectUrlInPackage[];
+  extern const char kInvalidOptionsPageInHostedApp[];
+  extern const char kInvalidPageAction[];
+  extern const char kInvalidPageActionDefaultTitle[];
+  extern const char kInvalidPageActionIconPath[];
+  extern const char kInvalidPageActionId[];
+  extern const char kInvalidPageActionName[];
+  extern const char kInvalidPageActionOldAndNewKeys[];
+  extern const char kInvalidPageActionPopup[];
+  extern const char kInvalidPageActionPopupHeight[];
+  extern const char kInvalidPageActionPopupPath[];
+  extern const char kInvalidPageActionsList[];
+  extern const char kInvalidPageActionsListSize[];
+  extern const char kInvalidPageActionTypeValue[];
+  extern const char kInvalidPermission[];
+  extern const char kInvalidPermissions[];
+  extern const char kInvalidPermissionScheme[];
+  extern const char kInvalidPlugins[];
+  extern const char kInvalidPluginsPath[];
+  extern const char kInvalidPluginsPublic[];
+  extern const char kInvalidRequirement[];
+  extern const char kInvalidRequirements[];
+  extern const char kInvalidRunAt[];
+  extern const char kInvalidSandboxedPagesList[];
+  extern const char kInvalidSandboxedPage[];
+  extern const char kInvalidSandboxedPagesCSP[];
+  extern const char kInvalidScriptBadge[];
+  extern const char kInvalidSignature[];
+  extern const char kInvalidTheme[];
+  extern const char kInvalidThemeColors[];
+  extern const char kInvalidThemeImages[];
+  extern const char kInvalidThemeImagesMissing[];
+  extern const char kInvalidThemeTints[];
+  extern const char kInvalidTts[];
+  extern const char kInvalidTtsVoices[];
+  extern const char kInvalidTtsVoicesEventTypes[];
+  extern const char kInvalidTtsVoicesGender[];
+  extern const char kInvalidTtsVoicesLang[];
+  extern const char kInvalidTtsVoicesVoiceName[];
+  extern const char kInvalidUpdateURL[];
+  extern const char kInvalidURLPatternError[];
+  extern const char kInvalidVersion[];
+  extern const char kInvalidWebAccessibleResourcesList[];
+  extern const char kInvalidWebAccessibleResource[];
+  extern const char kInvalidWebURL[];
+  extern const char kInvalidWebURLs[];
+  extern const char kInvalidZipHash[];
+  extern const char kInsecureContentSecurityPolicy[];
+  extern const char kLaunchPathAndExtentAreExclusive[];
+  extern const char kLaunchPathAndURLAreExclusive[];
+  extern const char kLaunchURLRequired[];
+  extern const char kLocalesMessagesFileMissing[];
+  extern const char kLocalesNoDefaultLocaleSpecified[];
+  extern const char kLocalesNoDefaultMessages[];
+  extern const char kLocalesNoValidLocaleNamesListed[];
+  extern const char kLocalesTreeMissing[];
+  extern const char kManifestParseError[];
+  extern const char kManifestUnreadable[];
+  extern const char kMissingFile[];
+  extern const char kMultipleOverrides[];
+  extern const char kNoWildCardsInPaths[];
+  extern const char kPermissionNotAllowed[];
+  extern const char kPlatformAppNeedsManifestVersion2[];
+  extern const char kOneUISurfaceOnly[];
+  extern const char kReservedMessageFound[];
+  extern const char kScriptBadgeRequiresFlag[];
+  extern const char kScriptBadgeIconIgnored[];
+  extern const char kScriptBadgeTitleIgnored[];
+  extern const char kWebRequestConflictsWithLazyBackground[];
+#if defined(OS_CHROMEOS)
+  extern const char kIllegalPlugins[];
+#endif
+}  // namespace extension_manifest_errors
+
+#endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_MANIFEST_CONSTANTS_H_
diff --git a/chrome/common/extensions/extension_messages.cc b/chrome/common/extensions/extension_messages.cc
new file mode 100644
index 0000000..f7bc04a
--- /dev/null
+++ b/chrome/common/extensions/extension_messages.cc
@@ -0,0 +1,238 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_messages.h"
+
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/extensions/manifest.h"
+#include "chrome/common/extensions/permissions/permissions_info.h"
+#include "content/public/common/common_param_traits.h"
+
+using extensions::APIPermission;
+using extensions::APIPermissionInfo;
+using extensions::APIPermissionMap;
+using extensions::APIPermissionSet;
+using extensions::Extension;
+using extensions::PermissionSet;
+using extensions::SocketPermissionData;
+
+ExtensionMsg_Loaded_Params::ExtensionMsg_Loaded_Params()
+    : location(Extension::INVALID),
+      creation_flags(Extension::NO_FLAGS){}
+
+ExtensionMsg_Loaded_Params::~ExtensionMsg_Loaded_Params() {}
+
+ExtensionMsg_Loaded_Params::ExtensionMsg_Loaded_Params(
+    const Extension* extension)
+    : manifest(extension->manifest()->value()->DeepCopy()),
+      location(extension->location()),
+      path(extension->path()),
+      apis(extension->GetActivePermissions()->apis()),
+      explicit_hosts(extension->GetActivePermissions()->explicit_hosts()),
+      scriptable_hosts(extension->GetActivePermissions()->scriptable_hosts()),
+      id(extension->id()),
+      creation_flags(extension->creation_flags()) {
+}
+
+scoped_refptr<Extension>
+    ExtensionMsg_Loaded_Params::ConvertToExtension() const {
+  std::string error;
+
+  scoped_refptr<Extension> extension(
+      Extension::Create(path, location, *manifest, creation_flags,
+                        &error));
+  if (!extension.get()) {
+    DLOG(ERROR) << "Error deserializing extension: " << error;
+    return extension;
+  }
+
+  extension->SetActivePermissions(
+        new PermissionSet(apis, explicit_hosts, scriptable_hosts));
+
+  return extension;
+}
+
+namespace IPC {
+
+template <>
+struct ParamTraits<Extension::Location> {
+  typedef Extension::Location param_type;
+  static void Write(Message* m, const param_type& p) {
+    int val = static_cast<int>(p);
+    WriteParam(m, val);
+  }
+  static bool Read(const Message* m, PickleIterator* iter, param_type* p) {
+    int val = 0;
+    if (!ReadParam(m, iter, &val) ||
+        val < Extension::INVALID ||
+        val >= Extension::NUM_LOCATIONS)
+      return false;
+    *p = static_cast<param_type>(val);
+    return true;
+  }
+  static void Log(const param_type& p, std::string* l) {
+    ParamTraits<int>::Log(static_cast<int>(p), l);
+  }
+};
+
+void ParamTraits<URLPattern>::Write(Message* m, const param_type& p) {
+  WriteParam(m, p.valid_schemes());
+  WriteParam(m, p.GetAsString());
+}
+
+bool ParamTraits<URLPattern>::Read(const Message* m, PickleIterator* iter,
+                                   param_type* p) {
+  int valid_schemes;
+  std::string spec;
+  if (!ReadParam(m, iter, &valid_schemes) ||
+      !ReadParam(m, iter, &spec))
+    return false;
+
+  // TODO(jstritar): We don't want the URLPattern to fail parsing when the
+  // scheme is invalid. Instead, the pattern should parse but it should not
+  // match the invalid patterns. We get around this by setting the valid
+  // schemes after parsing the pattern. Update these method calls once we can
+  // ignore scheme validation with URLPattern parse options. crbug.com/90544
+  p->SetValidSchemes(URLPattern::SCHEME_ALL);
+  URLPattern::ParseResult result = p->Parse(spec);
+  p->SetValidSchemes(valid_schemes);
+  return URLPattern::PARSE_SUCCESS == result;
+}
+
+void ParamTraits<URLPattern>::Log(const param_type& p, std::string* l) {
+  LogParam(p.GetAsString(), l);
+}
+
+void ParamTraits<URLPatternSet>::Write(Message* m, const param_type& p) {
+  WriteParam(m, p.patterns());
+}
+
+bool ParamTraits<URLPatternSet>::Read(const Message* m, PickleIterator* iter,
+                                        param_type* p) {
+  std::set<URLPattern> patterns;
+  if (!ReadParam(m, iter, &patterns))
+    return false;
+
+  for (std::set<URLPattern>::iterator i = patterns.begin();
+       i != patterns.end(); ++i)
+    p->AddPattern(*i);
+  return true;
+}
+
+void ParamTraits<URLPatternSet>::Log(const param_type& p, std::string* l) {
+  LogParam(p.patterns(), l);
+}
+
+void ParamTraits<APIPermission::ID>::Write(
+    Message* m, const param_type& p) {
+  WriteParam(m, static_cast<int>(p));
+}
+
+bool ParamTraits<APIPermission::ID>::Read(
+    const Message* m, PickleIterator* iter, param_type* p) {
+  int api_id = -2;
+  if (!ReadParam(m, iter, &api_id))
+    return false;
+
+  *p = static_cast<APIPermission::ID>(api_id);
+  return true;
+}
+
+void ParamTraits<APIPermission::ID>::Log(
+    const param_type& p, std::string* l) {
+  LogParam(static_cast<int>(p), l);
+}
+
+void ParamTraits<APIPermission*>::Log(
+    const param_type& p, std::string* l) {
+  p->Log(l);
+}
+
+void ParamTraits<APIPermissionSet>::Write(
+    Message* m, const param_type& p) {
+  APIPermissionSet::const_iterator it = p.begin();
+  const APIPermissionSet::const_iterator end = p.end();
+  WriteParam(m, p.size());
+  for (; it != end; ++it) {
+    WriteParam(m, it->id());
+    it->Write(m);
+  }
+}
+
+bool ParamTraits<APIPermissionSet>::Read(
+    const Message* m, PickleIterator* iter, param_type* r) {
+  size_t size;
+  if (!ReadParam(m, iter, &size))
+    return false;
+  for (size_t i = 0; i < size; ++i) {
+    APIPermission::ID id;
+    if (!ReadParam(m, iter, &id))
+      return false;
+    const APIPermissionInfo* permission_info =
+      extensions::PermissionsInfo::GetInstance()->GetByID(id);
+    if (!permission_info)
+      return false;
+    scoped_ptr<APIPermission> p(permission_info->CreateAPIPermission());
+    if (!p->Read(m, iter))
+      return false;
+    r->insert(p.release());
+  }
+  return true;
+}
+
+void ParamTraits<APIPermissionSet>::Log(
+    const param_type& p, std::string* l) {
+  LogParam(p.map(), l);
+}
+
+void ParamTraits<SocketPermissionData>::Write(
+    Message* m, const param_type& p) {
+  WriteParam(m, p.GetAsString());
+}
+
+bool ParamTraits<SocketPermissionData>::Read(
+    const Message* m, PickleIterator* iter, param_type* r) {
+  std::string spec;
+  if (!ReadParam(m, iter, &spec))
+    return false;
+
+  return r->Parse(spec);
+}
+
+void ParamTraits<SocketPermissionData>::Log(
+    const param_type& p, std::string* l) {
+  LogParam(std::string("<SocketPermissionData>"), l);
+}
+
+void ParamTraits<ExtensionMsg_Loaded_Params>::Write(Message* m,
+                                                    const param_type& p) {
+  WriteParam(m, p.location);
+  WriteParam(m, p.path);
+  WriteParam(m, *(p.manifest));
+  WriteParam(m, p.creation_flags);
+  WriteParam(m, p.apis);
+  WriteParam(m, p.explicit_hosts);
+  WriteParam(m, p.scriptable_hosts);
+}
+
+bool ParamTraits<ExtensionMsg_Loaded_Params>::Read(const Message* m,
+                                                   PickleIterator* iter,
+                                                   param_type* p) {
+  p->manifest.reset(new DictionaryValue());
+  return ReadParam(m, iter, &p->location) &&
+         ReadParam(m, iter, &p->path) &&
+         ReadParam(m, iter, p->manifest.get()) &&
+         ReadParam(m, iter, &p->creation_flags) &&
+         ReadParam(m, iter, &p->apis) &&
+         ReadParam(m, iter, &p->explicit_hosts) &&
+         ReadParam(m, iter, &p->scriptable_hosts);
+}
+
+void ParamTraits<ExtensionMsg_Loaded_Params>::Log(const param_type& p,
+                                                  std::string* l) {
+  l->append(p.id);
+}
+
+}  // namespace IPC
diff --git a/chrome/common/extensions/extension_messages.h b/chrome/common/extensions/extension_messages.h
new file mode 100644
index 0000000..a20301e
--- /dev/null
+++ b/chrome/common/extensions/extension_messages.h
@@ -0,0 +1,556 @@
+// Copyright (c) 2012 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.
+
+// IPC messages for extensions.
+// Multiply-included message file, hence no include guard.
+
+#include "base/shared_memory.h"
+#include "base/values.h"
+#include "chrome/common/extensions/draggable_region.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/permissions/permission_set.h"
+#include "chrome/common/extensions/permissions/socket_permission_data.h"
+#include "chrome/common/extensions/url_pattern.h"
+#include "chrome/common/extensions/url_pattern_set.h"
+#include "chrome/common/view_type.h"
+#include "chrome/common/web_apps.h"
+#include "content/public/common/common_param_traits.h"
+#include "googleurl/src/gurl.h"
+#include "ipc/ipc_message_macros.h"
+
+#define IPC_MESSAGE_START ExtensionMsgStart
+
+IPC_ENUM_TRAITS(chrome::ViewType)
+
+// Parameters structure for ExtensionHostMsg_Request.
+IPC_STRUCT_BEGIN(ExtensionHostMsg_Request_Params)
+  // Message name.
+  IPC_STRUCT_MEMBER(std::string, name)
+
+  // List of message arguments.
+  IPC_STRUCT_MEMBER(ListValue, arguments)
+
+  // Extension ID this request was sent from. This can be empty, in the case
+  // where we expose APIs to normal web pages using the extension function
+  // system.
+  IPC_STRUCT_MEMBER(std::string, extension_id)
+
+  // URL of the frame the request was sent from. This isn't necessarily an
+  // extension url. Extension requests can also originate from content scripts,
+  // in which case extension_id will indicate the ID of the associated
+  // extension. Or, they can originate from hosted apps or normal web pages.
+  IPC_STRUCT_MEMBER(GURL, source_url)
+
+  // Web security origin of the frame the request was sent from.
+  IPC_STRUCT_MEMBER(string16, source_origin)
+
+  // Unique request id to match requests and responses.
+  IPC_STRUCT_MEMBER(int, request_id)
+
+  // True if request has a callback specified.
+  IPC_STRUCT_MEMBER(bool, has_callback)
+
+  // True if request is executed in response to an explicit user gesture.
+  IPC_STRUCT_MEMBER(bool, user_gesture)
+IPC_STRUCT_END()
+
+// Allows an extension to execute code in a tab.
+IPC_STRUCT_BEGIN(ExtensionMsg_ExecuteCode_Params)
+  // The extension API request id, for responding.
+  IPC_STRUCT_MEMBER(int, request_id)
+
+  // The ID of the requesting extension. To know which isolated world to
+  // execute the code inside of.
+  IPC_STRUCT_MEMBER(std::string, extension_id)
+
+  // Whether the code is JavaScript or CSS.
+  IPC_STRUCT_MEMBER(bool, is_javascript)
+
+  // String of code to execute.
+  IPC_STRUCT_MEMBER(std::string, code)
+
+  // Whether to inject into all frames, or only the root frame.
+  IPC_STRUCT_MEMBER(bool, all_frames)
+
+  // When to inject the code.
+  IPC_STRUCT_MEMBER(int, run_at)
+
+  // Whether to execute code in the main world (as opposed to an isolated
+  // world).
+  IPC_STRUCT_MEMBER(bool, in_main_world)
+IPC_STRUCT_END()
+
+IPC_STRUCT_TRAITS_BEGIN(WebApplicationInfo::IconInfo)
+  IPC_STRUCT_TRAITS_MEMBER(url)
+  IPC_STRUCT_TRAITS_MEMBER(width)
+  IPC_STRUCT_TRAITS_MEMBER(height)
+  IPC_STRUCT_TRAITS_MEMBER(data)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(WebApplicationInfo)
+  IPC_STRUCT_TRAITS_MEMBER(title)
+  IPC_STRUCT_TRAITS_MEMBER(description)
+  IPC_STRUCT_TRAITS_MEMBER(app_url)
+  IPC_STRUCT_TRAITS_MEMBER(icons)
+  IPC_STRUCT_TRAITS_MEMBER(permissions)
+  IPC_STRUCT_TRAITS_MEMBER(launch_container)
+  IPC_STRUCT_TRAITS_MEMBER(is_offline_enabled)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(extensions::DraggableRegion)
+  IPC_STRUCT_TRAITS_MEMBER(draggable)
+  IPC_STRUCT_TRAITS_MEMBER(bounds)
+IPC_STRUCT_TRAITS_END()
+
+// Singly-included section for custom IPC traits.
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_MESSAGES_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_MESSAGES_H_
+
+// IPC_MESSAGE macros choke on extra , in the std::map, when expanding. We need
+// to typedef it to avoid that.
+// Substitution map for l10n messages.
+typedef std::map<std::string, std::string> SubstitutionMap;
+
+// Map of extensions IDs to the executing script paths.
+typedef std::map<std::string, std::set<std::string> > ExecutingScriptsMap;
+
+struct ExtensionMsg_Loaded_Params {
+  ExtensionMsg_Loaded_Params();
+  ~ExtensionMsg_Loaded_Params();
+  explicit ExtensionMsg_Loaded_Params(const extensions::Extension* extension);
+
+  // Creates a new extension from the data in this object.
+  scoped_refptr<extensions::Extension> ConvertToExtension() const;
+
+  // The subset of the extension manifest data we send to renderers.
+  linked_ptr<DictionaryValue> manifest;
+
+  // The location the extension was installed from.
+  extensions::Extension::Location location;
+
+  // The path the extension was loaded from. This is used in the renderer only
+  // to generate the extension ID for extensions that are loaded unpacked.
+  FilePath path;
+
+  // The extension's active permissions.
+  extensions::APIPermissionSet apis;
+  URLPatternSet explicit_hosts;
+  URLPatternSet scriptable_hosts;
+
+  // We keep this separate so that it can be used in logging.
+  std::string id;
+
+  // Send creation flags so extension is initialized identically.
+  int creation_flags;
+};
+
+namespace IPC {
+
+template <>
+struct ParamTraits<URLPattern> {
+  typedef URLPattern param_type;
+  static void Write(Message* m, const param_type& p);
+  static bool Read(const Message* m, PickleIterator* iter, param_type* p);
+  static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<URLPatternSet> {
+  typedef URLPatternSet param_type;
+  static void Write(Message* m, const param_type& p);
+  static bool Read(const Message* m, PickleIterator* iter, param_type* p);
+  static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<extensions::APIPermission::ID> {
+  typedef extensions::APIPermission::ID param_type;
+  static void Write(Message* m, const param_type& p);
+  static bool Read(const Message* m, PickleIterator* iter, param_type* p);
+  static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<extensions::APIPermission*> {
+  typedef extensions::APIPermission* param_type;
+  static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<extensions::APIPermissionSet> {
+  typedef extensions::APIPermissionSet param_type;
+  static void Write(Message* m, const param_type& p);
+  static bool Read(const Message* m, PickleIterator* iter, param_type* r);
+  static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<extensions::SocketPermissionData> {
+  typedef extensions::SocketPermissionData param_type;
+  static void Write(Message* m, const param_type& p);
+  static bool Read(const Message* m, PickleIterator* iter, param_type* r);
+  static void Log(const param_type& p, std::string* l);
+};
+
+template <>
+struct ParamTraits<ExtensionMsg_Loaded_Params> {
+  typedef ExtensionMsg_Loaded_Params param_type;
+  static void Write(Message* m, const param_type& p);
+  static bool Read(const Message* m, PickleIterator* iter, param_type* p);
+  static void Log(const param_type& p, std::string* l);
+};
+
+}  // namespace IPC
+
+#endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_MESSAGES_H_
+
+// Messages sent from the browser to the renderer.
+
+// The browser sends this message in response to all extension api calls. The
+// response data (if any) is one of the base::Value subclasses, wrapped as the
+// first element in a ListValue.
+IPC_MESSAGE_ROUTED4(ExtensionMsg_Response,
+                    int /* request_id */,
+                    bool /* success */,
+                    ListValue /* response wrapper (see comment above) */,
+                    std::string /* error */)
+
+// This message is optionally routed.  If used as a control message, it
+// will call a javascript function in every registered context in the
+// target process.  If routed, it will be restricted to the contexts that
+// are part of the target RenderView.
+// If |extension_id| is non-empty, the function will be invoked only in
+// contexts owned by the extension. |args| is a list of primitive Value types
+// that are passed to the function.
+IPC_MESSAGE_ROUTED5(ExtensionMsg_MessageInvoke,
+                    std::string /* extension_id */,
+                    std::string /* function_name */,
+                    ListValue /* args */,
+                    GURL /* event URL */,
+                    bool /* delivered as part of a user gesture */)
+
+// Tell the renderer process all known extension function names.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_SetFunctionNames,
+                     std::vector<std::string>)
+
+// Marks an extension as 'active' in an extension process. 'Active' extensions
+// have more privileges than other extension content that might end up running
+// in the process (e.g. because of iframes or content scripts).
+IPC_MESSAGE_CONTROL1(ExtensionMsg_ActivateExtension,
+                     std::string /* extension_id */)
+
+// Notifies the renderer that extensions were loaded in the browser.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_Loaded,
+                     std::vector<ExtensionMsg_Loaded_Params>)
+
+// Notifies the renderer that an extension was unloaded in the browser.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_Unloaded,
+                     std::string)
+
+// Updates the scripting whitelist for extensions in the render process. This is
+// only used for testing.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_SetScriptingWhitelist,
+                     // extension ids
+                     extensions::Extension::ScriptingWhitelist)
+
+// Notification that renderer should run some JavaScript code.
+IPC_MESSAGE_ROUTED1(ExtensionMsg_ExecuteCode,
+                    ExtensionMsg_ExecuteCode_Params)
+
+// Notification that the user scripts have been updated. It has one
+// SharedMemoryHandle argument consisting of the pickled script data. This
+// handle is valid in the context of the renderer.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_UpdateUserScripts,
+                     base::SharedMemoryHandle)
+
+// Requests application info for the page. The renderer responds back with
+// ExtensionHostMsg_DidGetApplicationInfo.
+IPC_MESSAGE_ROUTED1(ExtensionMsg_GetApplicationInfo,
+                    int32 /*page_id*/)
+
+// Tell the render view which browser window it's being attached to.
+IPC_MESSAGE_ROUTED1(ExtensionMsg_UpdateBrowserWindowId,
+                    int /* id of browser window */)
+
+// Tell the render view what its tab ID is.
+IPC_MESSAGE_ROUTED1(ExtensionMsg_SetTabId,
+                    int /* id of tab */)
+
+// Tell the renderer to update an extension's permission set.
+IPC_MESSAGE_CONTROL5(ExtensionMsg_UpdatePermissions,
+                     int /* UpdateExtensionPermissionsInfo::REASON */,
+                     std::string /* extension_id */,
+                     extensions::APIPermissionSet /* permissions */,
+                     URLPatternSet /* explicit_hosts */,
+                     URLPatternSet /* scriptable_hosts */)
+
+// Tell the renderer about new tab-specific permissions for an extension.
+IPC_MESSAGE_CONTROL4(ExtensionMsg_UpdateTabSpecificPermissions,
+                     int32 /* page_id (only relevant for the target tab) */,
+                     int /* tab_id */,
+                     std::string /* extension_id */,
+                     URLPatternSet /* hosts */)
+
+// Tell the renderer to clear tab-specific permissions for some extensions.
+IPC_MESSAGE_CONTROL2(ExtensionMsg_ClearTabSpecificPermissions,
+                     int /* tab_id */,
+                     std::vector<std::string> /* extension_ids */)
+
+// Tell the renderer which type this view is.
+IPC_MESSAGE_ROUTED1(ExtensionMsg_NotifyRenderViewType,
+                    chrome::ViewType /* view_type */)
+
+// Deliver a message sent with ExtensionHostMsg_PostMessage.
+IPC_MESSAGE_CONTROL3(ExtensionMsg_UsingWebRequestAPI,
+                     bool /* adblock */,
+                     bool /* adblock_plus */,
+                     bool /* other_webrequest */)
+
+// Ask the lazy background page if it is ready to unload. This is sent when the
+// page is considered idle. The renderer will reply with the same sequence_id
+// so that we can tell which message it is responding to.
+IPC_MESSAGE_CONTROL2(ExtensionMsg_ShouldUnload,
+                     std::string /* extension_id */,
+                     int /* sequence_id */)
+
+// If we complete a round of ShouldUnload->ShouldUnloadAck messages without the
+// lazy background page becoming active again, we are ready to unload. This
+// message tells the page to dispatch the unload event.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_Unload,
+                     std::string /* extension_id */)
+
+// The browser changed its mind about unloading this extension.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_CancelUnload,
+                     std::string /* extension_id */)
+
+// Send to renderer once the installation mentioned on
+// ExtensionHostMsg_InlineWebstoreInstall is complete.
+IPC_MESSAGE_ROUTED3(ExtensionMsg_InlineWebstoreInstallResponse,
+                    int32 /* install id */,
+                    bool /* whether the install was successful */,
+                    std::string /* error */)
+
+// Response to the renderer for ExtensionHostMsg_GetAppNotifyChannel.
+IPC_MESSAGE_ROUTED3(ExtensionMsg_GetAppNotifyChannelResponse,
+                    std::string /* channel_id */,
+                    std::string /* error */,
+                    int32 /* callback_id */)
+
+// Response to the renderer for ExtensionHostMsg_GetAppInstallState.
+IPC_MESSAGE_ROUTED2(ExtensionMsg_GetAppInstallStateResponse,
+                    std::string /* state */,
+                    int32 /* callback_id */)
+
+// Dispatch the Port.onConnect event for message channels.
+IPC_MESSAGE_ROUTED5(ExtensionMsg_DispatchOnConnect,
+                    int /* target_port_id */,
+                    std::string /* channel_name */,
+                    std::string /* tab_json */,
+                    std::string /* source_extension_id */,
+                    std::string /* target_extension_id */)
+
+// Deliver a message sent with ExtensionHostMsg_PostMessage.
+IPC_MESSAGE_ROUTED2(ExtensionMsg_DeliverMessage,
+                    int /* target_port_id */,
+                    std::string /* message */)
+
+// Dispatch the Port.onDisconnect event for message channels.
+IPC_MESSAGE_ROUTED2(ExtensionMsg_DispatchOnDisconnect,
+                    int /* port_id */,
+                    bool /* connection_error */)
+
+// Informs the renderer what channel (dev, beta, stable, etc) is running.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_SetChannel,
+                     int /* channel */)
+
+// Adds a logging message to the renderer's root frame DevTools console.
+IPC_MESSAGE_ROUTED2(ExtensionMsg_AddMessageToConsole,
+                    content::ConsoleMessageLevel /* level */,
+                    std::string /* message */)
+
+// Notify the renderer that its window has closed.
+IPC_MESSAGE_ROUTED0(ExtensionMsg_AppWindowClosed)
+
+// Messages sent from the renderer to the browser.
+
+// A renderer sends this message when an extension process starts an API
+// request. The browser will always respond with a ExtensionMsg_Response.
+IPC_MESSAGE_ROUTED1(ExtensionHostMsg_Request,
+                    ExtensionHostMsg_Request_Params)
+
+// A renderer sends this message when an extension process starts an API
+// request. The browser will always respond with a ExtensionMsg_Response.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_RequestForIOThread,
+                     int /* routing_id */,
+                     ExtensionHostMsg_Request_Params)
+
+// Notify the browser that the given extension added a listener to an event.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_AddListener,
+                     std::string /* extension_id */,
+                     std::string /* name */)
+
+// Notify the browser that the given extension removed a listener from an
+// event.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_RemoveListener,
+                     std::string /* extension_id */,
+                     std::string /* name */)
+
+// Notify the browser that the given extension added a listener to an event from
+// a lazy background page.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_AddLazyListener,
+                     std::string /* extension_id */,
+                     std::string /* name */)
+
+// Notify the browser that the given extension is no longer interested in
+// receiving the given event from a lazy background page.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_RemoveLazyListener,
+                     std::string /* extension_id */,
+                     std::string /* name */)
+
+// Notify the browser that the given extension added a listener to instances of
+// the named event that satisfy the filter.
+IPC_MESSAGE_CONTROL4(ExtensionHostMsg_AddFilteredListener,
+                     std::string /* extension_id */,
+                     std::string /* name */,
+                     DictionaryValue /* filter */,
+                     bool /* lazy */)
+
+// Notify the browser that the given extension is no longer interested in
+// instances of the named event that satisfy the filter.
+IPC_MESSAGE_CONTROL4(ExtensionHostMsg_RemoveFilteredListener,
+                     std::string /* extension_id */,
+                     std::string /* name */,
+                     DictionaryValue /* filter */,
+                     bool /* lazy */)
+
+// Notify the browser that an event has finished being dispatched.
+IPC_MESSAGE_ROUTED0(ExtensionHostMsg_EventAck)
+
+// Open a channel to all listening contexts owned by the extension with
+// the given ID.  This always returns a valid port ID which can be used for
+// sending messages.  If an error occurred, the opener will be notified
+// asynchronously.
+IPC_SYNC_MESSAGE_CONTROL4_1(ExtensionHostMsg_OpenChannelToExtension,
+                            int /* routing_id */,
+                            std::string /* source_extension_id */,
+                            std::string /* target_extension_id */,
+                            std::string /* channel_name */,
+                            int /* port_id */)
+
+IPC_SYNC_MESSAGE_CONTROL5_1(ExtensionHostMsg_OpenChannelToNativeApp,
+                            int /* routing_id */,
+                            std::string /* source_extension_id */,
+                            std::string /* native_app_name */,
+                            std::string /* channel_name */,
+                            std::string /* connection_message */,
+                            int /* port_id */)
+
+// Get a port handle to the given tab.  The handle can be used for sending
+// messages to the extension.
+IPC_SYNC_MESSAGE_CONTROL4_1(ExtensionHostMsg_OpenChannelToTab,
+                            int /* routing_id */,
+                            int /* tab_id */,
+                            std::string /* extension_id */,
+                            std::string /* channel_name */,
+                            int /* port_id */)
+
+// Send a message to an extension process.  The handle is the value returned
+// by ViewHostMsg_OpenChannelTo*.
+IPC_MESSAGE_ROUTED2(ExtensionHostMsg_PostMessage,
+                    int /* port_id */,
+                    std::string /* message */)
+
+// Send a message to an extension process.  The handle is the value returned
+// by ViewHostMsg_OpenChannelTo*.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_CloseChannel,
+                     int /* port_id */,
+                     bool /* connection_error */)
+
+// Used to get the extension message bundle.
+IPC_SYNC_MESSAGE_CONTROL1_1(ExtensionHostMsg_GetMessageBundle,
+                            std::string /* extension id */,
+                            SubstitutionMap /* message bundle */)
+
+// Sent from the renderer to the browser to return the script running result.
+IPC_MESSAGE_ROUTED5(ExtensionHostMsg_ExecuteCodeFinished,
+                    int /* request id */,
+                    std::string /* error; empty implies success */,
+                    int32 /* page_id the code executed on.
+                             May be -1 if unsuccessful */,
+                    GURL /* URL of the code executed on.
+                            May be empty if unsuccessful. */,
+                    ListValue /* result of the script */)
+
+// Sent from the renderer to the browser to notify that content scripts are
+// running in the renderer that the IPC originated from.
+// Note that the page_id is for the parent (or more accurately the topmost)
+// frame (e.g. if executing in an iframe this is the page ID of the parent,
+// unless the parent is an iframe... etc).
+IPC_MESSAGE_ROUTED3(ExtensionHostMsg_ContentScriptsExecuting,
+                    ExecutingScriptsMap,
+                    int32 /* page_id of the _topmost_ frame */,
+                    GURL /* url of the _topmost_ frame */)
+
+IPC_MESSAGE_ROUTED2(ExtensionHostMsg_DidGetApplicationInfo,
+                    int32 /* page_id */,
+                    WebApplicationInfo)
+
+// Sent by the renderer to implement chrome.app.install().
+IPC_MESSAGE_ROUTED1(ExtensionHostMsg_InstallApplication,
+                    WebApplicationInfo)
+
+// Sent by the renderer to implement chrome.webstore.install().
+IPC_MESSAGE_ROUTED4(ExtensionHostMsg_InlineWebstoreInstall,
+                    int32 /* install id */,
+                    int32 /* return route id */,
+                    std::string /* Web Store item ID */,
+                    GURL /* requestor URL */)
+
+// Sent by the renderer when an App is requesting permission to send server
+// pushed notifications.
+IPC_MESSAGE_ROUTED4(ExtensionHostMsg_GetAppNotifyChannel,
+                    GURL /* requestor_url */,
+                    std::string /* client_id */,
+                    int32 /* return_route_id */,
+                    int32 /* callback_id */)
+
+// Sent by the renderer when a web page is checking if its app is installed.
+IPC_MESSAGE_ROUTED3(ExtensionHostMsg_GetAppInstallState,
+                    GURL /* requestor_url */,
+                    int32 /* return_route_id */,
+                    int32 /* callback_id */)
+
+// Optional Ack message sent to the browser to notify that the response to a
+// function has been processed.
+IPC_MESSAGE_ROUTED1(ExtensionHostMsg_ResponseAck,
+                    int /* request_id */)
+
+// Response to ExtensionMsg_ShouldUnload.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_ShouldUnloadAck,
+                     std::string /* extension_id */,
+                     int /* sequence_id */)
+
+// Response to ExtensionMsg_Unload, after we dispatch the unload event.
+IPC_MESSAGE_CONTROL1(ExtensionHostMsg_UnloadAck,
+                     std::string /* extension_id */)
+
+// Informs the browser to increment the keepalive count for the lazy background
+// page, keeping it alive.
+IPC_MESSAGE_ROUTED0(ExtensionHostMsg_IncrementLazyKeepaliveCount)
+
+// Informs the browser there is one less thing keeping the lazy background page
+// alive.
+IPC_MESSAGE_ROUTED0(ExtensionHostMsg_DecrementLazyKeepaliveCount)
+
+// Fetches a globally unique ID (for the lifetime of the browser) from the
+// browser process.
+IPC_SYNC_MESSAGE_CONTROL0_1(ExtensionHostMsg_GenerateUniqueID,
+                            int /* unique_id */)
+
+// Resumes resource requests for a newly created app window.
+IPC_MESSAGE_CONTROL1(ExtensionHostMsg_ResumeRequests, int /* route_id */)
+
+// Sent by the renderer when the draggable regions are updated.
+IPC_MESSAGE_ROUTED1(ExtensionHostMsg_UpdateDraggableRegions,
+                    std::vector<extensions::DraggableRegion> /* regions */)
diff --git a/chrome/common/extensions/extension_process_policy.cc b/chrome/common/extensions/extension_process_policy.cc
new file mode 100644
index 0000000..00256c4
--- /dev/null
+++ b/chrome/common/extensions/extension_process_policy.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_process_policy.h"
+
+#include "chrome/common/extensions/extension_set.h"
+
+namespace extensions {
+
+const extensions::Extension* GetNonBookmarkAppExtension(
+    const ExtensionSet& extensions, const ExtensionURLInfo& url) {
+  // Exclude bookmark apps, which do not use the app process model.
+  const extensions::Extension* extension =
+      extensions.GetExtensionOrAppByURL(url);
+  if (extension && extension->from_bookmark())
+    extension = NULL;
+  return extension;
+}
+
+bool CrossesExtensionProcessBoundary(
+    const ExtensionSet& extensions,
+    const ExtensionURLInfo& old_url,
+    const ExtensionURLInfo& new_url,
+    bool should_consider_workaround) {
+  const extensions::Extension* old_url_extension = GetNonBookmarkAppExtension(
+      extensions,
+      old_url);
+  const extensions::Extension* new_url_extension = GetNonBookmarkAppExtension(
+      extensions,
+      new_url);
+
+  // TODO(creis): Temporary workaround for crbug.com/59285: Do not swap process
+  // to navigate from a hosted app to a normal page or another hosted app
+  // (unless either is the web store).  This is because some OAuth providers
+  // use non-app popups that communicate with non-app iframes inside the app
+  // (e.g., Facebook).  This would require out-of-process iframes to support.
+  // See http://crbug.com/99379.
+  // Note that we skip this exception for isolated apps, which require strict
+  // process separation from non-app pages.
+  if (should_consider_workaround) {
+    bool old_url_is_hosted_app = old_url_extension &&
+        !old_url_extension->web_extent().is_empty() &&
+        !old_url_extension->is_storage_isolated();
+    bool new_url_is_normal_or_hosted = !new_url_extension ||
+        (!new_url_extension->web_extent().is_empty() &&
+        !new_url_extension->is_storage_isolated());
+    bool either_is_web_store =
+        (old_url_extension &&
+        old_url_extension->id() == extension_misc::kWebStoreAppId) ||
+        (new_url_extension &&
+        new_url_extension->id() == extension_misc::kWebStoreAppId);
+    if (old_url_is_hosted_app &&
+        new_url_is_normal_or_hosted &&
+        !either_is_web_store)
+      return false;
+  }
+
+  return old_url_extension != new_url_extension;
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/extension_process_policy.h b/chrome/common/extensions/extension_process_policy.h
new file mode 100644
index 0000000..19ad254
--- /dev/null
+++ b/chrome/common/extensions/extension_process_policy.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_PROCESS_POLICY_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_PROCESS_POLICY_H_
+
+class ExtensionSet;
+class ExtensionURLInfo;
+
+namespace extensions {
+
+class Extension;
+
+// Returns the extension for the given URL.  Excludes extension objects for
+// bookmark apps, which do not use the app process model.
+const Extension* GetNonBookmarkAppExtension(const ExtensionSet& extensions,
+                                            const ExtensionURLInfo& url);
+
+// Check if navigating a toplevel page from |old_url| to |new_url| would cross
+// an extension process boundary (e.g. navigating from a web URL into an
+// extension URL).
+// We temporarily consider a workaround where we will keep non-app URLs in
+// an app process, but only if |should_consider_workaround| is true.  See
+// http://crbug.com/59285.
+bool CrossesExtensionProcessBoundary(
+    const ExtensionSet& extensions,
+    const ExtensionURLInfo& old_url,
+    const ExtensionURLInfo& new_url,
+    bool should_consider_workaround);
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_PROCESS_POLICY_H_
diff --git a/chrome/common/extensions/extension_resource.cc b/chrome/common/extensions/extension_resource.cc
new file mode 100644
index 0000000..048d0af
--- /dev/null
+++ b/chrome/common/extensions/extension_resource.cc
@@ -0,0 +1,121 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_resource.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/threading/thread_restrictions.h"
+
+ExtensionResource::ExtensionResource() : follow_symlinks_anywhere_(false) {
+}
+
+ExtensionResource::ExtensionResource(const std::string& extension_id,
+                                     const FilePath& extension_root,
+                                     const FilePath& relative_path)
+    : extension_id_(extension_id),
+      extension_root_(extension_root),
+      relative_path_(relative_path),
+      follow_symlinks_anywhere_(false) {
+}
+
+ExtensionResource::~ExtensionResource() {}
+
+void ExtensionResource::set_follow_symlinks_anywhere() {
+  follow_symlinks_anywhere_ = true;
+}
+
+const FilePath& ExtensionResource::GetFilePath() const {
+  if (extension_root_.empty() || relative_path_.empty()) {
+    DCHECK(full_resource_path_.empty());
+    return full_resource_path_;
+  }
+
+  // We've already checked, just return last value.
+  if (!full_resource_path_.empty())
+    return full_resource_path_;
+
+  full_resource_path_ = GetFilePath(
+      extension_root_, relative_path_,
+      follow_symlinks_anywhere_ ?
+          FOLLOW_SYMLINKS_ANYWHERE : SYMLINKS_MUST_RESOLVE_WITHIN_ROOT);
+  return full_resource_path_;
+}
+
+// static
+FilePath ExtensionResource::GetFilePath(
+    const FilePath& extension_root,
+    const FilePath& relative_path,
+    SymlinkPolicy symlink_policy) {
+  // We need to resolve the parent references in the extension_root
+  // path on its own because IsParent doesn't like parent references.
+  FilePath clean_extension_root(extension_root);
+  if (!file_util::AbsolutePath(&clean_extension_root))
+    return FilePath();
+
+  FilePath full_path = clean_extension_root.Append(relative_path);
+
+  // If we are allowing the file to be a symlink outside of the root, then the
+  // path before resolving the symlink must still be within it.
+  if (symlink_policy == FOLLOW_SYMLINKS_ANYWHERE) {
+    std::vector<FilePath::StringType> components;
+    relative_path.GetComponents(&components);
+    int depth = 0;
+
+    for (std::vector<FilePath::StringType>::const_iterator
+         i = components.begin(); i != components.end(); i++) {
+      if (*i == FilePath::kParentDirectory) {
+        depth--;
+      } else {
+        depth++;
+      }
+      if (depth < 0) {
+        return FilePath();
+      }
+    }
+  }
+
+  // We must resolve the absolute path of the combined path when
+  // the relative path contains references to a parent folder (i.e., '..').
+  // We also check if the path exists because the posix version of AbsolutePath
+  // will fail if the path doesn't exist, and we want the same behavior on
+  // Windows... So until the posix and Windows version of AbsolutePath are
+  // unified, we need an extra call to PathExists, unfortunately.
+  // TODO(mad): Fix this once AbsolutePath is unified.
+  if (file_util::AbsolutePath(&full_path) &&
+      file_util::PathExists(full_path) &&
+      (symlink_policy == FOLLOW_SYMLINKS_ANYWHERE ||
+       clean_extension_root.IsParent(full_path))) {
+    return full_path;
+  }
+
+  return FilePath();
+}
+
+// Unit-testing helpers.
+FilePath::StringType ExtensionResource::NormalizeSeperators(
+    const FilePath::StringType& path) const {
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+  FilePath::StringType win_path = path;
+  for (size_t i = 0; i < win_path.length(); i++) {
+    if (FilePath::IsSeparator(win_path[i]))
+      win_path[i] = FilePath::kSeparators[0];
+  }
+  return win_path;
+#else
+  return path;
+#endif  // FILE_PATH_USES_WIN_SEPARATORS
+}
+
+bool ExtensionResource::ComparePathWithDefault(const FilePath& path) const {
+  // Make sure we have a cached value to test against...
+  if (full_resource_path_.empty())
+    GetFilePath();
+  if (NormalizeSeperators(path.value()) ==
+    NormalizeSeperators(full_resource_path_.value())) {
+    return true;
+  } else {
+    return false;
+  }
+}
diff --git a/chrome/common/extensions/extension_resource.h b/chrome/common/extensions/extension_resource.h
new file mode 100644
index 0000000..36d8218
--- /dev/null
+++ b/chrome/common/extensions/extension_resource.h
@@ -0,0 +1,85 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_RESOURCE_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_RESOURCE_H_
+
+#include <string>
+
+#include "base/file_path.h"
+
+// Represents a resource inside an extension. For example, an image, or a
+// JavaScript file. This is more complicated than just a simple FilePath
+// because extension resources can come from multiple physical file locations
+// depending on locale.
+class ExtensionResource {
+ public:
+  // SymlinkPolicy decides whether we'll allow resources to be a symlink to
+  // anywhere, or whether they must end up within the extension root.
+  enum SymlinkPolicy {
+    SYMLINKS_MUST_RESOLVE_WITHIN_ROOT,
+    FOLLOW_SYMLINKS_ANYWHERE,
+  };
+
+  ExtensionResource();
+
+  ExtensionResource(const std::string& extension_id,
+                    const FilePath& extension_root,
+                    const FilePath& relative_path);
+
+  ~ExtensionResource();
+
+  // set_follow_symlinks_anywhere allows the resource to be a symlink to
+  // anywhere in the filesystem. By default, resources have to be within
+  // |extension_root| after resolving symlinks.
+  void set_follow_symlinks_anywhere();
+
+  // Returns actual path to the resource (default or locale specific). In the
+  // browser process, this will DCHECK if not called on the file thread. To
+  // easily load extension images on the UI thread, see ImageLoadingTracker.
+  const FilePath& GetFilePath() const;
+
+  // Gets the physical file path for the extension resource, taking into account
+  // localization. In the browser process, this will DCHECK if not called on the
+  // file thread. To easily load extension images on the UI thread, see
+  // ImageLoadingTracker.
+  //
+  // The relative path must not resolve to a location outside of
+  // |extension_root|. Iff |file_can_symlink_outside_root| is true, then the
+  // file can be a symlink that links outside of |extension_root|.
+  static FilePath GetFilePath(const FilePath& extension_root,
+                              const FilePath& relative_path,
+                              SymlinkPolicy symlink_policy);
+
+  // Getters
+  const std::string& extension_id() const { return extension_id_; }
+  const FilePath& extension_root() const { return extension_root_; }
+  const FilePath& relative_path() const { return relative_path_; }
+
+  bool empty() const { return extension_root().empty(); }
+
+  // Unit test helpers.
+  FilePath::StringType NormalizeSeperators(
+      const FilePath::StringType& path) const;
+  bool ComparePathWithDefault(const FilePath& path) const;
+
+ private:
+  // The id of the extension that this resource is associated with.
+  std::string extension_id_;
+
+  // Extension root.
+  FilePath extension_root_;
+
+  // Relative path to resource.
+  FilePath relative_path_;
+
+  // If |follow_symlinks_anywhere_| is true then the resource itself must be
+  // within |extension_root|, but it can be a symlink to a file that is not.
+  bool follow_symlinks_anywhere_;
+
+  // Full path to extension resource. Starts empty.
+  mutable FilePath full_resource_path_;
+};
+
+#endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_RESOURCE_H_
diff --git a/chrome/common/extensions/extension_resource_unittest.cc b/chrome/common/extensions/extension_resource_unittest.cc
new file mode 100644
index 0000000..67c9ebd
--- /dev/null
+++ b/chrome/common/extensions/extension_resource_unittest.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 2012 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.
+
+#include <algorithm>
+
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/scoped_temp_dir.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_l10n_util.h"
+#include "chrome/common/extensions/extension_resource.h"
+#include "chrome/common/extensions/extension_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+TEST(ExtensionResourceTest, CreateEmptyResource) {
+  ExtensionResource resource;
+
+  EXPECT_TRUE(resource.extension_root().empty());
+  EXPECT_TRUE(resource.relative_path().empty());
+  EXPECT_TRUE(resource.GetFilePath().empty());
+}
+
+const FilePath::StringType ToLower(const FilePath::StringType& in_str) {
+  FilePath::StringType str(in_str);
+  std::transform(str.begin(), str.end(), str.begin(), tolower);
+  return str;
+}
+
+TEST(ExtensionResourceTest, CreateWithMissingResourceOnDisk) {
+  FilePath root_path;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &root_path));
+  FilePath relative_path;
+  relative_path = relative_path.AppendASCII("cira.js");
+  std::string extension_id = extension_test_util::MakeId("test");
+  ExtensionResource resource(extension_id, root_path, relative_path);
+
+  // The path doesn't exist on disk, we will be returned an empty path.
+  EXPECT_EQ(root_path.value(), resource.extension_root().value());
+  EXPECT_EQ(relative_path.value(), resource.relative_path().value());
+  EXPECT_TRUE(resource.GetFilePath().empty());
+}
+
+TEST(ExtensionResourceTest, ResourcesOutsideOfPath) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  FilePath inner_dir = temp.path().AppendASCII("directory");
+  ASSERT_TRUE(file_util::CreateDirectory(inner_dir));
+  FilePath inner_file = inner_dir.AppendASCII("inner");
+  FilePath outer_file = temp.path().AppendASCII("outer");
+  ASSERT_TRUE(file_util::WriteFile(outer_file, "X", 1));
+  ASSERT_TRUE(file_util::WriteFile(inner_file, "X", 1));
+  std::string extension_id = extension_test_util::MakeId("test");
+
+#if defined(OS_POSIX)
+  FilePath symlink_file = inner_dir.AppendASCII("symlink");
+  file_util::CreateSymbolicLink(
+      FilePath().AppendASCII("..").AppendASCII("outer"),
+      symlink_file);
+#endif
+
+  // A non-packing extension should be able to access the file within the
+  // directory.
+  ExtensionResource r1(extension_id, inner_dir,
+                       FilePath().AppendASCII("inner"));
+  EXPECT_FALSE(r1.GetFilePath().empty());
+
+  // ... but not a relative path that walks out of |inner_dir|.
+  ExtensionResource r2(extension_id, inner_dir,
+                       FilePath().AppendASCII("..").AppendASCII("outer"));
+  EXPECT_TRUE(r2.GetFilePath().empty());
+
+  // A packing extension should also be able to access the file within the
+  // directory.
+  ExtensionResource r3(extension_id, inner_dir,
+                       FilePath().AppendASCII("inner"));
+  r3.set_follow_symlinks_anywhere();
+  EXPECT_FALSE(r3.GetFilePath().empty());
+
+  // ... but, again, not a relative path that walks out of |inner_dir|.
+  ExtensionResource r4(extension_id, inner_dir,
+                       FilePath().AppendASCII("..").AppendASCII("outer"));
+  r4.set_follow_symlinks_anywhere();
+  EXPECT_TRUE(r4.GetFilePath().empty());
+
+#if defined(OS_POSIX)
+  // The non-packing extension should also not be able to access a resource that
+  // symlinks out of the directory.
+  ExtensionResource r5(extension_id, inner_dir,
+                       FilePath().AppendASCII("symlink"));
+  EXPECT_TRUE(r5.GetFilePath().empty());
+
+  // ... but a packing extension can.
+  ExtensionResource r6(extension_id, inner_dir,
+                       FilePath().AppendASCII("symlink"));
+  r6.set_follow_symlinks_anywhere();
+  EXPECT_FALSE(r6.GetFilePath().empty());
+#endif
+}
+
+// crbug.com/108721. Disabled on Windows due to crashing on Vista.
+#if defined(OS_WIN)
+#define CreateWithAllResourcesOnDisk DISABLED_CreateWithAllResourcesOnDisk
+#endif
+TEST(ExtensionResourceTest, CreateWithAllResourcesOnDisk) {
+  ScopedTempDir temp;
+  ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+  // Create resource in the extension root.
+  const char* filename = "res.ico";
+  FilePath root_resource = temp.path().AppendASCII(filename);
+  std::string data = "some foo";
+  ASSERT_TRUE(file_util::WriteFile(root_resource, data.c_str(), data.length()));
+
+  // Create l10n resources (for current locale and its parents).
+  FilePath l10n_path = temp.path().Append(extensions::Extension::kLocaleFolder);
+  ASSERT_TRUE(file_util::CreateDirectory(l10n_path));
+
+  std::vector<std::string> locales;
+  l10n_util::GetParentLocales(l10n_util::GetApplicationLocale(""), &locales);
+  ASSERT_FALSE(locales.empty());
+  for (size_t i = 0; i < locales.size(); i++) {
+    FilePath make_path;
+    make_path = l10n_path.AppendASCII(locales[i]);
+    ASSERT_TRUE(file_util::CreateDirectory(make_path));
+    ASSERT_TRUE(file_util::WriteFile(make_path.AppendASCII(filename),
+        data.c_str(), data.length()));
+  }
+
+  FilePath path;
+  std::string extension_id = extension_test_util::MakeId("test");
+  ExtensionResource resource(extension_id, temp.path(),
+                             FilePath().AppendASCII(filename));
+  FilePath resolved_path = resource.GetFilePath();
+
+  FilePath expected_path;
+  // Expect default path only, since fallback logic is disabled.
+  // See http://crbug.com/27359.
+  expected_path = root_resource;
+  ASSERT_TRUE(file_util::AbsolutePath(&expected_path));
+
+  EXPECT_EQ(ToLower(expected_path.value()), ToLower(resolved_path.value()));
+  EXPECT_EQ(ToLower(temp.path().value()),
+            ToLower(resource.extension_root().value()));
+  EXPECT_EQ(ToLower(FilePath().AppendASCII(filename).value()),
+            ToLower(resource.relative_path().value()));
+}
diff --git a/chrome/common/extensions/extension_set.cc b/chrome/common/extensions/extension_set.cc
new file mode 100644
index 0000000..857471c
--- /dev/null
+++ b/chrome/common/extensions/extension_set.cc
@@ -0,0 +1,168 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_set.h"
+
+#include "base/logging.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/url_constants.h"
+
+using WebKit::WebSecurityOrigin;
+using extensions::Extension;
+
+ExtensionURLInfo::ExtensionURLInfo(WebSecurityOrigin origin, const GURL& url)
+  : origin_(origin),
+    url_(url) {
+  DCHECK(!origin_.isNull());
+}
+
+ExtensionURLInfo::ExtensionURLInfo(const GURL& url)
+  : url_(url) {
+}
+
+ExtensionSet::const_iterator::const_iterator() {}
+
+ExtensionSet::const_iterator::const_iterator(const const_iterator& other)
+    : it_(other.it_) {
+}
+
+ExtensionSet::const_iterator::const_iterator(ExtensionMap::const_iterator it)
+    : it_(it) {
+}
+
+ExtensionSet::ExtensionSet() {
+}
+
+ExtensionSet::~ExtensionSet() {
+}
+
+size_t ExtensionSet::size() const {
+  return extensions_.size();
+}
+
+bool ExtensionSet::is_empty() const {
+  return extensions_.empty();
+}
+
+bool ExtensionSet::Contains(const std::string& extension_id) const {
+  return extensions_.find(extension_id) != extensions_.end();
+}
+
+void ExtensionSet::Insert(const scoped_refptr<const Extension>& extension) {
+  extensions_[extension->id()] = extension;
+}
+
+bool ExtensionSet::InsertAll(const ExtensionSet& extensions) {
+  size_t before = size();
+  for (ExtensionSet::const_iterator iter = extensions.begin();
+       iter != extensions.end(); ++iter) {
+    Insert(*iter);
+  }
+  return size() != before;
+}
+
+void ExtensionSet::Remove(const std::string& id) {
+  extensions_.erase(id);
+}
+
+void ExtensionSet::Clear() {
+  extensions_.clear();
+}
+
+std::string ExtensionSet::GetExtensionOrAppIDByURL(
+    const ExtensionURLInfo& info) const {
+  DCHECK(!info.origin().isNull());
+
+  if (info.url().SchemeIs(chrome::kExtensionScheme))
+    return info.origin().isUnique() ? "" : info.url().host();
+
+  const Extension* extension = GetExtensionOrAppByURL(info);
+  if (!extension)
+    return "";
+
+  return extension->id();
+}
+
+const Extension* ExtensionSet::GetExtensionOrAppByURL(
+    const ExtensionURLInfo& info) const {
+  // In the common case, the document's origin will correspond to its URL,
+  // but in some rare cases involving sandboxing, the two will be different.
+  // We catch those cases by checking whether the document's origin is unique.
+  // If that's not the case, then we conclude that the document's security
+  // context is well-described by its URL and proceed to use only the URL.
+  if (!info.origin().isNull() && info.origin().isUnique())
+    return NULL;
+
+  if (info.url().SchemeIs(chrome::kExtensionScheme))
+    return GetByID(info.url().host());
+
+  return GetHostedAppByURL(info);
+}
+
+const Extension* ExtensionSet::GetHostedAppByURL(
+    const ExtensionURLInfo& info) const {
+  for (ExtensionMap::const_iterator iter = extensions_.begin();
+       iter != extensions_.end(); ++iter) {
+    if (iter->second->web_extent().MatchesURL(info.url()))
+      return iter->second.get();
+  }
+
+  return NULL;
+}
+
+const Extension* ExtensionSet::GetHostedAppByOverlappingWebExtent(
+    const URLPatternSet& extent) const {
+  for (ExtensionMap::const_iterator iter = extensions_.begin();
+       iter != extensions_.end(); ++iter) {
+    if (iter->second->web_extent().OverlapsWith(extent))
+      return iter->second.get();
+  }
+
+  return NULL;
+}
+
+bool ExtensionSet::InSameExtent(const GURL& old_url,
+                                const GURL& new_url) const {
+  return GetExtensionOrAppByURL(ExtensionURLInfo(old_url)) ==
+      GetExtensionOrAppByURL(ExtensionURLInfo(new_url));
+}
+
+const Extension* ExtensionSet::GetByID(const std::string& id) const {
+  ExtensionMap::const_iterator i = extensions_.find(id);
+  if (i != extensions_.end())
+    return i->second.get();
+  else
+    return NULL;
+}
+
+bool ExtensionSet::ExtensionBindingsAllowed(
+    const ExtensionURLInfo& info) const {
+  if (info.origin().isUnique() || IsSandboxedPage(info))
+    return false;
+
+  if (info.url().SchemeIs(chrome::kExtensionScheme))
+    return true;
+
+  ExtensionMap::const_iterator i = extensions_.begin();
+  for (; i != extensions_.end(); ++i) {
+    if (i->second->location() == Extension::COMPONENT &&
+        i->second->web_extent().MatchesURL(info.url()))
+      return true;
+  }
+
+  return false;
+}
+
+bool ExtensionSet::IsSandboxedPage(const ExtensionURLInfo& info) const {
+  if (info.origin().isUnique())
+    return true;
+
+  if (info.url().SchemeIs(chrome::kExtensionScheme)) {
+    const Extension* extension = GetByID(info.url().host());
+    if (extension) {
+      return extension->IsSandboxedPage(info.url().path());
+    }
+  }
+  return false;
+}
diff --git a/chrome/common/extensions/extension_set.h b/chrome/common/extensions/extension_set.h
new file mode 100644
index 0000000..43635ba
--- /dev/null
+++ b/chrome/common/extensions/extension_set.h
@@ -0,0 +1,143 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_SET_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_SET_H_
+
+#include <iterator>
+#include <map>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "chrome/common/extensions/extension.h"
+#include "googleurl/src/gurl.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h"
+
+class ExtensionURLInfo {
+ public:
+  // The extension system uses both a document's origin and its URL to
+  // grant permissions. Ideally, we would use only the origin, but because
+  // the web extent of a hosted app can be less than an entire origin, we
+  // take the URL into account as well
+  ExtensionURLInfo(WebKit::WebSecurityOrigin origin, const GURL& url);
+
+  // WARNING! Using this constructor can miss important security checks if
+  //          you're trying to find a running extension. For example, if the
+  //          URL in question is being rendered inside an iframe sandbox, then
+  //          we might incorrectly grant it access to powerful extension APIs.
+  explicit ExtensionURLInfo(const GURL& url);
+
+  const WebKit::WebSecurityOrigin& origin() const { return origin_; }
+  const GURL& url() const { return url_; }
+
+ private:
+  WebKit::WebSecurityOrigin origin_;
+  GURL url_;
+};
+
+// The one true extension container. Extensions are identified by their id.
+// Only one extension can be in the set with a given ID.
+class ExtensionSet {
+ public:
+  typedef std::pair<FilePath, std::string> ExtensionPathAndDefaultLocale;
+  typedef std::map<std::string, scoped_refptr<const extensions::Extension> >
+      ExtensionMap;
+
+  // Iteration over the values of the map (given that it's an ExtensionSet,
+  // it should iterate like a set iterator).
+  class const_iterator :
+      public std::iterator<std::input_iterator_tag,
+                           scoped_refptr<const extensions::Extension> > {
+   public:
+    const_iterator();
+    const_iterator(const const_iterator& other);
+    explicit const_iterator(ExtensionMap::const_iterator it);
+    const_iterator& operator++() {
+      ++it_;
+      return *this;
+    }
+    const scoped_refptr<const extensions::Extension> operator*() {
+      return it_->second;
+    }
+    bool operator!=(const const_iterator& other) { return it_ != other.it_; }
+    bool operator==(const const_iterator& other) { return it_ == other.it_; }
+
+   private:
+    ExtensionMap::const_iterator it_;
+  };
+
+  ExtensionSet();
+  ~ExtensionSet();
+
+  size_t size() const;
+  bool is_empty() const;
+
+  // Iteration support.
+  const_iterator begin() const { return const_iterator(extensions_.begin()); }
+  const_iterator end() const { return const_iterator(extensions_.end()); }
+
+  // Returns true if the set contains the specified extension.
+  bool Contains(const std::string& id) const;
+
+  // Adds the specified extension to the set. The set becomes an owner. Any
+  // previous extension with the same ID is removed.
+  void Insert(const scoped_refptr<const extensions::Extension>& extension);
+
+  // Copies different items from |extensions| to the current set and returns
+  // whether anything changed.
+  bool InsertAll(const ExtensionSet& extensions);
+
+  // Removes the specified extension.
+  void Remove(const std::string& id);
+
+  // Removes all extensions.
+  void Clear();
+
+  // Returns the extension ID, or empty if none. This includes web URLs that
+  // are part of an extension's web extent.
+  std::string GetExtensionOrAppIDByURL(const ExtensionURLInfo& info) const;
+
+  // Returns the Extension, or NULL if none.  This includes web URLs that are
+  // part of an extension's web extent.
+  // NOTE: This can return NULL if called before UpdateExtensions receives
+  // bulk extension data (e.g. if called from
+  // EventBindings::HandleContextCreated)
+  const extensions::Extension* GetExtensionOrAppByURL(
+      const ExtensionURLInfo& info) const;
+
+  // Returns the hosted app whose web extent contains the URL.
+  const extensions::Extension* GetHostedAppByURL(
+      const ExtensionURLInfo& info) const;
+
+  // Returns a hosted app that contains any URL that overlaps with the given
+  // extent, if one exists.
+  const extensions::Extension* GetHostedAppByOverlappingWebExtent(
+      const URLPatternSet& extent) const;
+
+  // Returns true if |new_url| is in the extent of the same extension as
+  // |old_url|.  Also returns true if neither URL is in an app.
+  bool InSameExtent(const GURL& old_url, const GURL& new_url) const;
+
+  // Look up an Extension object by id.
+  const extensions::Extension* GetByID(const std::string& id) const;
+
+  // Returns true if |info| should get extension api bindings and be permitted
+  // to make api calls. Note that this is independent of what extension
+  // permissions the given extension has been granted.
+  bool ExtensionBindingsAllowed(const ExtensionURLInfo& info) const;
+
+  // Returns true if |info| is an extension page that is to be served in a
+  // unique sandboxed origin.
+  bool IsSandboxedPage(const ExtensionURLInfo& info) const;
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(ExtensionSetTest, ExtensionSet);
+
+  ExtensionMap extensions_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExtensionSet);
+};
+
+#endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_SET_H_
diff --git a/chrome/common/extensions/extension_set_unittest.cc b/chrome/common/extensions/extension_set_unittest.cc
new file mode 100644
index 0000000..0bc8e92
--- /dev/null
+++ b/chrome/common/extensions/extension_set_unittest.cc
@@ -0,0 +1,136 @@
+// Copyright (c) 2012 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.
+
+#include "base/file_path.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_set.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::Extension;
+
+namespace {
+
+scoped_refptr<Extension> CreateTestExtension(const std::string& name,
+                                             const std::string& launch_url,
+                                             const std::string& extent) {
+#if defined(OS_WIN)
+  FilePath path(FILE_PATH_LITERAL("c:\\"));
+#else
+  FilePath path(FILE_PATH_LITERAL("/"));
+#endif
+  path = path.AppendASCII(name);
+
+  DictionaryValue manifest;
+  manifest.SetString("name", name);
+  manifest.SetString("version", "1");
+
+  if (!launch_url.empty())
+    manifest.SetString("app.launch.web_url", launch_url);
+
+  if (!extent.empty()) {
+    ListValue* urls = new ListValue();
+    manifest.Set("app.urls", urls);
+    urls->Append(Value::CreateStringValue(extent));
+  }
+
+  std::string error;
+  scoped_refptr<Extension> extension(
+      Extension::Create(path, Extension::INTERNAL, manifest,
+                        Extension::NO_FLAGS, &error));
+  EXPECT_TRUE(extension.get()) << error;
+  return extension;
+}
+
+} // namespace
+
+TEST(ExtensionSetTest, ExtensionSet) {
+  scoped_refptr<Extension> ext1(CreateTestExtension(
+      "a", "https://chrome.google.com/launch", "https://chrome.google.com/"));
+
+  scoped_refptr<Extension> ext2(CreateTestExtension(
+      "a", "http://code.google.com/p/chromium",
+      "http://code.google.com/p/chromium/"));
+
+  scoped_refptr<Extension> ext3(CreateTestExtension(
+      "b", "http://dev.chromium.org/", "http://dev.chromium.org/"));
+
+  scoped_refptr<Extension> ext4(CreateTestExtension("c", "", ""));
+
+  ASSERT_TRUE(ext1 && ext2 && ext3 && ext4);
+
+  ExtensionSet extensions;
+
+  // Add an extension.
+  extensions.Insert(ext1);
+  EXPECT_EQ(1u, extensions.size());
+  EXPECT_EQ(ext1, extensions.GetByID(ext1->id()));
+
+  // Since extension2 has same ID, it should overwrite extension1.
+  extensions.Insert(ext2);
+  EXPECT_EQ(1u, extensions.size());
+  EXPECT_EQ(ext2, extensions.GetByID(ext1->id()));
+
+  // Add the other extensions.
+  extensions.Insert(ext3);
+  extensions.Insert(ext4);
+  EXPECT_EQ(3u, extensions.size());
+
+  // Get extension by its chrome-extension:// URL
+  EXPECT_EQ(ext2, extensions.GetExtensionOrAppByURL(
+      ExtensionURLInfo(ext2->GetResourceURL("test.html"))));
+  EXPECT_EQ(ext3, extensions.GetExtensionOrAppByURL(
+      ExtensionURLInfo(ext3->GetResourceURL("test.html"))));
+  EXPECT_EQ(ext4, extensions.GetExtensionOrAppByURL(
+      ExtensionURLInfo(ext4->GetResourceURL("test.html"))));
+
+  // Get extension by web extent.
+  EXPECT_EQ(ext2, extensions.GetExtensionOrAppByURL(
+      ExtensionURLInfo(GURL("http://code.google.com/p/chromium/monkey"))));
+  EXPECT_EQ(ext3, extensions.GetExtensionOrAppByURL(
+      ExtensionURLInfo(GURL("http://dev.chromium.org/design-docs/"))));
+  EXPECT_FALSE(extensions.GetExtensionOrAppByURL(
+      ExtensionURLInfo(GURL("http://blog.chromium.org/"))));
+
+  // Test InSameExtent().
+  EXPECT_TRUE(extensions.InSameExtent(
+      GURL("http://code.google.com/p/chromium/monkey/"),
+      GURL("http://code.google.com/p/chromium/")));
+  EXPECT_FALSE(extensions.InSameExtent(
+      GURL("http://code.google.com/p/chromium/"),
+      GURL("https://code.google.com/p/chromium/")));
+  EXPECT_FALSE(extensions.InSameExtent(
+      GURL("http://code.google.com/p/chromium/"),
+      GURL("http://dev.chromium.org/design-docs/")));
+
+  // Both of these should be NULL, which mean true for InSameExtent.
+  EXPECT_TRUE(extensions.InSameExtent(
+      GURL("http://www.google.com/"),
+      GURL("http://blog.chromium.org/")));
+
+  // Remove one of the extensions.
+  extensions.Remove(ext2->id());
+  EXPECT_EQ(2u, extensions.size());
+  EXPECT_FALSE(extensions.GetByID(ext2->id()));
+
+  // Make a union of a set with 3 more extensions (only 2 are new).
+  scoped_refptr<Extension> ext5(CreateTestExtension("d", "", ""));
+  scoped_refptr<Extension> ext6(CreateTestExtension("e", "", ""));
+  ASSERT_TRUE(ext5 && ext6);
+
+  scoped_ptr<ExtensionSet> to_add(new ExtensionSet());
+  to_add->Insert(ext3);  // Already in |extensions|, should not affect size.
+  to_add->Insert(ext5);
+  to_add->Insert(ext6);
+
+  ASSERT_TRUE(extensions.Contains(ext3->id()));
+  ASSERT_TRUE(extensions.InsertAll(*to_add));
+  EXPECT_EQ(4u, extensions.size());
+
+  ASSERT_FALSE(extensions.InsertAll(*to_add));  // Re-adding same set no-ops.
+  EXPECT_EQ(4u, extensions.size());
+}
diff --git a/chrome/common/extensions/extension_test_util.cc b/chrome/common/extensions/extension_test_util.cc
new file mode 100644
index 0000000..5dfa9a6
--- /dev/null
+++ b/chrome/common/extensions/extension_test_util.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 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.
+
+#include "base/values.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/extension_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::Extension;
+
+namespace extension_test_util {
+
+std::string MakeId(std::string seed) {
+  std::string result;
+  bool success = Extension::GenerateId(seed, &result);
+  EXPECT_TRUE(success);
+  EXPECT_FALSE(result.empty());
+  EXPECT_TRUE(Extension::IdIsValid(result));
+  return result;
+}
+
+scoped_refptr<Extension> CreateExtensionWithID(std::string id) {
+  DictionaryValue values;
+  values.SetString(extension_manifest_keys::kName, "test");
+  values.SetString(extension_manifest_keys::kVersion, "0.1");
+  std::string error;
+  return Extension::Create(FilePath(), Extension::INTERNAL, values,
+                           Extension::NO_FLAGS, id, &error);
+}
+
+}  // namespace extension_test_util
diff --git a/chrome/common/extensions/extension_test_util.h b/chrome/common/extensions/extension_test_util.h
new file mode 100644
index 0000000..a549d98
--- /dev/null
+++ b/chrome/common/extensions/extension_test_util.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_TEST_UTIL_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_TEST_UTIL_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+
+namespace extensions {
+class Extension;
+}
+
+namespace extension_test_util {
+
+// Makes a fake extension id using the given |seed|.
+std::string MakeId(std::string seed);
+
+// Return a very simple extension with id |id|.
+scoped_refptr<extensions::Extension> CreateExtensionWithID(std::string id);
+
+}  // namespace extension_test_util
+
+#endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_TEST_UTIL_H_
diff --git a/chrome/common/extensions/extension_unittest.cc b/chrome/common/extensions/extension_unittest.cc
new file mode 100644
index 0000000..2c6cf09
--- /dev/null
+++ b/chrome/common/extensions/extension_unittest.cc
@@ -0,0 +1,1267 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension.h"
+
+#include "base/format_macros.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/path_service.h"
+#include "base/stringprintf.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/command.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/extension_file_util.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/extension_resource.h"
+#include "chrome/common/extensions/features/feature.h"
+#include "chrome/common/extensions/permissions/api_permission.h"
+#include "chrome/common/extensions/permissions/permission_set.h"
+#include "chrome/common/extensions/permissions/socket_permission.h"
+#include "chrome/common/url_constants.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/mime_sniffer.h"
+#include "skia/ext/image_operations.h"
+#include "net/base/mock_host_resolver.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/codec/png_codec.h"
+
+using content::SocketPermissionRequest;
+using extensions::APIPermission;
+using extensions::APIPermissionSet;
+using extensions::Extension;
+using extensions::Feature;
+using extensions::PermissionSet;
+using extensions::SocketPermission;
+
+namespace keys = extension_manifest_keys;
+namespace values = extension_manifest_values;
+namespace errors = extension_manifest_errors;
+
+namespace {
+
+scoped_refptr<Extension> LoadManifestUnchecked(
+    const std::string& dir,
+    const std::string& test_file,
+    Extension::Location location,
+    int extra_flags,
+    std::string* error) {
+  FilePath path;
+  PathService::Get(chrome::DIR_TEST_DATA, &path);
+  path = path.AppendASCII("extensions")
+             .AppendASCII(dir)
+             .AppendASCII(test_file);
+
+  JSONFileValueSerializer serializer(path);
+  scoped_ptr<Value> result(serializer.Deserialize(NULL, error));
+  if (!result.get())
+    return NULL;
+
+  scoped_refptr<Extension> extension = Extension::Create(
+      path.DirName(), location, *static_cast<DictionaryValue*>(result.get()),
+      extra_flags, error);
+  return extension;
+}
+
+static scoped_refptr<Extension> LoadManifest(const std::string& dir,
+                                             const std::string& test_file,
+                                             Extension::Location location,
+                                             int extra_flags) {
+  std::string error;
+  scoped_refptr<Extension> extension = LoadManifestUnchecked(dir, test_file,
+    location, extra_flags, &error);
+
+  EXPECT_TRUE(extension) << test_file << ":" << error;
+  return extension;
+}
+
+static scoped_refptr<Extension> LoadManifest(const std::string& dir,
+                                             const std::string& test_file,
+                                             int extra_flags) {
+  return LoadManifest(dir, test_file, Extension::INVALID, extra_flags);
+}
+
+static scoped_refptr<Extension> LoadManifest(const std::string& dir,
+                                             const std::string& test_file) {
+  return LoadManifest(dir, test_file, Extension::NO_FLAGS);
+}
+
+static scoped_refptr<Extension> LoadManifestStrict(
+    const std::string& dir,
+    const std::string& test_file) {
+  return LoadManifest(dir, test_file, Extension::NO_FLAGS);
+}
+
+static scoped_ptr<Extension::ActionInfo> LoadAction(
+    const std::string& manifest) {
+  scoped_refptr<Extension> extension = LoadManifest("page_action",
+      manifest);
+  EXPECT_TRUE(extension->page_action_info());
+  if (extension->page_action_info()) {
+    return make_scoped_ptr(new Extension::ActionInfo(
+        *extension->page_action_info()));
+  }
+  ADD_FAILURE() << "Expected manifest in " << manifest
+                << " to include a page_action section.";
+  return scoped_ptr<Extension::ActionInfo>();
+}
+
+static void LoadActionAndExpectError(const std::string& manifest,
+                                     const std::string& expected_error) {
+  std::string error;
+  scoped_refptr<Extension> extension = LoadManifestUnchecked("page_action",
+      manifest, Extension::INTERNAL, Extension::NO_FLAGS, &error);
+  EXPECT_FALSE(extension);
+  EXPECT_EQ(expected_error, error);
+}
+
+}
+
+class ExtensionTest : public testing::Test {
+};
+
+// We persist location values in the preferences, so this is a sanity test that
+// someone doesn't accidentally change them.
+TEST(ExtensionTest, LocationValuesTest) {
+  ASSERT_EQ(0, Extension::INVALID);
+  ASSERT_EQ(1, Extension::INTERNAL);
+  ASSERT_EQ(2, Extension::EXTERNAL_PREF);
+  ASSERT_EQ(3, Extension::EXTERNAL_REGISTRY);
+  ASSERT_EQ(4, Extension::LOAD);
+  ASSERT_EQ(5, Extension::COMPONENT);
+  ASSERT_EQ(6, Extension::EXTERNAL_PREF_DOWNLOAD);
+  ASSERT_EQ(7, Extension::EXTERNAL_POLICY_DOWNLOAD);
+}
+
+TEST(ExtensionTest, LocationPriorityTest) {
+  for (int i = 0; i < Extension::NUM_LOCATIONS; i++) {
+    Extension::Location loc = static_cast<Extension::Location>(i);
+
+    // INVALID is not a valid location.
+    if (loc == Extension::INVALID)
+      continue;
+
+    // Comparing a location that has no rank will hit a CHECK. Do a
+    // compare with every valid location, to be sure each one is covered.
+
+    // Check that no install source can override a componenet extension.
+    ASSERT_EQ(Extension::COMPONENT,
+              Extension::GetHigherPriorityLocation(Extension::COMPONENT, loc));
+    ASSERT_EQ(Extension::COMPONENT,
+              Extension::GetHigherPriorityLocation(loc, Extension::COMPONENT));
+
+    // Check that any source can override a user install. This might change
+    // in the future, in which case this test should be updated.
+    ASSERT_EQ(loc,
+              Extension::GetHigherPriorityLocation(Extension::INTERNAL, loc));
+    ASSERT_EQ(loc,
+              Extension::GetHigherPriorityLocation(loc, Extension::INTERNAL));
+  }
+
+  // Check a few interesting cases that we know can happen:
+  ASSERT_EQ(Extension::EXTERNAL_POLICY_DOWNLOAD,
+            Extension::GetHigherPriorityLocation(
+                Extension::EXTERNAL_POLICY_DOWNLOAD,
+                Extension::EXTERNAL_PREF));
+
+  ASSERT_EQ(Extension::EXTERNAL_PREF,
+            Extension::GetHigherPriorityLocation(
+                Extension::INTERNAL,
+                Extension::EXTERNAL_PREF));
+}
+
+TEST(ExtensionTest, GetResourceURLAndPath) {
+  scoped_refptr<Extension> extension = LoadManifestStrict("empty_manifest",
+      "empty.json");
+  EXPECT_TRUE(extension.get());
+
+  EXPECT_EQ(extension->url().spec() + "bar/baz.js",
+            Extension::GetResourceURL(extension->url(), "bar/baz.js").spec());
+  EXPECT_EQ(extension->url().spec() + "baz.js",
+            Extension::GetResourceURL(extension->url(),
+                                      "bar/../baz.js").spec());
+  EXPECT_EQ(extension->url().spec() + "baz.js",
+            Extension::GetResourceURL(extension->url(), "../baz.js").spec());
+
+  // Test that absolute-looking paths ("/"-prefixed) are pasted correctly.
+  EXPECT_EQ(extension->url().spec() + "test.html",
+            extension->GetResourceURL("/test.html").spec());
+}
+
+TEST(ExtensionTest, GetAbsolutePathNoError) {
+  scoped_refptr<Extension> extension = LoadManifestStrict("absolute_path",
+      "absolute.json");
+  EXPECT_TRUE(extension.get());
+  std::string err;
+  Extension::InstallWarningVector warnings;
+  EXPECT_TRUE(extension_file_util::ValidateExtension(extension.get(),
+                                                     &err, &warnings));
+  EXPECT_EQ(0U, warnings.size());
+
+  EXPECT_EQ(extension->path().AppendASCII("test.html").value(),
+            extension->GetResource("test.html").GetFilePath().value());
+  EXPECT_EQ(extension->path().AppendASCII("test.js").value(),
+            extension->GetResource("test.js").GetFilePath().value());
+}
+
+TEST(ExtensionTest, LoadPageActionHelper) {
+  scoped_ptr<Extension::ActionInfo> action;
+
+  // First try with an empty dictionary.
+  action = LoadAction("page_action_empty.json");
+  ASSERT_TRUE(action != NULL);
+
+  // Now setup some values to use in the action.
+  const std::string id("MyExtensionActionId");
+  const std::string name("MyExtensionActionName");
+  std::string img1("image1.png");
+
+  action = LoadAction("page_action.json");
+  ASSERT_TRUE(NULL != action.get());
+  ASSERT_EQ(id, action->id);
+
+  // No title, so fall back to name.
+  ASSERT_EQ(name, action->default_title);
+  ASSERT_EQ(img1,
+            action->default_icon.Get(extension_misc::EXTENSION_ICON_ACTION,
+                                     ExtensionIconSet::MATCH_EXACTLY));
+
+  // Same test with explicitly set type.
+  action = LoadAction("page_action_type.json");
+  ASSERT_TRUE(NULL != action.get());
+
+  // Try an action without id key.
+  action = LoadAction("page_action_no_id.json");
+  ASSERT_TRUE(NULL != action.get());
+
+  // Then try without the name key. It's optional, so no error.
+  action = LoadAction("page_action_no_name.json");
+  ASSERT_TRUE(NULL != action.get());
+  ASSERT_TRUE(action->default_title.empty());
+
+  // Then try without the icon paths key.
+  action = LoadAction("page_action_no_icon.json");
+  ASSERT_TRUE(NULL != action.get());
+
+  // Now test that we can parse the new format for page actions.
+  const std::string kTitle("MyExtensionActionTitle");
+  const std::string kIcon("image1.png");
+  const std::string kPopupHtmlFile("a_popup.html");
+
+  action = LoadAction("page_action_new_format.json");
+  ASSERT_TRUE(action.get());
+  ASSERT_EQ(kTitle, action->default_title);
+  ASSERT_FALSE(action->default_icon.empty());
+
+  // Invalid title should give an error even with a valid name.
+  LoadActionAndExpectError("page_action_invalid_title.json",
+      errors::kInvalidPageActionDefaultTitle);
+
+  // Invalid name should give an error only with no title.
+  action = LoadAction("page_action_invalid_name.json");
+  ASSERT_TRUE(NULL != action.get());
+  ASSERT_EQ(kTitle, action->default_title);
+
+  LoadActionAndExpectError("page_action_invalid_name_no_title.json",
+      errors::kInvalidPageActionName);
+
+  // Test that keys "popup" and "default_popup" both work, but can not
+  // be used at the same time.
+  // These tests require an extension_url, so we also load the manifest.
+
+  // Only use "popup", expect success.
+  scoped_refptr<Extension> extension = LoadManifest("page_action",
+             "page_action_popup.json");
+  action = LoadAction("page_action_popup.json");
+  ASSERT_TRUE(NULL != action.get());
+  ASSERT_STREQ(
+      extension->url().Resolve(kPopupHtmlFile).spec().c_str(),
+      action->default_popup_url.spec().c_str());
+
+  // Use both "popup" and "default_popup", expect failure.
+  LoadActionAndExpectError("page_action_popup_and_default_popup.json",
+      ExtensionErrorUtils::FormatErrorMessage(
+          errors::kInvalidPageActionOldAndNewKeys,
+          keys::kPageActionDefaultPopup,
+          keys::kPageActionPopup));
+
+  // Use only "default_popup", expect success.
+  extension = LoadManifest("page_action", "page_action_popup.json");
+  action = LoadAction("page_action_default_popup.json");
+  ASSERT_TRUE(NULL != action.get());
+  ASSERT_STREQ(
+      extension->url().Resolve(kPopupHtmlFile).spec().c_str(),
+      action->default_popup_url.spec().c_str());
+
+  // Setting default_popup to "" is the same as having no popup.
+  action = LoadAction("page_action_empty_default_popup.json");
+  ASSERT_TRUE(NULL != action.get());
+  EXPECT_TRUE(action->default_popup_url.is_empty());
+  ASSERT_STREQ(
+      "",
+      action->default_popup_url.spec().c_str());
+
+  // Setting popup to "" is the same as having no popup.
+  action = LoadAction("page_action_empty_popup.json");
+
+  ASSERT_TRUE(NULL != action.get());
+  EXPECT_TRUE(action->default_popup_url.is_empty());
+  ASSERT_STREQ(
+      "",
+      action->default_popup_url.spec().c_str());
+}
+
+TEST(ExtensionTest, IdIsValid) {
+  EXPECT_TRUE(Extension::IdIsValid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
+  EXPECT_TRUE(Extension::IdIsValid("pppppppppppppppppppppppppppppppp"));
+  EXPECT_TRUE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmnop"));
+  EXPECT_TRUE(Extension::IdIsValid("ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP"));
+  EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmno"));
+  EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmnopa"));
+  EXPECT_FALSE(Extension::IdIsValid("0123456789abcdef0123456789abcdef"));
+  EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmnoq"));
+  EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmno0"));
+}
+
+TEST(ExtensionTest, GenerateID) {
+  const uint8 public_key_info[] = {
+    0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+    0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81,
+    0x89, 0x02, 0x81, 0x81, 0x00, 0xb8, 0x7f, 0x2b, 0x20, 0xdc, 0x7c, 0x9b,
+    0x0c, 0xdc, 0x51, 0x61, 0x99, 0x0d, 0x36, 0x0f, 0xd4, 0x66, 0x88, 0x08,
+    0x55, 0x84, 0xd5, 0x3a, 0xbf, 0x2b, 0xa4, 0x64, 0x85, 0x7b, 0x0c, 0x04,
+    0x13, 0x3f, 0x8d, 0xf4, 0xbc, 0x38, 0x0d, 0x49, 0xfe, 0x6b, 0xc4, 0x5a,
+    0xb0, 0x40, 0x53, 0x3a, 0xd7, 0x66, 0x09, 0x0f, 0x9e, 0x36, 0x74, 0x30,
+    0xda, 0x8a, 0x31, 0x4f, 0x1f, 0x14, 0x50, 0xd7, 0xc7, 0x20, 0x94, 0x17,
+    0xde, 0x4e, 0xb9, 0x57, 0x5e, 0x7e, 0x0a, 0xe5, 0xb2, 0x65, 0x7a, 0x89,
+    0x4e, 0xb6, 0x47, 0xff, 0x1c, 0xbd, 0xb7, 0x38, 0x13, 0xaf, 0x47, 0x85,
+    0x84, 0x32, 0x33, 0xf3, 0x17, 0x49, 0xbf, 0xe9, 0x96, 0xd0, 0xd6, 0x14,
+    0x6f, 0x13, 0x8d, 0xc5, 0xfc, 0x2c, 0x72, 0xba, 0xac, 0xea, 0x7e, 0x18,
+    0x53, 0x56, 0xa6, 0x83, 0xa2, 0xce, 0x93, 0x93, 0xe7, 0x1f, 0x0f, 0xe6,
+    0x0f, 0x02, 0x03, 0x01, 0x00, 0x01
+  };
+
+  std::string extension_id;
+  EXPECT_TRUE(
+      Extension::GenerateId(
+          std::string(reinterpret_cast<const char*>(&public_key_info[0]),
+                      arraysize(public_key_info)),
+          &extension_id));
+  EXPECT_EQ("melddjfinppjdikinhbgehiennejpfhp", extension_id);
+}
+
+// This test ensures that the mimetype sniffing code stays in sync with the
+// actual crx files that we test other parts of the system with.
+TEST(ExtensionTest, MimeTypeSniffing) {
+  FilePath path;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
+  path = path.AppendASCII("extensions").AppendASCII("good.crx");
+
+  std::string data;
+  ASSERT_TRUE(file_util::ReadFileToString(path, &data));
+
+  std::string result;
+  EXPECT_TRUE(net::SniffMimeType(data.c_str(), data.size(),
+              GURL("http://www.example.com/foo.crx"), "", &result));
+  EXPECT_EQ(std::string(Extension::kMimeType), result);
+
+  data.clear();
+  result.clear();
+  path = path.DirName().AppendASCII("bad_magic.crx");
+  ASSERT_TRUE(file_util::ReadFileToString(path, &data));
+  EXPECT_TRUE(net::SniffMimeType(data.c_str(), data.size(),
+              GURL("http://www.example.com/foo.crx"), "", &result));
+  EXPECT_EQ("application/octet-stream", result);
+}
+
+TEST(ExtensionTest, EffectiveHostPermissions) {
+  scoped_refptr<Extension> extension;
+  URLPatternSet hosts;
+
+  extension = LoadManifest("effective_host_permissions", "empty.json");
+  EXPECT_EQ(0u, extension->GetEffectiveHostPermissions().patterns().size());
+  EXPECT_FALSE(hosts.MatchesURL(GURL("http://www.google.com")));
+  EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions", "one_host.json");
+  hosts = extension->GetEffectiveHostPermissions();
+  EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.google.com")));
+  EXPECT_FALSE(hosts.MatchesURL(GURL("https://www.google.com")));
+  EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions",
+                           "one_host_wildcard.json");
+  hosts = extension->GetEffectiveHostPermissions();
+  EXPECT_TRUE(hosts.MatchesURL(GURL("http://google.com")));
+  EXPECT_TRUE(hosts.MatchesURL(GURL("http://foo.google.com")));
+  EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions", "two_hosts.json");
+  hosts = extension->GetEffectiveHostPermissions();
+  EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.google.com")));
+  EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.reddit.com")));
+  EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions",
+                           "https_not_considered.json");
+  hosts = extension->GetEffectiveHostPermissions();
+  EXPECT_TRUE(hosts.MatchesURL(GURL("http://google.com")));
+  EXPECT_TRUE(hosts.MatchesURL(GURL("https://google.com")));
+  EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions",
+                           "two_content_scripts.json");
+  hosts = extension->GetEffectiveHostPermissions();
+  EXPECT_TRUE(hosts.MatchesURL(GURL("http://google.com")));
+  EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.reddit.com")));
+  EXPECT_TRUE(extension->GetActivePermissions()->HasEffectiveAccessToURL(
+      GURL("http://www.reddit.com")));
+  EXPECT_TRUE(hosts.MatchesURL(GURL("http://news.ycombinator.com")));
+  EXPECT_TRUE(extension->GetActivePermissions()->HasEffectiveAccessToURL(
+      GURL("http://news.ycombinator.com")));
+  EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions", "all_hosts.json");
+  hosts = extension->GetEffectiveHostPermissions();
+  EXPECT_TRUE(hosts.MatchesURL(GURL("http://test/")));
+  EXPECT_FALSE(hosts.MatchesURL(GURL("https://test/")));
+  EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.google.com")));
+  EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions", "all_hosts2.json");
+  hosts = extension->GetEffectiveHostPermissions();
+  EXPECT_TRUE(hosts.MatchesURL(GURL("http://test/")));
+  EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.google.com")));
+  EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions", "all_hosts3.json");
+  hosts = extension->GetEffectiveHostPermissions();
+  EXPECT_FALSE(hosts.MatchesURL(GURL("http://test/")));
+  EXPECT_TRUE(hosts.MatchesURL(GURL("https://test/")));
+  EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.google.com")));
+  EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts());
+}
+
+static bool CheckSocketPermission(scoped_refptr<Extension> extension,
+    SocketPermissionRequest::OperationType type,
+    const char* host,
+    int port) {
+  SocketPermission::CheckParam param(type, host, port);
+  return extension->CheckAPIPermissionWithParam(
+      APIPermission::kSocket, &param);
+}
+
+TEST(ExtensionTest, SocketPermissions) {
+  // Set feature current channel to appropriate value.
+  Feature::ScopedCurrentChannel scoped_channel(
+      chrome::VersionInfo::CHANNEL_DEV);
+  scoped_refptr<Extension> extension;
+  std::string error;
+
+  extension = LoadManifest("socket_permissions", "empty.json");
+  EXPECT_FALSE(CheckSocketPermission(extension,
+      SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
+
+  extension = LoadManifestUnchecked("socket_permissions",
+                                    "socket1.json",
+                                    Extension::INTERNAL, Extension::NO_FLAGS,
+                                    &error);
+  EXPECT_TRUE(extension == NULL);
+  ASSERT_EQ(ExtensionErrorUtils::FormatErrorMessage(
+        errors::kInvalidPermission, "socket"), error);
+
+  extension = LoadManifest("socket_permissions", "socket2.json");
+  EXPECT_TRUE(CheckSocketPermission(extension,
+      SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
+  EXPECT_FALSE(CheckSocketPermission(
+        extension, SocketPermissionRequest::UDP_BIND, "", 80));
+  EXPECT_TRUE(CheckSocketPermission(
+        extension, SocketPermissionRequest::UDP_BIND, "", 8888));
+
+  EXPECT_FALSE(CheckSocketPermission(
+        extension, SocketPermissionRequest::UDP_SEND_TO, "example.com", 1900));
+  EXPECT_TRUE(CheckSocketPermission(
+        extension,
+        SocketPermissionRequest::UDP_SEND_TO,
+        "239.255.255.250", 1900));
+}
+
+// Returns a copy of |source| resized to |size| x |size|.
+static SkBitmap ResizedCopy(const SkBitmap& source, int size) {
+  return skia::ImageOperations::Resize(source,
+                                       skia::ImageOperations::RESIZE_LANCZOS3,
+                                       size,
+                                       size);
+}
+
+static bool SizeEquals(const SkBitmap& bitmap, const gfx::Size& size) {
+  return bitmap.width() == size.width() && bitmap.height() == size.height();
+}
+
+TEST(ExtensionTest, ImageCaching) {
+  FilePath path;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
+  path = path.AppendASCII("extensions");
+
+  // Initialize the Extension.
+  std::string errors;
+  DictionaryValue values;
+  values.SetString(keys::kName, "test");
+  values.SetString(keys::kVersion, "0.1");
+  scoped_refptr<Extension> extension(Extension::Create(
+      path, Extension::INVALID, values, Extension::NO_FLAGS, &errors));
+  ASSERT_TRUE(extension.get());
+
+  // Create an ExtensionResource pointing at an icon.
+  FilePath icon_relative_path(FILE_PATH_LITERAL("icon3.png"));
+  ExtensionResource resource(extension->id(),
+                             extension->path(),
+                             icon_relative_path);
+
+  // Read in the icon file.
+  FilePath icon_absolute_path = extension->path().Append(icon_relative_path);
+  std::string raw_png;
+  ASSERT_TRUE(file_util::ReadFileToString(icon_absolute_path, &raw_png));
+  SkBitmap image;
+  ASSERT_TRUE(gfx::PNGCodec::Decode(
+      reinterpret_cast<const unsigned char*>(raw_png.data()),
+      raw_png.length(),
+      &image));
+
+  // Make sure the icon file is the size we expect.
+  gfx::Size original_size(66, 66);
+  ASSERT_EQ(image.width(), original_size.width());
+  ASSERT_EQ(image.height(), original_size.height());
+
+  // Create two resized versions at size 16x16 and 24x24.
+  SkBitmap image16 = ResizedCopy(image, 16);
+  SkBitmap image24 = ResizedCopy(image, 24);
+
+  gfx::Size size16(16, 16);
+  gfx::Size size24(24, 24);
+
+  // Cache the 16x16 copy.
+  EXPECT_FALSE(extension->HasCachedImage(resource, size16));
+  extension->SetCachedImage(resource, image16, original_size);
+  EXPECT_TRUE(extension->HasCachedImage(resource, size16));
+  EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, size16), size16));
+  EXPECT_FALSE(extension->HasCachedImage(resource, size24));
+  EXPECT_FALSE(extension->HasCachedImage(resource, original_size));
+
+  // Cache the 24x24 copy.
+  extension->SetCachedImage(resource, image24, original_size);
+  EXPECT_TRUE(extension->HasCachedImage(resource, size24));
+  EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, size24), size24));
+  EXPECT_FALSE(extension->HasCachedImage(resource, original_size));
+
+  // Cache the original, and verify that it gets returned when we ask for a
+  // max_size that is larger than the original.
+  gfx::Size size128(128, 128);
+  EXPECT_TRUE(image.width() < size128.width() &&
+              image.height() < size128.height());
+  extension->SetCachedImage(resource, image, original_size);
+  EXPECT_TRUE(extension->HasCachedImage(resource, original_size));
+  EXPECT_TRUE(extension->HasCachedImage(resource, size128));
+  EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, original_size),
+                         original_size));
+  EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, size128),
+                         original_size));
+  EXPECT_EQ(extension->GetCachedImage(resource, original_size).getPixels(),
+            extension->GetCachedImage(resource, size128).getPixels());
+}
+
+// This tests the API permissions with an empty manifest (one that just
+// specifies a name and a version and nothing else).
+TEST(ExtensionTest, ApiPermissions) {
+  const struct {
+    const char* permission_name;
+    bool expect_success;
+  } kTests[] = {
+    // Negative test.
+    { "non_existing_permission", false },
+    // Test default module/package permission.
+    { "browserAction",  true },
+    { "devtools",       true },
+    { "extension",      true },
+    { "i18n",           true },
+    { "pageAction",     true },
+    { "pageActions",    true },
+    { "test",           true },
+    // Some negative tests.
+    { "bookmarks",      false },
+    { "cookies",        false },
+    { "history",        false },
+    // Make sure we find the module name after stripping '.' and '/'.
+    { "browserAction/abcd/onClick",  true },
+    { "browserAction.abcd.onClick",  true },
+    // Test Tabs functions.
+    { "tabs.create",      true},
+    { "tabs.duplicate",   true},
+    { "tabs.onRemoved",   true},
+    { "tabs.remove",      true},
+    { "tabs.update",      true},
+    { "tabs.getSelected", true},
+    { "tabs.onUpdated",   true },
+    // Test getPermissionWarnings functions. Only one requires permissions.
+    { "management.getPermissionWarningsById", false },
+    { "management.getPermissionWarningsByManifest", true },
+  };
+
+  scoped_refptr<Extension> extension;
+  extension = LoadManifest("empty_manifest", "empty.json");
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) {
+    EXPECT_EQ(kTests[i].expect_success,
+              extension->HasAPIPermission(kTests[i].permission_name))
+                  << "Permission being tested: " << kTests[i].permission_name;
+  }
+}
+
+TEST(ExtensionTest, GetPermissionMessages_ManyApiPermissions) {
+  scoped_refptr<Extension> extension;
+  extension = LoadManifest("permissions", "many-apis.json");
+  std::vector<string16> warnings = extension->GetPermissionMessageStrings();
+  ASSERT_EQ(6u, warnings.size());
+  EXPECT_EQ("Access your data on api.flickr.com",
+            UTF16ToUTF8(warnings[0]));
+  EXPECT_EQ("Read and modify your bookmarks", UTF16ToUTF8(warnings[1]));
+  EXPECT_EQ("Detect your physical location", UTF16ToUTF8(warnings[2]));
+  EXPECT_EQ("Read and modify your browsing history", UTF16ToUTF8(warnings[3]));
+  EXPECT_EQ("Access your tabs and browsing activity", UTF16ToUTF8(warnings[4]));
+  EXPECT_EQ("Manage your apps, extensions, and themes",
+            UTF16ToUTF8(warnings[5]));
+}
+
+TEST(ExtensionTest, GetPermissionMessages_ManyHosts) {
+  scoped_refptr<Extension> extension;
+  extension = LoadManifest("permissions", "many-hosts.json");
+  std::vector<string16> warnings = extension->GetPermissionMessageStrings();
+  ASSERT_EQ(1u, warnings.size());
+  EXPECT_EQ("Access your data on encrypted.google.com and www.google.com",
+            UTF16ToUTF8(warnings[0]));
+}
+
+TEST(ExtensionTest, GetPermissionMessages_Plugins) {
+  scoped_refptr<Extension> extension;
+  extension = LoadManifest("permissions", "plugins.json");
+  std::vector<string16> warnings = extension->GetPermissionMessageStrings();
+  // We don't parse the plugins key on Chrome OS, so it should not ask for any
+  // permissions.
+#if defined(OS_CHROMEOS)
+  ASSERT_EQ(0u, warnings.size());
+#else
+  ASSERT_EQ(1u, warnings.size());
+  EXPECT_EQ("Access all data on your computer and the websites you visit",
+            UTF16ToUTF8(warnings[0]));
+#endif
+}
+
+TEST(ExtensionTest, WantsFileAccess) {
+  scoped_refptr<Extension> extension;
+  GURL file_url("file:///etc/passwd");
+
+  // <all_urls> permission
+  extension = LoadManifest("permissions", "permissions_all_urls.json");
+  EXPECT_TRUE(extension->wants_file_access());
+  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
+      file_url, file_url, -1, NULL, NULL));
+  extension = LoadManifest(
+      "permissions", "permissions_all_urls.json", Extension::ALLOW_FILE_ACCESS);
+  EXPECT_TRUE(extension->wants_file_access());
+  EXPECT_TRUE(extension->CanExecuteScriptOnPage(
+      file_url, file_url, -1, NULL, NULL));
+
+  // file:///* permission
+  extension = LoadManifest("permissions", "permissions_file_scheme.json");
+  EXPECT_TRUE(extension->wants_file_access());
+  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
+      file_url, file_url, -1, NULL, NULL));
+  extension = LoadManifest("permissions", "permissions_file_scheme.json",
+      Extension::ALLOW_FILE_ACCESS);
+  EXPECT_TRUE(extension->wants_file_access());
+  EXPECT_TRUE(extension->CanExecuteScriptOnPage(
+      file_url, file_url, -1, NULL, NULL));
+
+  // http://* permission
+  extension = LoadManifest("permissions", "permissions_http_scheme.json");
+  EXPECT_FALSE(extension->wants_file_access());
+  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
+      file_url, file_url, -1, NULL, NULL));
+  extension = LoadManifest("permissions", "permissions_http_scheme.json",
+      Extension::ALLOW_FILE_ACCESS);
+  EXPECT_FALSE(extension->wants_file_access());
+  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
+      file_url, file_url, -1, NULL, NULL));
+
+  // <all_urls> content script match
+  extension = LoadManifest("permissions", "content_script_all_urls.json");
+  EXPECT_TRUE(extension->wants_file_access());
+  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
+      file_url, file_url, -1, &extension->content_scripts()[0], NULL));
+  extension = LoadManifest("permissions", "content_script_all_urls.json",
+      Extension::ALLOW_FILE_ACCESS);
+  EXPECT_TRUE(extension->wants_file_access());
+  EXPECT_TRUE(extension->CanExecuteScriptOnPage(
+      file_url, file_url, -1, &extension->content_scripts()[0], NULL));
+
+  // file:///* content script match
+  extension = LoadManifest("permissions", "content_script_file_scheme.json");
+  EXPECT_TRUE(extension->wants_file_access());
+  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
+      file_url, file_url, -1, &extension->content_scripts()[0], NULL));
+  extension = LoadManifest("permissions", "content_script_file_scheme.json",
+      Extension::ALLOW_FILE_ACCESS);
+  EXPECT_TRUE(extension->wants_file_access());
+  EXPECT_TRUE(extension->CanExecuteScriptOnPage(
+      file_url, file_url, -1, &extension->content_scripts()[0], NULL));
+
+  // http://* content script match
+  extension = LoadManifest("permissions", "content_script_http_scheme.json");
+  EXPECT_FALSE(extension->wants_file_access());
+  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
+      file_url, file_url, -1, &extension->content_scripts()[0], NULL));
+  extension = LoadManifest("permissions", "content_script_http_scheme.json",
+      Extension::ALLOW_FILE_ACCESS);
+  EXPECT_FALSE(extension->wants_file_access());
+  EXPECT_FALSE(extension->CanExecuteScriptOnPage(
+      file_url, file_url, -1, &extension->content_scripts()[0], NULL));
+}
+
+TEST(ExtensionTest, ExtraFlags) {
+  scoped_refptr<Extension> extension;
+  extension = LoadManifest("app", "manifest.json", Extension::FROM_WEBSTORE);
+  EXPECT_TRUE(extension->from_webstore());
+
+  extension = LoadManifest("app", "manifest.json", Extension::FROM_BOOKMARK);
+  EXPECT_TRUE(extension->from_bookmark());
+
+  extension = LoadManifest("app", "manifest.json", Extension::NO_FLAGS);
+  EXPECT_FALSE(extension->from_bookmark());
+  EXPECT_FALSE(extension->from_webstore());
+}
+
+TEST(ExtensionTest, BrowserActionSynthesizesCommand) {
+  scoped_refptr<Extension> extension;
+
+  extension = LoadManifest("api_test/browser_action/synthesized",
+                           "manifest.json");
+  // An extension with a browser action but no extension command specified
+  // should get a command assigned to it.
+  const extensions::Command* command = extension->browser_action_command();
+  ASSERT_TRUE(command != NULL);
+  ASSERT_EQ(ui::VKEY_UNKNOWN, command->accelerator().key_code());
+}
+
+// Base class for testing the CanExecuteScriptOnPage and CanCaptureVisiblePage
+// methods of Extension for extensions with various permissions.
+class ExtensionScriptAndCaptureVisibleTest : public testing::Test {
+ protected:
+  ExtensionScriptAndCaptureVisibleTest()
+      : http_url("http://www.google.com"),
+        http_url_with_path("http://www.google.com/index.html"),
+        https_url("https://www.google.com"),
+        file_url("file:///foo/bar"),
+        favicon_url("chrome://favicon/http://www.google.com"),
+        extension_url("chrome-extension://" +
+            Extension::GenerateIdForPath(FilePath(FILE_PATH_LITERAL("foo")))),
+        settings_url("chrome://settings"),
+        about_url("about:flags") {
+    urls_.insert(http_url);
+    urls_.insert(http_url_with_path);
+    urls_.insert(https_url);
+    urls_.insert(file_url);
+    urls_.insert(favicon_url);
+    urls_.insert(extension_url);
+    urls_.insert(settings_url);
+    urls_.insert(about_url);
+  }
+
+  bool AllowedScript(const Extension* extension, const GURL& url,
+                     const GURL& top_url) {
+    return extension->CanExecuteScriptOnPage(url, top_url, -1, NULL, NULL);
+  }
+
+  bool BlockedScript(const Extension* extension, const GURL& url,
+                     const GURL& top_url) {
+    return !extension->CanExecuteScriptOnPage(url, top_url, -1, NULL, NULL);
+  }
+
+  bool Allowed(const Extension* extension, const GURL& url) {
+    return Allowed(extension, url, -1);
+  }
+
+  bool Allowed(const Extension* extension, const GURL& url, int tab_id) {
+    return (extension->CanExecuteScriptOnPage(url, url, tab_id, NULL, NULL) &&
+            extension->CanCaptureVisiblePage(url, tab_id, NULL));
+  }
+
+  bool CaptureOnly(const Extension* extension, const GURL& url) {
+    return CaptureOnly(extension, url, -1);
+  }
+
+  bool CaptureOnly(const Extension* extension, const GURL& url, int tab_id) {
+    return !extension->CanExecuteScriptOnPage(url, url, tab_id, NULL, NULL) &&
+            extension->CanCaptureVisiblePage(url, tab_id, NULL);
+  }
+
+  bool Blocked(const Extension* extension, const GURL& url) {
+    return Blocked(extension, url, -1);
+  }
+
+  bool Blocked(const Extension* extension, const GURL& url, int tab_id) {
+    return !(extension->CanExecuteScriptOnPage(url, url, tab_id, NULL, NULL) ||
+             extension->CanCaptureVisiblePage(url, tab_id, NULL));
+  }
+
+  bool AllowedExclusivelyOnTab(
+      const Extension* extension,
+      const std::set<GURL>& allowed_urls,
+      int tab_id) {
+    bool result = true;
+    for (std::set<GURL>::iterator it = urls_.begin(); it != urls_.end(); ++it) {
+      const GURL& url = *it;
+      if (allowed_urls.count(url))
+        result &= Allowed(extension, url, tab_id);
+      else
+        result &= Blocked(extension, url, tab_id);
+    }
+    return result;
+  }
+
+  // URLs that are "safe" to provide scripting and capture visible tab access
+  // to if the permissions allow it.
+  const GURL http_url;
+  const GURL http_url_with_path;
+  const GURL https_url;
+  const GURL file_url;
+
+  // We should allow host permission but not scripting permission for favicon
+  // urls.
+  const GURL favicon_url;
+
+  // URLs that regular extensions should never get access to.
+  const GURL extension_url;
+  const GURL settings_url;
+  const GURL about_url;
+
+ private:
+  // The set of all URLs above.
+  std::set<GURL> urls_;
+};
+
+TEST_F(ExtensionScriptAndCaptureVisibleTest, Permissions) {
+  scoped_refptr<Extension> extension;
+
+  // Test <all_urls> for regular extensions.
+  extension = LoadManifestStrict("script_and_capture",
+      "extension_regular_all.json");
+  EXPECT_TRUE(Allowed(extension, http_url));
+  EXPECT_TRUE(Allowed(extension, https_url));
+  EXPECT_TRUE(Blocked(extension, file_url));
+  EXPECT_TRUE(Blocked(extension, settings_url));
+  EXPECT_TRUE(CaptureOnly(extension, favicon_url));
+  EXPECT_TRUE(Blocked(extension, about_url));
+  EXPECT_TRUE(Blocked(extension, extension_url));
+
+  // Test access to iframed content.
+  GURL within_extension_url = extension->GetResourceURL("page.html");
+  EXPECT_TRUE(AllowedScript(extension, http_url, http_url_with_path));
+  EXPECT_TRUE(AllowedScript(extension, https_url, http_url_with_path));
+  EXPECT_TRUE(AllowedScript(extension, http_url, within_extension_url));
+  EXPECT_TRUE(AllowedScript(extension, https_url, within_extension_url));
+  EXPECT_TRUE(BlockedScript(extension, http_url, extension_url));
+  EXPECT_TRUE(BlockedScript(extension, https_url, extension_url));
+
+  EXPECT_FALSE(extension->HasHostPermission(settings_url));
+  EXPECT_FALSE(extension->HasHostPermission(about_url));
+  EXPECT_TRUE(extension->HasHostPermission(favicon_url));
+
+  // Test * for scheme, which implies just the http/https schemes.
+  extension = LoadManifestStrict("script_and_capture",
+      "extension_wildcard.json");
+  EXPECT_TRUE(Allowed(extension, http_url));
+  EXPECT_TRUE(Allowed(extension, https_url));
+  EXPECT_TRUE(Blocked(extension, settings_url));
+  EXPECT_TRUE(Blocked(extension, about_url));
+  EXPECT_TRUE(Blocked(extension, file_url));
+  EXPECT_TRUE(Blocked(extension, favicon_url));
+  extension = LoadManifest("script_and_capture",
+      "extension_wildcard_settings.json");
+  EXPECT_TRUE(Blocked(extension, settings_url));
+
+  // Having chrome://*/ should not work for regular extensions. Note that
+  // for favicon access, we require the explicit pattern chrome://favicon/*.
+  std::string error;
+  extension = LoadManifestUnchecked("script_and_capture",
+                                    "extension_wildcard_chrome.json",
+                                    Extension::INTERNAL, Extension::NO_FLAGS,
+                                    &error);
+  EXPECT_TRUE(extension == NULL);
+  EXPECT_EQ(ExtensionErrorUtils::FormatErrorMessage(
+      errors::kInvalidPermissionScheme, "chrome://*/"), error);
+
+  // Having chrome://favicon/* should not give you chrome://*
+  extension = LoadManifestStrict("script_and_capture",
+      "extension_chrome_favicon_wildcard.json");
+  EXPECT_TRUE(Blocked(extension, settings_url));
+  EXPECT_TRUE(CaptureOnly(extension, favicon_url));
+  EXPECT_TRUE(Blocked(extension, about_url));
+  EXPECT_TRUE(extension->HasHostPermission(favicon_url));
+
+  // Having http://favicon should not give you chrome://favicon
+  extension = LoadManifestStrict("script_and_capture",
+      "extension_http_favicon.json");
+  EXPECT_TRUE(Blocked(extension, settings_url));
+  EXPECT_TRUE(Blocked(extension, favicon_url));
+
+  // Component extensions with <all_urls> should get everything.
+  extension = LoadManifest("script_and_capture", "extension_component_all.json",
+      Extension::COMPONENT, Extension::NO_FLAGS);
+  EXPECT_TRUE(Allowed(extension, http_url));
+  EXPECT_TRUE(Allowed(extension, https_url));
+  EXPECT_TRUE(Allowed(extension, settings_url));
+  EXPECT_TRUE(Allowed(extension, about_url));
+  EXPECT_TRUE(Allowed(extension, favicon_url));
+  EXPECT_TRUE(extension->HasHostPermission(favicon_url));
+
+  // Component extensions should only get access to what they ask for.
+  extension = LoadManifest("script_and_capture",
+      "extension_component_google.json", Extension::COMPONENT,
+      Extension::NO_FLAGS);
+  EXPECT_TRUE(Allowed(extension, http_url));
+  EXPECT_TRUE(Blocked(extension, https_url));
+  EXPECT_TRUE(Blocked(extension, file_url));
+  EXPECT_TRUE(Blocked(extension, settings_url));
+  EXPECT_TRUE(Blocked(extension, favicon_url));
+  EXPECT_TRUE(Blocked(extension, about_url));
+  EXPECT_TRUE(Blocked(extension, extension_url));
+  EXPECT_FALSE(extension->HasHostPermission(settings_url));
+}
+
+TEST_F(ExtensionScriptAndCaptureVisibleTest, TabSpecific) {
+  scoped_refptr<Extension> extension =
+      LoadManifestStrict("script_and_capture", "tab_specific.json");
+
+  EXPECT_FALSE(extension->GetTabSpecificPermissions(0).get());
+  EXPECT_FALSE(extension->GetTabSpecificPermissions(1).get());
+  EXPECT_FALSE(extension->GetTabSpecificPermissions(2).get());
+
+  std::set<GURL> no_urls;
+
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 0));
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 1));
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));
+
+  URLPatternSet allowed_hosts;
+  allowed_hosts.AddPattern(URLPattern(URLPattern::SCHEME_ALL,
+                                      http_url.spec()));
+  std::set<GURL> allowed_urls;
+  allowed_urls.insert(http_url);
+  // http_url_with_path() will also be allowed, because Extension should be
+  // considering the security origin of the URL not the URL itself, and
+  // http_url is in allowed_hosts.
+  allowed_urls.insert(http_url_with_path);
+
+  {
+    scoped_refptr<PermissionSet> permissions(
+        new PermissionSet(APIPermissionSet(), allowed_hosts, URLPatternSet()));
+    extension->UpdateTabSpecificPermissions(0, permissions);
+    EXPECT_EQ(permissions->explicit_hosts(),
+              extension->GetTabSpecificPermissions(0)->explicit_hosts());
+  }
+
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, allowed_urls, 0));
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 1));
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));
+
+  extension->ClearTabSpecificPermissions(0);
+  EXPECT_FALSE(extension->GetTabSpecificPermissions(0).get());
+
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 0));
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 1));
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));
+
+  std::set<GURL> more_allowed_urls = allowed_urls;
+  more_allowed_urls.insert(https_url);
+  URLPatternSet more_allowed_hosts = allowed_hosts;
+  more_allowed_hosts.AddPattern(URLPattern(URLPattern::SCHEME_ALL,
+                                           https_url.spec()));
+
+  {
+    scoped_refptr<PermissionSet> permissions(
+        new PermissionSet(APIPermissionSet(), allowed_hosts, URLPatternSet()));
+    extension->UpdateTabSpecificPermissions(0, permissions);
+    EXPECT_EQ(permissions->explicit_hosts(),
+              extension->GetTabSpecificPermissions(0)->explicit_hosts());
+
+    permissions = new PermissionSet(APIPermissionSet(),
+                                    more_allowed_hosts,
+                                    URLPatternSet());
+    extension->UpdateTabSpecificPermissions(1, permissions);
+    EXPECT_EQ(permissions->explicit_hosts(),
+              extension->GetTabSpecificPermissions(1)->explicit_hosts());
+  }
+
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, allowed_urls, 0));
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, more_allowed_urls, 1));
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));
+
+  extension->ClearTabSpecificPermissions(0);
+  EXPECT_FALSE(extension->GetTabSpecificPermissions(0).get());
+
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 0));
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, more_allowed_urls, 1));
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));
+
+  extension->ClearTabSpecificPermissions(1);
+  EXPECT_FALSE(extension->GetTabSpecificPermissions(1).get());
+
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 0));
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 1));
+  EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));
+}
+
+TEST(ExtensionTest, GenerateId) {
+  std::string result;
+  EXPECT_TRUE(Extension::GenerateId("", &result));
+
+  EXPECT_TRUE(Extension::GenerateId("test", &result));
+  EXPECT_EQ(result, "jpignaibiiemhngfjkcpokkamffknabf");
+
+  EXPECT_TRUE(Extension::GenerateId("_", &result));
+  EXPECT_EQ(result, "ncocknphbhhlhkikpnnlmbcnbgdempcd");
+
+  EXPECT_TRUE(Extension::GenerateId(
+      "this_string_is_longer_than_a_single_sha256_hash_digest", &result));
+  EXPECT_EQ(result, "jimneklojkjdibfkgiiophfhjhbdgcfi");
+}
+
+namespace {
+enum SyncTestExtensionType {
+  EXTENSION,
+  APP,
+  USER_SCRIPT,
+  THEME
+};
+
+static scoped_refptr<Extension> MakeSyncTestExtension(
+    SyncTestExtensionType type,
+    const GURL& update_url,
+    const GURL& launch_url,
+    Extension::Location location,
+    int num_plugins,
+    const FilePath& extension_path,
+    int creation_flags) {
+  DictionaryValue source;
+  source.SetString(extension_manifest_keys::kName,
+                   "PossiblySyncableExtension");
+  source.SetString(extension_manifest_keys::kVersion, "0.0.0.0");
+  if (type == APP)
+    source.SetString(extension_manifest_keys::kApp, "true");
+  if (type == THEME)
+    source.Set(extension_manifest_keys::kTheme, new DictionaryValue());
+  if (!update_url.is_empty()) {
+    source.SetString(extension_manifest_keys::kUpdateURL,
+                     update_url.spec());
+  }
+  if (!launch_url.is_empty()) {
+    source.SetString(extension_manifest_keys::kLaunchWebURL,
+                     launch_url.spec());
+  }
+  if (type != THEME) {
+    source.SetBoolean(extension_manifest_keys::kConvertedFromUserScript,
+                      type == USER_SCRIPT);
+    ListValue* plugins = new ListValue();
+    for (int i = 0; i < num_plugins; ++i) {
+      DictionaryValue* plugin = new DictionaryValue();
+      plugin->SetString(extension_manifest_keys::kPluginsPath, "");
+      plugins->Set(i, plugin);
+    }
+    source.Set(extension_manifest_keys::kPlugins, plugins);
+  }
+
+  std::string error;
+  scoped_refptr<Extension> extension = Extension::Create(
+      extension_path, location, source, creation_flags, &error);
+  EXPECT_TRUE(extension);
+  EXPECT_EQ("", error);
+  return extension;
+}
+
+static const char kValidUpdateUrl1[] =
+    "http://clients2.google.com/service/update2/crx";
+static const char kValidUpdateUrl2[] =
+    "https://clients2.google.com/service/update2/crx";
+}
+
+TEST(ExtensionTest, GetSyncTypeNormalExtensionNoUpdateUrl) {
+  scoped_refptr<Extension> extension(
+      MakeSyncTestExtension(EXTENSION, GURL(), GURL(),
+                            Extension::INTERNAL, 0, FilePath(),
+                            Extension::NO_FLAGS));
+  EXPECT_NE(extension->GetSyncType(), Extension::SYNC_TYPE_NONE);
+}
+
+TEST(ExtensionTest, GetSyncTypeUserScriptValidUpdateUrl) {
+  scoped_refptr<Extension> extension(
+      MakeSyncTestExtension(USER_SCRIPT, GURL(kValidUpdateUrl1), GURL(),
+                            Extension::INTERNAL, 0, FilePath(),
+                            Extension::NO_FLAGS));
+  EXPECT_NE(extension->GetSyncType(), Extension::SYNC_TYPE_NONE);
+}
+
+TEST(ExtensionTest, GetSyncTypeUserScriptNoUpdateUrl) {
+  scoped_refptr<Extension> extension(
+      MakeSyncTestExtension(USER_SCRIPT, GURL(), GURL(),
+                            Extension::INTERNAL, 0, FilePath(),
+                            Extension::NO_FLAGS));
+  EXPECT_EQ(extension->GetSyncType(), Extension::SYNC_TYPE_NONE);
+}
+
+TEST(ExtensionTest, GetSyncTypeThemeNoUpdateUrl) {
+  scoped_refptr<Extension> extension(
+      MakeSyncTestExtension(THEME, GURL(), GURL(),
+                            Extension::INTERNAL, 0, FilePath(),
+                            Extension::NO_FLAGS));
+  EXPECT_EQ(extension->GetSyncType(), Extension::SYNC_TYPE_NONE);
+}
+
+TEST(ExtensionTest, GetSyncTypeExtensionWithLaunchUrl) {
+  scoped_refptr<Extension> extension(
+      MakeSyncTestExtension(EXTENSION, GURL(), GURL("http://www.google.com"),
+                            Extension::INTERNAL, 0, FilePath(),
+                            Extension::NO_FLAGS));
+  EXPECT_NE(extension->GetSyncType(), Extension::SYNC_TYPE_NONE);
+}
+
+TEST(ExtensionTest, GetSyncTypeExtensionExternal) {
+  scoped_refptr<Extension> extension(
+      MakeSyncTestExtension(EXTENSION, GURL(), GURL(),
+                            Extension::EXTERNAL_PREF, 0, FilePath(),
+                            Extension::NO_FLAGS));
+
+  EXPECT_EQ(extension->GetSyncType(), Extension::SYNC_TYPE_NONE);
+}
+
+TEST(ExtensionTest, GetSyncTypeUserScriptThirdPartyUpdateUrl) {
+  scoped_refptr<Extension> extension(
+      MakeSyncTestExtension(
+          USER_SCRIPT, GURL("http://third-party.update_url.com"), GURL(),
+          Extension::INTERNAL, 0, FilePath(), Extension::NO_FLAGS));
+  EXPECT_EQ(extension->GetSyncType(), Extension::SYNC_TYPE_NONE);
+}
+
+TEST(ExtensionTest, OnlyDisplayAppsInLauncher) {
+  scoped_refptr<Extension> extension(
+      MakeSyncTestExtension(EXTENSION, GURL(), GURL(),
+                            Extension::INTERNAL, 0, FilePath(),
+                            Extension::NO_FLAGS));
+
+  EXPECT_FALSE(extension->ShouldDisplayInAppLauncher());
+  EXPECT_FALSE(extension->ShouldDisplayInNewTabPage());
+
+  scoped_refptr<Extension> app(
+      MakeSyncTestExtension(APP, GURL(), GURL("http://www.google.com"),
+                            Extension::INTERNAL, 0, FilePath(),
+                            Extension::NO_FLAGS));
+  EXPECT_TRUE(app->ShouldDisplayInAppLauncher());
+  EXPECT_TRUE(app->ShouldDisplayInNewTabPage());
+}
+
+TEST(ExtensionTest, DisplayInXManifestProperties) {
+  DictionaryValue manifest;
+  manifest.SetString(keys::kName, "TestComponentApp");
+  manifest.SetString(keys::kVersion, "0.0.0.0");
+  manifest.SetString(keys::kApp, "true");
+  manifest.SetString(keys::kPlatformAppBackgroundPage, "");
+
+  std::string error;
+  scoped_refptr<Extension> app;
+
+  // Default to true.
+  app = Extension::Create(
+      FilePath(), Extension::COMPONENT, manifest, 0, &error);
+  EXPECT_EQ(error, std::string());
+  EXPECT_TRUE(app->ShouldDisplayInAppLauncher());
+  EXPECT_TRUE(app->ShouldDisplayInNewTabPage());
+
+  // Value display_in_NTP defaults to display_in_launcher.
+  manifest.SetBoolean(keys::kDisplayInLauncher, false);
+  app = Extension::Create(
+      FilePath(), Extension::COMPONENT, manifest, 0, &error);
+  EXPECT_EQ(error, std::string());
+  EXPECT_FALSE(app->ShouldDisplayInAppLauncher());
+  EXPECT_FALSE(app->ShouldDisplayInNewTabPage());
+
+  // Value display_in_NTP = true overriding display_in_launcher = false.
+  manifest.SetBoolean(keys::kDisplayInNewTabPage, true);
+  app = Extension::Create(
+      FilePath(), Extension::COMPONENT, manifest, 0, &error);
+  EXPECT_EQ(error, std::string());
+  EXPECT_FALSE(app->ShouldDisplayInAppLauncher());
+  EXPECT_TRUE(app->ShouldDisplayInNewTabPage());
+
+  // Value display_in_NTP = false only, overrides default = true.
+  manifest.Remove(keys::kDisplayInLauncher, NULL);
+  manifest.SetBoolean(keys::kDisplayInNewTabPage, false);
+  app = Extension::Create(
+      FilePath(), Extension::COMPONENT, manifest, 0, &error);
+  EXPECT_EQ(error, std::string());
+  EXPECT_TRUE(app->ShouldDisplayInAppLauncher());
+  EXPECT_FALSE(app->ShouldDisplayInNewTabPage());
+
+  // Error checking.
+  manifest.SetString(keys::kDisplayInNewTabPage, "invalid");
+  app = Extension::Create(
+      FilePath(), Extension::COMPONENT, manifest, 0, &error);
+  EXPECT_EQ(error, std::string(errors::kInvalidDisplayInNewTabPage));
+}
+
+TEST(ExtensionTest, OnlySyncInternal) {
+  scoped_refptr<Extension> extension_internal(
+      MakeSyncTestExtension(EXTENSION, GURL(), GURL(),
+                            Extension::INTERNAL, 0, FilePath(),
+                            Extension::NO_FLAGS));
+  EXPECT_TRUE(extension_internal->IsSyncable());
+
+  scoped_refptr<Extension> extension_noninternal(
+      MakeSyncTestExtension(EXTENSION, GURL(), GURL(),
+                            Extension::COMPONENT, 0, FilePath(),
+                            Extension::NO_FLAGS));
+  EXPECT_FALSE(extension_noninternal->IsSyncable());
+}
+
+TEST(ExtensionTest, DontSyncDefault) {
+  scoped_refptr<Extension> extension_default(
+      MakeSyncTestExtension(EXTENSION, GURL(), GURL(),
+                            Extension::INTERNAL, 0, FilePath(),
+                            Extension::WAS_INSTALLED_BY_DEFAULT));
+  EXPECT_FALSE(extension_default->IsSyncable());
+}
+
+// These last 2 tests don't make sense on Chrome OS, where extension plugins
+// are not allowed.
+#if !defined(OS_CHROMEOS)
+TEST(ExtensionTest, GetSyncTypeExtensionWithPlugin) {
+  scoped_refptr<Extension> extension(
+      MakeSyncTestExtension(EXTENSION, GURL(), GURL(),
+                            Extension::INTERNAL, 1, FilePath(),
+                            Extension::NO_FLAGS));
+  if (extension)
+    EXPECT_EQ(extension->GetSyncType(), Extension::SYNC_TYPE_NONE);
+}
+
+TEST(ExtensionTest, GetSyncTypeExtensionWithTwoPlugins) {
+  scoped_refptr<Extension> extension(
+      MakeSyncTestExtension(EXTENSION, GURL(), GURL(),
+                            Extension::INTERNAL, 2, FilePath(),
+                            Extension::NO_FLAGS));
+  if (extension)
+    EXPECT_EQ(extension->GetSyncType(), Extension::SYNC_TYPE_NONE);
+}
+#endif // !defined(OS_CHROMEOS)
diff --git a/chrome/common/extensions/feature_switch.cc b/chrome/common/extensions/feature_switch.cc
new file mode 100644
index 0000000..0e62437
--- /dev/null
+++ b/chrome/common/extensions/feature_switch.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/feature_switch.h"
+
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/metrics/field_trial.h"
+#include "base/string_util.h"
+#include "chrome/common/chrome_switches.h"
+
+namespace extensions {
+
+namespace {
+
+class CommonSwitches {
+ public:
+  CommonSwitches()
+      : action_box(
+            switches::kActionBox,
+            FeatureSwitch::DEFAULT_DISABLED),
+        easy_off_store_install(
+            switches::kEasyOffStoreExtensionInstall,
+            FeatureSwitch::DEFAULT_DISABLED),
+        extensions_in_action_box(
+            switches::kExtensionsInActionBox,
+            FeatureSwitch::DEFAULT_DISABLED),
+        script_badges(
+            switches::kScriptBadges,
+            FeatureSwitch::DEFAULT_DISABLED),
+        script_bubble(
+            switches::kScriptBubble,
+            FeatureSwitch::DEFAULT_DISABLED),
+        // TODO(finnur): When enabling this, only enable for OS_WIN.
+        sideload_wipeout(
+            switches::kSideloadWipeout,
+            base::FieldTrialList::FindFullName("SideloadWipeout") == "Enabled" ?
+                FeatureSwitch::DEFAULT_ENABLED :
+                FeatureSwitch::DEFAULT_DISABLED),
+        prompt_for_external_extensions(
+            switches::kPromptForExternalExtensions,
+            base::FieldTrialList::FindFullName("SideloadWipeout") == "Enabled" ?
+                FeatureSwitch::DEFAULT_ENABLED :
+                FeatureSwitch::DEFAULT_DISABLED),
+        tab_capture(
+            switches::kTabCapture,
+            FeatureSwitch::DEFAULT_DISABLED)
+  {
+    if (!action_box.IsEnabled()){
+      extensions_in_action_box.SetOverrideValue(
+          FeatureSwitch::OVERRIDE_DISABLED);
+    }
+  }
+
+  FeatureSwitch action_box;
+  FeatureSwitch easy_off_store_install;
+  FeatureSwitch extensions_in_action_box;
+  FeatureSwitch script_badges;
+  FeatureSwitch script_bubble;
+  FeatureSwitch sideload_wipeout;
+  FeatureSwitch prompt_for_external_extensions;
+  FeatureSwitch tab_capture;
+};
+
+base::LazyInstance<CommonSwitches> g_common_switches =
+    LAZY_INSTANCE_INITIALIZER;
+
+}  // namespace
+
+
+FeatureSwitch* FeatureSwitch::action_box() {
+  return &g_common_switches.Get().action_box;
+}
+FeatureSwitch* FeatureSwitch::easy_off_store_install() {
+  return &g_common_switches.Get().easy_off_store_install;
+}
+FeatureSwitch* FeatureSwitch::extensions_in_action_box() {
+  return &g_common_switches.Get().extensions_in_action_box;
+}
+FeatureSwitch* FeatureSwitch::script_badges() {
+  return &g_common_switches.Get().script_badges;
+}
+FeatureSwitch* FeatureSwitch::script_bubble() {
+  return &g_common_switches.Get().script_bubble;
+}
+FeatureSwitch* FeatureSwitch::sideload_wipeout() {
+  return &g_common_switches.Get().sideload_wipeout;
+}
+FeatureSwitch* FeatureSwitch::prompt_for_external_extensions() {
+  return &g_common_switches.Get().prompt_for_external_extensions;
+}
+FeatureSwitch* FeatureSwitch::tab_capture() {
+  return &g_common_switches.Get().tab_capture;
+}
+
+
+FeatureSwitch::ScopedOverride::ScopedOverride(FeatureSwitch* feature,
+                                              bool override_value)
+    : feature_(feature),
+      previous_value_(feature->GetOverrideValue()) {
+  feature_->SetOverrideValue(
+      override_value ? OVERRIDE_ENABLED : OVERRIDE_DISABLED);
+}
+
+FeatureSwitch::ScopedOverride::~ScopedOverride() {
+  feature_->SetOverrideValue(previous_value_);
+}
+
+FeatureSwitch::FeatureSwitch(const char* switch_name,
+                             DefaultValue default_value) {
+  Init(CommandLine::ForCurrentProcess(), switch_name, default_value);
+}
+
+FeatureSwitch::FeatureSwitch(const CommandLine* command_line,
+                             const char* switch_name,
+                             DefaultValue default_value) {
+  Init(command_line, switch_name, default_value);
+}
+
+void FeatureSwitch::Init(const CommandLine* command_line,
+                         const char* switch_name,
+                         DefaultValue default_value) {
+  command_line_ = command_line;
+  switch_name_ = switch_name;
+  default_value_ = default_value == DEFAULT_ENABLED;
+  override_value_ = OVERRIDE_NONE;
+}
+
+bool FeatureSwitch::IsEnabled() const {
+  if (override_value_ != OVERRIDE_NONE)
+    return override_value_ == OVERRIDE_ENABLED;
+
+  std::string temp = command_line_->GetSwitchValueASCII(switch_name_);
+  std::string switch_value;
+  TrimWhitespaceASCII(temp, TRIM_ALL, &switch_value);
+
+  if (switch_value == "1")
+    return true;
+
+  if (switch_value == "0")
+    return false;
+
+  if (!default_value_ && command_line_->HasSwitch(GetLegacyEnableFlag()))
+    return true;
+
+  if (default_value_ && command_line_->HasSwitch(GetLegacyDisableFlag()))
+    return false;
+
+  return default_value_;
+}
+
+std::string FeatureSwitch::GetLegacyEnableFlag() const {
+  return std::string("enable-") + switch_name_;
+}
+
+std::string FeatureSwitch::GetLegacyDisableFlag() const {
+  return std::string("disable-") + switch_name_;
+}
+
+void FeatureSwitch::SetOverrideValue(OverrideValue override_value) {
+  override_value_ = override_value;
+}
+
+FeatureSwitch::OverrideValue FeatureSwitch::GetOverrideValue() const {
+  return override_value_;
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/feature_switch.h b/chrome/common/extensions/feature_switch.h
new file mode 100644
index 0000000..ba9e184
--- /dev/null
+++ b/chrome/common/extensions/feature_switch.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_FEATURE_SWITCH_H_
+#define CHROME_COMMON_EXTENSIONS_FEATURE_SWITCH_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+
+class CommandLine;
+
+namespace extensions {
+
+// A switch that can turn a feature on or off. Typically controlled via
+// command-line switches but can be overridden, e.g., for testing.
+class FeatureSwitch {
+ public:
+  static FeatureSwitch* action_box();
+  static FeatureSwitch* easy_off_store_install();
+  static FeatureSwitch* extensions_in_action_box();
+  static FeatureSwitch* script_badges();
+  static FeatureSwitch* script_bubble();
+  static FeatureSwitch* sideload_wipeout();
+  static FeatureSwitch* prompt_for_external_extensions();
+  static FeatureSwitch* tab_capture();
+
+  enum DefaultValue {
+    DEFAULT_ENABLED,
+    DEFAULT_DISABLED
+  };
+
+  enum OverrideValue {
+    OVERRIDE_NONE,
+    OVERRIDE_ENABLED,
+    OVERRIDE_DISABLED
+  };
+
+  // A temporary override for the switch value.
+  class ScopedOverride {
+   public:
+    ScopedOverride(FeatureSwitch* feature, bool override_value);
+    ~ScopedOverride();
+   private:
+    FeatureSwitch* feature_;
+    FeatureSwitch::OverrideValue previous_value_;
+    DISALLOW_COPY_AND_ASSIGN(ScopedOverride);
+  };
+
+  FeatureSwitch(const char* switch_name,
+                DefaultValue default_value);
+  FeatureSwitch(const CommandLine* command_line,
+                const char* switch_name,
+                DefaultValue default_value);
+
+  // Consider using ScopedOverride instead.
+  void SetOverrideValue(OverrideValue value);
+  OverrideValue GetOverrideValue() const;
+
+  bool IsEnabled() const;
+
+  std::string GetLegacyEnableFlag() const;
+  std::string GetLegacyDisableFlag() const;
+
+ private:
+  void Init(const CommandLine* command_line,
+            const char* switch_name,
+            DefaultValue default_value);
+
+  const CommandLine* command_line_;
+  const char* switch_name_;
+  bool default_value_;
+  OverrideValue override_value_;
+
+  DISALLOW_COPY_AND_ASSIGN(FeatureSwitch);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_FEATURE_SWITCH_H_
diff --git a/chrome/common/extensions/feature_switch_unittest.cc b/chrome/common/extensions/feature_switch_unittest.cc
new file mode 100644
index 0000000..4c7e204
--- /dev/null
+++ b/chrome/common/extensions/feature_switch_unittest.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/feature_switch.h"
+
+#include "base/command_line.h"
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::FeatureSwitch;
+
+namespace {
+
+const char kSwitchName[] = "test-switch";
+
+template<FeatureSwitch::DefaultValue T>
+class FeatureSwitchTest : public testing::Test {
+ public:
+  FeatureSwitchTest()
+      : command_line_(CommandLine::NO_PROGRAM),
+        feature_(&command_line_, kSwitchName, T) {
+  }
+ protected:
+  CommandLine command_line_;
+  FeatureSwitch feature_;
+};
+
+typedef FeatureSwitchTest<FeatureSwitch::DEFAULT_DISABLED>
+    FeatureSwitchDisabledTest;
+typedef FeatureSwitchTest<FeatureSwitch::DEFAULT_ENABLED>
+    FeatureSwitchEnabledTest;
+
+}  // namespace
+
+TEST_F(FeatureSwitchDisabledTest, NoSwitchValue) {
+  EXPECT_FALSE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchDisabledTest, FalseSwitchValue) {
+  command_line_.AppendSwitchASCII(kSwitchName, "0");
+  EXPECT_FALSE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchDisabledTest, GibberishSwitchValue) {
+  command_line_.AppendSwitchASCII(kSwitchName, "monkey");
+  EXPECT_FALSE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchDisabledTest, Override) {
+  {
+    FeatureSwitch::ScopedOverride override(&feature_, false);
+    EXPECT_FALSE(feature_.IsEnabled());
+  }
+  EXPECT_FALSE(feature_.IsEnabled());
+
+  {
+    FeatureSwitch::ScopedOverride override(&feature_, true);
+    EXPECT_TRUE(feature_.IsEnabled());
+  }
+  EXPECT_FALSE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchDisabledTest, TrueSwitchValue) {
+  command_line_.AppendSwitchASCII(kSwitchName, "1");
+  EXPECT_TRUE(feature_.IsEnabled());
+
+  {
+    FeatureSwitch::ScopedOverride override(&feature_, false);
+    EXPECT_FALSE(feature_.IsEnabled());
+  }
+  EXPECT_TRUE(feature_.IsEnabled());
+
+  {
+    FeatureSwitch::ScopedOverride override(&feature_, true);
+    EXPECT_TRUE(feature_.IsEnabled());
+  }
+  EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchDisabledTest, TrimSwitchValue) {
+  command_line_.AppendSwitchASCII(kSwitchName, " \t  1\n  ");
+  EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, NoSwitchValue) {
+  EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, TrueSwitchValue) {
+  command_line_.AppendSwitchASCII(kSwitchName, "1");
+  EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, GibberishSwitchValue) {
+  command_line_.AppendSwitchASCII(kSwitchName, "monkey");
+  EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, Override) {
+  {
+    FeatureSwitch::ScopedOverride override(&feature_, true);
+    EXPECT_TRUE(feature_.IsEnabled());
+  }
+  EXPECT_TRUE(feature_.IsEnabled());
+
+  {
+    FeatureSwitch::ScopedOverride override(&feature_, false);
+    EXPECT_FALSE(feature_.IsEnabled());
+  }
+  EXPECT_TRUE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, FalseSwitchValue) {
+  command_line_.AppendSwitchASCII(kSwitchName, "0");
+  EXPECT_FALSE(feature_.IsEnabled());
+
+  {
+    FeatureSwitch::ScopedOverride override(&feature_, true);
+    EXPECT_TRUE(feature_.IsEnabled());
+  }
+  EXPECT_FALSE(feature_.IsEnabled());
+
+  {
+    FeatureSwitch::ScopedOverride override(&feature_, false);
+    EXPECT_FALSE(feature_.IsEnabled());
+  }
+  EXPECT_FALSE(feature_.IsEnabled());
+}
+
+TEST_F(FeatureSwitchEnabledTest, TrimSwitchValue) {
+  command_line_.AppendSwitchASCII(kSwitchName, "\t\t 0 \n");
+  EXPECT_FALSE(feature_.IsEnabled());
+}
diff --git a/chrome/common/extensions/features/feature.cc b/chrome/common/extensions/features/feature.cc
new file mode 100644
index 0000000..8e4bb64
--- /dev/null
+++ b/chrome/common/extensions/features/feature.cc
@@ -0,0 +1,402 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/features/feature.h"
+
+#include <map>
+
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/stringprintf.h"
+#include "base/string_util.h"
+#include "chrome/common/chrome_switches.h"
+
+using chrome::VersionInfo;
+using extensions::Extension;
+
+namespace {
+
+struct Mappings {
+  Mappings() {
+    extension_types["extension"] = Extension::TYPE_EXTENSION;
+    extension_types["theme"] = Extension::TYPE_THEME;
+    extension_types["packaged_app"]
+        = Extension::TYPE_LEGACY_PACKAGED_APP;
+    extension_types["hosted_app"] = Extension::TYPE_HOSTED_APP;
+    extension_types["platform_app"] = Extension::TYPE_PLATFORM_APP;
+
+    contexts["blessed_extension"] =
+        extensions::Feature::BLESSED_EXTENSION_CONTEXT;
+    contexts["unblessed_extension"] =
+        extensions::Feature::UNBLESSED_EXTENSION_CONTEXT;
+    contexts["content_script"] = extensions::Feature::CONTENT_SCRIPT_CONTEXT;
+    contexts["web_page"] = extensions::Feature::WEB_PAGE_CONTEXT;
+
+    locations["component"] = extensions::Feature::COMPONENT_LOCATION;
+
+    platforms["chromeos"] = extensions::Feature::CHROMEOS_PLATFORM;
+
+    channels["trunk"] = VersionInfo::CHANNEL_UNKNOWN;
+    channels["canary"] = VersionInfo::CHANNEL_CANARY;
+    channels["dev"] = VersionInfo::CHANNEL_DEV;
+    channels["beta"] = VersionInfo::CHANNEL_BETA;
+    channels["stable"] = VersionInfo::CHANNEL_STABLE;
+  }
+
+  std::map<std::string, Extension::Type> extension_types;
+  std::map<std::string, extensions::Feature::Context> contexts;
+  std::map<std::string, extensions::Feature::Location> locations;
+  std::map<std::string, extensions::Feature::Platform> platforms;
+  std::map<std::string, VersionInfo::Channel> channels;
+};
+
+base::LazyInstance<Mappings> g_mappings = LAZY_INSTANCE_INITIALIZER;
+
+std::string GetChannelName(VersionInfo::Channel channel) {
+  typedef std::map<std::string, VersionInfo::Channel> ChannelsMap;
+  ChannelsMap channels = g_mappings.Get().channels;
+  for (ChannelsMap::iterator i = channels.begin(); i != channels.end(); ++i) {
+    if (i->second == channel)
+      return i->first;
+  }
+  NOTREACHED();
+  return "unknown";
+}
+
+const VersionInfo::Channel kDefaultChannel = VersionInfo::CHANNEL_STABLE;
+VersionInfo::Channel g_current_channel = kDefaultChannel;
+
+// TODO(aa): Can we replace all this manual parsing with JSON schema stuff?
+
+void ParseSet(const DictionaryValue* value,
+              const std::string& property,
+              std::set<std::string>* set) {
+  const ListValue* list_value = NULL;
+  if (!value->GetList(property, &list_value))
+    return;
+
+  set->clear();
+  for (size_t i = 0; i < list_value->GetSize(); ++i) {
+    std::string str_val;
+    CHECK(list_value->GetString(i, &str_val)) << property << " " << i;
+    set->insert(str_val);
+  }
+}
+
+template<typename T>
+void ParseEnum(const std::string& string_value,
+               T* enum_value,
+               const std::map<std::string, T>& mapping) {
+  typename std::map<std::string, T>::const_iterator iter =
+      mapping.find(string_value);
+  CHECK(iter != mapping.end()) << string_value;
+  *enum_value = iter->second;
+}
+
+template<typename T>
+void ParseEnum(const DictionaryValue* value,
+               const std::string& property,
+               T* enum_value,
+               const std::map<std::string, T>& mapping) {
+  std::string string_value;
+  if (!value->GetString(property, &string_value))
+    return;
+
+  ParseEnum(string_value, enum_value, mapping);
+}
+
+template<typename T>
+void ParseEnumSet(const DictionaryValue* value,
+                  const std::string& property,
+                  std::set<T>* enum_set,
+                  const std::map<std::string, T>& mapping) {
+  if (!value->HasKey(property))
+    return;
+
+  enum_set->clear();
+
+  std::string property_string;
+  if (value->GetString(property, &property_string)) {
+    if (property_string == "all") {
+      for (typename std::map<std::string, T>::const_iterator j =
+               mapping.begin(); j != mapping.end(); ++j) {
+        enum_set->insert(j->second);
+      }
+    }
+    return;
+  }
+
+  std::set<std::string> string_set;
+  ParseSet(value, property, &string_set);
+  for (std::set<std::string>::iterator iter = string_set.begin();
+       iter != string_set.end(); ++iter) {
+    T enum_value = static_cast<T>(0);
+    ParseEnum(*iter, &enum_value, mapping);
+    enum_set->insert(enum_value);
+  }
+}
+
+// Gets a human-readable name for the given extension type.
+std::string GetDisplayTypeName(Extension::Type type) {
+  switch (type) {
+    case Extension::TYPE_UNKNOWN:
+      return "unknown";
+    case Extension::TYPE_EXTENSION:
+      return "extension";
+    case Extension::TYPE_HOSTED_APP:
+      return "hosted app";
+    case Extension::TYPE_LEGACY_PACKAGED_APP:
+      return "legacy packaged app";
+    case Extension::TYPE_PLATFORM_APP:
+      return "packaged app";
+    case Extension::TYPE_THEME:
+      return "theme";
+    case Extension::TYPE_USER_SCRIPT:
+      return "user script";
+  }
+
+  NOTREACHED();
+  return "";
+}
+
+}  // namespace
+
+namespace extensions {
+
+Feature::Feature()
+  : location_(UNSPECIFIED_LOCATION),
+    platform_(UNSPECIFIED_PLATFORM),
+    min_manifest_version_(0),
+    max_manifest_version_(0),
+    channel_(VersionInfo::CHANNEL_UNKNOWN) {
+}
+
+Feature::Feature(const Feature& other)
+    : whitelist_(other.whitelist_),
+      extension_types_(other.extension_types_),
+      contexts_(other.contexts_),
+      location_(other.location_),
+      platform_(other.platform_),
+      min_manifest_version_(other.min_manifest_version_),
+      max_manifest_version_(other.max_manifest_version_),
+      channel_(other.channel_) {
+}
+
+Feature::~Feature() {
+}
+
+bool Feature::Equals(const Feature& other) const {
+  return whitelist_ == other.whitelist_ &&
+      extension_types_ == other.extension_types_ &&
+      contexts_ == other.contexts_ &&
+      location_ == other.location_ &&
+      platform_ == other.platform_ &&
+      min_manifest_version_ == other.min_manifest_version_ &&
+      max_manifest_version_ == other.max_manifest_version_ &&
+      channel_ == other.channel_;
+}
+
+// static
+Feature::Platform Feature::GetCurrentPlatform() {
+#if defined(OS_CHROMEOS)
+  return CHROMEOS_PLATFORM;
+#else
+  return UNSPECIFIED_PLATFORM;
+#endif
+}
+
+// static
+Feature::Location Feature::ConvertLocation(Extension::Location location) {
+  if (location == Extension::COMPONENT)
+    return COMPONENT_LOCATION;
+  else
+    return UNSPECIFIED_LOCATION;
+}
+
+void Feature::Parse(const DictionaryValue* value) {
+  ParseSet(value, "whitelist", &whitelist_);
+  ParseEnumSet<Extension::Type>(value, "extension_types", &extension_types_,
+                                g_mappings.Get().extension_types);
+  ParseEnumSet<Context>(value, "contexts", &contexts_,
+                        g_mappings.Get().contexts);
+  ParseEnum<Location>(value, "location", &location_,
+                      g_mappings.Get().locations);
+  ParseEnum<Platform>(value, "platform", &platform_,
+                      g_mappings.Get().platforms);
+  value->GetInteger("min_manifest_version", &min_manifest_version_);
+  value->GetInteger("max_manifest_version", &max_manifest_version_);
+  ParseEnum<VersionInfo::Channel>(
+      value, "channel", &channel_,
+      g_mappings.Get().channels);
+}
+
+Feature::Availability Feature::IsAvailableToManifest(
+    const std::string& extension_id,
+    Extension::Type type,
+    Location location,
+    int manifest_version,
+    Platform platform) const {
+  // Component extensions can access any feature.
+  if (location == COMPONENT_LOCATION)
+    return CreateAvailability(IS_AVAILABLE, type);
+
+  if (!whitelist_.empty()) {
+    if (whitelist_.find(extension_id) == whitelist_.end()) {
+      // TODO(aa): This is gross. There should be a better way to test the
+      // whitelist.
+      CommandLine* command_line = CommandLine::ForCurrentProcess();
+      if (!command_line->HasSwitch(switches::kWhitelistedExtensionID))
+        return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
+
+      std::string whitelist_switch_value =
+          CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+              switches::kWhitelistedExtensionID);
+      if (extension_id != whitelist_switch_value)
+        return CreateAvailability(NOT_FOUND_IN_WHITELIST, type);
+    }
+  }
+
+  if (!extension_types_.empty() &&
+      extension_types_.find(type) == extension_types_.end()) {
+    return CreateAvailability(INVALID_TYPE, type);
+  }
+
+  if (location_ != UNSPECIFIED_LOCATION && location_ != location)
+    return CreateAvailability(INVALID_LOCATION, type);
+
+  if (platform_ != UNSPECIFIED_PLATFORM && platform_ != platform)
+    return CreateAvailability(INVALID_PLATFORM, type);
+
+  if (min_manifest_version_ != 0 && manifest_version < min_manifest_version_)
+    return CreateAvailability(INVALID_MIN_MANIFEST_VERSION, type);
+
+  if (max_manifest_version_ != 0 && manifest_version > max_manifest_version_)
+    return CreateAvailability(INVALID_MAX_MANIFEST_VERSION, type);
+
+  if (channel_ < g_current_channel)
+    return CreateAvailability(UNSUPPORTED_CHANNEL, type);
+
+  return CreateAvailability(IS_AVAILABLE, type);
+}
+
+Feature::Availability Feature::IsAvailableToContext(
+    const Extension* extension,
+    Feature::Context context,
+    Feature::Platform platform) const {
+  Availability result = IsAvailableToManifest(
+      extension->id(),
+      extension->GetType(),
+      ConvertLocation(extension->location()),
+      extension->manifest_version(),
+      platform);
+  if (!result.is_available())
+    return result;
+
+  if (!contexts_.empty() &&
+      contexts_.find(context) == contexts_.end()) {
+    return CreateAvailability(INVALID_CONTEXT, extension->GetType());
+  }
+
+  return CreateAvailability(IS_AVAILABLE);
+}
+
+// static
+chrome::VersionInfo::Channel Feature::GetCurrentChannel() {
+  return g_current_channel;
+}
+
+// static
+void Feature::SetCurrentChannel(VersionInfo::Channel channel) {
+  g_current_channel = channel;
+}
+
+// static
+chrome::VersionInfo::Channel Feature::GetDefaultChannel() {
+  return kDefaultChannel;
+}
+
+Feature::Availability Feature::CreateAvailability(
+    AvailabilityResult result) const {
+  return Availability(
+      result, GetAvailabilityMessage(result, Extension::TYPE_UNKNOWN));
+}
+
+Feature::Availability Feature::CreateAvailability(
+    AvailabilityResult result, Extension::Type type) const {
+  return Availability(result, GetAvailabilityMessage(result, type));
+}
+
+std::string Feature::GetAvailabilityMessage(
+    AvailabilityResult result, Extension::Type type) const {
+  switch (result) {
+    case IS_AVAILABLE:
+      return "";
+    case NOT_FOUND_IN_WHITELIST:
+      return base::StringPrintf(
+          "'%s' is not allowed for specified extension ID.",
+          name().c_str());
+    case INVALID_TYPE: {
+      std::string allowed_type_names;
+      // Turn the set of allowed types into a vector so that it's easier to
+      // inject the appropriate separator into the display string.
+      std::vector<Extension::Type> extension_types(
+          extension_types_.begin(), extension_types_.end());
+      for (size_t i = 0; i < extension_types.size(); i++) {
+        // Pluralize type name.
+        allowed_type_names += GetDisplayTypeName(extension_types[i]) + "s";
+        if (i == extension_types_.size() - 2) {
+          allowed_type_names += " and ";
+        } else if (i != extension_types_.size() - 1) {
+          allowed_type_names += ", ";
+        }
+      }
+
+      return base::StringPrintf(
+          "'%s' is only allowed for %s, and this is a %s.",
+          name().c_str(),
+          allowed_type_names.c_str(),
+          GetDisplayTypeName(type).c_str());
+    }
+    case INVALID_CONTEXT:
+      return base::StringPrintf(
+          "'%s' is not allowed for specified context type content script, "
+          " extension page, web page, etc.).",
+          name().c_str());
+    case INVALID_LOCATION:
+      return base::StringPrintf(
+          "'%s' is not allowed for specified install location.",
+          name().c_str());
+    case INVALID_PLATFORM:
+      return base::StringPrintf(
+          "'%s' is not allowed for specified platform.",
+          name().c_str());
+    case INVALID_MIN_MANIFEST_VERSION:
+      return base::StringPrintf(
+          "'%s' requires manifest version of at least %d.",
+          name().c_str(),
+          min_manifest_version_);
+    case INVALID_MAX_MANIFEST_VERSION:
+      return base::StringPrintf(
+          "'%s' requires manifest version of %d or lower.",
+          name().c_str(),
+          max_manifest_version_);
+    case NOT_PRESENT:
+      return base::StringPrintf(
+          "'%s' requires a different Feature that is not present.",
+          name().c_str());
+    case UNSUPPORTED_CHANNEL:
+      return base::StringPrintf(
+          "'%s' requires Google Chrome %s channel or newer, and this is the "
+              "%s channel.",
+          name().c_str(),
+          GetChannelName(channel_).c_str(),
+          GetChannelName(GetCurrentChannel()).c_str());
+  }
+
+  NOTREACHED();
+  return "";
+}
+
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/features/feature.h b/chrome/common/extensions/features/feature.h
new file mode 100644
index 0000000..4171183
--- /dev/null
+++ b/chrome/common/extensions/features/feature.h
@@ -0,0 +1,207 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_FEATURES_FEATURE_H_
+#define CHROME_COMMON_EXTENSIONS_FEATURES_FEATURE_H_
+
+#include <set>
+#include <string>
+
+#include "base/values.h"
+#include "chrome/common/chrome_version_info.h"
+#include "chrome/common/extensions/extension.h"
+
+namespace extensions {
+
+// Represents a single feature accessible to an extension developer, such as a
+// top-level manifest key, a permission, or a programmatic API. A feature can
+// express requirements for where it can be accessed, and supports testing
+// support for those requirements.
+class Feature {
+ public:
+  // The JavaScript contexts the feature is supported in.
+  enum Context {
+    UNSPECIFIED_CONTEXT,
+
+    // A context in a privileged extension process.
+    BLESSED_EXTENSION_CONTEXT,
+
+    // A context in an unprivileged extension process.
+    UNBLESSED_EXTENSION_CONTEXT,
+
+    // A context from a content script.
+    CONTENT_SCRIPT_CONTEXT,
+
+    // A normal web page. This should have an associated URL matching pattern.
+    WEB_PAGE_CONTEXT,
+  };
+
+  // The location required of extensions the feature is supported in.
+  enum Location {
+    UNSPECIFIED_LOCATION,
+    COMPONENT_LOCATION
+  };
+
+  // The platforms the feature is supported in.
+  enum Platform {
+    UNSPECIFIED_PLATFORM,
+    CHROMEOS_PLATFORM
+  };
+
+  // Whether a feature is available in a given situation or not, and if not,
+  // why not.
+  enum AvailabilityResult {
+    IS_AVAILABLE,
+    NOT_FOUND_IN_WHITELIST,
+    INVALID_TYPE,
+    INVALID_CONTEXT,
+    INVALID_LOCATION,
+    INVALID_PLATFORM,
+    INVALID_MIN_MANIFEST_VERSION,
+    INVALID_MAX_MANIFEST_VERSION,
+    NOT_PRESENT,
+    UNSUPPORTED_CHANNEL,
+  };
+
+  // Container for AvailabiltyResult that also exposes a user-visible error
+  // message in cases where the feature is not available.
+  class Availability {
+   public:
+    AvailabilityResult result() const { return result_; }
+    bool is_available() const { return result_ == IS_AVAILABLE; }
+    const std::string& message() const { return message_; }
+
+   private:
+    friend class Feature;
+
+    // Instances should be created via Feature::CreateAvailability.
+    Availability(AvailabilityResult result, const std::string& message)
+        : result_(result), message_(message) { }
+
+    const AvailabilityResult result_;
+    const std::string message_;
+  };
+
+  Feature();
+  Feature(const Feature& other);
+  virtual ~Feature();
+
+  // Gets the current channel as seen by the Feature system.
+  static chrome::VersionInfo::Channel GetCurrentChannel();
+
+  // Sets the current channel as seen by the Feature system. In the browser
+  // process this should be chrome::VersionInfo::GetChannel(), and in the
+  // renderer this will need to come from an IPC.
+  static void SetCurrentChannel(chrome::VersionInfo::Channel channel);
+
+  // Gets the default channel as seen by the Feature system.
+  static chrome::VersionInfo::Channel GetDefaultChannel();
+
+  // Scoped channel setter. Use for tests.
+  class ScopedCurrentChannel {
+   public:
+    explicit ScopedCurrentChannel(chrome::VersionInfo::Channel channel)
+        : original_channel_(chrome::VersionInfo::CHANNEL_UNKNOWN) {
+      original_channel_ = GetCurrentChannel();
+      SetCurrentChannel(channel);
+    }
+
+    ~ScopedCurrentChannel() {
+      SetCurrentChannel(original_channel_);
+    }
+
+   private:
+    chrome::VersionInfo::Channel original_channel_;
+  };
+
+  const std::string& name() const { return name_; }
+  void set_name(const std::string& name) { name_ = name; }
+
+  // Gets the platform the code is currently running on.
+  static Platform GetCurrentPlatform();
+
+  // Gets the Feature::Location value for the specified Extension::Location.
+  static Location ConvertLocation(Extension::Location extension_location);
+
+  std::set<std::string>* whitelist() { return &whitelist_; }
+  std::set<Extension::Type>* extension_types() { return &extension_types_; }
+  std::set<Context>* contexts() { return &contexts_; }
+
+  Location location() const { return location_; }
+  void set_location(Location location) { location_ = location; }
+
+  Platform platform() const { return platform_; }
+  void set_platform(Platform platform) { platform_ = platform; }
+
+  int min_manifest_version() const { return min_manifest_version_; }
+  void set_min_manifest_version(int min_manifest_version) {
+    min_manifest_version_ = min_manifest_version;
+  }
+
+  int max_manifest_version() const { return max_manifest_version_; }
+  void set_max_manifest_version(int max_manifest_version) {
+    max_manifest_version_ = max_manifest_version;
+  }
+
+  // Parses the JSON representation of a feature into the fields of this object.
+  // Unspecified values in the JSON are not modified in the object. This allows
+  // us to implement inheritance by parsing one value after another.
+  void Parse(const DictionaryValue* value);
+
+  // Returns true if the feature contains the same values as another.
+  bool Equals(const Feature& other) const;
+
+  // Returns true if the feature is available to be parsed into a new extension
+  // manifest.
+  Availability IsAvailableToManifest(const std::string& extension_id,
+                                     Extension::Type type,
+                                     Location location,
+                                     int manifest_version) const {
+    return IsAvailableToManifest(extension_id, type, location, manifest_version,
+                                 GetCurrentPlatform());
+  }
+  Availability IsAvailableToManifest(const std::string& extension_id,
+                                     Extension::Type type,
+                                     Location location,
+                                     int manifest_version,
+                                     Platform platform) const;
+
+  // Returns true if the feature is available to be used in the specified
+  // extension and context.
+  Availability IsAvailableToContext(const Extension* extension,
+                                    Context context) const {
+    return IsAvailableToContext(extension, context, GetCurrentPlatform());
+  }
+  virtual Availability IsAvailableToContext(const Extension* extension,
+                                            Context context,
+                                            Platform platform) const;
+
+ protected:
+  Availability CreateAvailability(AvailabilityResult result) const;
+  Availability CreateAvailability(AvailabilityResult result,
+                                  Extension::Type type) const;
+
+ private:
+  std::string GetAvailabilityMessage(
+      AvailabilityResult result, Extension::Type type) const;
+
+  std::string name_;
+
+  // For clarify and consistency, we handle the default value of each of these
+  // members the same way: it matches everything. It is up to the higher level
+  // code that reads Features out of static data to validate that data and set
+  // sensible defaults.
+  std::set<std::string> whitelist_;
+  std::set<Extension::Type> extension_types_;
+  std::set<Context> contexts_;
+  Location location_;  // we only care about component/not-component now
+  Platform platform_;  // we only care about chromeos/not-chromeos now
+  int min_manifest_version_;
+  int max_manifest_version_;
+  chrome::VersionInfo::Channel channel_;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_FEATURES_FEATURE_H_
diff --git a/chrome/common/extensions/features/feature_provider.h b/chrome/common/extensions/features/feature_provider.h
new file mode 100644
index 0000000..81e4611
--- /dev/null
+++ b/chrome/common/extensions/features/feature_provider.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_FEATURES_FEATURE_PROVIDER_H_
+#define CHROME_COMMON_EXTENSIONS_FEATURES_FEATURE_PROVIDER_H_
+
+#include <string>
+
+namespace extensions {
+
+class Feature;
+
+// Implemented by classes that can vend features.
+class FeatureProvider {
+ public:
+  FeatureProvider() {}
+  virtual ~FeatureProvider() {}
+
+  // Returns the feature with the specified name.
+  virtual Feature* GetFeature(const std::string& name) = 0;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_FEATURES_FEATURE_PROVIDER_H_
diff --git a/chrome/common/extensions/features/feature_unittest.cc b/chrome/common/extensions/features/feature_unittest.cc
new file mode 100644
index 0000000..24134db
--- /dev/null
+++ b/chrome/common/extensions/features/feature_unittest.cc
@@ -0,0 +1,523 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/features/feature.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using chrome::VersionInfo;
+using extensions::Extension;
+using extensions::Feature;
+
+namespace {
+
+struct IsAvailableTestData {
+  std::string extension_id;
+  Extension::Type extension_type;
+  Feature::Location location;
+  Feature::Platform platform;
+  int manifest_version;
+  Feature::AvailabilityResult expected_result;
+};
+
+class ExtensionFeatureTest : public testing::Test {
+ protected:
+  ExtensionFeatureTest()
+      : current_channel_(VersionInfo::CHANNEL_UNKNOWN) {}
+  virtual ~ExtensionFeatureTest() {}
+
+ private:
+  Feature::ScopedCurrentChannel current_channel_;
+};
+
+TEST_F(ExtensionFeatureTest, IsAvailableNullCase) {
+  const IsAvailableTestData tests[] = {
+    { "", Extension::TYPE_UNKNOWN,
+      Feature::UNSPECIFIED_LOCATION, Feature::UNSPECIFIED_PLATFORM, -1,
+      Feature::IS_AVAILABLE },
+    { "random-extension", Extension::TYPE_UNKNOWN,
+      Feature::UNSPECIFIED_LOCATION, Feature::UNSPECIFIED_PLATFORM, -1,
+      Feature::IS_AVAILABLE },
+    { "", Extension::TYPE_LEGACY_PACKAGED_APP,
+      Feature::UNSPECIFIED_LOCATION, Feature::UNSPECIFIED_PLATFORM, -1,
+      Feature::IS_AVAILABLE },
+    { "", Extension::TYPE_UNKNOWN,
+      Feature::UNSPECIFIED_LOCATION, Feature::UNSPECIFIED_PLATFORM, -1,
+      Feature::IS_AVAILABLE },
+    { "", Extension::TYPE_UNKNOWN,
+      Feature::COMPONENT_LOCATION, Feature::UNSPECIFIED_PLATFORM, -1,
+      Feature::IS_AVAILABLE },
+    { "", Extension::TYPE_UNKNOWN,
+      Feature::UNSPECIFIED_LOCATION, Feature::CHROMEOS_PLATFORM, -1,
+      Feature::IS_AVAILABLE },
+    { "", Extension::TYPE_UNKNOWN,
+      Feature::UNSPECIFIED_LOCATION, Feature::UNSPECIFIED_PLATFORM, 25,
+      Feature::IS_AVAILABLE }
+  };
+
+  Feature feature;
+  for (size_t i = 0; i < arraysize(tests); ++i) {
+    const IsAvailableTestData& test = tests[i];
+    EXPECT_EQ(test.expected_result,
+              feature.IsAvailableToManifest(test.extension_id,
+                                            test.extension_type,
+                                            test.location,
+                                            test.manifest_version,
+                                            test.platform).result());
+  }
+}
+
+TEST_F(ExtensionFeatureTest, Whitelist) {
+  Feature feature;
+  feature.whitelist()->insert("foo");
+  feature.whitelist()->insert("bar");
+
+  EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+      "foo", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+      Feature::UNSPECIFIED_PLATFORM).result());
+  EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+      "bar", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+      Feature::UNSPECIFIED_PLATFORM).result());
+
+  EXPECT_EQ(Feature::NOT_FOUND_IN_WHITELIST, feature.IsAvailableToManifest(
+      "baz", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+      Feature::UNSPECIFIED_PLATFORM).result());
+  EXPECT_EQ(Feature::NOT_FOUND_IN_WHITELIST, feature.IsAvailableToManifest(
+      "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+      Feature::UNSPECIFIED_PLATFORM).result());
+
+  feature.extension_types()->insert(Extension::TYPE_LEGACY_PACKAGED_APP);
+  EXPECT_EQ(Feature::NOT_FOUND_IN_WHITELIST, feature.IsAvailableToManifest(
+      "baz", Extension::TYPE_LEGACY_PACKAGED_APP,
+      Feature::UNSPECIFIED_LOCATION, -1,
+      Feature::UNSPECIFIED_PLATFORM).result());
+}
+
+TEST_F(ExtensionFeatureTest, PackageType) {
+  Feature feature;
+  feature.extension_types()->insert(Extension::TYPE_EXTENSION);
+  feature.extension_types()->insert(Extension::TYPE_LEGACY_PACKAGED_APP);
+
+  EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+      "", Extension::TYPE_EXTENSION, Feature::UNSPECIFIED_LOCATION, -1,
+      Feature::UNSPECIFIED_PLATFORM).result());
+  EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+      "", Extension::TYPE_LEGACY_PACKAGED_APP, Feature::UNSPECIFIED_LOCATION,
+      -1, Feature::UNSPECIFIED_PLATFORM).result());
+
+  EXPECT_EQ(Feature::INVALID_TYPE, feature.IsAvailableToManifest(
+      "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+      Feature::UNSPECIFIED_PLATFORM).result());
+  EXPECT_EQ(Feature::INVALID_TYPE, feature.IsAvailableToManifest(
+      "", Extension::TYPE_THEME, Feature::UNSPECIFIED_LOCATION, -1,
+      Feature::UNSPECIFIED_PLATFORM).result());
+}
+
+TEST_F(ExtensionFeatureTest, Context) {
+  Feature feature;
+  feature.contexts()->insert(Feature::BLESSED_EXTENSION_CONTEXT);
+  feature.extension_types()->insert(Extension::TYPE_LEGACY_PACKAGED_APP);
+  feature.set_platform(Feature::CHROMEOS_PLATFORM);
+  feature.set_min_manifest_version(21);
+  feature.set_max_manifest_version(25);
+
+  DictionaryValue manifest;
+  manifest.SetString("name", "test");
+  manifest.SetString("version", "1");
+  manifest.SetInteger("manifest_version", 21);
+  manifest.SetString("app.launch.local_path", "foo.html");
+
+  std::string error;
+  scoped_refptr<const Extension> extension(Extension::Create(
+      FilePath(), Extension::INTERNAL, manifest, Extension::NO_FLAGS, &error));
+  EXPECT_EQ("", error);
+  ASSERT_TRUE(extension.get());
+
+  feature.whitelist()->insert("monkey");
+  EXPECT_EQ(Feature::NOT_FOUND_IN_WHITELIST, feature.IsAvailableToContext(
+      extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+      Feature::CHROMEOS_PLATFORM).result());
+  feature.whitelist()->clear();
+
+  feature.extension_types()->clear();
+  feature.extension_types()->insert(Extension::TYPE_THEME);
+  EXPECT_EQ(Feature::INVALID_TYPE, feature.IsAvailableToContext(
+      extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+      Feature::CHROMEOS_PLATFORM).result());
+  feature.extension_types()->clear();
+  feature.extension_types()->insert(Extension::TYPE_LEGACY_PACKAGED_APP);
+
+  feature.contexts()->clear();
+  feature.contexts()->insert(Feature::UNBLESSED_EXTENSION_CONTEXT);
+  EXPECT_EQ(Feature::INVALID_CONTEXT, feature.IsAvailableToContext(
+      extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+      Feature::CHROMEOS_PLATFORM).result());
+  feature.contexts()->clear();
+  feature.contexts()->insert(Feature::BLESSED_EXTENSION_CONTEXT);
+
+  feature.set_location(Feature::COMPONENT_LOCATION);
+  EXPECT_EQ(Feature::INVALID_LOCATION, feature.IsAvailableToContext(
+      extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+      Feature::CHROMEOS_PLATFORM).result());
+  feature.set_location(Feature::UNSPECIFIED_LOCATION);
+
+  EXPECT_EQ(Feature::INVALID_PLATFORM, feature.IsAvailableToContext(
+      extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+      Feature::UNSPECIFIED_PLATFORM).result());
+
+  feature.set_min_manifest_version(22);
+  EXPECT_EQ(Feature::INVALID_MIN_MANIFEST_VERSION, feature.IsAvailableToContext(
+      extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+      Feature::CHROMEOS_PLATFORM).result());
+  feature.set_min_manifest_version(21);
+
+  feature.set_max_manifest_version(18);
+  EXPECT_EQ(Feature::INVALID_MAX_MANIFEST_VERSION, feature.IsAvailableToContext(
+      extension.get(), Feature::BLESSED_EXTENSION_CONTEXT,
+      Feature::CHROMEOS_PLATFORM).result());
+  feature.set_max_manifest_version(25);
+}
+
+TEST_F(ExtensionFeatureTest, Location) {
+  Feature feature;
+
+  // If the feature specifies "component" as its location, then only component
+  // extensions can access it.
+  feature.set_location(Feature::COMPONENT_LOCATION);
+  EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+      "", Extension::TYPE_UNKNOWN, Feature::COMPONENT_LOCATION, -1,
+      Feature::UNSPECIFIED_PLATFORM).result());
+  EXPECT_EQ(Feature::INVALID_LOCATION, feature.IsAvailableToManifest(
+      "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+      Feature::UNSPECIFIED_PLATFORM).result());
+
+  // But component extensions can access anything else, whatever their location.
+  feature.set_location(Feature::UNSPECIFIED_LOCATION);
+  EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+      "", Extension::TYPE_UNKNOWN, Feature::COMPONENT_LOCATION, -1,
+      Feature::UNSPECIFIED_PLATFORM).result());
+}
+
+TEST_F(ExtensionFeatureTest, Platform) {
+  Feature feature;
+  feature.set_platform(Feature::CHROMEOS_PLATFORM);
+  EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+      "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+      Feature::CHROMEOS_PLATFORM).result());
+  EXPECT_EQ(Feature::INVALID_PLATFORM, feature.IsAvailableToManifest(
+      "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION, -1,
+      Feature::UNSPECIFIED_PLATFORM).result());
+}
+
+TEST_F(ExtensionFeatureTest, Version) {
+  Feature feature;
+  feature.set_min_manifest_version(5);
+
+  EXPECT_EQ(Feature::INVALID_MIN_MANIFEST_VERSION,
+            feature.IsAvailableToManifest(
+                "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
+                0, Feature::UNSPECIFIED_PLATFORM).result());
+  EXPECT_EQ(Feature::INVALID_MIN_MANIFEST_VERSION,
+            feature.IsAvailableToManifest(
+                "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
+                4, Feature::UNSPECIFIED_PLATFORM).result());
+
+  EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+      "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
+      5, Feature::UNSPECIFIED_PLATFORM).result());
+  EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+      "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
+      10, Feature::UNSPECIFIED_PLATFORM).result());
+
+  feature.set_max_manifest_version(8);
+
+  EXPECT_EQ(Feature::INVALID_MAX_MANIFEST_VERSION,
+            feature.IsAvailableToManifest(
+                "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
+                10, Feature::UNSPECIFIED_PLATFORM).result());
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+            feature.IsAvailableToManifest(
+                "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
+                8, Feature::UNSPECIFIED_PLATFORM).result());
+  EXPECT_EQ(Feature::IS_AVAILABLE, feature.IsAvailableToManifest(
+      "", Extension::TYPE_UNKNOWN, Feature::UNSPECIFIED_LOCATION,
+      7, Feature::UNSPECIFIED_PLATFORM).result());
+}
+
+TEST_F(ExtensionFeatureTest, ParseNull) {
+  scoped_ptr<DictionaryValue> value(new DictionaryValue());
+  scoped_ptr<Feature> feature(new Feature());
+  feature->Parse(value.get());
+  EXPECT_TRUE(feature->whitelist()->empty());
+  EXPECT_TRUE(feature->extension_types()->empty());
+  EXPECT_TRUE(feature->contexts()->empty());
+  EXPECT_EQ(Feature::UNSPECIFIED_LOCATION, feature->location());
+  EXPECT_EQ(Feature::UNSPECIFIED_PLATFORM, feature->platform());
+  EXPECT_EQ(0, feature->min_manifest_version());
+  EXPECT_EQ(0, feature->max_manifest_version());
+}
+
+TEST_F(ExtensionFeatureTest, ParseWhitelist) {
+  scoped_ptr<DictionaryValue> value(new DictionaryValue());
+  ListValue* whitelist = new ListValue();
+  whitelist->Append(Value::CreateStringValue("foo"));
+  whitelist->Append(Value::CreateStringValue("bar"));
+  value->Set("whitelist", whitelist);
+  scoped_ptr<Feature> feature(new Feature());
+  feature->Parse(value.get());
+  EXPECT_EQ(2u, feature->whitelist()->size());
+  EXPECT_TRUE(feature->whitelist()->count("foo"));
+  EXPECT_TRUE(feature->whitelist()->count("bar"));
+}
+
+TEST_F(ExtensionFeatureTest, ParsePackageTypes) {
+  scoped_ptr<DictionaryValue> value(new DictionaryValue());
+  ListValue* extension_types = new ListValue();
+  extension_types->Append(Value::CreateStringValue("extension"));
+  extension_types->Append(Value::CreateStringValue("theme"));
+  extension_types->Append(Value::CreateStringValue("packaged_app"));
+  extension_types->Append(Value::CreateStringValue("hosted_app"));
+  extension_types->Append(Value::CreateStringValue("platform_app"));
+  value->Set("extension_types", extension_types);
+  scoped_ptr<Feature> feature(new Feature());
+  feature->Parse(value.get());
+  EXPECT_EQ(5u, feature->extension_types()->size());
+  EXPECT_TRUE(feature->extension_types()->count(Extension::TYPE_EXTENSION));
+  EXPECT_TRUE(feature->extension_types()->count(Extension::TYPE_THEME));
+  EXPECT_TRUE(feature->extension_types()->count(
+      Extension::TYPE_LEGACY_PACKAGED_APP));
+  EXPECT_TRUE(feature->extension_types()->count(Extension::TYPE_HOSTED_APP));
+  EXPECT_TRUE(feature->extension_types()->count(Extension::TYPE_PLATFORM_APP));
+
+  value->SetString("extension_types", "all");
+  scoped_ptr<Feature> feature2(new Feature());
+  feature2->Parse(value.get());
+  EXPECT_EQ(*(feature->extension_types()), *(feature2->extension_types()));
+}
+
+TEST_F(ExtensionFeatureTest, ParseContexts) {
+  scoped_ptr<DictionaryValue> value(new DictionaryValue());
+  ListValue* contexts = new ListValue();
+  contexts->Append(Value::CreateStringValue("blessed_extension"));
+  contexts->Append(Value::CreateStringValue("unblessed_extension"));
+  contexts->Append(Value::CreateStringValue("content_script"));
+  contexts->Append(Value::CreateStringValue("web_page"));
+  value->Set("contexts", contexts);
+  scoped_ptr<Feature> feature(new Feature());
+  feature->Parse(value.get());
+  EXPECT_EQ(4u, feature->contexts()->size());
+  EXPECT_TRUE(feature->contexts()->count(Feature::BLESSED_EXTENSION_CONTEXT));
+  EXPECT_TRUE(feature->contexts()->count(Feature::UNBLESSED_EXTENSION_CONTEXT));
+  EXPECT_TRUE(feature->contexts()->count(Feature::CONTENT_SCRIPT_CONTEXT));
+  EXPECT_TRUE(feature->contexts()->count(Feature::WEB_PAGE_CONTEXT));
+
+  value->SetString("contexts", "all");
+  scoped_ptr<Feature> feature2(new Feature());
+  feature2->Parse(value.get());
+  EXPECT_EQ(*(feature->contexts()), *(feature2->contexts()));
+}
+
+TEST_F(ExtensionFeatureTest, ParseLocation) {
+  scoped_ptr<DictionaryValue> value(new DictionaryValue());
+  value->SetString("location", "component");
+  scoped_ptr<Feature> feature(new Feature());
+  feature->Parse(value.get());
+  EXPECT_EQ(Feature::COMPONENT_LOCATION, feature->location());
+}
+
+TEST_F(ExtensionFeatureTest, ParsePlatform) {
+  scoped_ptr<DictionaryValue> value(new DictionaryValue());
+  value->SetString("platform", "chromeos");
+  scoped_ptr<Feature> feature(new Feature());
+  feature->Parse(value.get());
+  EXPECT_EQ(Feature::CHROMEOS_PLATFORM, feature->platform());
+}
+
+TEST_F(ExtensionFeatureTest, ManifestVersion) {
+  scoped_ptr<DictionaryValue> value(new DictionaryValue());
+  value->SetInteger("min_manifest_version", 1);
+  value->SetInteger("max_manifest_version", 5);
+  scoped_ptr<Feature> feature(new Feature());
+  feature->Parse(value.get());
+  EXPECT_EQ(1, feature->min_manifest_version());
+  EXPECT_EQ(5, feature->max_manifest_version());
+}
+
+TEST_F(ExtensionFeatureTest, Inheritance) {
+  Feature feature;
+  feature.whitelist()->insert("foo");
+  feature.extension_types()->insert(Extension::TYPE_THEME);
+  feature.contexts()->insert(Feature::BLESSED_EXTENSION_CONTEXT);
+  feature.set_location(Feature::COMPONENT_LOCATION);
+  feature.set_platform(Feature::CHROMEOS_PLATFORM);
+  feature.set_min_manifest_version(1);
+  feature.set_max_manifest_version(2);
+
+  Feature feature2 = feature;
+  EXPECT_TRUE(feature2.Equals(feature));
+
+  DictionaryValue definition;
+  feature2.Parse(&definition);
+  EXPECT_TRUE(feature2.Equals(feature));
+
+  ListValue* whitelist = new ListValue();
+  ListValue* extension_types = new ListValue();
+  ListValue* contexts = new ListValue();
+  whitelist->Append(Value::CreateStringValue("bar"));
+  extension_types->Append(Value::CreateStringValue("extension"));
+  contexts->Append(Value::CreateStringValue("unblessed_extension"));
+  definition.Set("whitelist", whitelist);
+  definition.Set("extension_types", extension_types);
+  definition.Set("contexts", contexts);
+  // Can't test location or platform because we only have one value so far.
+  definition.Set("min_manifest_version", Value::CreateIntegerValue(2));
+  definition.Set("max_manifest_version", Value::CreateIntegerValue(3));
+
+  feature2.Parse(&definition);
+  EXPECT_FALSE(feature2.Equals(feature));
+  EXPECT_EQ(1u, feature2.whitelist()->size());
+  EXPECT_EQ(1u, feature2.extension_types()->size());
+  EXPECT_EQ(1u, feature2.contexts()->size());
+  EXPECT_EQ(1u, feature2.whitelist()->count("bar"));
+  EXPECT_EQ(1u, feature2.extension_types()->count(Extension::TYPE_EXTENSION));
+  EXPECT_EQ(1u,
+            feature2.contexts()->count(Feature::UNBLESSED_EXTENSION_CONTEXT));
+  EXPECT_EQ(2, feature2.min_manifest_version());
+  EXPECT_EQ(3, feature2.max_manifest_version());
+}
+
+TEST_F(ExtensionFeatureTest, Equals) {
+  Feature feature;
+  feature.whitelist()->insert("foo");
+  feature.extension_types()->insert(Extension::TYPE_THEME);
+  feature.contexts()->insert(Feature::UNBLESSED_EXTENSION_CONTEXT);
+  feature.set_location(Feature::COMPONENT_LOCATION);
+  feature.set_platform(Feature::CHROMEOS_PLATFORM);
+  feature.set_min_manifest_version(18);
+  feature.set_max_manifest_version(25);
+
+  Feature feature2(feature);
+  EXPECT_TRUE(feature2.Equals(feature));
+
+  feature2.whitelist()->clear();
+  EXPECT_FALSE(feature2.Equals(feature));
+
+  feature2 = feature;
+  feature2.extension_types()->clear();
+  EXPECT_FALSE(feature2.Equals(feature));
+
+  feature2 = feature;
+  feature2.contexts()->clear();
+  EXPECT_FALSE(feature2.Equals(feature));
+
+  feature2 = feature;
+  feature2.set_location(Feature::UNSPECIFIED_LOCATION);
+  EXPECT_FALSE(feature2.Equals(feature));
+
+  feature2 = feature;
+  feature2.set_platform(Feature::UNSPECIFIED_PLATFORM);
+  EXPECT_FALSE(feature2.Equals(feature));
+
+  feature2 = feature;
+  feature2.set_min_manifest_version(0);
+  EXPECT_FALSE(feature2.Equals(feature));
+
+  feature2 = feature;
+  feature2.set_max_manifest_version(0);
+  EXPECT_FALSE(feature2.Equals(feature));
+}
+
+Feature::AvailabilityResult IsAvailableInChannel(
+    const std::string& channel, VersionInfo::Channel channel_for_testing) {
+  Feature::ScopedCurrentChannel current_channel(channel_for_testing);
+
+  Feature feature;
+  if (!channel.empty()) {
+    DictionaryValue feature_value;
+    feature_value.SetString("channel", channel);
+    feature.Parse(&feature_value);
+  }
+
+  return feature.IsAvailableToManifest(
+      "random-extension",
+      Extension::TYPE_UNKNOWN,
+      Feature::UNSPECIFIED_LOCATION,
+      -1).result();
+}
+
+TEST_F(ExtensionFeatureTest, SupportedChannel) {
+  // stable supported.
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("stable", VersionInfo::CHANNEL_UNKNOWN));
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("stable", VersionInfo::CHANNEL_CANARY));
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("stable", VersionInfo::CHANNEL_DEV));
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("stable", VersionInfo::CHANNEL_BETA));
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("stable", VersionInfo::CHANNEL_STABLE));
+
+  // beta supported.
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("beta", VersionInfo::CHANNEL_UNKNOWN));
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("beta", VersionInfo::CHANNEL_CANARY));
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("beta", VersionInfo::CHANNEL_DEV));
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("beta", VersionInfo::CHANNEL_BETA));
+  EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
+      IsAvailableInChannel("beta", VersionInfo::CHANNEL_STABLE));
+
+  // dev supported.
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("dev", VersionInfo::CHANNEL_UNKNOWN));
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("dev", VersionInfo::CHANNEL_CANARY));
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("dev", VersionInfo::CHANNEL_DEV));
+  EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
+      IsAvailableInChannel("dev", VersionInfo::CHANNEL_BETA));
+  EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
+      IsAvailableInChannel("dev", VersionInfo::CHANNEL_STABLE));
+
+  // canary supported.
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("canary", VersionInfo::CHANNEL_UNKNOWN));
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("canary", VersionInfo::CHANNEL_CANARY));
+  EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
+      IsAvailableInChannel("canary", VersionInfo::CHANNEL_DEV));
+  EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
+      IsAvailableInChannel("canary", VersionInfo::CHANNEL_BETA));
+  EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
+      IsAvailableInChannel("canary", VersionInfo::CHANNEL_STABLE));
+
+  // trunk supported.
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("trunk", VersionInfo::CHANNEL_UNKNOWN));
+  EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
+      IsAvailableInChannel("trunk", VersionInfo::CHANNEL_CANARY));
+  EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
+      IsAvailableInChannel("trunk", VersionInfo::CHANNEL_DEV));
+  EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
+      IsAvailableInChannel("trunk", VersionInfo::CHANNEL_BETA));
+  EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
+      IsAvailableInChannel("trunk", VersionInfo::CHANNEL_STABLE));
+
+  // Default supported channel (trunk).
+  EXPECT_EQ(Feature::IS_AVAILABLE,
+      IsAvailableInChannel("", VersionInfo::CHANNEL_UNKNOWN));
+  EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
+      IsAvailableInChannel("", VersionInfo::CHANNEL_CANARY));
+  EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
+      IsAvailableInChannel("", VersionInfo::CHANNEL_DEV));
+  EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
+      IsAvailableInChannel("", VersionInfo::CHANNEL_BETA));
+  EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
+      IsAvailableInChannel("", VersionInfo::CHANNEL_STABLE));
+}
+
+}  // namespace
diff --git a/chrome/common/extensions/features/manifest_feature.cc b/chrome/common/extensions/features/manifest_feature.cc
new file mode 100644
index 0000000..5d7c5a4
--- /dev/null
+++ b/chrome/common/extensions/features/manifest_feature.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/features/manifest_feature.h"
+
+#include "chrome/common/extensions/manifest.h"
+
+namespace extensions {
+
+ManifestFeature::ManifestFeature() {
+}
+
+ManifestFeature::~ManifestFeature() {
+}
+
+Feature::Availability ManifestFeature::IsAvailableToContext(
+    const Extension* extension,
+    Feature::Context context,
+    Feature::Platform platform) const {
+  Availability availability = Feature::IsAvailableToContext(extension,
+                                                            context,
+                                                            platform);
+  if (!availability.is_available())
+    return availability;
+
+  // We know we can skip manifest()->GetKey() here because we just did the same
+  // validation it would do above.
+  if (!extension->manifest()->value()->HasKey(name()))
+    return CreateAvailability(NOT_PRESENT, extension->GetType());
+
+  return CreateAvailability(IS_AVAILABLE);
+}
+
+}  // namespace
diff --git a/chrome/common/extensions/features/manifest_feature.h b/chrome/common/extensions/features/manifest_feature.h
new file mode 100644
index 0000000..0ec0cd7
--- /dev/null
+++ b/chrome/common/extensions/features/manifest_feature.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_FEATURES_MANIFEST_FEATURE_H_
+#define CHROME_COMMON_EXTENSIONS_FEATURES_MANIFEST_FEATURE_H_
+
+#include "chrome/common/extensions/features/feature.h"
+
+namespace extensions {
+
+class ManifestFeature : public Feature {
+ public:
+  ManifestFeature();
+  virtual ~ManifestFeature();
+
+  virtual Feature::Availability IsAvailableToContext(
+      const Extension* extension,
+      Feature::Context context,
+      Feature::Platform platform) const OVERRIDE;
+};
+
+}  // extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_FEATURES_MANIFEST_FEATURE_H_
diff --git a/chrome/common/extensions/features/permission_feature.cc b/chrome/common/extensions/features/permission_feature.cc
new file mode 100644
index 0000000..ea02083
--- /dev/null
+++ b/chrome/common/extensions/features/permission_feature.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/features/permission_feature.h"
+
+namespace extensions {
+
+PermissionFeature::PermissionFeature() {
+}
+
+PermissionFeature::~PermissionFeature() {
+}
+
+Feature::Availability PermissionFeature::IsAvailableToContext(
+    const Extension* extension,
+    Feature::Context context,
+    Feature::Platform platform) const {
+  Availability availability = Feature::IsAvailableToContext(extension,
+                                                            context,
+                                                            platform);
+  if (!availability.is_available())
+    return availability;
+
+  if (!extension->HasAPIPermission(name()))
+    return CreateAvailability(NOT_PRESENT, extension->GetType());
+
+  return CreateAvailability(IS_AVAILABLE);
+}
+
+}  // namespace
diff --git a/chrome/common/extensions/features/permission_feature.h b/chrome/common/extensions/features/permission_feature.h
new file mode 100644
index 0000000..cf5b323
--- /dev/null
+++ b/chrome/common/extensions/features/permission_feature.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_FEATURES_PERMISSION_FEATURE_H_
+#define CHROME_COMMON_EXTENSIONS_FEATURES_PERMISSION_FEATURE_H_
+
+#include "chrome/common/extensions/features/feature.h"
+
+namespace extensions {
+
+class PermissionFeature : public Feature {
+ public:
+  PermissionFeature();
+  virtual ~PermissionFeature();
+
+  virtual Feature::Availability IsAvailableToContext(
+      const Extension* extension,
+      Feature::Context context,
+      Feature::Platform platform) const OVERRIDE;
+};
+
+}  // extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_FEATURES_PERMISSION_FEATURE_H_
diff --git a/chrome/common/extensions/features/simple_feature_provider.cc b/chrome/common/extensions/features/simple_feature_provider.cc
new file mode 100644
index 0000000..4edcfe1
--- /dev/null
+++ b/chrome/common/extensions/features/simple_feature_provider.cc
@@ -0,0 +1,124 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/features/simple_feature_provider.h"
+
+#include "base/json/json_reader.h"
+#include "base/lazy_instance.h"
+#include "chrome/common/extensions/features/manifest_feature.h"
+#include "chrome/common/extensions/features/permission_feature.h"
+#include "grit/common_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace extensions {
+
+namespace {
+
+template<class FeatureClass>
+Feature* CreateFeature() {
+  return new FeatureClass();
+}
+
+struct Static {
+  Static()
+      : manifest_features(
+            LoadProvider("manifest",
+                         &CreateFeature<ManifestFeature>,
+                         IDR_EXTENSION_MANIFEST_FEATURES)),
+        permission_features(
+            LoadProvider("permissions",
+                         &CreateFeature<PermissionFeature>,
+                         IDR_EXTENSION_PERMISSION_FEATURES)) {
+  }
+
+  scoped_ptr<SimpleFeatureProvider> manifest_features;
+  scoped_ptr<SimpleFeatureProvider> permission_features;
+
+ private:
+  scoped_ptr<SimpleFeatureProvider> LoadProvider(
+      const std::string& debug_string,
+      SimpleFeatureProvider::FeatureFactory factory,
+      int resource_id) {
+    std::string manifest_features =
+        ResourceBundle::GetSharedInstance().GetRawDataResource(
+            resource_id).as_string();
+    int error_code = 0;
+    std::string error_message;
+    Value* value = base::JSONReader::ReadAndReturnError(
+        manifest_features, base::JSON_PARSE_RFC,
+        &error_code, &error_message);
+    CHECK(value) << "Could not load features: " << debug_string << " "
+                 << error_message;
+    CHECK(value->IsType(Value::TYPE_DICTIONARY)) << debug_string;
+    scoped_ptr<DictionaryValue> dictionary_value(
+        static_cast<DictionaryValue*>(value));
+    return scoped_ptr<SimpleFeatureProvider>(
+        new SimpleFeatureProvider(dictionary_value.get(), factory));
+  }
+};
+
+base::LazyInstance<Static> g_static = LAZY_INSTANCE_INITIALIZER;
+
+}  // namespace
+
+SimpleFeatureProvider::SimpleFeatureProvider(DictionaryValue* root,
+                                             FeatureFactory factory)
+    : factory_(factory ? factory :
+               static_cast<FeatureFactory>(&CreateFeature<Feature>)) {
+  for (DictionaryValue::Iterator iter(*root); iter.HasNext(); iter.Advance()) {
+    if (iter.value().GetType() != Value::TYPE_DICTIONARY) {
+      LOG(ERROR) << iter.key() << ": Feature description must be dictionary.";
+      continue;
+    }
+
+    linked_ptr<Feature> feature((*factory_)());
+    feature->set_name(iter.key());
+    feature->Parse(static_cast<const DictionaryValue*>(&iter.value()));
+
+    if (feature->extension_types()->empty()) {
+      LOG(ERROR) << iter.key() << ": Simple features must specify atleast one "
+                 << "value for extension_types.";
+      continue;
+    }
+
+    if (!feature->contexts()->empty()) {
+      LOG(ERROR) << iter.key() << ": Simple features do not support contexts.";
+      continue;
+    }
+
+    features_[iter.key()] = feature;
+  }
+}
+
+SimpleFeatureProvider::~SimpleFeatureProvider() {
+}
+
+// static
+SimpleFeatureProvider* SimpleFeatureProvider::GetManifestFeatures() {
+  return g_static.Get().manifest_features.get();
+}
+
+// static
+SimpleFeatureProvider* SimpleFeatureProvider::GetPermissionFeatures() {
+  return g_static.Get().permission_features.get();
+}
+
+std::set<std::string> SimpleFeatureProvider::GetAllFeatureNames() const {
+  std::set<std::string> result;
+  for (FeatureMap::const_iterator iter = features_.begin();
+       iter != features_.end(); ++iter) {
+    result.insert(iter->first);
+  }
+  return result;
+}
+
+Feature* SimpleFeatureProvider::GetFeature(const std::string& name) {
+  FeatureMap::iterator iter = features_.find(name);
+  if (iter != features_.end())
+    return iter->second.get();
+  else
+    return NULL;
+}
+
+}  // namespace
diff --git a/chrome/common/extensions/features/simple_feature_provider.h b/chrome/common/extensions/features/simple_feature_provider.h
new file mode 100644
index 0000000..6da884e
--- /dev/null
+++ b/chrome/common/extensions/features/simple_feature_provider.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_FEATURES_SIMPLE_FEATURE_PROVIDER_H_
+#define CHROME_COMMON_EXTENSIONS_FEATURES_SIMPLE_FEATURE_PROVIDER_H_
+
+#include <set>
+#include <string>
+
+#include "base/memory/linked_ptr.h"
+#include "base/values.h"
+#include "chrome/common/extensions/features/feature.h"
+#include "chrome/common/extensions/features/feature_provider.h"
+
+namespace extensions {
+
+// Reads Features out of a simple JSON file description.
+class SimpleFeatureProvider : public FeatureProvider {
+ public:
+  typedef Feature*(*FeatureFactory)();
+
+  // Creates a new SimpleFeatureProvider. Pass null to |factory| to have the
+  // provider create plain old Feature instances.
+  SimpleFeatureProvider(DictionaryValue* root, FeatureFactory factory);
+  virtual ~SimpleFeatureProvider();
+
+  // Gets an instance for the _manifest_features.json file that is baked into
+  // Chrome as a resource.
+  static SimpleFeatureProvider* GetManifestFeatures();
+
+  // Gets an instance for the _permission_features.json file that is baked into
+  // Chrome as a resource.
+  static SimpleFeatureProvider* GetPermissionFeatures();
+
+  // Returns all features described by this instance.
+  std::set<std::string> GetAllFeatureNames() const;
+
+  // Gets the feature |feature_name|, if it exists.
+  virtual Feature* GetFeature(const std::string& feature_name) OVERRIDE;
+
+ private:
+  typedef std::map<std::string, linked_ptr<Feature> > FeatureMap;
+  FeatureMap features_;
+
+  FeatureFactory factory_;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_FEATURES_SIMPLE_FEATURE_PROVIDER_H_
diff --git a/chrome/common/extensions/features/simple_feature_provider_unittest.cc b/chrome/common/extensions/features/simple_feature_provider_unittest.cc
new file mode 100644
index 0000000..0503ddc
--- /dev/null
+++ b/chrome/common/extensions/features/simple_feature_provider_unittest.cc
@@ -0,0 +1,124 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/features/simple_feature_provider.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::Extension;
+using extensions::Feature;
+using extensions::SimpleFeatureProvider;
+
+TEST(SimpleFeatureProvider, ManifestFeatures) {
+  SimpleFeatureProvider* provider =
+      SimpleFeatureProvider::GetManifestFeatures();
+  Feature* feature = provider->GetFeature("description");
+  ASSERT_TRUE(feature);
+  EXPECT_EQ(5u, feature->extension_types()->size());
+  EXPECT_EQ(1u, feature->extension_types()->count(Extension::TYPE_EXTENSION));
+  EXPECT_EQ(1u,
+      feature->extension_types()->count(Extension::TYPE_LEGACY_PACKAGED_APP));
+  EXPECT_EQ(1u,
+            feature->extension_types()->count(Extension::TYPE_PLATFORM_APP));
+  EXPECT_EQ(1u, feature->extension_types()->count(Extension::TYPE_HOSTED_APP));
+  EXPECT_EQ(1u, feature->extension_types()->count(Extension::TYPE_THEME));
+
+  DictionaryValue manifest;
+  manifest.SetString("name", "test extension");
+  manifest.SetString("version", "1");
+  manifest.SetString("description", "hello there");
+
+  std::string error;
+  scoped_refptr<const Extension> extension(Extension::Create(
+      FilePath(), Extension::INTERNAL, manifest, Extension::NO_FLAGS,
+      &error));
+
+  ASSERT_TRUE(extension.get());
+  EXPECT_EQ(Feature::IS_AVAILABLE, feature->IsAvailableToContext(
+      extension.get(), Feature::UNSPECIFIED_CONTEXT).result());
+
+  feature = provider->GetFeature("theme");
+  ASSERT_TRUE(feature);
+  EXPECT_EQ(Feature::INVALID_TYPE, feature->IsAvailableToContext(
+      extension.get(), Feature::UNSPECIFIED_CONTEXT).result());
+
+  feature = provider->GetFeature("devtools_page");
+  ASSERT_TRUE(feature);
+  EXPECT_EQ(Feature::NOT_PRESENT, feature->IsAvailableToContext(
+      extension.get(), Feature::UNSPECIFIED_CONTEXT).result());
+}
+
+TEST(SimpleFeatureProvider, PermissionFeatures) {
+  SimpleFeatureProvider* provider =
+      SimpleFeatureProvider::GetPermissionFeatures();
+  Feature* feature = provider->GetFeature("contextMenus");
+  ASSERT_TRUE(feature);
+  EXPECT_EQ(3u, feature->extension_types()->size());
+  EXPECT_EQ(1u, feature->extension_types()->count(Extension::TYPE_EXTENSION));
+  EXPECT_EQ(1u,
+      feature->extension_types()->count(Extension::TYPE_LEGACY_PACKAGED_APP));
+  EXPECT_EQ(1u,
+            feature->extension_types()->count(Extension::TYPE_PLATFORM_APP));
+
+  DictionaryValue manifest;
+  manifest.SetString("name", "test extension");
+  manifest.SetString("version", "1");
+  ListValue* permissions = new ListValue();
+  manifest.Set("permissions", permissions);
+  permissions->Append(Value::CreateStringValue("contextMenus"));
+
+  std::string error;
+  scoped_refptr<const Extension> extension(Extension::Create(
+      FilePath(), Extension::INTERNAL, manifest, Extension::NO_FLAGS,
+      &error));
+
+  ASSERT_TRUE(extension.get());
+  EXPECT_EQ(Feature::IS_AVAILABLE, feature->IsAvailableToContext(
+      extension.get(), Feature::UNSPECIFIED_CONTEXT).result());
+
+  feature = provider->GetFeature("chromePrivate");
+  ASSERT_TRUE(feature);
+  EXPECT_EQ(Feature::NOT_FOUND_IN_WHITELIST, feature->IsAvailableToContext(
+      extension.get(), Feature::UNSPECIFIED_CONTEXT).result());
+
+  feature = provider->GetFeature("clipboardWrite");
+  ASSERT_TRUE(feature);
+  EXPECT_EQ(Feature::NOT_PRESENT, feature->IsAvailableToContext(
+      extension.get(), Feature::UNSPECIFIED_CONTEXT).result());
+}
+
+TEST(SimpleFeatureProvider, Validation) {
+  scoped_ptr<DictionaryValue> value(new DictionaryValue());
+
+  DictionaryValue* feature1 = new DictionaryValue();
+  value->Set("feature1", feature1);
+
+  DictionaryValue* feature2 = new DictionaryValue();
+  ListValue* extension_types = new ListValue();
+  extension_types->Append(Value::CreateStringValue("extension"));
+  feature2->Set("extension_types", extension_types);
+  ListValue* contexts = new ListValue();
+  contexts->Append(Value::CreateStringValue("blessed_extension"));
+  feature2->Set("contexts", contexts);
+  value->Set("feature2", feature2);
+
+  scoped_ptr<SimpleFeatureProvider> provider(
+      new SimpleFeatureProvider(value.get(), NULL));
+
+  // feature1 won't validate because it lacks an extension type.
+  EXPECT_FALSE(provider->GetFeature("feature1"));
+
+  // If we add one, it works.
+  feature1->Set("extension_types", extension_types->DeepCopy());
+  provider.reset(new SimpleFeatureProvider(value.get(), NULL));
+  EXPECT_TRUE(provider->GetFeature("feature1"));
+
+  // feature2 won't validate because of the presence of "contexts".
+  EXPECT_FALSE(provider->GetFeature("feature2"));
+
+  // If we remove it, it works.
+  feature2->Remove("contexts", NULL);
+  provider.reset(new SimpleFeatureProvider(value.get(), NULL));
+  EXPECT_TRUE(provider->GetFeature("feature2"));
+}
diff --git a/chrome/common/extensions/file_browser_handler.cc b/chrome/common/extensions/file_browser_handler.cc
new file mode 100644
index 0000000..58dea7c
--- /dev/null
+++ b/chrome/common/extensions/file_browser_handler.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/file_browser_handler.h"
+
+#include "base/logging.h"
+#include "chrome/common/extensions/url_pattern.h"
+#include "googleurl/src/gurl.h"
+
+namespace {
+
+const char kReadAccessString[] = "read";
+const char kReadWriteAccessString[] = "read-write";
+const char kCreateAccessString[] = "create";
+
+unsigned int kPermissionsNotDefined = 0;
+unsigned int kReadPermission = 1;
+unsigned int kWritePermission = 1 << 1;
+unsigned int kCreatePermission = 1 << 2;
+unsigned int kInvalidPermission = 1 << 3;
+
+unsigned int GetAccessPermissionFlagFromString(const std::string& access_str) {
+  if (access_str == kReadAccessString)
+    return kReadPermission;
+  if (access_str == kReadWriteAccessString)
+    return kReadPermission | kWritePermission;
+  if (access_str == kCreateAccessString)
+    return kCreatePermission;
+  return kInvalidPermission;
+}
+
+}
+
+
+FileBrowserHandler::FileBrowserHandler()
+    : file_access_permission_flags_(kPermissionsNotDefined) {
+}
+
+FileBrowserHandler::~FileBrowserHandler() {
+}
+
+void FileBrowserHandler::AddPattern(const URLPattern& pattern) {
+  url_set_.AddPattern(pattern);
+}
+
+void FileBrowserHandler::ClearPatterns() {
+  url_set_.ClearPatterns();
+}
+
+bool FileBrowserHandler::MatchesURL(const GURL& url) const {
+  return url_set_.MatchesURL(url);
+}
+
+bool FileBrowserHandler::AddFileAccessPermission(
+    const std::string& access) {
+  file_access_permission_flags_ |= GetAccessPermissionFlagFromString(access);
+  return (file_access_permission_flags_ & kInvalidPermission) != 0U;
+}
+
+bool FileBrowserHandler::ValidateFileAccessPermissions() {
+  bool is_invalid = (file_access_permission_flags_ & kInvalidPermission) != 0U;
+  bool can_create = (file_access_permission_flags_ & kCreatePermission) != 0U;
+  bool can_read_or_write = (file_access_permission_flags_ &
+      (kReadPermission | kWritePermission)) != 0U;
+  if (is_invalid || (can_create && can_read_or_write)) {
+    file_access_permission_flags_ = kInvalidPermission;
+    return false;
+  }
+
+  if (file_access_permission_flags_ == kPermissionsNotDefined)
+    file_access_permission_flags_ = kReadPermission | kWritePermission;
+  return true;
+}
+
+bool FileBrowserHandler::CanRead() const {
+  DCHECK(!(file_access_permission_flags_ & kInvalidPermission));
+  return (file_access_permission_flags_ & kReadPermission) != 0;
+}
+
+bool FileBrowserHandler::CanWrite() const {
+  DCHECK(!(file_access_permission_flags_ & kInvalidPermission));
+  return (file_access_permission_flags_ & kWritePermission) != 0;
+}
+
+bool FileBrowserHandler::HasCreateAccessPermission() const {
+  DCHECK(!(file_access_permission_flags_ & kInvalidPermission));
+  return (file_access_permission_flags_ & kCreatePermission) != 0;
+}
diff --git a/chrome/common/extensions/file_browser_handler.h b/chrome/common/extensions/file_browser_handler.h
new file mode 100644
index 0000000..e4bf1ea
--- /dev/null
+++ b/chrome/common/extensions/file_browser_handler.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_FILE_BROWSER_HANDLER_H_
+#define CHROME_COMMON_EXTENSIONS_FILE_BROWSER_HANDLER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "chrome/common/extensions/url_pattern.h"
+#include "chrome/common/extensions/url_pattern_set.h"
+#include "googleurl/src/gurl.h"
+
+class URLPattern;
+
+// FileBrowserHandler encapsulates the state of a file browser action.
+class FileBrowserHandler {
+ public:
+  FileBrowserHandler();
+  ~FileBrowserHandler();
+
+  // extension id
+  std::string extension_id() const { return extension_id_; }
+  void set_extension_id(const std::string& extension_id) {
+    extension_id_ = extension_id;
+  }
+
+  // action id
+  const std::string& id() const { return id_; }
+  void set_id(const std::string& id) { id_ = id; }
+
+  // default title
+  const std::string& title() const { return title_; }
+  void set_title(const std::string& title) { title_ = title; }
+
+  // File schema URL patterns.
+  const URLPatternSet& file_url_patterns() const {
+    return url_set_;
+  }
+  void AddPattern(const URLPattern& pattern);
+  bool MatchesURL(const GURL& url) const;
+  void ClearPatterns();
+
+  // Action icon path.
+  const std::string icon_path() const { return default_icon_path_; }
+  void set_icon_path(const std::string& path) {
+    default_icon_path_ = path;
+  }
+
+  // File access permissions.
+  // Adjusts file_access_permission_flags_ to allow specified permission.
+  bool AddFileAccessPermission(const std::string& permission_str);
+  // Checks that specified file access permissions are valid (all set
+  // permissions are valid and there is no other permission specified with
+  // "create")
+  // If no access permissions were set, initialize them to default value.
+  bool ValidateFileAccessPermissions();
+  // Checks if handler has read access.
+  bool CanRead() const;
+  // Checks if handler has write access.
+  bool CanWrite() const;
+  // Checks if handler has "create" access specified.
+  bool HasCreateAccessPermission() const;
+
+ private:
+  // The id for the extension this action belongs to (as defined in the
+  // extension manifest).
+  std::string extension_id_;
+  std::string title_;
+  std::string default_icon_path_;
+  // The id for the FileBrowserHandler, for example: "PdfFileAction".
+  std::string id_;
+  unsigned int file_access_permission_flags_;
+
+  // A list of file filters.
+  URLPatternSet url_set_;
+};
+
+#endif  // CHROME_COMMON_EXTENSIONS_FILE_BROWSER_HANDLER_H_
diff --git a/chrome/common/extensions/manifest.cc b/chrome/common/extensions/manifest.cc
new file mode 100644
index 0000000..42362a5
--- /dev/null
+++ b/chrome/common/extensions/manifest.cc
@@ -0,0 +1,179 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest.h"
+
+#include "base/basictypes.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/string_split.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/features/simple_feature_provider.h"
+
+namespace errors = extension_manifest_errors;
+namespace keys = extension_manifest_keys;
+
+namespace extensions {
+
+Manifest::Manifest(Extension::Location location,
+                   scoped_ptr<DictionaryValue> value)
+    : location_(location),
+      value_(value.Pass()),
+      type_(Extension::TYPE_UNKNOWN) {
+  if (value_->HasKey(keys::kTheme)) {
+    type_ = Extension::TYPE_THEME;
+  } else if (value_->HasKey(keys::kApp)) {
+    if (value_->Get(keys::kWebURLs, NULL) ||
+        value_->Get(keys::kLaunchWebURL, NULL)) {
+      type_ = Extension::TYPE_HOSTED_APP;
+    } else if (value_->Get(keys::kPlatformAppBackground, NULL)) {
+      type_ = Extension::TYPE_PLATFORM_APP;
+    } else {
+      type_ = Extension::TYPE_LEGACY_PACKAGED_APP;
+    }
+  } else {
+    type_ = Extension::TYPE_EXTENSION;
+  }
+  CHECK_NE(type_, Extension::TYPE_UNKNOWN);
+}
+
+Manifest::~Manifest() {
+}
+
+void Manifest::ValidateManifest(
+    std::string* error,
+    Extension::InstallWarningVector* warnings) const {
+  *error = "";
+  if (type_ == Extension::TYPE_PLATFORM_APP && GetManifestVersion() < 2) {
+    *error = errors::kPlatformAppNeedsManifestVersion2;
+    return;
+  }
+
+  // Check every feature to see if its in the manifest. Note that this means
+  // we will ignore keys that are not features; we do this for forward
+  // compatibility.
+  // TODO(aa): Consider having an error here in the case of strict error
+  // checking to let developers know when they screw up.
+
+  std::set<std::string> feature_names =
+      SimpleFeatureProvider::GetManifestFeatures()->GetAllFeatureNames();
+  for (std::set<std::string>::iterator feature_name = feature_names.begin();
+       feature_name != feature_names.end(); ++feature_name) {
+    // Use Get instead of HasKey because the former uses path expansion.
+    if (!value_->Get(*feature_name, NULL))
+      continue;
+
+    Feature* feature =
+        SimpleFeatureProvider::GetManifestFeatures()->GetFeature(*feature_name);
+    Feature::Availability result = feature->IsAvailableToManifest(
+        extension_id_, type_, Feature::ConvertLocation(location_),
+        GetManifestVersion());
+    if (!result.is_available())
+      warnings->push_back(Extension::InstallWarning(
+          Extension::InstallWarning::FORMAT_TEXT, result.message()));
+  }
+
+  // Also generate warnings for keys that are not features.
+  for (DictionaryValue::key_iterator key = value_->begin_keys();
+      key != value_->end_keys(); ++key) {
+    if (!SimpleFeatureProvider::GetManifestFeatures()->GetFeature(*key)) {
+      warnings->push_back(Extension::InstallWarning(
+          Extension::InstallWarning::FORMAT_TEXT,
+          base::StringPrintf("Unrecognized manifest key '%s'.",
+                             (*key).c_str())));
+    }
+  }
+}
+
+bool Manifest::HasKey(const std::string& key) const {
+  return CanAccessKey(key) && value_->HasKey(key);
+}
+
+bool Manifest::HasPath(const std::string& path) const {
+  Value* ignored = NULL;
+  return CanAccessPath(path) && value_->Get(path, &ignored);
+}
+
+bool Manifest::Get(
+    const std::string& path, Value** out_value) const {
+  return CanAccessPath(path) && value_->Get(path, out_value);
+}
+
+bool Manifest::GetBoolean(
+    const std::string& path, bool* out_value) const {
+  return CanAccessPath(path) && value_->GetBoolean(path, out_value);
+}
+
+bool Manifest::GetInteger(
+    const std::string& path, int* out_value) const {
+  return CanAccessPath(path) && value_->GetInteger(path, out_value);
+}
+
+bool Manifest::GetString(
+    const std::string& path, std::string* out_value) const {
+  return CanAccessPath(path) && value_->GetString(path, out_value);
+}
+
+bool Manifest::GetString(
+    const std::string& path, string16* out_value) const {
+  return CanAccessPath(path) && value_->GetString(path, out_value);
+}
+
+bool Manifest::GetDictionary(
+    const std::string& path, DictionaryValue** out_value) const {
+  return CanAccessPath(path) && value_->GetDictionary(path, out_value);
+}
+
+bool Manifest::GetList(
+    const std::string& path, ListValue** out_value) const {
+  return CanAccessPath(path) && value_->GetList(path, out_value);
+}
+
+Manifest* Manifest::DeepCopy() const {
+  Manifest* manifest = new Manifest(
+      location_, scoped_ptr<DictionaryValue>(value_->DeepCopy()));
+  manifest->set_extension_id(extension_id_);
+  return manifest;
+}
+
+bool Manifest::Equals(const Manifest* other) const {
+  return other && value_->Equals(other->value());
+}
+
+int Manifest::GetManifestVersion() const {
+  // Platform apps were launched after manifest version 2 was the preferred
+  // version, so they default to that.
+  int manifest_version = type_ == Extension::TYPE_PLATFORM_APP ? 2 : 1;
+  value_->GetInteger(keys::kManifestVersion, &manifest_version);
+  return manifest_version;
+}
+
+bool Manifest::CanAccessPath(const std::string& path) const {
+  std::vector<std::string> components;
+  base::SplitString(path, '.', &components);
+  std::string key;
+  for (size_t i = 0; i < components.size(); ++i) {
+    key += components[i];
+    if (!CanAccessKey(key))
+      return false;
+    key += '.';
+  }
+  return true;
+}
+
+bool Manifest::CanAccessKey(const std::string& key) const {
+  Feature* feature =
+      SimpleFeatureProvider::GetManifestFeatures()->GetFeature(key);
+  if (!feature)
+    return true;
+
+  return feature->IsAvailableToManifest(
+      extension_id_, type_, Feature::ConvertLocation(location_),
+      GetManifestVersion()).is_available();
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/manifest.h b/chrome/common/extensions/manifest.h
new file mode 100644
index 0000000..da5f542
--- /dev/null
+++ b/chrome/common/extensions/manifest.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MANIFEST_H_
+#define CHROME_COMMON_EXTENSIONS_MANIFEST_H_
+
+#include <map>
+#include <string>
+#include <set>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/string16.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension.h"
+
+namespace extensions {
+
+// Wraps the DictionaryValue form of extension's manifest. Enforces access to
+// properties of the manifest using ManifestFeatureProvider.
+class Manifest {
+ public:
+  Manifest(Extension::Location location, scoped_ptr<DictionaryValue> value);
+  virtual ~Manifest();
+
+  const std::string& extension_id() const { return extension_id_; }
+  void set_extension_id(const std::string& id) { extension_id_ = id; }
+
+  Extension::Location location() const { return location_; }
+
+  // |error| will be non-empty if the manifest is malformed. |warnings| will
+  // be populated if there are keys in the manifest that cannot be specified by
+  // the extension type.
+  void ValidateManifest(std::string* error,
+                        Extension::InstallWarningVector* warnings) const;
+
+  // The version of this extension's manifest. We increase the manifest
+  // version when making breaking changes to the extension system. If the
+  // manifest contains no explicit manifest version, this returns the current
+  // system default.
+  int GetManifestVersion() const;
+
+  // Returns the manifest type.
+  Extension::Type type() const { return type_; }
+
+  bool is_theme() const { return type_ == Extension::TYPE_THEME; }
+  bool is_platform_app() const { return type_ == Extension::TYPE_PLATFORM_APP; }
+  bool is_hosted_app() const { return type_ == Extension::TYPE_HOSTED_APP; }
+  bool is_legacy_packaged_app() const {
+    return type_ == Extension::TYPE_LEGACY_PACKAGED_APP;
+  }
+
+  // These access the wrapped manifest value, returning false when the property
+  // does not exist or if the manifest type can't access it.
+  bool HasKey(const std::string& key) const;
+  bool HasPath(const std::string& path) const;
+  bool Get(const std::string& path, base::Value** out_value) const;
+  bool GetBoolean(const std::string& path, bool* out_value) const;
+  bool GetInteger(const std::string& path, int* out_value) const;
+  bool GetString(const std::string& path, std::string* out_value) const;
+  bool GetString(const std::string& path, string16* out_value) const;
+  bool GetDictionary(const std::string& path,
+                     base::DictionaryValue** out_value) const;
+  bool GetList(const std::string& path, base::ListValue** out_value) const;
+
+  // Returns a new Manifest equal to this one, passing ownership to
+  // the caller.
+  Manifest* DeepCopy() const;
+
+  // Returns true if this equals the |other| manifest.
+  bool Equals(const Manifest* other) const;
+
+  // Gets the underlying DictionaryValue representing the manifest.
+  // Note: only use this when you KNOW you don't need the validation.
+  const base::DictionaryValue* value() const { return value_.get(); }
+
+ private:
+  // Returns true if the extension can specify the given |path|.
+  bool CanAccessPath(const std::string& path) const;
+  bool CanAccessKey(const std::string& key) const;
+
+  // A persistent, globally unique ID. An extension's ID is used in things
+  // like directory structures and URLs, and is expected to not change across
+  // versions. It is generated as a SHA-256 hash of the extension's public
+  // key, or as a hash of the path in the case of unpacked extensions.
+  std::string extension_id_;
+
+  // The location the extension was loaded from.
+  Extension::Location location_;
+
+  // The underlying dictionary representation of the manifest.
+  scoped_ptr<base::DictionaryValue> value_;
+
+  Extension::Type type_;
+
+  DISALLOW_COPY_AND_ASSIGN(Manifest);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_MANIFEST_H_
diff --git a/chrome/common/extensions/manifest_tests/extension_manifest_test.cc b/chrome/common/extensions/manifest_tests/extension_manifest_test.cc
new file mode 100644
index 0000000..7584ff2
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifest_test.cc
@@ -0,0 +1,246 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/values.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/path_service.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_l10n_util.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using extensions::Extension;
+
+ExtensionManifestTest::ExtensionManifestTest()
+    : enable_apps_(true),
+      // UNKNOWN == trunk.
+      current_channel_(chrome::VersionInfo::CHANNEL_UNKNOWN) {}
+
+// static
+DictionaryValue* ExtensionManifestTest::LoadManifestFile(
+    const std::string& filename,
+    std::string* error) {
+  FilePath filename_path(FilePath::FromUTF8Unsafe(filename));
+  FilePath extension_path;
+  FilePath manifest_path;
+
+  if (filename_path.IsAbsolute()) {
+    extension_path = filename_path.DirName();
+    manifest_path = filename_path;
+  } else {
+    PathService::Get(chrome::DIR_TEST_DATA, &extension_path);
+    extension_path = extension_path.AppendASCII("extensions")
+        .AppendASCII("manifest_tests");
+    manifest_path = extension_path.AppendASCII(filename.c_str());
+  }
+
+  EXPECT_TRUE(file_util::PathExists(manifest_path)) <<
+      "Couldn't find " << manifest_path.value();
+
+  JSONFileValueSerializer serializer(manifest_path);
+  DictionaryValue* manifest =
+      static_cast<DictionaryValue*>(serializer.Deserialize(NULL, error));
+
+  // Most unit tests don't need localization, and they'll fail if we try to
+  // localize them, since their manifests don't have a default_locale key.
+  // Only localize manifests that indicate they want to be localized.
+  // Calling LocalizeExtension at this point mirrors
+  // extension_file_util::LoadExtension.
+  if (manifest && filename.find("localized") != std::string::npos)
+    extension_l10n_util::LocalizeExtension(extension_path, manifest, error);
+
+  return manifest;
+}
+
+// Helper class that simplifies creating methods that take either a filename
+// to a manifest or the manifest itself.
+ExtensionManifestTest::Manifest::Manifest(const char* name)
+    : name_(name), manifest_(NULL) {
+}
+
+ExtensionManifestTest::Manifest::Manifest(DictionaryValue* manifest,
+                                          const char* name)
+    : name_(name), manifest_(manifest) {
+  CHECK(manifest_) << "Manifest NULL";
+}
+
+ExtensionManifestTest::Manifest::Manifest(const Manifest& m) {
+  NOTREACHED();
+}
+
+ExtensionManifestTest::Manifest::~Manifest() {
+}
+
+DictionaryValue* ExtensionManifestTest::Manifest::GetManifest(
+    std::string* error) const {
+  if (manifest_)
+    return manifest_;
+
+  manifest_ = LoadManifestFile(name_, error);
+  manifest_holder_.reset(manifest_);
+  return manifest_;
+}
+
+scoped_refptr<Extension> ExtensionManifestTest::LoadExtension(
+    const Manifest& manifest,
+    std::string* error,
+    Extension::Location location,
+    int flags) {
+  DictionaryValue* value = manifest.GetManifest(error);
+  if (!value)
+    return NULL;
+  FilePath path;
+  PathService::Get(chrome::DIR_TEST_DATA, &path);
+  path = path.AppendASCII("extensions").AppendASCII("manifest_tests");
+  return Extension::Create(path.DirName(), location, *value, flags, error);
+}
+
+scoped_refptr<Extension> ExtensionManifestTest::LoadAndExpectSuccess(
+    const Manifest& manifest,
+    Extension::Location location,
+    int flags) {
+  std::string error;
+  scoped_refptr<Extension> extension =
+      LoadExtension(manifest, &error, location, flags);
+  EXPECT_TRUE(extension) << manifest.name();
+  EXPECT_EQ("", error) << manifest.name();
+  return extension;
+}
+
+scoped_refptr<Extension> ExtensionManifestTest::LoadAndExpectSuccess(
+    char const* manifest_name,
+    Extension::Location location,
+    int flags) {
+  return LoadAndExpectSuccess(Manifest(manifest_name), location, flags);
+}
+
+scoped_refptr<Extension> ExtensionManifestTest::LoadAndExpectWarning(
+    const Manifest& manifest,
+    const std::string& expected_warning,
+    Extension::Location location,
+    int flags) {
+  std::string error;
+  scoped_refptr<Extension> extension =
+      LoadExtension(manifest, &error, location, flags);
+  EXPECT_TRUE(extension) << manifest.name();
+  EXPECT_EQ("", error) << manifest.name();
+  EXPECT_EQ(1u, extension->install_warnings().size());
+  EXPECT_EQ(expected_warning, extension->install_warnings()[0].message);
+  return extension;
+}
+
+scoped_refptr<Extension> ExtensionManifestTest::LoadAndExpectWarning(
+    char const* manifest_name,
+    const std::string& expected_warning,
+    Extension::Location location,
+    int flags) {
+  return LoadAndExpectWarning(
+      Manifest(manifest_name), expected_warning, location, flags);
+}
+
+void ExtensionManifestTest::VerifyExpectedError(
+    Extension* extension,
+    const std::string& name,
+    const std::string& error,
+    const std::string& expected_error) {
+  EXPECT_FALSE(extension) <<
+      "Expected failure loading extension '" << name <<
+      "', but didn't get one.";
+  EXPECT_TRUE(MatchPattern(error, expected_error)) << name <<
+      " expected '" << expected_error << "' but got '" << error << "'";
+}
+
+void ExtensionManifestTest::LoadAndExpectError(
+    const Manifest& manifest,
+    const std::string& expected_error,
+    Extension::Location location,
+    int flags) {
+  std::string error;
+  scoped_refptr<Extension> extension(
+      LoadExtension(manifest, &error, location, flags));
+  VerifyExpectedError(extension.get(), manifest.name(), error,
+                      expected_error);
+}
+
+void ExtensionManifestTest::LoadAndExpectError(
+    char const* manifest_name,
+    const std::string& expected_error,
+    Extension::Location location,
+    int flags) {
+  return LoadAndExpectError(
+      Manifest(manifest_name), expected_error, location, flags);
+}
+
+void ExtensionManifestTest::AddPattern(URLPatternSet* extent,
+                                       const std::string& pattern) {
+  int schemes = URLPattern::SCHEME_ALL;
+  extent->AddPattern(URLPattern(schemes, pattern));
+}
+
+ExtensionManifestTest::Testcase::Testcase(std::string manifest_filename,
+                                          std::string expected_error,
+                                          Extension::Location location,
+                                          int flags)
+    : manifest_filename_(manifest_filename),
+      expected_error_(expected_error),
+      location_(location), flags_(flags) {
+}
+
+ExtensionManifestTest::Testcase::Testcase(std::string manifest_filename,
+                                          std::string expected_error)
+    : manifest_filename_(manifest_filename),
+      expected_error_(expected_error),
+      location_(Extension::INTERNAL),
+      flags_(Extension::NO_FLAGS) {
+}
+
+ExtensionManifestTest::Testcase::Testcase(std::string manifest_filename)
+    : manifest_filename_(manifest_filename),
+      expected_error_(""),
+      location_(Extension::INTERNAL),
+      flags_(Extension::NO_FLAGS) {
+}
+
+ExtensionManifestTest::Testcase::Testcase(std::string manifest_filename,
+                                          Extension::Location location,
+                                          int flags)
+    : manifest_filename_(manifest_filename),
+      expected_error_(""),
+      location_(location),
+      flags_(flags) {
+}
+
+void ExtensionManifestTest::RunTestcases(const Testcase* testcases,
+                                         size_t num_testcases,
+                                         EXPECT_TYPE type) {
+  switch (type) {
+    case EXPECT_TYPE_ERROR:
+      for (size_t i = 0; i < num_testcases; ++i) {
+        LoadAndExpectError(testcases[i].manifest_filename_.c_str(),
+                           testcases[i].expected_error_,
+                           testcases[i].location_,
+                           testcases[i].flags_);
+      }
+      break;
+    case EXPECT_TYPE_WARNING:
+      for (size_t i = 0; i < num_testcases; ++i) {
+        LoadAndExpectWarning(testcases[i].manifest_filename_.c_str(),
+                             testcases[i].expected_error_,
+                             testcases[i].location_,
+                             testcases[i].flags_);
+      }
+      break;
+    case EXPECT_TYPE_SUCCESS:
+      for (size_t i = 0; i < num_testcases; ++i) {
+        LoadAndExpectSuccess(testcases[i].manifest_filename_.c_str(),
+                             testcases[i].location_,
+                             testcases[i].flags_);
+      }
+      break;
+   }
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifest_test.h b/chrome/common/extensions/manifest_tests/extension_manifest_test.h
new file mode 100644
index 0000000..8cc938b
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifest_test.h
@@ -0,0 +1,149 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MANIFEST_TESTS_EXTENSION_MANIFEST_TEST_H_
+#define CHROME_COMMON_EXTENSIONS_MANIFEST_TESTS_EXTENSION_MANIFEST_TEST_H_
+
+#include "base/values.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/features/feature.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+namespace keys = extension_manifest_keys;
+
+class ExtensionManifestTest : public testing::Test {
+ public:
+  ExtensionManifestTest();
+
+ protected:
+  // If filename is a relative path, LoadManifestFile will treat it relative to
+  // the appropriate test directory.
+  static DictionaryValue* LoadManifestFile(const std::string& filename,
+                                           std::string* error);
+
+  // Helper class that simplifies creating methods that take either a filename
+  // to a manifest or the manifest itself.
+  class Manifest {
+   public:
+    explicit Manifest(const char* name);
+    Manifest(DictionaryValue* manifest, const char* name);
+    // C++98 requires the copy constructor for a type to be visible if you
+    // take a const-ref of a temporary for that type.  Since Manifest
+    // contains a scoped_ptr, its implicit copy constructor is declared
+    // Manifest(Manifest&) according to spec 12.8.5.  This breaks the first
+    // requirement and thus you cannot use it with LoadAndExpectError() or
+    // LoadAndExpectSuccess() easily.
+    //
+    // To get around this spec pedantry, we declare the copy constructor
+    // explicitly.  It will never get invoked.
+    Manifest(const Manifest& m);
+
+    ~Manifest();
+
+    const std::string& name() const { return name_; };
+
+    DictionaryValue* GetManifest(std::string* error) const;
+
+   private:
+    const std::string name_;
+    mutable DictionaryValue* manifest_;
+    mutable scoped_ptr<DictionaryValue> manifest_holder_;
+  };
+
+  scoped_refptr<extensions::Extension> LoadExtension(
+      const Manifest& manifest,
+      std::string* error,
+      extensions::Extension::Location location =
+          extensions::Extension::INTERNAL,
+      int flags = extensions::Extension::NO_FLAGS);
+
+  scoped_refptr<extensions::Extension> LoadAndExpectSuccess(
+      const Manifest& manifest,
+      extensions::Extension::Location location =
+          extensions::Extension::INTERNAL,
+      int flags = extensions::Extension::NO_FLAGS);
+
+  scoped_refptr<extensions::Extension> LoadAndExpectSuccess(
+      char const* manifest_name,
+      extensions::Extension::Location location =
+          extensions::Extension::INTERNAL,
+      int flags = extensions::Extension::NO_FLAGS);
+
+  scoped_refptr<extensions::Extension> LoadAndExpectWarning(
+      const Manifest& manifest,
+      const std::string& expected_error,
+      extensions::Extension::Location location =
+          extensions::Extension::INTERNAL,
+      int flags = extensions::Extension::NO_FLAGS);
+
+  scoped_refptr<extensions::Extension> LoadAndExpectWarning(
+      char const* manifest_name,
+      const std::string& expected_error,
+      extensions::Extension::Location location =
+          extensions::Extension::INTERNAL,
+      int flags = extensions::Extension::NO_FLAGS);
+
+  void VerifyExpectedError(extensions::Extension* extension,
+                           const std::string& name,
+                           const std::string& error,
+                           const std::string& expected_error);
+
+  void LoadAndExpectError(char const* manifest_name,
+                          const std::string& expected_error,
+                          extensions::Extension::Location location =
+                              extensions::Extension::INTERNAL,
+                          int flags = extensions::Extension::NO_FLAGS);
+
+  void LoadAndExpectError(const Manifest& manifest,
+                          const std::string& expected_error,
+                          extensions::Extension::Location location =
+                              extensions::Extension::INTERNAL,
+                          int flags = extensions::Extension::NO_FLAGS);
+
+  void AddPattern(URLPatternSet* extent, const std::string& pattern);
+
+  // used to differentiate between calls to LoadAndExpectError,
+  // LoadAndExpectWarning and LoadAndExpectSuccess via function RunTestcases.
+  enum EXPECT_TYPE {
+    EXPECT_TYPE_ERROR,
+    EXPECT_TYPE_WARNING,
+    EXPECT_TYPE_SUCCESS
+  };
+
+  struct Testcase {
+    std::string manifest_filename_;
+    std::string expected_error_; // only used for ExpectedError tests
+    extensions::Extension::Location location_;
+    int flags_;
+
+    Testcase(std::string manifest_filename, std::string expected_error,
+        extensions::Extension::Location location, int flags);
+
+    Testcase(std::string manifest_filename, std::string expected_error);
+
+    explicit Testcase(std::string manifest_filename);
+
+    Testcase(std::string manifest_filename,
+             extensions::Extension::Location location,
+             int flags);
+  };
+
+  void RunTestcases(const Testcase* testcases, size_t num_testcases,
+      EXPECT_TYPE type);
+
+  bool enable_apps_;
+
+  // Force the manifest tests to run as though they are on trunk, since several
+  // tests rely on manifest features being available that aren't on
+  // stable/beta.
+  //
+  // These objects nest, so if a test wants to explicitly test the behaviour
+  // on stable or beta, declare it inside that test.
+  extensions::Feature::ScopedCurrentChannel current_channel_;
+};
+
+#endif  // CHROME_COMMON_EXTENSIONS_MANIFEST_TESTS_EXTENSION_MANIFEST_TEST_H_
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_auth_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_auth_unittest.cc
new file mode 100644
index 0000000..e998322
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_auth_unittest.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+
+namespace keys = extension_manifest_keys;
+
+TEST_F(ExtensionManifestTest, OAuth2SectionParsing) {
+  DictionaryValue base_manifest;
+
+  base_manifest.SetString(keys::kName, "test");
+  base_manifest.SetString(keys::kVersion, "0.1");
+  base_manifest.SetInteger(keys::kManifestVersion, 2);
+  base_manifest.SetString(keys::kOAuth2ClientId, "client1");
+  ListValue* scopes = new ListValue();
+  scopes->Append(Value::CreateStringValue("scope1"));
+  scopes->Append(Value::CreateStringValue("scope2"));
+  base_manifest.Set(keys::kOAuth2Scopes, scopes);
+
+  // OAuth2 section should be parsed for an extension.
+  {
+    DictionaryValue ext_manifest;
+    // Lack of "app" section representa an extension. So the base manifest
+    // itself represents an extension.
+    ext_manifest.MergeDictionary(&base_manifest);
+
+    Manifest manifest(&ext_manifest, "test");
+    scoped_refptr<extensions::Extension> extension =
+        LoadAndExpectSuccess(manifest);
+    EXPECT_TRUE(extension->install_warnings().empty());
+    EXPECT_EQ("client1", extension->oauth2_info().client_id);
+    EXPECT_EQ(2U, extension->oauth2_info().scopes.size());
+    EXPECT_EQ("scope1", extension->oauth2_info().scopes[0]);
+    EXPECT_EQ("scope2", extension->oauth2_info().scopes[1]);
+  }
+
+  // OAuth2 section should be parsed for a packaged app.
+  {
+    DictionaryValue app_manifest;
+    app_manifest.SetString(keys::kLaunchLocalPath, "launch.html");
+    app_manifest.MergeDictionary(&base_manifest);
+
+    Manifest manifest(&app_manifest, "test");
+    scoped_refptr<extensions::Extension> extension =
+        LoadAndExpectSuccess(manifest);
+    EXPECT_TRUE(extension->install_warnings().empty());
+    EXPECT_EQ("client1", extension->oauth2_info().client_id);
+    EXPECT_EQ(2U, extension->oauth2_info().scopes.size());
+    EXPECT_EQ("scope1", extension->oauth2_info().scopes[0]);
+    EXPECT_EQ("scope2", extension->oauth2_info().scopes[1]);
+  }
+
+  // OAuth2 section should NOT be parsed for a hosted app.
+  {
+    DictionaryValue app_manifest;
+    app_manifest.SetString(keys::kLaunchWebURL, "http://www.google.com");
+    app_manifest.MergeDictionary(&base_manifest);
+
+    Manifest manifest(&app_manifest, "test");
+    scoped_refptr<extensions::Extension> extension =
+        LoadAndExpectSuccess(manifest);
+    EXPECT_EQ(1U, extension->install_warnings().size());
+    const extensions::Extension::InstallWarning& warning =
+        extension->install_warnings()[0];
+    EXPECT_EQ("'oauth2' is only allowed for extensions, legacy packaged apps "
+                  "and packaged apps, and this is a hosted app.",
+              warning.message);
+    EXPECT_EQ("", extension->oauth2_info().client_id);
+    EXPECT_TRUE(extension->oauth2_info().scopes.empty());
+  }
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_background_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_background_unittest.cc
new file mode 100644
index 0000000..7323ef8
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_background_unittest.cc
@@ -0,0 +1,106 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "base/command_line.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/features/feature.h"
+#include "chrome/common/extensions/features/simple_feature_provider.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+namespace extensions {
+
+TEST_F(ExtensionManifestTest, BackgroundPermission) {
+  LoadAndExpectError("background_permission.json",
+                     errors::kBackgroundPermissionNeeded);
+}
+
+TEST_F(ExtensionManifestTest, BackgroundScripts) {
+  std::string error;
+  scoped_ptr<DictionaryValue> manifest(
+      LoadManifestFile("background_scripts.json", &error));
+  ASSERT_TRUE(manifest.get());
+
+  scoped_refptr<Extension> extension(
+      LoadAndExpectSuccess(Manifest(manifest.get(), "")));
+  ASSERT_TRUE(extension);
+  EXPECT_EQ(2u, extension->background_scripts().size());
+  EXPECT_EQ("foo.js", extension->background_scripts()[0u]);
+  EXPECT_EQ("bar/baz.js", extension->background_scripts()[1u]);
+
+  EXPECT_TRUE(extension->has_background_page());
+  EXPECT_EQ(std::string("/") +
+            extension_filenames::kGeneratedBackgroundPageFilename,
+            extension->GetBackgroundURL().path());
+
+  manifest->SetString("background_page", "monkey.html");
+  LoadAndExpectError(Manifest(manifest.get(), ""),
+                     errors::kInvalidBackgroundCombination);
+}
+
+TEST_F(ExtensionManifestTest, BackgroundPage) {
+  scoped_refptr<Extension> extension(
+      LoadAndExpectSuccess("background_page.json"));
+  ASSERT_TRUE(extension);
+  EXPECT_EQ("/foo.html", extension->GetBackgroundURL().path());
+  EXPECT_TRUE(extension->allow_background_js_access());
+
+  std::string error;
+  scoped_ptr<DictionaryValue> manifest(
+      LoadManifestFile("background_page_legacy.json", &error));
+  ASSERT_TRUE(manifest.get());
+  extension = LoadAndExpectSuccess(Manifest(manifest.get(), ""));
+  ASSERT_TRUE(extension);
+  EXPECT_EQ("/foo.html", extension->GetBackgroundURL().path());
+
+  manifest->SetInteger(keys::kManifestVersion, 2);
+  LoadAndExpectWarning(
+      Manifest(manifest.get(), ""),
+      "'background_page' requires manifest version of 1 or lower.");
+}
+
+TEST_F(ExtensionManifestTest, BackgroundAllowNoJsAccess) {
+  scoped_refptr<Extension> extension;
+  extension = LoadAndExpectSuccess("background_allow_no_js_access.json");
+  ASSERT_TRUE(extension);
+  EXPECT_FALSE(extension->allow_background_js_access());
+
+  extension = LoadAndExpectSuccess("background_allow_no_js_access2.json");
+  ASSERT_TRUE(extension);
+  EXPECT_FALSE(extension->allow_background_js_access());
+}
+
+TEST_F(ExtensionManifestTest, BackgroundPageWebRequest) {
+  CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kEnableExperimentalExtensionApis);
+  Feature::ScopedCurrentChannel current_channel(
+      chrome::VersionInfo::CHANNEL_DEV);
+
+  std::string error;
+  scoped_ptr<DictionaryValue> manifest(
+      LoadManifestFile("background_page.json", &error));
+  ASSERT_TRUE(manifest.get());
+  manifest->SetBoolean(keys::kBackgroundPersistent, false);
+  manifest->SetInteger(keys::kManifestVersion, 2);
+  scoped_refptr<Extension> extension(
+      LoadAndExpectSuccess(Manifest(manifest.get(), "")));
+  ASSERT_TRUE(extension);
+  EXPECT_TRUE(extension->has_lazy_background_page());
+
+  ListValue* permissions = new ListValue();
+  permissions->Append(Value::CreateStringValue("webRequest"));
+  manifest->Set(keys::kPermissions, permissions);
+  LoadAndExpectError(Manifest(manifest.get(), ""),
+                     errors::kWebRequestConflictsWithLazyBackground);
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_browseraction_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_browseraction_unittest.cc
new file mode 100644
index 0000000..f51b002
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_browseraction_unittest.cc
@@ -0,0 +1,103 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_builder.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/extension_icon_set.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+#include "chrome/common/extensions/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+namespace extensions {
+namespace {
+
+TEST_F(ExtensionManifestTest, BrowserActionManifestIcons_NoDefaultIcons) {
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder()
+      .SetManifest(DictionaryBuilder()
+                   .Set("name", "No default properties")
+                   .Set("version", "1.0.0")
+                   .Set("manifest_version", 2)
+                   .Set("browser_action", DictionaryBuilder()
+                       .Set("default_title", "Title")))
+      .Build();
+
+  ASSERT_TRUE(extension.get());
+  ASSERT_TRUE(extension->browser_action_info());
+  EXPECT_TRUE(extension->browser_action_info()->default_icon.empty());
+}
+
+
+TEST_F(ExtensionManifestTest, BrowserActionManifestIcons_StringDefaultIcon) {
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder()
+      .SetManifest(DictionaryBuilder()
+                   .Set("name", "String default icon")
+                   .Set("version", "1.0.0")
+                   .Set("manifest_version", 2)
+                   .Set("browser_action", DictionaryBuilder()
+                       .Set("default_icon", "icon.png")))
+      .Build();
+
+  ASSERT_TRUE(extension.get());
+  ASSERT_TRUE(extension->browser_action_info());
+  ASSERT_FALSE(extension->browser_action_info()->default_icon.empty());
+
+  const ExtensionIconSet& icons =
+      extension->browser_action_info()->default_icon;
+
+  EXPECT_EQ(1u, icons.map().size());
+  EXPECT_EQ("icon.png", icons.Get(19, ExtensionIconSet::MATCH_EXACTLY));
+}
+
+TEST_F(ExtensionManifestTest, BrowserActionManifestIcons_DictDefaultIcon) {
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder()
+      .SetManifest(DictionaryBuilder()
+                   .Set("name", "Dictionary default icon")
+                   .Set("version", "1.0.0")
+                   .Set("manifest_version", 2)
+                   .Set("browser_action", DictionaryBuilder()
+                       .Set("default_icon", DictionaryBuilder()
+                           .Set("19", "icon19.png")
+                           .Set("24", "icon24.png")  // Should be ignored.
+                           .Set("38", "icon38.png"))))
+      .Build();
+
+  ASSERT_TRUE(extension.get());
+  ASSERT_TRUE(extension->browser_action_info());
+  ASSERT_FALSE(extension->browser_action_info()->default_icon.empty());
+
+  const ExtensionIconSet& icons =
+      extension->browser_action_info()->default_icon;
+
+  // 24px icon should be ignored.
+  EXPECT_EQ(2u, icons.map().size());
+  EXPECT_EQ("icon19.png", icons.Get(19, ExtensionIconSet::MATCH_EXACTLY));
+  EXPECT_EQ("icon38.png", icons.Get(38, ExtensionIconSet::MATCH_EXACTLY));
+}
+
+TEST_F(ExtensionManifestTest, BrowserActionManifestIcons_InvalidDefaultIcon) {
+  scoped_ptr<DictionaryValue> manifest_value = DictionaryBuilder()
+      .Set("name", "Invalid default icon")
+      .Set("version", "1.0.0")
+      .Set("manifest_version", 2)
+      .Set("browser_action", DictionaryBuilder()
+          .Set("default_icon", DictionaryBuilder()
+              .Set("19", "")  // Invalid value.
+              .Set("24", "icon24.png")
+              .Set("38", "icon38.png")))
+     .Build();
+
+  string16 error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+      errors::kInvalidIconPath, "19");
+  LoadAndExpectError(Manifest(manifest_value.get(), "Invalid default icon"),
+                     errors::kInvalidIconPath);
+}
+
+}  // namespace
+}  // namespace extensions
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_chromepermission_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_chromepermission_unittest.cc
new file mode 100644
index 0000000..d3ba8b2
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_chromepermission_unittest.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, ChromeURLPermissionInvalid) {
+  LoadAndExpectError("permission_chrome_url_invalid.json",
+                     errors::kInvalidPermissionScheme);
+}
+
+TEST_F(ExtensionManifestTest, ChromeResourcesPermissionValidOnlyForComponents) {
+  LoadAndExpectError("permission_chrome_resources_url.json",
+                     errors::kInvalidPermissionScheme);
+  std::string error;
+  LoadExtension(Manifest("permission_chrome_resources_url.json"),
+                &error, extensions::Extension::COMPONENT,
+                extensions::Extension::NO_FLAGS);
+  EXPECT_EQ("", error);
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_command_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_command_unittest.cc
new file mode 100644
index 0000000..a9e4ffb
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_command_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "base/command_line.h"
+#include "base/string_util.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, CommandManifestSimple) {
+  CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kEnableExperimentalExtensionApis);
+
+#if defined(OS_MACOSX)
+  int ctrl = ui::EF_COMMAND_DOWN;
+#else
+  int ctrl = ui::EF_CONTROL_DOWN;
+#endif
+
+  const ui::Accelerator ctrl_f = ui::Accelerator(ui::VKEY_F, ctrl);
+  const ui::Accelerator ctrl_shift_f =
+      ui::Accelerator(ui::VKEY_F, ctrl | ui::EF_SHIFT_DOWN);
+  const ui::Accelerator alt_shift_f =
+      ui::Accelerator(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN);
+
+  scoped_refptr<extensions::Extension> extension =
+      LoadAndExpectSuccess("command_simple.json");
+  ASSERT_TRUE(extension);
+
+  const extensions::CommandMap& commands = extension->named_commands();
+  ASSERT_EQ(1u, commands.size());
+  extensions::CommandMap::const_iterator iter = commands.begin();
+  ASSERT_TRUE(commands.end() != iter);
+  const extensions::Command* named_command = &(*iter).second;
+  ASSERT_STREQ("feature1", named_command->command_name().c_str());
+  ASSERT_STREQ("desc", UTF16ToASCII(named_command->description()).c_str());
+  ASSERT_EQ(ctrl_shift_f, named_command->accelerator());
+
+  const extensions::Command* browser_action =
+      extension->browser_action_command();
+  ASSERT_TRUE(NULL != browser_action);
+  ASSERT_STREQ("_execute_browser_action",
+               browser_action->command_name().c_str());
+  ASSERT_STREQ("", UTF16ToASCII(browser_action->description()).c_str());
+  ASSERT_EQ(alt_shift_f, browser_action->accelerator());
+
+  const extensions::Command* page_action =
+      extension->page_action_command();
+  ASSERT_TRUE(NULL != page_action);
+  ASSERT_STREQ("_execute_page_action",
+      page_action->command_name().c_str());
+  ASSERT_STREQ("", UTF16ToASCII(page_action->description()).c_str());
+  ASSERT_EQ(ctrl_f, page_action->accelerator());
+}
+
+TEST_F(ExtensionManifestTest, CommandManifestTooMany) {
+  CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kEnableExperimentalExtensionApis);
+
+  LoadAndExpectError("command_too_many.json",
+                     errors::kInvalidKeyBindingTooMany);
+}
+
+TEST_F(ExtensionManifestTest, CommandManifestAllowNumbers) {
+  CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kEnableExperimentalExtensionApis);
+
+  scoped_refptr<extensions::Extension> extension =
+      LoadAndExpectSuccess("command_allow_numbers.json");
+}
+
+TEST_F(ExtensionManifestTest, CommandManifestRejectJustShift) {
+  CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kEnableExperimentalExtensionApis);
+
+  LoadAndExpectError("command_reject_just_shift.json",
+      errors::kInvalidKeyBinding);
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_contentscript_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_contentscript_unittest.cc
new file mode 100644
index 0000000..8536ffd
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_contentscript_unittest.cc
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "base/string_number_conversions.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, ContentScriptMatchPattern) {
+  Testcase testcases[] = {
+    // chrome:// urls are not allowed.
+    Testcase("content_script_chrome_url_invalid.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidMatch,
+                 base::IntToString(0),
+                 base::IntToString(0),
+                 URLPattern::GetParseResultString(
+                     URLPattern::PARSE_ERROR_INVALID_SCHEME))),
+
+    // Match paterns must be strings.
+    Testcase("content_script_match_pattern_not_string.json",
+             ExtensionErrorUtils::FormatErrorMessage(errors::kInvalidMatch,
+                                                     base::IntToString(0),
+                                                     base::IntToString(0),
+                                                     errors::kExpectString))
+  };
+  RunTestcases(testcases, arraysize(testcases),
+               EXPECT_TYPE_ERROR);
+
+  LoadAndExpectSuccess("ports_in_content_scripts.json");
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_contentsecuritypolicy_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_contentsecuritypolicy_unittest.cc
new file mode 100644
index 0000000..950bc4c
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_contentsecuritypolicy_unittest.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, InsecureContentSecurityPolicy) {
+  Testcase testcases[] = {
+    Testcase("insecure_contentsecuritypolicy_1.json",
+        errors::kInsecureContentSecurityPolicy),
+    Testcase("insecure_contentsecuritypolicy_2.json",
+        errors::kInsecureContentSecurityPolicy),
+    Testcase("insecure_contentsecuritypolicy_3.json",
+        errors::kInsecureContentSecurityPolicy),
+  };
+  RunTestcases(testcases, arraysize(testcases),
+               EXPECT_TYPE_ERROR);
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_default_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_default_unittest.cc
new file mode 100644
index 0000000..959a161
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_default_unittest.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, DefaultPathForExtent) {
+  scoped_refptr<extensions::Extension> extension(
+      LoadAndExpectSuccess("default_path_for_extent.json"));
+
+  ASSERT_EQ(1u, extension->web_extent().patterns().size());
+  EXPECT_EQ("/*", extension->web_extent().patterns().begin()->path());
+  EXPECT_TRUE(extension->web_extent().MatchesURL(
+      GURL("http://www.google.com/monkey")));
+}
+
+TEST_F(ExtensionManifestTest, DefaultLocale) {
+  LoadAndExpectError("default_locale_invalid.json",
+                     errors::kInvalidDefaultLocale);
+
+  scoped_refptr<extensions::Extension> extension(
+      LoadAndExpectSuccess("default_locale_valid.json"));
+  EXPECT_EQ("de-AT", extension->default_locale());
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_devtools_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_devtools_unittest.cc
new file mode 100644
index 0000000..4ee81e7
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_devtools_unittest.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, DevToolsExtensions) {
+  LoadAndExpectError("devtools_extension_url_invalid_type.json",
+                     errors::kInvalidDevToolsPage);
+
+  scoped_refptr<extensions::Extension> extension;
+  extension = LoadAndExpectSuccess("devtools_extension.json");
+  EXPECT_EQ(extension->url().spec() + "devtools.html",
+            extension->devtools_url().spec());
+  EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts());
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_excludematches_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_excludematches_unittest.cc
new file mode 100644
index 0000000..0f01638
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_excludematches_unittest.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST_F(ExtensionManifestTest, ExcludeMatchPatterns) {
+  Testcase testcases[] = {
+    Testcase("exclude_matches.json"),
+    Testcase("exclude_matches_empty.json")
+  };
+  RunTestcases(testcases, arraysize(testcases),
+               EXPECT_TYPE_SUCCESS);
+
+  Testcase testcases2[] = {
+    Testcase("exclude_matches_not_list.json",
+             "Invalid value for 'content_scripts[0].exclude_matches'."),
+    Testcase("exclude_matches_invalid_host.json",
+             "Invalid value for 'content_scripts[0].exclude_matches[0]': "
+                 "Invalid host wildcard.")
+  };
+  RunTestcases(testcases2, arraysize(testcases2),
+               EXPECT_TYPE_ERROR);
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_experimental_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_experimental_unittest.cc
new file mode 100644
index 0000000..4fe31c4
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_experimental_unittest.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "base/command_line.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, ExperimentalPermission) {
+  LoadAndExpectError("experimental.json", errors::kExperimentalFlagRequired);
+  LoadAndExpectSuccess("experimental.json", extensions::Extension::COMPONENT);
+  LoadAndExpectSuccess("experimental.json", extensions::Extension::INTERNAL,
+                       extensions::Extension::FROM_WEBSTORE);
+  CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kEnableExperimentalExtensionApis);
+  LoadAndExpectSuccess("experimental.json");
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_filebrowser_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_filebrowser_unittest.cc
new file mode 100644
index 0000000..72f2291
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_filebrowser_unittest.cc
@@ -0,0 +1,93 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "base/string_number_conversions.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/file_browser_handler.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::Extension;
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, FileBrowserHandlers) {
+  Testcase testcases[] = {
+    Testcase("filebrowser_invalid_access_permission.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidFileAccessValue, base::IntToString(1))),
+    Testcase("filebrowser_invalid_access_permission_list.json",
+             errors::kInvalidFileAccessList),
+    Testcase("filebrowser_invalid_empty_access_permission_list.json",
+             errors::kInvalidFileAccessList),
+    Testcase("filebrowser_invalid_actions_1.json",
+             errors::kInvalidFileBrowserHandler),
+    Testcase("filebrowser_invalid_actions_2.json",
+             errors::kInvalidFileBrowserHandler),
+    Testcase("filebrowser_invalid_action_id.json",
+             errors::kInvalidPageActionId),
+    Testcase("filebrowser_invalid_action_title.json",
+             errors::kInvalidPageActionDefaultTitle),
+    Testcase("filebrowser_invalid_file_filters_1.json",
+             errors::kInvalidFileFiltersList),
+    Testcase("filebrowser_invalid_file_filters_2.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                errors::kInvalidFileFilterValue, base::IntToString(0))),
+    Testcase("filebrowser_invalid_file_filters_url.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                errors::kInvalidURLPatternError, "http:*.html"))
+  };
+  RunTestcases(testcases, arraysize(testcases),
+               EXPECT_TYPE_ERROR);
+
+  scoped_refptr<Extension> extension(
+      LoadAndExpectSuccess("filebrowser_valid.json"));
+  ASSERT_TRUE(extension.get());
+  ASSERT_TRUE(extension->file_browser_handlers() != NULL);
+  ASSERT_EQ(extension->file_browser_handlers()->size(), 1U);
+  const FileBrowserHandler* action =
+      extension->file_browser_handlers()->at(0).get();
+  EXPECT_EQ(action->title(), "Default title");
+  EXPECT_EQ(action->icon_path(), "icon.png");
+  const URLPatternSet& patterns = action->file_url_patterns();
+  ASSERT_EQ(patterns.patterns().size(), 1U);
+  ASSERT_TRUE(action->MatchesURL(
+      GURL("filesystem:chrome-extension://foo/local/test.txt")));
+  ASSERT_FALSE(action->HasCreateAccessPermission());
+  ASSERT_TRUE(action->CanRead());
+  ASSERT_TRUE(action->CanWrite());
+
+  scoped_refptr<Extension> create_extension(
+      LoadAndExpectSuccess("filebrowser_valid_with_create.json"));
+  ASSERT_TRUE(create_extension->file_browser_handlers() != NULL);
+  ASSERT_EQ(create_extension->file_browser_handlers()->size(), 1U);
+  const FileBrowserHandler* create_action =
+      create_extension->file_browser_handlers()->at(0).get();
+  EXPECT_EQ(create_action->title(), "Default title");
+  EXPECT_EQ(create_action->icon_path(), "icon.png");
+  const URLPatternSet& create_patterns = create_action->file_url_patterns();
+  ASSERT_EQ(create_patterns.patterns().size(), 0U);
+  ASSERT_TRUE(create_action->HasCreateAccessPermission());
+  ASSERT_FALSE(create_action->CanRead());
+  ASSERT_FALSE(create_action->CanWrite());
+}
+
+TEST_F(ExtensionManifestTest, FileManagerURLOverride) {
+  // A component extention can override chrome://files/ URL.
+  std::string error;
+  LoadExtension(Manifest("filebrowser_url_override.json"),
+                &error, Extension::COMPONENT, Extension::NO_FLAGS);
+#if defined(FILE_MANAGER_EXTENSION)
+  EXPECT_EQ("", error);
+#else
+  EXPECT_EQ(std::string(errors::kInvalidChromeURLOverrides), error);
+#endif
+
+  // Extensions of other types can't ovverride chrome://files/ URL.
+  LoadAndExpectError("filebrowser_url_override.json",
+                     errors::kInvalidChromeURLOverrides);
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_homepage_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_homepage_unittest.cc
new file mode 100644
index 0000000..59836af
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_homepage_unittest.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, ParseHomepageURLs) {
+  scoped_refptr<extensions::Extension> extension(
+      LoadAndExpectSuccess("homepage_valid.json"));
+
+  Testcase testcases[] = {
+    Testcase("homepage_empty.json",
+             errors::kInvalidHomepageURL),
+    Testcase("homepage_invalid.json",
+             errors::kInvalidHomepageURL),
+    Testcase("homepage_bad_schema.json",
+             errors::kInvalidHomepageURL)
+  };
+  RunTestcases(testcases, arraysize(testcases),
+               EXPECT_TYPE_ERROR);
+}
+
+TEST_F(ExtensionManifestTest, GetHomepageURL) {
+  scoped_refptr<extensions::Extension> extension(
+      LoadAndExpectSuccess("homepage_valid.json"));
+  EXPECT_EQ(GURL("http://foo.com#bar"), extension->GetHomepageURL());
+
+  // The Google Gallery URL ends with the id, which depends on the path, which
+  // can be different in testing, so we just check the part before id.
+  extension = LoadAndExpectSuccess("homepage_google_hosted.json");
+  EXPECT_TRUE(StartsWithASCII(extension->GetHomepageURL().spec(),
+                              "https://chrome.google.com/webstore/detail/",
+                              false));
+
+  extension = LoadAndExpectSuccess("homepage_externally_hosted.json");
+  EXPECT_EQ(GURL(), extension->GetHomepageURL());
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_icon_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_icon_unittest.cc
new file mode 100644
index 0000000..352be04
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_icon_unittest.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST_F(ExtensionManifestTest, NormalizeIconPaths) {
+  scoped_refptr<extensions::Extension> extension(
+      LoadAndExpectSuccess("normalize_icon_paths.json"));
+  EXPECT_EQ("16.png", extension->icons().Get(
+      extension_misc::EXTENSION_ICON_BITTY, ExtensionIconSet::MATCH_EXACTLY));
+  EXPECT_EQ("48.png", extension->icons().Get(
+      extension_misc::EXTENSION_ICON_MEDIUM,
+      ExtensionIconSet::MATCH_EXACTLY));
+}
+
+TEST_F(ExtensionManifestTest, InvalidIconSizes) {
+  scoped_refptr<extensions::Extension> extension(
+      LoadAndExpectSuccess("init_ignored_icon_size.json"));
+  EXPECT_EQ("",
+            extension->icons().Get(300, ExtensionIconSet::MATCH_EXACTLY));
+}
+
+TEST_F(ExtensionManifestTest, ValidIconSizes) {
+  scoped_refptr<extensions::Extension> extension(
+      LoadAndExpectSuccess("init_valid_icon_size.json"));
+  EXPECT_EQ("16.png", extension->icons().Get(
+      extension_misc::EXTENSION_ICON_BITTY, ExtensionIconSet::MATCH_EXACTLY));
+  EXPECT_EQ("24.png", extension->icons().Get(
+      extension_misc::EXTENSION_ICON_SMALLISH,
+      ExtensionIconSet::MATCH_EXACTLY));
+  EXPECT_EQ("32.png", extension->icons().Get(
+      extension_misc::EXTENSION_ICON_SMALL, ExtensionIconSet::MATCH_EXACTLY));
+  EXPECT_EQ("48.png", extension->icons().Get(
+      extension_misc::EXTENSION_ICON_MEDIUM,
+      ExtensionIconSet::MATCH_EXACTLY));
+  EXPECT_EQ("128.png", extension->icons().Get(
+      extension_misc::EXTENSION_ICON_LARGE, ExtensionIconSet::MATCH_EXACTLY));
+  EXPECT_EQ("256.png", extension->icons().Get(
+      extension_misc::EXTENSION_ICON_EXTRA_LARGE,
+      ExtensionIconSet::MATCH_EXACTLY));
+  EXPECT_EQ("512.png", extension->icons().Get(
+      extension_misc::EXTENSION_ICON_GIGANTOR,
+      ExtensionIconSet::MATCH_EXACTLY));
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_initvalue_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_initvalue_unittest.cc
new file mode 100644
index 0000000..30e597d
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_initvalue_unittest.cc
@@ -0,0 +1,155 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "base/i18n/rtl.h"
+#include "base/path_service.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+#if defined(TOOLKIT_GTK)
+#include <gtk/gtk.h>
+#endif
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, InitFromValueInvalid) {
+  Testcase testcases[] = {
+    Testcase("init_invalid_version_missing.json", errors::kInvalidVersion),
+    Testcase("init_invalid_version_invalid.json", errors::kInvalidVersion),
+    Testcase("init_invalid_name_missing.json", errors::kInvalidName),
+    Testcase("init_invalid_name_invalid.json", errors::kInvalidName),
+    Testcase("init_invalid_description_invalid.json",
+             errors::kInvalidDescription),
+    Testcase("init_invalid_icons_invalid.json", errors::kInvalidIcons),
+    Testcase("init_invalid_icons_path_invalid.json", errors::kInvalidIconPath),
+    Testcase("init_invalid_script_invalid.json",
+             errors::kInvalidContentScriptsList),
+    Testcase("init_invalid_script_item_invalid.json",
+             errors::kInvalidContentScript),
+    Testcase("init_invalid_script_matches_missing.json",
+             errors::kInvalidMatches),
+    Testcase("init_invalid_script_matches_invalid.json",
+             errors::kInvalidMatches),
+    Testcase("init_invalid_script_matches_empty.json",
+             errors::kInvalidMatchCount),
+    Testcase("init_invalid_script_match_item_invalid.json",
+             errors::kInvalidMatch),
+    Testcase("init_invalid_script_match_item_invalid_2.json",
+             errors::kInvalidMatch),
+    Testcase("init_invalid_script_files_missing.json", errors::kMissingFile),
+    Testcase("init_invalid_files_js_invalid.json", errors::kInvalidJsList),
+    Testcase("init_invalid_files_empty.json", errors::kMissingFile),
+    Testcase("init_invalid_files_js_empty_css_missing.json",
+             errors::kMissingFile),
+    Testcase("init_invalid_files_js_item_invalid.json", errors::kInvalidJs),
+    Testcase("init_invalid_files_css_invalid.json", errors::kInvalidCssList),
+    Testcase("init_invalid_files_css_item_invalid.json", errors::kInvalidCss),
+    Testcase("init_invalid_permissions_invalid.json",
+             errors::kInvalidPermissions),
+    Testcase("init_invalid_permissions_item_invalid.json",
+             errors::kInvalidPermission),
+    Testcase("init_invalid_page_actions_multi.json",
+             errors::kInvalidPageActionsListSize),
+    Testcase("init_invalid_options_url_invalid.json",
+             errors::kInvalidOptionsPage),
+    Testcase("init_invalid_locale_invalid.json", errors::kInvalidDefaultLocale),
+    Testcase("init_invalid_locale_empty.json", errors::kInvalidDefaultLocale),
+    Testcase("init_invalid_min_chrome_invalid.json",
+             errors::kInvalidMinimumChromeVersion),
+    Testcase("init_invalid_chrome_version_too_low.json",
+             errors::kChromeVersionTooLow),
+  };
+
+  RunTestcases(testcases, arraysize(testcases),
+               EXPECT_TYPE_ERROR);
+}
+
+TEST_F(ExtensionManifestTest, InitFromValueValid) {
+  scoped_refptr<extensions::Extension> extension(LoadAndExpectSuccess(
+      "init_valid_minimal.json"));
+
+  FilePath path;
+  PathService::Get(chrome::DIR_TEST_DATA, &path);
+  path = path.AppendASCII("extensions");
+
+  EXPECT_TRUE(extensions::Extension::IdIsValid(extension->id()));
+  EXPECT_EQ("1.0.0.0", extension->VersionString());
+  EXPECT_EQ("my extension", extension->name());
+  EXPECT_EQ(extension->id(), extension->url().host());
+  EXPECT_EQ(extension->path(), path);
+  EXPECT_EQ(path, extension->path());
+
+  // Test permissions scheme.
+  // We allow unknown API permissions, so this will be valid until we better
+  // distinguish between API and host permissions.
+  LoadAndExpectSuccess("init_valid_permissions.json");
+
+  // Test with an options page.
+  extension = LoadAndExpectSuccess("init_valid_options.json");
+  EXPECT_EQ("chrome-extension", extension->options_url().scheme());
+  EXPECT_EQ("/options.html", extension->options_url().path());
+
+  Testcase testcases[] = {
+    // Test that an empty list of page actions does not stop a browser action
+    // from being loaded.
+    Testcase("init_valid_empty_page_actions.json"),
+
+    // Test with a minimum_chrome_version.
+    Testcase("init_valid_minimum_chrome.json"),
+
+    // Test a hosted app with a minimum_chrome_version.
+    Testcase("init_valid_app_minimum_chrome.json"),
+
+    // Test a hosted app with a requirements section.
+    Testcase("init_valid_app_requirements.json"),
+
+    // Verify empty permission settings are considered valid.
+    Testcase("init_valid_permissions_empty.json"),
+
+    // We allow unknown API permissions, so this will be valid until we better
+    // distinguish between API and host permissions.
+    Testcase("init_valid_permissions_unknown.json")
+  };
+
+  RunTestcases(testcases, arraysize(testcases),
+               EXPECT_TYPE_SUCCESS);
+}
+
+TEST_F(ExtensionManifestTest, InitFromValueValidNameInRTL) {
+#if defined(TOOLKIT_GTK)
+  GtkTextDirection gtk_dir = gtk_widget_get_default_direction();
+  gtk_widget_set_default_direction(GTK_TEXT_DIR_RTL);
+#else
+  std::string locale = l10n_util::GetApplicationLocale("");
+  base::i18n::SetICUDefaultLocale("he");
+#endif
+
+  // No strong RTL characters in name.
+  scoped_refptr<extensions::Extension> extension(LoadAndExpectSuccess(
+      "init_valid_name_no_rtl.json"));
+
+  string16 localized_name(ASCIIToUTF16("Dictionary (by Google)"));
+  base::i18n::AdjustStringForLocaleDirection(&localized_name);
+  EXPECT_EQ(localized_name, UTF8ToUTF16(extension->name()));
+
+  // Strong RTL characters in name.
+  extension = LoadAndExpectSuccess("init_valid_name_strong_rtl.json");
+
+  localized_name = WideToUTF16(L"Dictionary (\x05D1\x05D2"L" Google)");
+  base::i18n::AdjustStringForLocaleDirection(&localized_name);
+  EXPECT_EQ(localized_name, UTF8ToUTF16(extension->name()));
+
+  // Reset locale.
+#if defined(TOOLKIT_GTK)
+  gtk_widget_set_default_direction(gtk_dir);
+#else
+  base::i18n::SetICUDefaultLocale(locale);
+#endif
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_isolatedapp_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_isolatedapp_unittest.cc
new file mode 100644
index 0000000..6181ad0
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_isolatedapp_unittest.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "base/command_line.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, IsolatedApps) {
+  // Requires --enable-experimental-extension-apis
+  LoadAndExpectError("isolated_app_valid.json",
+                     errors::kExperimentalFlagRequired);
+
+  CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kEnableExperimentalExtensionApis);
+  scoped_refptr<extensions::Extension> extension2(
+      LoadAndExpectSuccess("isolated_app_valid.json"));
+  EXPECT_TRUE(extension2->is_storage_isolated());
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_launch_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_launch_unittest.cc
new file mode 100644
index 0000000..35df234
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_launch_unittest.cc
@@ -0,0 +1,121 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "base/command_line.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, AppLaunchContainer) {
+  scoped_refptr<extensions::Extension> extension;
+
+  extension = LoadAndExpectSuccess("launch_tab.json");
+  EXPECT_EQ(extension_misc::LAUNCH_TAB, extension->launch_container());
+
+  extension = LoadAndExpectSuccess("launch_panel.json");
+  EXPECT_EQ(extension_misc::LAUNCH_PANEL, extension->launch_container());
+
+  extension = LoadAndExpectSuccess("launch_default.json");
+  EXPECT_EQ(extension_misc::LAUNCH_TAB, extension->launch_container());
+
+  extension = LoadAndExpectSuccess("launch_width.json");
+  EXPECT_EQ(640, extension->launch_width());
+
+  extension = LoadAndExpectSuccess("launch_height.json");
+  EXPECT_EQ(480, extension->launch_height());
+
+  Testcase testcases[] = {
+    Testcase("launch_window.json", errors::kInvalidLaunchContainer),
+    Testcase("launch_container_invalid_type.json",
+             errors::kInvalidLaunchContainer),
+    Testcase("launch_container_invalid_value.json",
+             errors::kInvalidLaunchContainer),
+    Testcase("launch_container_without_launch_url.json",
+             errors::kLaunchURLRequired),
+    Testcase("launch_width_invalid.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidLaunchValueContainer,
+                 keys::kLaunchWidth)),
+    Testcase("launch_width_negative.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidLaunchValue,
+                 keys::kLaunchWidth)),
+    Testcase("launch_height_invalid.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidLaunchValueContainer,
+                 keys::kLaunchHeight)),
+    Testcase("launch_height_negative.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidLaunchValue,
+                 keys::kLaunchHeight))
+  };
+  RunTestcases(testcases, arraysize(testcases),
+      EXPECT_TYPE_ERROR);
+}
+
+TEST_F(ExtensionManifestTest, AppLaunchURL) {
+  Testcase testcases[] = {
+    Testcase("launch_path_and_url.json",
+             errors::kLaunchPathAndURLAreExclusive),
+    Testcase("launch_path_and_extent.json",
+             errors::kLaunchPathAndExtentAreExclusive),
+    Testcase("launch_path_invalid_type.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidLaunchValue,
+                 keys::kLaunchLocalPath)),
+    Testcase("launch_path_invalid_value.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidLaunchValue,
+                 keys::kLaunchLocalPath)),
+    Testcase("launch_path_invalid_localized.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidLaunchValue,
+                 keys::kLaunchLocalPath)),
+    Testcase("launch_url_invalid_type_1.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidLaunchValue,
+                 keys::kLaunchWebURL)),
+    Testcase("launch_url_invalid_type_2.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidLaunchValue,
+                 keys::kLaunchWebURL)),
+    Testcase("launch_url_invalid_type_3.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidLaunchValue,
+                 keys::kLaunchWebURL)),
+    Testcase("launch_url_invalid_localized.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidLaunchValue,
+                 keys::kLaunchWebURL))
+  };
+  RunTestcases(testcases, arraysize(testcases),
+      EXPECT_TYPE_ERROR);
+
+  scoped_refptr<extensions::Extension> extension;
+  extension = LoadAndExpectSuccess("launch_local_path.json");
+  EXPECT_EQ(extension->url().spec() + "launch.html",
+            extension->GetFullLaunchURL().spec());
+
+  extension = LoadAndExpectSuccess("launch_local_path_localized.json");
+  EXPECT_EQ(extension->url().spec() + "launch.html",
+            extension->GetFullLaunchURL().spec());
+
+  LoadAndExpectError("launch_web_url_relative.json",
+                     ExtensionErrorUtils::FormatErrorMessage(
+                         errors::kInvalidLaunchValue,
+                         keys::kLaunchWebURL));
+
+  extension = LoadAndExpectSuccess("launch_web_url_absolute.json");
+  EXPECT_EQ(GURL("http://www.google.com/launch.html"),
+            extension->GetFullLaunchURL());
+  extension = LoadAndExpectSuccess("launch_web_url_localized.json");
+  EXPECT_EQ(GURL("http://www.google.com/launch.html"),
+            extension->GetFullLaunchURL());
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_manifest_version_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_manifest_version_unittest.cc
new file mode 100644
index 0000000..1f53bca
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_manifest_version_unittest.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using extensions::Extension;
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, ManifestVersionError) {
+  scoped_ptr<DictionaryValue> manifest1(new DictionaryValue());
+  manifest1->SetString("name", "Miles");
+  manifest1->SetString("version", "0.55");
+
+  scoped_ptr<DictionaryValue> manifest2(manifest1->DeepCopy());
+  manifest2->SetInteger("manifest_version", 1);
+
+  scoped_ptr<DictionaryValue> manifest3(manifest1->DeepCopy());
+  manifest3->SetInteger("manifest_version", 2);
+
+  struct {
+    const char* test_name;
+    bool require_modern_manifest_version;
+    DictionaryValue* manifest;
+    bool expect_error;
+  } test_data[] = {
+    { "require_modern_with_default", true, manifest1.get(), true },
+    { "require_modern_with_v1", true, manifest2.get(), true },
+    { "require_modern_with_v2", true, manifest3.get(), false },
+    { "dont_require_modern_with_default", false, manifest1.get(), false },
+    { "dont_require_modern_with_v1", false, manifest2.get(), false },
+    { "dont_require_modern_with_v2", false, manifest3.get(), false },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+    int create_flags = Extension::NO_FLAGS;
+    if (test_data[i].require_modern_manifest_version)
+      create_flags |= Extension::REQUIRE_MODERN_MANIFEST_VERSION;
+    if (test_data[i].expect_error) {
+        LoadAndExpectError(
+            Manifest(test_data[i].manifest,
+                     test_data[i].test_name),
+            errors::kInvalidManifestVersionOld,
+            Extension::LOAD,
+            create_flags);
+    } else {
+      LoadAndExpectSuccess(Manifest(test_data[i].manifest,
+                                    test_data[i].test_name),
+                           Extension::LOAD,
+                           create_flags);
+    }
+  }
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_offline_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_offline_unittest.cc
new file mode 100644
index 0000000..14384f8
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_offline_unittest.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::Extension;
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, OfflineEnabled) {
+  LoadAndExpectError("offline_enabled_invalid.json",
+                     errors::kInvalidOfflineEnabled);
+  scoped_refptr<Extension> extension_0(
+      LoadAndExpectSuccess("offline_enabled_extension.json"));
+  EXPECT_TRUE(extension_0->offline_enabled());
+  scoped_refptr<Extension> extension_1(
+      LoadAndExpectSuccess("offline_enabled_packaged_app.json"));
+  EXPECT_TRUE(extension_1->offline_enabled());
+  scoped_refptr<Extension> extension_2(
+      LoadAndExpectSuccess("offline_disabled_packaged_app.json"));
+  EXPECT_FALSE(extension_2->offline_enabled());
+  scoped_refptr<Extension> extension_3(
+      LoadAndExpectSuccess("offline_default_packaged_app.json"));
+  EXPECT_FALSE(extension_3->offline_enabled());
+  scoped_refptr<Extension> extension_4(
+      LoadAndExpectSuccess("offline_enabled_hosted_app.json"));
+  EXPECT_TRUE(extension_4->offline_enabled());
+  scoped_refptr<Extension> extension_5(
+      LoadAndExpectSuccess("offline_default_platform_app.json"));
+  EXPECT_TRUE(extension_5->offline_enabled());
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_old_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_old_unittest.cc
new file mode 100644
index 0000000..ab49557
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_old_unittest.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Tests that the old permission name "unlimited_storage" still works for
+// backwards compatibility (we renamed it to "unlimitedStorage").
+TEST_F(ExtensionManifestTest, OldUnlimitedStoragePermission) {
+  scoped_refptr<extensions::Extension> extension = LoadAndExpectSuccess(
+      "old_unlimited_storage.json", extensions::Extension::INTERNAL,
+      extensions::Extension::NO_FLAGS);
+  EXPECT_TRUE(extension->HasAPIPermission(
+      extensions::APIPermission::kUnlimitedStorage));
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_options_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_options_unittest.cc
new file mode 100644
index 0000000..2eef91d
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_options_unittest.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, OptionsPageInApps) {
+  scoped_refptr<extensions::Extension> extension;
+
+  // Allow options page with absolute URL in hosted apps.
+  extension = LoadAndExpectSuccess("hosted_app_absolute_options.json");
+  EXPECT_STREQ("http",
+               extension->options_url().scheme().c_str());
+  EXPECT_STREQ("example.com",
+               extension->options_url().host().c_str());
+  EXPECT_STREQ("options.html",
+               extension->options_url().ExtractFileName().c_str());
+
+  Testcase testcases[] = {
+    // Forbid options page with relative URL in hosted apps.
+    Testcase("hosted_app_relative_options.json",
+             errors::kInvalidOptionsPageInHostedApp),
+
+    // Forbid options page with non-(http|https) scheme in hosted app.
+    Testcase("hosted_app_file_options.json",
+             errors::kInvalidOptionsPageInHostedApp),
+
+    // Forbid absolute URL for options page in packaged apps.
+    Testcase("packaged_app_absolute_options.json",
+             errors::kInvalidOptionsPageExpectUrlInPackage)
+  };
+  RunTestcases(testcases, arraysize(testcases),
+               EXPECT_TYPE_ERROR);
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_override_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_override_unittest.cc
new file mode 100644
index 0000000..b7d4c0e
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_override_unittest.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, Override) {
+  Testcase testcases[] = {
+    Testcase("override_newtab_and_history.json", errors::kMultipleOverrides),
+    Testcase("override_invalid_page.json", errors::kInvalidChromeURLOverrides)
+  };
+  RunTestcases(testcases, arraysize(testcases),
+               EXPECT_TYPE_ERROR);
+
+  scoped_refptr<extensions::Extension> extension;
+
+  extension = LoadAndExpectSuccess("override_new_tab.json");
+  EXPECT_EQ(extension->url().spec() + "newtab.html",
+            extension->GetChromeURLOverrides().find("newtab")->second.spec());
+
+  extension = LoadAndExpectSuccess("override_history.json");
+  EXPECT_EQ(extension->url().spec() + "history.html",
+            extension->GetChromeURLOverrides().find("history")->second.spec());
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_pageaction_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_pageaction_unittest.cc
new file mode 100644
index 0000000..b77092b
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_pageaction_unittest.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, PageActionManifestVersion2) {
+  scoped_refptr<extensions::Extension> extension(
+      LoadAndExpectSuccess("page_action_manifest_version_2.json"));
+  ASSERT_TRUE(extension.get());
+  ASSERT_TRUE(extension->page_action_info());
+
+  EXPECT_EQ("", extension->page_action_info()->id);
+  EXPECT_TRUE(extension->page_action_info()->default_icon.empty());
+  EXPECT_EQ("", extension->page_action_info()->default_title);
+  EXPECT_TRUE(extension->page_action_info()->default_popup_url.is_empty());
+
+  LoadAndExpectError("page_action_manifest_version_2b.json",
+                     errors::kInvalidPageActionPopup);
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc
new file mode 100644
index 0000000..2681513
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc
@@ -0,0 +1,145 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "base/command_line.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/scoped_temp_dir.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, PlatformApps) {
+  scoped_refptr<extensions::Extension> extension =
+      LoadAndExpectSuccess("init_valid_platform_app.json");
+  EXPECT_TRUE(extension->is_storage_isolated());
+  EXPECT_TRUE(extension->incognito_split_mode());
+
+  extension =
+      LoadAndExpectSuccess("init_valid_platform_app_no_manifest_version.json");
+  EXPECT_EQ(2, extension->manifest_version());
+
+  extension = LoadAndExpectSuccess("incognito_valid_platform_app.json");
+  EXPECT_TRUE(extension->incognito_split_mode());
+
+  Testcase error_testcases[] = {
+    Testcase("init_invalid_platform_app_2.json",
+        errors::kBackgroundRequiredForPlatformApps),
+    Testcase("init_invalid_platform_app_3.json",
+        errors::kPlatformAppNeedsManifestVersion2),
+    Testcase("incognito_invalid_platform_app.json",
+        errors::kInvalidIncognitoModeForPlatformApp),
+  };
+  RunTestcases(error_testcases, arraysize(error_testcases), EXPECT_TYPE_ERROR);
+
+  Testcase warning_testcases[] = {
+    Testcase(
+        "init_invalid_platform_app_1.json",
+        "'app.launch' is only allowed for hosted apps and legacy packaged "
+            "apps, and this is a packaged app."),
+    Testcase(
+        "init_invalid_platform_app_4.json",
+        "'background' is only allowed for extensions, hosted apps and legacy "
+            "packaged apps, and this is a packaged app."),
+    Testcase(
+        "init_invalid_platform_app_5.json",
+        "'background' is only allowed for extensions, hosted apps and legacy "
+            "packaged apps, and this is a packaged app.")
+  };
+  RunTestcases(
+      warning_testcases, arraysize(warning_testcases), EXPECT_TYPE_WARNING);
+}
+
+TEST_F(ExtensionManifestTest, PlatformAppContentSecurityPolicy) {
+  // Normal platform apps can't specify a CSP value.
+  Testcase warning_testcases[] = {
+    Testcase(
+        "init_platform_app_csp_warning_1.json",
+        "'content_security_policy' is only allowed for extensions and legacy "
+            "packaged apps, and this is a packaged app."),
+    Testcase(
+        "init_platform_app_csp_warning_2.json",
+        "'app.content_security_policy' is not allowed for specified extension "
+            "ID.")
+  };
+  RunTestcases(
+      warning_testcases, arraysize(warning_testcases), EXPECT_TYPE_WARNING);
+
+  // Whitelisted ones can (this is the ID corresponding to the base 64 encoded
+  // key in the init_platform_app_csp.json manifest.)
+  std::string test_id = "ahplfneplbnjcflhdgkkjeiglkkfeelb";
+  CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+      switches::kWhitelistedExtensionID, test_id);
+  scoped_refptr<extensions::Extension> extension =
+      LoadAndExpectSuccess("init_platform_app_csp.json");
+  EXPECT_EQ(0U, extension->install_warnings().size())
+      << "Unexpected warning " << extension->install_warnings()[0].message;
+  EXPECT_TRUE(extension->is_platform_app());
+  EXPECT_EQ(
+      "default-src 'self' https://www.google.com",
+      extension->GetResourceContentSecurityPolicy(""));
+
+  // But even whitelisted ones must specify a secure policy.
+  LoadAndExpectError(
+      "init_platform_app_csp_insecure.json",
+      errors::kInsecureContentSecurityPolicy);
+}
+
+TEST_F(ExtensionManifestTest, CertainApisRequirePlatformApps) {
+  // Put APIs here that should be restricted to platform apps, but that haven't
+  // yet graduated from experimental.
+  const char* kPlatformAppExperimentalApis[] = {
+    "dns",
+    "serial",
+  };
+  // TODO(miket): When the first platform-app API leaves experimental, write
+  // similar code that tests without the experimental flag.
+
+  // This manifest is a skeleton used to build more specific manifests for
+  // testing. The requirements are that (1) it be a valid platform app, and (2)
+  // it contain no permissions dictionary.
+  std::string error;
+  scoped_ptr<DictionaryValue> manifest(
+      LoadManifestFile("init_valid_platform_app.json", &error));
+
+  ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  // Create each manifest.
+  for (size_t i = 0; i < arraysize(kPlatformAppExperimentalApis); ++i) {
+    const char* api_name = kPlatformAppExperimentalApis[i];
+
+    // DictionaryValue will take ownership of this ListValue.
+    ListValue *permissions = new ListValue();
+    permissions->Append(base::Value::CreateStringValue("experimental"));
+    permissions->Append(base::Value::CreateStringValue(api_name));
+    manifest->Set("permissions", permissions);
+
+    // Each of these files lives in the scoped temp directory, so it will be
+    // cleaned up at test teardown.
+    FilePath file_path = temp_dir.path().AppendASCII(api_name);
+    JSONFileValueSerializer serializer(file_path);
+    serializer.Serialize(*(manifest.get()));
+  }
+
+  // First try to load without any flags. This should fail for every API.
+  for (size_t i = 0; i < arraysize(kPlatformAppExperimentalApis); ++i) {
+    const char* api_name = kPlatformAppExperimentalApis[i];
+    FilePath file_path = temp_dir.path().AppendASCII(api_name);
+    LoadAndExpectError(file_path.MaybeAsASCII().c_str(),
+                       errors::kExperimentalFlagRequired);
+  }
+
+  // Now try again with the experimental flag set.
+  CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kEnableExperimentalExtensionApis);
+  for (size_t i = 0; i < arraysize(kPlatformAppExperimentalApis); ++i) {
+    const char* api_name = kPlatformAppExperimentalApis[i];
+    FilePath file_path = temp_dir.path().AppendASCII(api_name);
+    LoadAndExpectSuccess(file_path.MaybeAsASCII().c_str());
+  }
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_portsinpermissions_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_portsinpermissions_unittest.cc
new file mode 100644
index 0000000..e9207cc
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_portsinpermissions_unittest.cc
@@ -0,0 +1,12 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST_F(ExtensionManifestTest, PortsInPermissions) {
+  // Loading as a user would shoud not trigger an error.
+  LoadAndExpectSuccess("ports_in_permissions.json");
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_requirements_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_requirements_unittest.cc
new file mode 100644
index 0000000..738057f
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_requirements_unittest.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, RequirementsInvalid) {
+  Testcase testcases[] = {
+    Testcase("requirements_invalid_requirements.json",
+             errors::kInvalidRequirements),
+    Testcase("requirements_invalid_keys.json", errors::kInvalidRequirements),
+    Testcase("requirements_invalid_3d.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidRequirement, "3D")),
+    Testcase("requirements_invalid_3d_features.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidRequirement, "3D")),
+    Testcase("requirements_invalid_3d_features_value.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidRequirement, "3D")),
+    Testcase("requirements_invalid_3d_no_features.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidRequirement, "3D")),
+    Testcase("requirements_invalid_plugins.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidRequirement, "plugins")),
+    Testcase("requirements_invalid_plugins_key.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidRequirement, "plugins")),
+    Testcase("requirements_invalid_plugins_value.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidRequirement, "plugins"))
+  };
+
+  RunTestcases(testcases, arraysize(testcases), EXPECT_TYPE_ERROR);
+}
+
+TEST_F(ExtensionManifestTest, RequirementsValid) {
+  // Test the defaults.
+  scoped_refptr<extensions::Extension> extension(LoadAndExpectSuccess(
+      "requirements_valid_empty.json"));
+  ASSERT_TRUE(extension.get());
+  EXPECT_EQ(extension->requirements().webgl, false);
+  EXPECT_EQ(extension->requirements().css3d, false);
+  EXPECT_EQ(extension->requirements().npapi, false);
+
+  // Test loading all the requirements.
+  extension = LoadAndExpectSuccess("requirements_valid_full.json");
+  ASSERT_TRUE(extension.get());
+  EXPECT_EQ(extension->requirements().webgl, true);
+  EXPECT_EQ(extension->requirements().css3d, true);
+  EXPECT_EQ(extension->requirements().npapi, true);
+}
+
+// When an npapi plugin is present, the default of the "npapi" requirement
+// changes.
+TEST_F(ExtensionManifestTest, RequirementsNpapiDefault) {
+  scoped_refptr<extensions::Extension> extension(LoadAndExpectSuccess(
+      "requirements_npapi_empty.json"));
+  ASSERT_TRUE(extension.get());
+  EXPECT_EQ(extension->requirements().webgl, false);
+  EXPECT_EQ(extension->requirements().css3d, false);
+  EXPECT_EQ(extension->requirements().npapi, true);
+
+  extension = LoadAndExpectSuccess(
+      "requirements_npapi_empty_plugins_empty.json");
+  ASSERT_TRUE(extension.get());
+  EXPECT_EQ(extension->requirements().webgl, false);
+  EXPECT_EQ(extension->requirements().css3d, false);
+  EXPECT_EQ(extension->requirements().npapi, false);
+
+  extension = LoadAndExpectSuccess("requirements_npapi.json");
+  ASSERT_TRUE(extension.get());
+  EXPECT_EQ(extension->requirements().webgl, false);
+  EXPECT_EQ(extension->requirements().css3d, false);
+  EXPECT_EQ(extension->requirements().npapi, false);
+
+  extension = LoadAndExpectSuccess("requirements_npapi_plugins_empty.json");
+  ASSERT_TRUE(extension.get());
+  EXPECT_EQ(extension->requirements().webgl, false);
+  EXPECT_EQ(extension->requirements().css3d, false);
+  EXPECT_EQ(extension->requirements().npapi, true);
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_sandboxed_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_sandboxed_unittest.cc
new file mode 100644
index 0000000..ba45308
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_sandboxed_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::Extension;
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, SandboxedPages) {
+  // Sandboxed pages specified, no custom CSP value.
+  scoped_refptr<Extension> extension1(
+      LoadAndExpectSuccess("sandboxed_pages_valid_1.json"));
+
+  // No sandboxed pages.
+  scoped_refptr<Extension> extension2(
+      LoadAndExpectSuccess("sandboxed_pages_valid_2.json"));
+
+  // Sandboxed pages specified with a custom CSP value.
+  scoped_refptr<Extension> extension3(
+      LoadAndExpectSuccess("sandboxed_pages_valid_3.json"));
+
+  // Sandboxed pages specified with wildcard, no custom CSP value.
+  scoped_refptr<Extension> extension4(
+      LoadAndExpectSuccess("sandboxed_pages_valid_4.json"));
+
+  // Sandboxed pages specified with filename wildcard, no custom CSP value.
+  scoped_refptr<Extension> extension5(
+      LoadAndExpectSuccess("sandboxed_pages_valid_5.json"));
+
+  const char kSandboxedCSP[] = "sandbox allow-scripts allow-forms allow-popups";
+  const char kDefaultCSP[] =
+      "script-src 'self' chrome-extension-resource:; object-src 'self'";
+  const char kCustomSandboxedCSP[] =
+      "sandbox; script-src: https://www.google.com";
+
+  EXPECT_EQ(kSandboxedCSP,
+      extension1->GetResourceContentSecurityPolicy("/test"));
+  EXPECT_EQ(kDefaultCSP, extension1->GetResourceContentSecurityPolicy("/none"));
+  EXPECT_EQ(kDefaultCSP, extension2->GetResourceContentSecurityPolicy("/test"));
+  EXPECT_EQ(kCustomSandboxedCSP,
+      extension3->GetResourceContentSecurityPolicy("/test"));
+  EXPECT_EQ(kDefaultCSP, extension3->GetResourceContentSecurityPolicy("/none"));
+  EXPECT_EQ(kSandboxedCSP,
+      extension4->GetResourceContentSecurityPolicy("/test"));
+  EXPECT_EQ(kSandboxedCSP,
+      extension5->GetResourceContentSecurityPolicy("/path/test.ext"));
+  EXPECT_EQ(kDefaultCSP,
+      extension5->GetResourceContentSecurityPolicy("/test"));
+
+  Testcase testcases[] = {
+    Testcase("sandboxed_pages_invalid_1.json",
+        errors::kInvalidSandboxedPagesList),
+    Testcase("sandboxed_pages_invalid_2.json",
+        errors::kInvalidSandboxedPage),
+    Testcase("sandboxed_pages_invalid_3.json",
+        errors::kInvalidSandboxedPagesCSP),
+    Testcase("sandboxed_pages_invalid_4.json",
+        errors::kInvalidSandboxedPagesCSP),
+    Testcase("sandboxed_pages_invalid_5.json",
+        errors::kInvalidSandboxedPagesCSP)
+  };
+  RunTestcases(testcases, arraysize(testcases),
+               EXPECT_TYPE_ERROR);
+}
+
+
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_scriptbadge_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_scriptbadge_unittest.cc
new file mode 100644
index 0000000..736be65
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_scriptbadge_unittest.cc
@@ -0,0 +1,138 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension_builder.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/extensions/extension_icon_set.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+using extensions::DictionaryBuilder;
+using extensions::Extension;
+using extensions::ExtensionBuilder;
+
+namespace {
+
+std::vector<Extension::InstallWarning> StripMissingFlagWarning(
+    const std::vector<Extension::InstallWarning>& install_warnings) {
+  std::vector<Extension::InstallWarning> result;
+  for (size_t i = 0; i < install_warnings.size(); ++i) {
+    if (install_warnings[i].message != errors::kScriptBadgeRequiresFlag)
+      result.push_back(install_warnings[i]);
+  }
+  return result;
+}
+
+TEST_F(ExtensionManifestTest, ScriptBadgeBasic) {
+  scoped_refptr<Extension> extension(
+      ExtensionBuilder()
+      .SetManifest(DictionaryBuilder()
+                   .Set("manifest_version", 2)
+                   .Set("name", "my extension")
+                   .Set("version", "1.0.0.0")
+                   .Set("description",
+                        "Check that a simple script_badge section parses")
+                   .Set("icons", DictionaryBuilder()
+                        .Set("16", "icon16.png")
+                        .Set("32", "icon32.png")
+                        .Set("19", "icon19.png")
+                        .Set("48", "icon48.png"))
+                   .Set("script_badge", DictionaryBuilder()
+                        .Set("default_popup", "popup.html")))
+      .Build());
+  ASSERT_TRUE(extension.get());
+  ASSERT_TRUE(extension->script_badge_info());
+  EXPECT_THAT(StripMissingFlagWarning(extension->install_warnings()),
+              testing::ElementsAre(/*empty*/));
+
+  const ExtensionIconSet& default_icon =
+      extension->script_badge_info()->default_icon;
+  // Should have a default icon set.
+  ASSERT_FALSE(default_icon.empty());
+
+  // Verify that correct icon paths are registered in default_icon.
+  EXPECT_EQ(2u, default_icon.map().size());
+  EXPECT_EQ("icon16.png",
+            default_icon.Get(extension_misc::EXTENSION_ICON_BITTY,
+                             ExtensionIconSet::MATCH_EXACTLY));
+  EXPECT_EQ("icon32.png",
+            default_icon.Get(2 * extension_misc::EXTENSION_ICON_BITTY,
+                             ExtensionIconSet::MATCH_EXACTLY));
+
+  EXPECT_EQ("my extension", extension->script_badge_info()->default_title);
+  EXPECT_FALSE(extension->script_badge_info()->default_popup_url.is_empty());
+}
+
+TEST_F(ExtensionManifestTest, ScriptBadgeExplicitTitleAndIconsIgnored) {
+  scoped_refptr<Extension> extension(
+      ExtensionBuilder()
+      .SetManifest(DictionaryBuilder()
+                   .Set("manifest_version", 2)
+                   .Set("name", "my extension")
+                   .Set("version", "1.0.0.0")
+                   .Set("description",
+                        "Check that a simple script_badge section parses")
+                   .Set("icons", DictionaryBuilder()
+                        .Set("16", "icon16.png"))
+                   .Set("script_badge", DictionaryBuilder()
+                        .Set("default_title", "Other Extension")
+                        .Set("default_icon", "malicious.png")))
+      .Build());
+  ASSERT_TRUE(extension.get());
+  ASSERT_TRUE(extension->script_badge_info());
+
+  EXPECT_THAT(StripMissingFlagWarning(extension->install_warnings()),
+              testing::ElementsAre(
+                  Extension::InstallWarning(
+                      Extension::InstallWarning::FORMAT_TEXT,
+                      errors::kScriptBadgeTitleIgnored),
+                  Extension::InstallWarning(
+                      Extension::InstallWarning::FORMAT_TEXT,
+                      errors::kScriptBadgeIconIgnored)));
+
+  const ExtensionIconSet& default_icon =
+      extension->script_badge_info()->default_icon;
+  ASSERT_FALSE(default_icon.empty());
+
+  EXPECT_EQ(1u, default_icon.map().size());
+  EXPECT_EQ("icon16.png",
+            default_icon.Get(extension_misc::EXTENSION_ICON_BITTY,
+                             ExtensionIconSet::MATCH_EXACTLY));
+
+  EXPECT_EQ("my extension", extension->script_badge_info()->default_title);
+}
+
+TEST_F(ExtensionManifestTest, ScriptBadgeIconFallsBackToPuzzlePiece) {
+  scoped_refptr<Extension> extension(
+      ExtensionBuilder()
+      .SetManifest(DictionaryBuilder()
+                   .Set("manifest_version", 2)
+                   .Set("name", "my extension")
+                   .Set("version", "1.0.0.0")
+                   .Set("description",
+                        "Check that a simple script_badge section parses")
+                   .Set("icons", DictionaryBuilder()
+                        .Set("128", "icon128.png")))
+      .Build());
+  ASSERT_TRUE(extension.get());
+  ASSERT_TRUE(extension->script_badge_info());
+  EXPECT_THAT(extension->install_warnings(),
+              testing::ElementsAre(/*empty*/));
+
+  const ExtensionIconSet& default_icon =
+      extension->script_badge_info()->default_icon;
+  ASSERT_FALSE(default_icon.empty()) << "Should fall back to the 128px icon.";
+  EXPECT_EQ(2u, default_icon.map().size());
+  EXPECT_EQ("icon128.png",
+            default_icon.Get(extension_misc::EXTENSION_ICON_BITTY,
+                             ExtensionIconSet::MATCH_EXACTLY));
+  EXPECT_EQ("icon128.png",
+            default_icon.Get(2 * extension_misc::EXTENSION_ICON_BITTY,
+                             ExtensionIconSet::MATCH_EXACTLY));
+}
+
+}  // namespace
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_storage_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_storage_unittest.cc
new file mode 100644
index 0000000..ad2ab7f
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_storage_unittest.cc
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+
+namespace keys = extension_manifest_keys;
+
+TEST_F(ExtensionManifestTest, StorageAPIManifestVersionAvailability) {
+  DictionaryValue base_manifest;
+  {
+    base_manifest.SetString(keys::kName, "test");
+    base_manifest.SetString(keys::kVersion, "0.1");
+    ListValue* permissions = new ListValue();
+    permissions->Append(Value::CreateStringValue("storage"));
+    base_manifest.Set(keys::kPermissions, permissions);
+  }
+
+  std::string kManifestVersionError =
+      "'storage' requires manifest version of at least 2.";
+
+  // Extension with no manifest version cannot use storage API.
+  {
+    Manifest manifest(&base_manifest, "test");
+    LoadAndExpectWarning(manifest, kManifestVersionError);
+  }
+
+  // Extension with manifest version 1 cannot use storage API.
+  {
+    DictionaryValue manifest_with_version;
+    manifest_with_version.SetInteger(keys::kManifestVersion, 1);
+    manifest_with_version.MergeDictionary(&base_manifest);
+
+    Manifest manifest(&manifest_with_version, "test");
+    LoadAndExpectWarning(manifest, kManifestVersionError);
+  }
+
+  // Extension with manifest version 2 *can* use storage API.
+  {
+    DictionaryValue manifest_with_version;
+    manifest_with_version.SetInteger(keys::kManifestVersion, 2);
+    manifest_with_version.MergeDictionary(&base_manifest);
+
+    Manifest manifest(&manifest_with_version, "test");
+    scoped_refptr<extensions::Extension> extension =
+        LoadAndExpectSuccess(manifest);
+    EXPECT_TRUE(extension->install_warnings().empty());
+  }
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_tts_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_tts_unittest.cc
new file mode 100644
index 0000000..954a172
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_tts_unittest.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, TtsEngine) {
+  Testcase testcases[] = {
+    Testcase("tts_engine_invalid_1.json",
+             errors::kInvalidTts),
+    Testcase("tts_engine_invalid_2.json",
+             errors::kInvalidTtsVoices),
+    Testcase("tts_engine_invalid_3.json",
+             errors::kInvalidTtsVoices),
+    Testcase("tts_engine_invalid_4.json",
+             errors::kInvalidTtsVoicesVoiceName),
+    Testcase("tts_engine_invalid_5.json",
+             errors::kInvalidTtsVoicesLang),
+    Testcase("tts_engine_invalid_6.json",
+             errors::kInvalidTtsVoicesLang),
+    Testcase("tts_engine_invalid_7.json",
+             errors::kInvalidTtsVoicesGender),
+    Testcase("tts_engine_invalid_8.json",
+             errors::kInvalidTtsVoicesEventTypes),
+    Testcase("tts_engine_invalid_9.json",
+             errors::kInvalidTtsVoicesEventTypes)
+  };
+  RunTestcases(testcases, arraysize(testcases),
+               EXPECT_TYPE_ERROR);
+
+  scoped_refptr<extensions::Extension> extension(
+      LoadAndExpectSuccess("tts_engine_valid.json"));
+
+  ASSERT_EQ(1u, extension->tts_voices().size());
+  EXPECT_EQ("name", extension->tts_voices()[0].voice_name);
+  EXPECT_EQ("en-US", extension->tts_voices()[0].lang);
+  EXPECT_EQ("female", extension->tts_voices()[0].gender);
+  EXPECT_EQ(3U, extension->tts_voices()[0].event_types.size());
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_ui_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_ui_unittest.cc
new file mode 100644
index 0000000..5e051c5
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_ui_unittest.cc
@@ -0,0 +1,14 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, DisallowMultipleUISurfaces) {
+  LoadAndExpectError("multiple_ui_surfaces.json", errors::kOneUISurfaceOnly);
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_update_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_update_unittest.cc
new file mode 100644
index 0000000..6d396fe
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_update_unittest.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::Extension;
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, UpdateUrls) {
+  // Test several valid update urls
+  Testcase testcases[] = {
+    Testcase("update_url_valid_1.json", Extension::INTERNAL,
+             Extension::NO_FLAGS),
+    Testcase("update_url_valid_2.json", Extension::INTERNAL,
+             Extension::NO_FLAGS),
+    Testcase("update_url_valid_3.json", Extension::INTERNAL,
+             Extension::NO_FLAGS),
+    Testcase("update_url_valid_4.json", Extension::INTERNAL,
+             Extension::NO_FLAGS)
+  };
+  RunTestcases(testcases, arraysize(testcases),
+               EXPECT_TYPE_SUCCESS);
+
+  // Test some invalid update urls
+  Testcase testcases2[] = {
+    Testcase("update_url_invalid_1.json", errors::kInvalidUpdateURL,
+             Extension::INTERNAL, Extension::NO_FLAGS),
+    Testcase("update_url_invalid_2.json", errors::kInvalidUpdateURL,
+             Extension::INTERNAL, Extension::NO_FLAGS),
+    Testcase("update_url_invalid_3.json", errors::kInvalidUpdateURL,
+             Extension::INTERNAL, Extension::NO_FLAGS)
+  };
+  RunTestcases(testcases2, arraysize(testcases2),
+               EXPECT_TYPE_ERROR);
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_validapp_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_validapp_unittest.cc
new file mode 100644
index 0000000..f8bd6d5
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_validapp_unittest.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "base/values.h"
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST_F(ExtensionManifestTest, ValidApp) {
+  scoped_refptr<extensions::Extension> extension(
+      LoadAndExpectSuccess("valid_app.json"));
+  URLPatternSet expected_patterns;
+  AddPattern(&expected_patterns, "http://www.google.com/mail/*");
+  AddPattern(&expected_patterns, "http://www.google.com/foobar/*");
+  EXPECT_EQ(expected_patterns, extension->web_extent());
+  EXPECT_EQ(extension_misc::LAUNCH_TAB, extension->launch_container());
+  EXPECT_EQ("http://www.google.com/mail/", extension->launch_web_url());
+}
+
+TEST_F(ExtensionManifestTest, AllowUnrecognizedPermissions) {
+  std::string error;
+  scoped_ptr<DictionaryValue> manifest(
+      LoadManifestFile("valid_app.json", &error));
+  ListValue* permissions = NULL;
+  ASSERT_TRUE(manifest->GetList("permissions", &permissions));
+  permissions->Append(new StringValue("not-a-valid-permission"));
+  LoadAndExpectSuccess(Manifest(manifest.get(), ""));
+}
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_web_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_web_unittest.cc
new file mode 100644
index 0000000..e6fc6e7
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_web_unittest.cc
@@ -0,0 +1,253 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/glue/web_intent_service_data.h"
+
+using extensions::Extension;
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, WebAccessibleResources) {
+  // Manifest version 2 with web accessible resources specified.
+  scoped_refptr<Extension> extension1(
+      LoadAndExpectSuccess("web_accessible_resources_1.json"));
+
+  // Manifest version 2 with no web accessible resources.
+  scoped_refptr<Extension> extension2(
+      LoadAndExpectSuccess("web_accessible_resources_2.json"));
+
+  // Default manifest version with web accessible resources specified.
+  scoped_refptr<Extension> extension3(
+      LoadAndExpectSuccess("web_accessible_resources_3.json"));
+
+  // Default manifest version with no web accessible resources.
+  scoped_refptr<Extension> extension4(
+      LoadAndExpectSuccess("web_accessible_resources_4.json"));
+
+  // Default manifest version with wildcard web accessible resource.
+  scoped_refptr<Extension> extension5(
+      LoadAndExpectSuccess("web_accessible_resources_5.json"));
+
+  // Default manifest version with wildcard with specific path and extension.
+  scoped_refptr<Extension> extension6(
+      LoadAndExpectSuccess("web_accessible_resources_6.json"));
+
+  EXPECT_TRUE(extension1->HasWebAccessibleResources());
+  EXPECT_FALSE(extension2->HasWebAccessibleResources());
+  EXPECT_TRUE(extension3->HasWebAccessibleResources());
+  EXPECT_FALSE(extension4->HasWebAccessibleResources());
+  EXPECT_TRUE(extension5->HasWebAccessibleResources());
+  EXPECT_TRUE(extension6->HasWebAccessibleResources());
+
+  EXPECT_TRUE(extension1->IsResourceWebAccessible("test"));
+  EXPECT_FALSE(extension1->IsResourceWebAccessible("none"));
+
+  EXPECT_FALSE(extension2->IsResourceWebAccessible("test"));
+
+  EXPECT_TRUE(extension3->IsResourceWebAccessible("test"));
+  EXPECT_FALSE(extension3->IsResourceWebAccessible("none"));
+
+  EXPECT_TRUE(extension4->IsResourceWebAccessible("test"));
+  EXPECT_TRUE(extension4->IsResourceWebAccessible("none"));
+
+  EXPECT_TRUE(extension5->IsResourceWebAccessible("anything"));
+  EXPECT_TRUE(extension5->IsResourceWebAccessible("path/anything"));
+
+  EXPECT_TRUE(extension6->IsResourceWebAccessible("path/anything.ext"));
+  EXPECT_FALSE(extension6->IsResourceWebAccessible("anything.ext"));
+  EXPECT_FALSE(extension6->IsResourceWebAccessible("path/anything.badext"));
+}
+
+TEST_F(ExtensionManifestTest, WebIntents) {
+  Testcase testcases[] = {
+    Testcase("intent_invalid_1.json", errors::kInvalidIntents),
+    Testcase("intent_invalid_2.json", errors::kInvalidIntent),
+    Testcase("intent_invalid_3.json", errors::kInvalidIntentHref),
+    Testcase("intent_invalid_4.json", errors::kInvalidIntentDisposition),
+    Testcase("intent_invalid_5.json", errors::kInvalidIntentType),
+    Testcase("intent_invalid_6.json", errors::kInvalidIntentTitle),
+    Testcase("intent_invalid_packaged_app.json", errors::kCannotAccessPage),
+    Testcase("intent_invalid_href_and_path.json",
+        errors::kInvalidIntentHrefOldAndNewKey),
+    Testcase("intent_invalid_multi_href.json", errors::kInvalidIntent)
+  };
+  RunTestcases(testcases, arraysize(testcases),
+               EXPECT_TYPE_ERROR);
+
+  scoped_refptr<Extension> extension(
+      LoadAndExpectSuccess("intent_valid.json"));
+  ASSERT_TRUE(extension.get() != NULL);
+
+  ASSERT_EQ(1u, extension->intents_services().size());
+  EXPECT_EQ("image/png", UTF16ToUTF8(extension->intents_services()[0].type));
+  EXPECT_EQ("http://webintents.org/share",
+            UTF16ToUTF8(extension->intents_services()[0].action));
+  EXPECT_EQ("chrome-extension",
+            extension->intents_services()[0].service_url.scheme());
+  EXPECT_EQ("/services/share",
+            extension->intents_services()[0].service_url.path());
+  EXPECT_EQ("Sample Sharing Intent",
+            UTF16ToUTF8(extension->intents_services()[0].title));
+  EXPECT_EQ(webkit_glue::WebIntentServiceData::DISPOSITION_INLINE,
+            extension->intents_services()[0].disposition);
+
+  // Verify that optional fields are filled with defaults.
+  extension = LoadAndExpectSuccess("intent_valid_minimal.json");
+  ASSERT_TRUE(extension.get() != NULL);
+
+  ASSERT_EQ(1u, extension->intents_services().size());
+  EXPECT_EQ("*", UTF16ToUTF8(extension->intents_services()[0].type));
+  EXPECT_EQ("http://webintents.org/share",
+            UTF16ToUTF8(extension->intents_services()[0].action));
+  EXPECT_EQ("", UTF16ToUTF8(extension->intents_services()[0].title));
+  EXPECT_EQ(webkit_glue::WebIntentServiceData::DISPOSITION_WINDOW,
+            extension->intents_services()[0].disposition);
+
+  // Make sure we support href instead of path.
+  extension = LoadAndExpectSuccess("intent_valid_using_href.json");
+  ASSERT_TRUE(extension.get() != NULL);
+  ASSERT_EQ(1u, extension->intents_services().size());
+  EXPECT_EQ("/services/share",
+      extension->intents_services()[0].service_url.path());
+}
+
+TEST_F(ExtensionManifestTest, WebIntentsWithMultipleMimeTypes) {
+  scoped_refptr<Extension> extension(
+      LoadAndExpectSuccess("intent_valid_multitype.json"));
+  ASSERT_TRUE(extension.get() != NULL);
+
+  ASSERT_EQ(2u, extension->intents_services().size());
+
+  // One registration with multiple types generates a separate service for
+  // each MIME type.
+  for (int i = 0; i < 2; ++i) {
+    EXPECT_EQ("http://webintents.org/share",
+              UTF16ToUTF8(extension->intents_services()[i].action));
+    EXPECT_EQ("chrome-extension",
+              extension->intents_services()[i].service_url.scheme());
+    EXPECT_EQ("/services/share",
+              extension->intents_services()[i].service_url.path());
+    EXPECT_EQ("Sample Sharing Intent",
+              UTF16ToUTF8(extension->intents_services()[i].title));
+    EXPECT_EQ(webkit_glue::WebIntentServiceData::DISPOSITION_INLINE,
+              extension->intents_services()[i].disposition);
+  }
+  EXPECT_EQ("image/jpeg", UTF16ToUTF8(extension->intents_services()[0].type));
+  EXPECT_EQ("image/bmp", UTF16ToUTF8(extension->intents_services()[1].type));
+
+  LoadAndExpectError("intent_invalid_type_element.json",
+                     extension_manifest_errors::kInvalidIntentTypeElement);
+}
+
+TEST_F(ExtensionManifestTest, WebIntentsInHostedApps) {
+  Testcase testcases[] = {
+    Testcase("intent_invalid_hosted_app_1.json",
+             errors::kInvalidIntentPageInHostedApp),
+    Testcase("intent_invalid_hosted_app_2.json",
+             errors::kInvalidIntentPageInHostedApp),
+    Testcase("intent_invalid_hosted_app_3.json",
+             errors::kInvalidIntentPageInHostedApp)
+  };
+  RunTestcases(testcases, arraysize(testcases),
+               EXPECT_TYPE_ERROR);
+
+  scoped_refptr<Extension> extension(
+      LoadAndExpectSuccess("intent_valid_hosted_app.json"));
+  ASSERT_TRUE(extension.get() != NULL);
+
+  ASSERT_EQ(3u, extension->intents_services().size());
+
+  EXPECT_EQ("http://www.cfp.com/intents/edit.html",
+            extension->intents_services()[0].service_url.spec());
+  EXPECT_EQ("http://www.cloudfilepicker.com/",
+            extension->intents_services()[1].service_url.spec());
+  EXPECT_EQ("http://www.cloudfilepicker.com/intents/share.html",
+            extension->intents_services()[2].service_url.spec());
+}
+
+TEST_F(ExtensionManifestTest, WebIntentsMultiHref) {
+  scoped_refptr<Extension> extension(
+      LoadAndExpectSuccess("intent_valid_multi_href.json"));
+  ASSERT_TRUE(extension.get() != NULL);
+  ASSERT_EQ(2u, extension->intents_services().size());
+
+  const std::vector<webkit_glue::WebIntentServiceData> &intents =
+      extension->intents_services();
+
+  EXPECT_EQ("chrome-extension", intents[0].service_url.scheme());
+  EXPECT_EQ("/services/sharelink.html",intents[0].service_url.path());
+  EXPECT_EQ("text/uri-list",UTF16ToUTF8(intents[0].type));
+
+  EXPECT_EQ("chrome-extension", intents[1].service_url.scheme());
+  EXPECT_EQ("/services/shareimage.html",intents[1].service_url.path());
+  EXPECT_EQ("image/*",UTF16ToUTF8(intents[1].type));
+}
+
+TEST_F(ExtensionManifestTest, WebIntentsBlankHref) {
+  LoadAndExpectError("intent_invalid_blank_action_extension.json",
+                     errors::kInvalidIntentHrefEmpty);
+
+  scoped_refptr<Extension> extension(
+      LoadAndExpectSuccess("intent_valid_blank_action_hosted.json"));
+  ASSERT_TRUE(extension.get() != NULL);
+  ASSERT_EQ(1u, extension->intents_services().size());
+  EXPECT_EQ("http://www.cloudfilepicker.com/",
+      extension->intents_services()[0].service_url.spec());
+
+  extension = LoadAndExpectSuccess("intent_valid_blank_action_packaged.json");
+  ASSERT_TRUE(extension.get() != NULL);
+  ASSERT_EQ(1u, extension->intents_services().size());
+  EXPECT_EQ("chrome-extension",
+      extension->intents_services()[0].service_url.scheme());
+  EXPECT_EQ("/main.html",extension->intents_services()[0].service_url.path());
+}
+
+TEST_F(ExtensionManifestTest, AppWebUrls) {
+  Testcase testcases[] = {
+    Testcase("web_urls_wrong_type.json", errors::kInvalidWebURLs),
+    Testcase("web_urls_invalid_1.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidWebURL,
+                 base::IntToString(0),
+                 errors::kExpectString)),
+    Testcase("web_urls_invalid_2.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidWebURL,
+                 base::IntToString(0),
+                 URLPattern::GetParseResultString(
+                 URLPattern::PARSE_ERROR_MISSING_SCHEME_SEPARATOR))),
+    Testcase("web_urls_invalid_3.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidWebURL,
+                 base::IntToString(0),
+                 errors::kNoWildCardsInPaths)),
+    Testcase("web_urls_invalid_4.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidWebURL,
+                 base::IntToString(0),
+                 errors::kCannotClaimAllURLsInExtent)),
+    Testcase("web_urls_invalid_5.json",
+             ExtensionErrorUtils::FormatErrorMessage(
+                 errors::kInvalidWebURL,
+                 base::IntToString(1),
+                 errors::kCannotClaimAllHostsInExtent))
+  };
+  RunTestcases(testcases, arraysize(testcases),
+               EXPECT_TYPE_ERROR);
+
+  LoadAndExpectSuccess("web_urls_has_port.json");
+
+  scoped_refptr<Extension> extension(
+      LoadAndExpectSuccess("web_urls_default.json"));
+  ASSERT_EQ(1u, extension->web_extent().patterns().size());
+  EXPECT_EQ("*://www.google.com/*",
+            extension->web_extent().patterns().begin()->GetAsString());
+}
diff --git a/chrome/common/extensions/manifest_unittest.cc b/chrome/common/extensions/manifest_unittest.cc
new file mode 100644
index 0000000..65c308f
--- /dev/null
+++ b/chrome/common/extensions/manifest_unittest.cc
@@ -0,0 +1,211 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/manifest.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/features/feature.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+namespace keys = extension_manifest_keys;
+
+namespace extensions {
+
+class ManifestTest : public testing::Test {
+ public:
+  ManifestTest() : default_value_("test") {}
+
+ protected:
+  void AssertType(Manifest* manifest, Extension::Type type) {
+    EXPECT_EQ(type, manifest->type());
+    EXPECT_EQ(type == Extension::TYPE_THEME, manifest->is_theme());
+    EXPECT_EQ(type == Extension::TYPE_PLATFORM_APP,
+              manifest->is_platform_app());
+    EXPECT_EQ(type == Extension::TYPE_LEGACY_PACKAGED_APP,
+              manifest->is_legacy_packaged_app());
+    EXPECT_EQ(type == Extension::TYPE_HOSTED_APP, manifest->is_hosted_app());
+  }
+
+  // Helper function that replaces the Manifest held by |manifest| with a copy
+  // with its |key| changed to |value|. If |value| is NULL, then |key| will
+  // instead be deleted.
+  void MutateManifest(
+      scoped_ptr<Manifest>* manifest, const std::string& key, Value* value) {
+    scoped_ptr<DictionaryValue> manifest_value(
+        manifest->get()->value()->DeepCopy());
+    if (value)
+      manifest_value->Set(key, value);
+    else
+      manifest_value->Remove(key, NULL);
+    manifest->reset(new Manifest(Extension::INTERNAL, manifest_value.Pass()));
+  }
+
+  std::string default_value_;
+};
+
+// Verifies that extensions can access the correct keys.
+TEST_F(ManifestTest, Extension) {
+  scoped_ptr<DictionaryValue> manifest_value(new DictionaryValue());
+  manifest_value->SetString(keys::kName, "extension");
+  manifest_value->SetString(keys::kVersion, "1");
+  // Only supported in manifest_version=1.
+  manifest_value->SetString(keys::kBackgroundPageLegacy, "bg.html");
+  manifest_value->SetString("unknown_key", "foo");
+
+  scoped_ptr<Manifest> manifest(
+      new Manifest(Extension::INTERNAL, manifest_value.Pass()));
+  std::string error;
+  Extension::InstallWarningVector warnings;
+  manifest->ValidateManifest(&error, &warnings);
+  EXPECT_TRUE(error.empty());
+  ASSERT_EQ(1u, warnings.size());
+  AssertType(manifest.get(), Extension::TYPE_EXTENSION);
+
+  // The known key 'background_page' should be accessible.
+  std::string value;
+  EXPECT_TRUE(manifest->GetString(keys::kBackgroundPageLegacy, &value));
+  EXPECT_EQ("bg.html", value);
+
+  // The unknown key 'unknown_key' should be accesible.
+  value.clear();
+  EXPECT_TRUE(manifest->GetString("unknown_key", &value));
+  EXPECT_EQ("foo", value);
+
+  // Set the manifest_version to 2; background_page should stop working.
+  value.clear();
+  MutateManifest(
+      &manifest, keys::kManifestVersion, Value::CreateIntegerValue(2));
+  EXPECT_FALSE(manifest->GetString("background_page", &value));
+  EXPECT_EQ("", value);
+
+  // Validate should also give a warning.
+  warnings.clear();
+  manifest->ValidateManifest(&error, &warnings);
+  EXPECT_TRUE(error.empty());
+  ASSERT_EQ(2u, warnings.size());
+  {
+    Feature feature;
+    feature.set_name("background_page");
+    feature.set_max_manifest_version(1);
+    EXPECT_EQ(
+        "'background_page' requires manifest version of 1 or lower.",
+        warnings[0].message);
+  }
+
+  // Test DeepCopy and Equals.
+  scoped_ptr<Manifest> manifest2(manifest->DeepCopy());
+  EXPECT_TRUE(manifest->Equals(manifest2.get()));
+  EXPECT_TRUE(manifest2->Equals(manifest.get()));
+  MutateManifest(
+      &manifest, "foo", Value::CreateStringValue("blah"));
+  EXPECT_FALSE(manifest->Equals(manifest2.get()));
+}
+
+// Verifies that key restriction based on type works.
+TEST_F(ManifestTest, ExtensionTypes) {
+  scoped_ptr<DictionaryValue> value(new DictionaryValue());
+  value->SetString(keys::kName, "extension");
+  value->SetString(keys::kVersion, "1");
+
+  scoped_ptr<Manifest> manifest(
+      new Manifest(Extension::INTERNAL, value.Pass()));
+  std::string error;
+  Extension::InstallWarningVector warnings;
+  manifest->ValidateManifest(&error, &warnings);
+  EXPECT_TRUE(error.empty());
+  EXPECT_TRUE(warnings.empty());
+
+  // By default, the type is Extension.
+  AssertType(manifest.get(), Extension::TYPE_EXTENSION);
+
+  // Theme.
+  MutateManifest(
+      &manifest, keys::kTheme, new DictionaryValue());
+  AssertType(manifest.get(), Extension::TYPE_THEME);
+  MutateManifest(
+      &manifest, keys::kTheme, NULL);
+
+  // Packaged app.
+  MutateManifest(
+      &manifest, keys::kApp, new DictionaryValue());
+  AssertType(manifest.get(), Extension::TYPE_LEGACY_PACKAGED_APP);
+
+  // Platform app.
+  MutateManifest(
+      &manifest, keys::kPlatformAppBackground, new DictionaryValue());
+  AssertType(manifest.get(), Extension::TYPE_PLATFORM_APP);
+  MutateManifest(
+      &manifest, keys::kPlatformAppBackground, NULL);
+
+  // Hosted app.
+  MutateManifest(
+      &manifest, keys::kWebURLs, new ListValue());
+  AssertType(manifest.get(), Extension::TYPE_HOSTED_APP);
+  MutateManifest(
+      &manifest, keys::kWebURLs, NULL);
+  MutateManifest(
+      &manifest, keys::kLaunchWebURL, Value::CreateStringValue("foo"));
+  AssertType(manifest.get(), Extension::TYPE_HOSTED_APP);
+  MutateManifest(
+      &manifest, keys::kLaunchWebURL, NULL);
+};
+
+// Verifies that the getters filter restricted keys.
+TEST_F(ManifestTest, RestrictedKeys) {
+  scoped_ptr<DictionaryValue> value(new DictionaryValue());
+  value->SetString(keys::kName, "extension");
+  value->SetString(keys::kVersion, "1");
+
+  scoped_ptr<Manifest> manifest(
+      new Manifest(Extension::INTERNAL, value.Pass()));
+  std::string error;
+  Extension::InstallWarningVector warnings;
+  manifest->ValidateManifest(&error, &warnings);
+  EXPECT_TRUE(error.empty());
+  EXPECT_TRUE(warnings.empty());
+
+  // Platform apps cannot have a "page_action" key.
+  MutateManifest(
+      &manifest, keys::kPageAction, new DictionaryValue());
+  AssertType(manifest.get(), Extension::TYPE_EXTENSION);
+  base::Value* output = NULL;
+  EXPECT_TRUE(manifest->HasKey(keys::kPageAction));
+  EXPECT_TRUE(manifest->Get(keys::kPageAction, &output));
+
+  MutateManifest(
+      &manifest, keys::kPlatformAppBackground, new DictionaryValue());
+  AssertType(manifest.get(), Extension::TYPE_PLATFORM_APP);
+  EXPECT_FALSE(manifest->HasKey(keys::kPageAction));
+  EXPECT_FALSE(manifest->Get(keys::kPageAction, &output));
+  MutateManifest(
+      &manifest, keys::kPlatformAppBackground, NULL);
+
+  // "commands" is restricted to manifest_version >= 2.
+  {
+    // ... and dev channel, for now.
+    Feature::ScopedCurrentChannel dev_channel_scope(
+        chrome::VersionInfo::CHANNEL_DEV);
+
+    MutateManifest(
+        &manifest, keys::kCommands, new DictionaryValue());
+    EXPECT_FALSE(manifest->HasKey(keys::kCommands));
+    EXPECT_FALSE(manifest->Get(keys::kCommands, &output));
+
+    MutateManifest(
+        &manifest, keys::kManifestVersion, Value::CreateIntegerValue(2));
+    EXPECT_TRUE(manifest->HasKey(keys::kCommands));
+    EXPECT_TRUE(manifest->Get(keys::kCommands, &output));
+  }
+};
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/matcher/OWNERS b/chrome/common/extensions/matcher/OWNERS
new file mode 100644
index 0000000..d5f125f
--- /dev/null
+++ b/chrome/common/extensions/matcher/OWNERS
@@ -0,0 +1 @@
+battre@chromium.org
diff --git a/chrome/common/extensions/matcher/regex_set_matcher.cc b/chrome/common/extensions/matcher/regex_set_matcher.cc
new file mode 100644
index 0000000..d2fc250
--- /dev/null
+++ b/chrome/common/extensions/matcher/regex_set_matcher.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/matcher/regex_set_matcher.h"
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "base/stl_util.h"
+#include "chrome/common/extensions/matcher/substring_set_matcher.h"
+#include "third_party/re2/re2/filtered_re2.h"
+#include "third_party/re2/re2/re2.h"
+
+namespace extensions {
+
+RegexSetMatcher::RegexSetMatcher() {}
+
+RegexSetMatcher::~RegexSetMatcher() {
+  DeleteSubstringPatterns();
+}
+
+void RegexSetMatcher::AddPatterns(
+    const std::vector<const StringPattern*>& regex_list) {
+  if (regex_list.empty())
+    return;
+  for (size_t i = 0; i < regex_list.size(); ++i) {
+    regexes_[regex_list[i]->id()] = regex_list[i];
+  }
+
+  RebuildMatcher();
+}
+
+void RegexSetMatcher::ClearPatterns() {
+  regexes_.clear();
+  RebuildMatcher();
+}
+
+bool RegexSetMatcher::Match(const std::string& text,
+                            std::set<StringPattern::ID>* matches) const {
+  size_t old_number_of_matches = matches->size();
+  if (regexes_.empty())
+    return false;
+  if (!filtered_re2_.get()) {
+    LOG(ERROR) << "RegexSetMatcher was not initialized";
+    return false;
+  }
+
+  // FilteredRE2 expects lowercase for prefiltering, but we still
+  // match case-sensitively.
+  std::vector<RE2ID> atoms(FindSubstringMatches(
+      StringToLowerASCII(text)));
+
+  std::vector<RE2ID> re2_ids;
+  filtered_re2_->AllMatches(text, atoms, &re2_ids);
+
+  std::set<StringPattern::ID> matched_ids;
+  for (size_t i = 0; i < re2_ids.size(); ++i) {
+    StringPattern::ID id = re2_id_map_[re2_ids[i]];
+    matches->insert(id);
+  }
+  return old_number_of_matches != matches->size();
+}
+
+std::vector<RegexSetMatcher::RE2ID> RegexSetMatcher::FindSubstringMatches(
+    const std::string& text) const {
+  std::set<int> atoms_set;
+  substring_matcher_->Match(text, &atoms_set);
+  return std::vector<RE2ID>(atoms_set.begin(), atoms_set.end());
+}
+
+void RegexSetMatcher::RebuildMatcher() {
+  re2_id_map_.clear();
+  filtered_re2_.reset(new re2::FilteredRE2());
+  if (regexes_.empty())
+    return;
+
+  for (RegexMap::iterator it = regexes_.begin(); it != regexes_.end(); ++it) {
+    RE2ID re2_id;
+    RE2::ErrorCode error = filtered_re2_->Add(
+        it->second->pattern(), RE2::DefaultOptions, &re2_id);
+    if (error == RE2::NoError) {
+      DCHECK_EQ(static_cast<RE2ID>(re2_id_map_.size()), re2_id);
+      re2_id_map_.push_back(it->first);
+    } else {
+      // Unparseable regexes should have been rejected already in
+      // URLMatcherFactory::CreateURLMatchesCondition.
+      LOG(ERROR) << "Could not parse regex (id=" << it->first << ", "
+                 << it->second->pattern() << ")";
+    }
+  }
+
+  std::vector<std::string> strings_to_match;
+  filtered_re2_->Compile(&strings_to_match);
+
+  substring_matcher_.reset(new SubstringSetMatcher);
+  DeleteSubstringPatterns();
+  // Build SubstringSetMatcher from |strings_to_match|.
+  // SubstringSetMatcher doesn't own its strings.
+  for (size_t i = 0; i < strings_to_match.size(); ++i) {
+    substring_patterns_.push_back(
+        new StringPattern(strings_to_match[i], i));
+  }
+  substring_matcher_->RegisterPatterns(substring_patterns_);
+}
+
+void RegexSetMatcher::DeleteSubstringPatterns() {
+  STLDeleteElements(&substring_patterns_);
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/matcher/regex_set_matcher.h b/chrome/common/extensions/matcher/regex_set_matcher.h
new file mode 100644
index 0000000..79c6641
--- /dev/null
+++ b/chrome/common/extensions/matcher/regex_set_matcher.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MATCHER_REGEX_SET_MATCHER_H_
+#define CHROME_COMMON_EXTENSIONS_MATCHER_REGEX_SET_MATCHER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "chrome/common/extensions/matcher/string_pattern.h"
+#include "chrome/common/extensions/matcher/substring_set_matcher.h"
+
+namespace re2 {
+class FilteredRE2;
+}
+
+namespace extensions {
+
+// Efficiently matches URLs against a collection of regular expressions,
+// using FilteredRE2 to reduce the number of regexes that must be matched
+// by pre-filtering with substring matching. See:
+// http://swtch.com/~rsc/regexp/regexp3.html#analysis
+class RegexSetMatcher {
+ public:
+  RegexSetMatcher();
+  virtual ~RegexSetMatcher();
+
+  // Adds the regex patterns in |regex_list| to the matcher. Also rebuilds
+  // the FilteredRE2 matcher; thus, for efficiency, prefer adding multiple
+  // patterns at once.
+  // Ownership of the patterns remains with the caller.
+  void AddPatterns(const std::vector<const StringPattern*>& regex_list);
+
+  // Removes all regex patterns.
+  void ClearPatterns();
+
+  // Appends the IDs of regular expressions in our set that match the |text|
+  // to |matches|.
+  bool Match(const std::string& text,
+             std::set<StringPattern::ID>* matches) const;
+
+ private:
+  typedef int RE2ID;
+  typedef std::map<StringPattern::ID, const StringPattern*> RegexMap;
+  typedef std::vector<StringPattern::ID> RE2IDMap;
+
+  // Use Aho-Corasick SubstringSetMatcher to find which literal patterns
+  // match the |text|.
+  std::vector<RE2ID> FindSubstringMatches(const std::string& text) const;
+
+  // Rebuild FilteredRE2 from scratch. Needs to be called whenever
+  // our set of regexes changes.
+  // TODO(yoz): investigate if it could be done incrementally;
+  // apparently not supported by FilteredRE2.
+  void RebuildMatcher();
+
+  // Clean up StringPatterns in |substring_patterns_|.
+  void DeleteSubstringPatterns();
+
+  // Mapping of regex StringPattern::IDs to regexes.
+  RegexMap regexes_;
+  // Mapping of RE2IDs from FilteredRE2 (which are assigned in order)
+  // to regex StringPattern::IDs.
+  RE2IDMap re2_id_map_;
+
+  scoped_ptr<re2::FilteredRE2> filtered_re2_;
+  scoped_ptr<SubstringSetMatcher> substring_matcher_;
+
+  // The substring patterns from FilteredRE2, which are used in
+  // |substring_matcher_| but whose lifetime is managed here.
+  std::vector<const StringPattern*> substring_patterns_;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_MATCHER_REGEX_SET_MATCHER_H_
diff --git a/chrome/common/extensions/matcher/regex_set_matcher_unittest.cc b/chrome/common/extensions/matcher/regex_set_matcher_unittest.cc
new file mode 100644
index 0000000..63cf1c2
--- /dev/null
+++ b/chrome/common/extensions/matcher/regex_set_matcher_unittest.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2012 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.
+
+#include <set>
+
+#include "base/stl_util.h"
+#include "chrome/common/extensions/matcher/regex_set_matcher.h"
+#include "googleurl/src/gurl.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::StringPattern;
+using extensions::RegexSetMatcher;
+
+TEST(RegexSetMatcherTest, MatchRegexes) {
+  StringPattern pattern_1("ab.*c", 42);
+  StringPattern pattern_2("f*f", 17);
+  StringPattern pattern_3("c(ar|ra)b|brac", 239);
+  std::vector<const StringPattern*> regexes;
+  regexes.push_back(&pattern_1);
+  regexes.push_back(&pattern_2);
+  regexes.push_back(&pattern_3);
+  RegexSetMatcher matcher;
+  matcher.AddPatterns(regexes);
+
+  std::set<StringPattern::ID> result1;
+  matcher.Match("http://abracadabra.com", &result1);
+  EXPECT_EQ(2U, result1.size());
+  EXPECT_TRUE(ContainsKey(result1, 42));
+  EXPECT_TRUE(ContainsKey(result1, 239));
+
+  std::set<StringPattern::ID> result2;
+  matcher.Match("https://abfffffffffffffffffffffffffffffff.fi/cf", &result2);
+  EXPECT_EQ(2U, result2.size());
+  EXPECT_TRUE(ContainsKey(result2, 17));
+  EXPECT_TRUE(ContainsKey(result2, 42));
+
+  std::set<StringPattern::ID> result3;
+  matcher.Match("http://nothing.com/", &result3);
+  EXPECT_EQ(0U, result3.size());
+}
+
+TEST(RegexSetMatcherTest, CaseSensitivity) {
+  StringPattern pattern_1("AAA", 51);
+  StringPattern pattern_2("aaA", 57);
+  std::vector<const StringPattern*> regexes;
+  regexes.push_back(&pattern_1);
+  regexes.push_back(&pattern_2);
+  RegexSetMatcher matcher;
+  matcher.AddPatterns(regexes);
+
+  std::set<StringPattern::ID> result1;
+  matcher.Match("http://aaa.net/", &result1);
+  EXPECT_EQ(0U, result1.size());
+
+  std::set<StringPattern::ID> result2;
+  matcher.Match("http://aaa.net/quaaACK", &result2);
+  EXPECT_EQ(1U, result2.size());
+  EXPECT_TRUE(ContainsKey(result2, 57));
+}
diff --git a/chrome/common/extensions/matcher/string_pattern.cc b/chrome/common/extensions/matcher/string_pattern.cc
new file mode 100644
index 0000000..673ed56
--- /dev/null
+++ b/chrome/common/extensions/matcher/string_pattern.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/matcher/string_pattern.h"
+
+namespace extensions {
+
+StringPattern::StringPattern(const std::string& pattern,
+                             StringPattern::ID id)
+    : pattern_(pattern), id_(id) {}
+
+StringPattern::~StringPattern() {}
+
+bool StringPattern::operator<(const StringPattern& rhs) const {
+  if (id_ != rhs.id_) return id_ < rhs.id_;
+  return pattern_ < rhs.pattern_;
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/matcher/string_pattern.h b/chrome/common/extensions/matcher/string_pattern.h
new file mode 100644
index 0000000..15e9b43
--- /dev/null
+++ b/chrome/common/extensions/matcher/string_pattern.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MATCHER_STRING_PATTERN_H_
+#define CHROME_COMMON_EXTENSIONS_MATCHER_STRING_PATTERN_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+
+namespace extensions {
+
+// An individual pattern of a substring or regex matcher. A pattern consists of
+// a string (interpreted as individual bytes, no character encoding) and an
+// identifier.
+// IDs are returned to the caller of SubstringSetMatcher::Match() or
+// RegexMatcher::MatchURL() to help the caller to figure out what
+// patterns matched a string. All patterns registered to a matcher
+// need to contain unique IDs.
+class StringPattern {
+ public:
+  typedef int ID;
+
+  StringPattern(const std::string& pattern, ID id);
+  ~StringPattern();
+  const std::string& pattern() const { return pattern_; }
+  ID id() const { return id_; }
+
+  bool operator<(const StringPattern& rhs) const;
+
+ private:
+  std::string pattern_;
+  ID id_;
+
+  DISALLOW_COPY_AND_ASSIGN(StringPattern);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_MATCHER_STRING_PATTERN_H_
diff --git a/chrome/common/extensions/matcher/string_pattern_unittest.cc b/chrome/common/extensions/matcher/string_pattern_unittest.cc
new file mode 100644
index 0000000..2580afa
--- /dev/null
+++ b/chrome/common/extensions/matcher/string_pattern_unittest.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/matcher/string_pattern.h"
+
+#include <string>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::StringPattern;
+
+TEST(StringPatternTest, StringPattern) {
+  StringPattern r1("Test", 2);
+  EXPECT_EQ("Test", r1.pattern());
+  EXPECT_EQ(2, r1.id());
+
+  EXPECT_FALSE(r1 < r1);
+  StringPattern r2("Test", 3);
+  EXPECT_TRUE(r1 < r2);
+  StringPattern r3("ZZZZ", 2);
+  EXPECT_TRUE(r1 < r3);
+}
diff --git a/chrome/common/extensions/matcher/substring_set_matcher.cc b/chrome/common/extensions/matcher/substring_set_matcher.cc
new file mode 100644
index 0000000..fb654d6
--- /dev/null
+++ b/chrome/common/extensions/matcher/substring_set_matcher.cc
@@ -0,0 +1,208 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/matcher/substring_set_matcher.h"
+
+#include <queue>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+
+namespace extensions {
+
+//
+// SubstringSetMatcher
+//
+
+SubstringSetMatcher::SubstringSetMatcher() {
+  RebuildAhoCorasickTree();
+}
+
+SubstringSetMatcher::~SubstringSetMatcher() {}
+
+void SubstringSetMatcher::RegisterPatterns(
+    const std::vector<const StringPattern*>& patterns) {
+  RegisterAndUnregisterPatterns(patterns,
+                                std::vector<const StringPattern*>());
+}
+
+void SubstringSetMatcher::UnregisterPatterns(
+    const std::vector<const StringPattern*>& patterns) {
+  RegisterAndUnregisterPatterns(std::vector<const StringPattern*>(),
+                                patterns);
+}
+
+void SubstringSetMatcher::RegisterAndUnregisterPatterns(
+      const std::vector<const StringPattern*>& to_register,
+      const std::vector<const StringPattern*>& to_unregister) {
+  // Register patterns.
+  for (std::vector<const StringPattern*>::const_iterator i =
+      to_register.begin(); i != to_register.end(); ++i) {
+    DCHECK(patterns_.find((*i)->id()) == patterns_.end());
+    patterns_[(*i)->id()] = *i;
+  }
+
+  // Unregister patterns
+  for (std::vector<const StringPattern*>::const_iterator i =
+      to_unregister.begin(); i != to_unregister.end(); ++i) {
+    patterns_.erase((*i)->id());
+  }
+
+  RebuildAhoCorasickTree();
+}
+
+bool SubstringSetMatcher::Match(const std::string& text,
+                                std::set<StringPattern::ID>* matches) const {
+  size_t old_number_of_matches = matches->size();
+
+  // Handle patterns matching the empty string.
+  matches->insert(tree_[0].matches().begin(), tree_[0].matches().end());
+
+  int current_node = 0;
+  size_t text_length = text.length();
+  for (size_t i = 0; i < text_length; ++i) {
+    while (!tree_[current_node].HasEdge(text[i]) && current_node != 0)
+      current_node = tree_[current_node].failure();
+    if (tree_[current_node].HasEdge(text[i])) {
+      current_node = tree_[current_node].GetEdge(text[i]);
+      matches->insert(tree_[current_node].matches().begin(),
+                      tree_[current_node].matches().end());
+    } else {
+      DCHECK_EQ(0, current_node);
+    }
+  }
+
+  return old_number_of_matches != matches->size();
+}
+
+bool SubstringSetMatcher::IsEmpty() const {
+  // An empty tree consists of only the root node.
+  return patterns_.empty() && tree_.size() == 1u;
+}
+
+void SubstringSetMatcher::RebuildAhoCorasickTree() {
+  tree_.clear();
+
+  // Initialize root note of tree.
+  AhoCorasickNode root;
+  root.set_failure(0);
+  tree_.push_back(root);
+
+  // Insert all patterns.
+  for (SubstringPatternSet::const_iterator i = patterns_.begin();
+       i != patterns_.end(); ++i) {
+    InsertPatternIntoAhoCorasickTree(i->second);
+  }
+
+  CreateFailureEdges();
+}
+
+void SubstringSetMatcher::InsertPatternIntoAhoCorasickTree(
+    const StringPattern* pattern) {
+  const std::string& text = pattern->pattern();
+  size_t text_length = text.length();
+
+  // Iterators on the tree and the text.
+  int current_node = 0;
+  size_t text_pos = 0;
+
+  // Follow existing paths for as long as possible.
+  while (text_pos < text_length &&
+         tree_[current_node].HasEdge(text[text_pos])) {
+    current_node = tree_[current_node].GetEdge(text[text_pos]);
+    ++text_pos;
+  }
+
+  // Create new nodes if necessary.
+  while (text_pos < text_length) {
+    AhoCorasickNode new_node;
+    tree_.push_back(new_node);
+    tree_[current_node].SetEdge(text[text_pos], tree_.size() - 1);
+    current_node = tree_.size() - 1;
+    ++text_pos;
+  }
+
+  // Register match.
+  tree_[current_node].AddMatch(pattern->id());
+}
+
+void SubstringSetMatcher::CreateFailureEdges() {
+  typedef AhoCorasickNode::Edges Edges;
+
+  std::queue<int> queue;
+
+  AhoCorasickNode& root = tree_[0];
+  root.set_failure(0);
+  const AhoCorasickNode::Edges& root_edges = root.edges();
+  for (Edges::const_iterator e = root_edges.begin(); e != root_edges.end();
+       ++e) {
+    int leads_to = e->second;
+    tree_[leads_to].set_failure(0);
+    queue.push(leads_to);
+  }
+
+  while (!queue.empty()) {
+    AhoCorasickNode& current_node = tree_[queue.front()];
+    queue.pop();
+    for (Edges::const_iterator e = current_node.edges().begin();
+         e != current_node.edges().end(); ++e) {
+      char edge_label = e->first;
+      int leads_to = e->second;
+      queue.push(leads_to);
+
+      int failure = current_node.failure();
+      while (!tree_[failure].HasEdge(edge_label) && failure != 0)
+        failure = tree_[failure].failure();
+
+      int follow_in_case_of_failure = tree_[failure].HasEdge(e->first) ?
+          tree_[failure].GetEdge(edge_label) : 0;
+      tree_[leads_to].set_failure(follow_in_case_of_failure);
+      tree_[leads_to].AddMatches(tree_[follow_in_case_of_failure].matches());
+    }
+  }
+}
+
+SubstringSetMatcher::AhoCorasickNode::AhoCorasickNode()
+    : failure_(-1) {}
+
+SubstringSetMatcher::AhoCorasickNode::~AhoCorasickNode() {}
+
+SubstringSetMatcher::AhoCorasickNode::AhoCorasickNode(
+    const SubstringSetMatcher::AhoCorasickNode& other)
+    : edges_(other.edges_),
+      failure_(other.failure_),
+      matches_(other.matches_) {}
+
+SubstringSetMatcher::AhoCorasickNode&
+SubstringSetMatcher::AhoCorasickNode::operator=(
+    const SubstringSetMatcher::AhoCorasickNode& other) {
+  edges_ = other.edges_;
+  failure_ = other.failure_;
+  matches_ = other.matches_;
+  return *this;
+}
+
+bool SubstringSetMatcher::AhoCorasickNode::HasEdge(char c) const {
+  return edges_.find(c) != edges_.end();
+}
+
+int SubstringSetMatcher::AhoCorasickNode::GetEdge(char c) const {
+  std::map<char, int>::const_iterator i = edges_.find(c);
+  return i == edges_.end() ? -1 : i->second;
+}
+
+void SubstringSetMatcher::AhoCorasickNode::SetEdge(char c, int node) {
+  edges_[c] = node;
+}
+
+void SubstringSetMatcher::AhoCorasickNode::AddMatch(StringPattern::ID id) {
+  matches_.insert(id);
+}
+
+void SubstringSetMatcher::AhoCorasickNode::AddMatches(
+    const SubstringSetMatcher::AhoCorasickNode::Matches& matches) {
+  matches_.insert(matches.begin(), matches.end());
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/matcher/substring_set_matcher.h b/chrome/common/extensions/matcher/substring_set_matcher.h
new file mode 100644
index 0000000..0b4bbc3
--- /dev/null
+++ b/chrome/common/extensions/matcher/substring_set_matcher.h
@@ -0,0 +1,136 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MATCHER_SUBSTRING_SET_MATCHER_H_
+#define CHROME_COMMON_EXTENSIONS_MATCHER_SUBSTRING_SET_MATCHER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "chrome/common/extensions/matcher/string_pattern.h"
+
+namespace extensions {
+
+// Class that store a set of string patterns and can find for a string S,
+// which string patterns occur in S.
+class SubstringSetMatcher {
+ public:
+  SubstringSetMatcher();
+  ~SubstringSetMatcher();
+
+  // Registers all |patterns|. The ownership remains with the caller.
+  // The same pattern cannot be registered twice and each pattern needs to have
+  // a unique ID.
+  // Ownership of the patterns remains with the caller.
+  void RegisterPatterns(const std::vector<const StringPattern*>& patterns);
+
+  // Unregisters the passed |patterns|.
+  void UnregisterPatterns(const std::vector<const StringPattern*>& patterns);
+
+  // Analogous to RegisterPatterns and UnregisterPatterns but executes both
+  // operations in one step, which is cheaper in the execution.
+  void RegisterAndUnregisterPatterns(
+      const std::vector<const StringPattern*>& to_register,
+      const std::vector<const StringPattern*>& to_unregister);
+
+  // Matches |text| against all registered StringPatterns. Stores the IDs
+  // of matching patterns in |matches|. |matches| is not cleared before adding
+  // to it.
+  bool Match(const std::string& text,
+             std::set<StringPattern::ID>* matches) const;
+
+  // Returns true if this object retains no allocated data. Only for debugging.
+  bool IsEmpty() const;
+
+ private:
+  // A node of an Aho Corasick Tree. This is implemented according to
+  // http://www.cs.uku.fi/~kilpelai/BSA05/lectures/slides04.pdf
+  //
+  // The algorithm is based on the idea of building a trie of all registered
+  // patterns. Each node of the tree is annotated with a set of pattern
+  // IDs that are used to report matches.
+  //
+  // The root of the trie represents an empty match. If we were looking whether
+  // any registered pattern matches a text at the beginning of the text (i.e.
+  // whether any pattern is a prefix of the text), we could just follow
+  // nodes in the trie according to the matching characters in the text.
+  // E.g., if text == "foobar", we would follow the trie from the root node
+  // to its child labeled 'f', from there to child 'o', etc. In this process we
+  // would report all pattern IDs associated with the trie nodes as matches.
+  //
+  // As we are not looking for all prefix matches but all substring matches,
+  // this algorithm would need to compare text.substr(0), text.substr(1), ...
+  // against the trie, which is in O(|text|^2).
+  //
+  // The Aho Corasick algorithm improves this runtime by using failure edges.
+  // In case we have found a partial match of length k in the text
+  // (text[i, ..., i + k - 1]) in the trie starting at the root and ending at
+  // a node at depth k, but cannot find a match in the trie for character
+  // text[i + k] at depth k + 1, we follow a failure edge. This edge
+  // corresponds to the longest proper suffix of text[i, ..., i + k - 1] that
+  // is a prefix of any registered pattern.
+  //
+  // If your brain thinks "Forget it, let's go shopping.", don't worry.
+  // Take a nap and read an introductory text on the Aho Corasick algorithm.
+  // It will make sense. Eventually.
+  class AhoCorasickNode {
+   public:
+    // Key: label of the edge, value: node index in |tree_| of parent class.
+    typedef std::map<char, int> Edges;
+    typedef std::set<StringPattern::ID> Matches;
+
+    AhoCorasickNode();
+    ~AhoCorasickNode();
+    AhoCorasickNode(const AhoCorasickNode& other);
+    AhoCorasickNode& operator=(const AhoCorasickNode& other);
+
+    bool HasEdge(char c) const;
+    int GetEdge(char c) const;
+    void SetEdge(char c, int node);
+    const Edges& edges() const { return edges_; }
+
+    int failure() const { return failure_; }
+    void set_failure(int failure) { failure_ = failure; }
+
+    void AddMatch(StringPattern::ID id);
+    void AddMatches(const Matches& matches);
+    const Matches& matches() const { return matches_; }
+
+   private:
+    // Outgoing edges of current node.
+    Edges edges_;
+
+    // Node index that failure edge leads to.
+    int failure_;
+
+    // Identifiers of matches.
+    Matches matches_;
+  };
+
+  void RebuildAhoCorasickTree();
+
+  // Inserts a path for |pattern->pattern()| into the tree and adds
+  // |pattern->id()| to the set of matches. Ownership of |pattern| remains with
+  // the caller.
+  void InsertPatternIntoAhoCorasickTree(const StringPattern* pattern);
+  void CreateFailureEdges();
+
+  // Set of all registered StringPatterns. Used to regenerate the
+  // Aho-Corasick tree in case patterns are registered or unregistered.
+  typedef std::map<StringPattern::ID, const StringPattern*>
+      SubstringPatternSet;
+  SubstringPatternSet patterns_;
+
+  // The nodes of a Aho-Corasick tree.
+  std::vector<AhoCorasickNode> tree_;
+
+  DISALLOW_COPY_AND_ASSIGN(SubstringSetMatcher);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_MATCHER_SUBSTRING_SET_MATCHER_H_
diff --git a/chrome/common/extensions/matcher/substring_set_matcher_unittest.cc b/chrome/common/extensions/matcher/substring_set_matcher_unittest.cc
new file mode 100644
index 0000000..6ea27e7
--- /dev/null
+++ b/chrome/common/extensions/matcher/substring_set_matcher_unittest.cc
@@ -0,0 +1,167 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/matcher/substring_set_matcher.h"
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::StringPattern;
+using extensions::SubstringSetMatcher;
+
+namespace {
+void TestOnePattern(const std::string& test_string,
+                    const std::string& pattern,
+                    bool is_match) {
+  std::string test =
+      "TestOnePattern(" + test_string + ", " + pattern + ", " +
+      (is_match ? "1" : "0") + ")";
+  std::vector<const StringPattern*> patterns;
+  StringPattern substring_pattern(pattern, 1);
+  patterns.push_back(&substring_pattern);
+  SubstringSetMatcher matcher;
+  matcher.RegisterPatterns(patterns);
+  std::set<int> matches;
+  matcher.Match(test_string, &matches);
+
+  size_t expected_matches = (is_match ? 1 : 0);
+  EXPECT_EQ(expected_matches, matches.size()) << test;
+  EXPECT_EQ(is_match, matches.find(1) != matches.end()) << test;
+}
+
+void TestTwoPatterns(const std::string& test_string,
+                     const std::string& pattern_1,
+                     const std::string& pattern_2,
+                     bool is_match_1,
+                     bool is_match_2) {
+  std::string test =
+      "TestTwoPatterns(" + test_string + ", " + pattern_1 + ", " + pattern_2 +
+      ", " + (is_match_1 ? "1" : "0") + ", " + (is_match_2 ? "1" : "0") + ")";
+  StringPattern substring_pattern_1(pattern_1, 1);
+  StringPattern substring_pattern_2(pattern_2, 2);
+  // In order to make sure that the order in which patterns are registered
+  // does not make any difference we try both permutations.
+  for (int permutation = 0; permutation < 2; ++permutation) {
+    std::vector<const StringPattern*> patterns;
+    if (permutation == 0) {
+      patterns.push_back(&substring_pattern_1);
+      patterns.push_back(&substring_pattern_2);
+    } else {
+      patterns.push_back(&substring_pattern_2);
+      patterns.push_back(&substring_pattern_1);
+    }
+    SubstringSetMatcher matcher;
+    matcher.RegisterPatterns(patterns);
+    std::set<int> matches;
+    matcher.Match(test_string, &matches);
+
+    size_t expected_matches = (is_match_1 ? 1 : 0) + (is_match_2 ? 1 : 0);
+    EXPECT_EQ(expected_matches, matches.size()) << test;
+    EXPECT_EQ(is_match_1, matches.find(1) != matches.end()) << test;
+    EXPECT_EQ(is_match_2, matches.find(2) != matches.end()) << test;
+  }
+}
+}
+
+TEST(SubstringSetMatcherTest, TestMatcher) {
+  // Test overlapping patterns
+  // String    abcde
+  // Pattern 1  bc
+  // Pattern 2   cd
+  TestTwoPatterns("abcde", "bc", "cd", true, true);
+
+  // Test subpatterns - part 1
+  // String    abcde
+  // Pattern 1  bc
+  // Pattern 2  b
+  TestTwoPatterns("abcde", "bc", "b", true, true);
+
+  // Test subpatterns - part 2
+  // String    abcde
+  // Pattern 1  bc
+  // Pattern 2   c
+  TestTwoPatterns("abcde", "bc", "c", true, true);
+
+  // Test identical matches
+  // String    abcde
+  // Pattern 1 abcde
+  TestOnePattern("abcde", "abcde", true);
+
+  // Test multiple matches
+  // String    aaaaa
+  // Pattern 1 a
+  TestOnePattern("abcde", "a", true);
+
+  // Test matches at beginning and end
+  // String    abcde
+  // Pattern 1 ab
+  // Pattern 2    de
+  TestTwoPatterns("abcde", "ab", "de", true, true);
+
+  // Test duplicate patterns with different IDs
+  // String    abcde
+  // Pattern 1  bc
+  // Pattern 2  bc
+  TestTwoPatterns("abcde", "bc", "bc", true, true);
+
+  // Test non-match
+  // String    abcde
+  // Pattern 1        fg
+  TestOnePattern("abcde", "fg", false);
+
+  // Test empty pattern and too long pattern
+  // String    abcde
+  // Pattern 1
+  // Pattern 2 abcdef
+  TestTwoPatterns("abcde", "", "abcdef", true, false);
+}
+
+TEST(SubstringSetMatcherTest, RegisterAndRemove) {
+  SubstringSetMatcher matcher;
+
+  StringPattern pattern_1("a", 1);
+  StringPattern pattern_2("b", 2);
+  StringPattern pattern_3("c", 3);
+
+  std::vector<const StringPattern*> patterns;
+  patterns.push_back(&pattern_1);
+  matcher.RegisterPatterns(patterns);
+
+  patterns.clear();
+  patterns.push_back(&pattern_2);
+  patterns.push_back(&pattern_3);
+  matcher.RegisterPatterns(patterns);
+
+  std::set<int> matches;
+  matcher.Match("abd", &matches);
+  EXPECT_EQ(2u, matches.size());
+  EXPECT_TRUE(matches.end() != matches.find(1));
+  EXPECT_TRUE(matches.end() != matches.find(2));
+
+  patterns.clear();
+  patterns.push_back(&pattern_2);
+  matcher.UnregisterPatterns(patterns);
+
+  matches.clear();
+  matcher.Match("abd", &matches);
+  EXPECT_EQ(1u, matches.size());
+  EXPECT_TRUE(matches.end() != matches.find(1));
+  EXPECT_TRUE(matches.end() == matches.find(2));
+
+  patterns.clear();
+  patterns.push_back(&pattern_1);
+  patterns.push_back(&pattern_3);
+  matcher.UnregisterPatterns(patterns);
+  EXPECT_TRUE(matcher.IsEmpty());
+}
+
+TEST(SubstringSetMatcherTest, TestEmptyMatcher) {
+  SubstringSetMatcher matcher;
+  std::set<int> matches;
+  matcher.Match("abd", &matches);
+  EXPECT_TRUE(matches.empty());
+}
diff --git a/chrome/common/extensions/matcher/url_matcher.cc b/chrome/common/extensions/matcher/url_matcher.cc
new file mode 100644
index 0000000..ac639d9
--- /dev/null
+++ b/chrome/common/extensions/matcher/url_matcher.cc
@@ -0,0 +1,795 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/matcher/url_matcher.h"
+
+#include <algorithm>
+#include <iterator>
+
+#include "base/logging.h"
+#include "content/public/common/url_constants.h"
+#include "googleurl/src/gurl.h"
+#include "googleurl/src/url_canon.h"
+
+namespace extensions {
+
+// This set of classes implement a mapping of URL Component Patterns, such as
+// host_prefix, host_suffix, host_equals, ..., etc., to StringPatterns
+// for use in substring comparisons.
+//
+// The idea of this mapping is to reduce the problem of comparing many
+// URL Component Patterns against one URL to the problem of searching many
+// substrings in one string:
+//
+// ----------------------                    -----------------
+// | URL Query operator | ----translate----> | StringPattern |
+// ----------------------                    -----------------
+//                                                   ^
+//                                                   |
+//                                                compare
+//                                                   |
+//                                                   v
+// ----------------------                    -----------------
+// | URL to compare     |                    |               |
+// | to all URL Query   | ----translate----> | String        |
+// | operators          |                    |               |
+// ----------------------                    -----------------
+//
+// The reason for this problem reduction is that there are efficient algorithms
+// for searching many substrings in one string (see Aho-Corasick algorithm).
+//
+// Additionally, some of the same pieces are reused to implement regular
+// expression comparisons. The FilteredRE2 implementation for matching many
+// regular expressions against one string uses prefiltering, in which a set
+// of substrings (derived from the regexes) are first searched for, to reduce
+// the number of regular expressions to test; the prefiltering step also
+// uses Aho-Corasick.
+//
+// Case 1: {host,path,query}_{prefix,suffix,equals} searches.
+// ==========================================================
+//
+// For searches in this class, we normalize URLs as follows:
+//
+// Step 1:
+// Remove scheme, port and segment from URL:
+// -> http://www.example.com:8080/index.html?search=foo#first_match becomes
+//    www.example.com/index.html?search=foo
+//
+// We remove the scheme and port number because they can be checked later
+// in a secondary filter step. We remove the segment (the #... part) because
+// this is not guaranteed to be ASCII-7 encoded.
+//
+// Step 2:
+// Translate URL to String and add the following position markers:
+// - BU = Beginning of URL
+// - ED = End of Domain
+// - EP = End of Path
+// - EU = End of URL
+// Furthermore, the hostname is canonicalized to start with a ".".
+//
+// Position markers are represented as characters >127, which are therefore
+// guaranteed not to be part of the ASCII-7 encoded URL character set.
+//
+// -> www.example.com/index.html?search=foo becomes
+// BU .www.example.com ED /index.html EP ?search=foo EU
+//
+// -> www.example.com/index.html becomes
+// BU .www.example.com ED /index.html EP EU
+//
+// Step 3:
+// Translate URL Component Patterns as follows:
+//
+// host_prefix(prefix) = BU add_missing_dot_prefix(prefix)
+// -> host_prefix("www.example") = BU .www.example
+//
+// host_suffix(suffix) = suffix ED
+// -> host_suffix("example.com") = example.com ED
+// -> host_suffix(".example.com") = .example.com ED
+//
+// host_equals(domain) = BU add_missing_dot_prefix(domain) ED
+// -> host_equals("www.example.com") = BU .www.example.com ED
+//
+// Similarly for path query parameters ({path, query}_{prefix, suffix, equals}).
+//
+// With this, we can search the StringPatterns in the normalized URL.
+//
+//
+// Case 2: url_{prefix,suffix,equals,contains} searches.
+// =====================================================
+//
+// Step 1: as above, except that
+// - the scheme is not removed
+// - the port is not removed if it is specified and does not match the default
+//   port for the given scheme.
+//
+// Step 2:
+// Translate URL to String and add the following position markers:
+// - BU = Beginning of URL
+// - EU = End of URL
+//
+// -> http://www.example.com:8080/index.html?search=foo#first_match becomes
+// BU http://www.example.com:8080/index.html?search=foo EU
+// -> http://www.example.com:80/index.html?search=foo#first_match becomes
+// BU http://www.example.com/index.html?search=foo EU
+//
+// url_prefix(prefix) = BU prefix
+// -> url_prefix("http://www.example") = BU http://www.example
+//
+// url_contains(substring) = substring
+// -> url_contains("index") = index
+//
+//
+// Case 3: {host,path,query}_contains searches.
+// ============================================
+//
+// These kinds of searches are not supported directly but can be derived
+// by a combination of a url_contains() query followed by an explicit test:
+//
+// host_contains(str) = url_contains(str) followed by test whether str occurs
+//   in host component of original URL.
+// -> host_contains("example.co") = example.co
+//    followed by gurl.host().find("example.co");
+//
+// [similarly for path_contains and query_contains].
+//
+//
+// Regular expression matching (url_matches searches)
+// ==================================================
+//
+// This class also supports matching regular expressions (RE2 syntax)
+// against full URLs, which are transformed as in case 2.
+
+namespace {
+
+bool IsRegexCriterion(URLMatcherCondition::Criterion criterion) {
+  return criterion == URLMatcherCondition::URL_MATCHES;
+}
+
+}  // namespace
+
+//
+// URLMatcherCondition
+//
+
+URLMatcherCondition::URLMatcherCondition()
+    : criterion_(HOST_PREFIX),
+      string_pattern_(NULL) {}
+
+URLMatcherCondition::~URLMatcherCondition() {}
+
+URLMatcherCondition::URLMatcherCondition(
+    Criterion criterion,
+    const StringPattern* string_pattern)
+    : criterion_(criterion),
+      string_pattern_(string_pattern) {}
+
+URLMatcherCondition::URLMatcherCondition(const URLMatcherCondition& rhs)
+    : criterion_(rhs.criterion_),
+      string_pattern_(rhs.string_pattern_) {}
+
+URLMatcherCondition& URLMatcherCondition::operator=(
+    const URLMatcherCondition& rhs) {
+  criterion_ = rhs.criterion_;
+  string_pattern_ = rhs.string_pattern_;
+  return *this;
+}
+
+bool URLMatcherCondition::operator<(const URLMatcherCondition& rhs) const {
+  if (criterion_ < rhs.criterion_) return true;
+  if (criterion_ > rhs.criterion_) return false;
+  if (string_pattern_ != NULL && rhs.string_pattern_ != NULL)
+    return *string_pattern_ < *rhs.string_pattern_;
+  if (string_pattern_ == NULL && rhs.string_pattern_ != NULL) return true;
+  // Either string_pattern_ != NULL && rhs.string_pattern_ == NULL,
+  // or both are NULL.
+  return false;
+}
+
+bool URLMatcherCondition::IsFullURLCondition() const {
+  // For these criteria the SubstringMatcher needs to be executed on the
+  // GURL that is canonicalized with
+  // URLMatcherConditionFactory::CanonicalizeURLForFullSearches.
+  switch (criterion_) {
+    case HOST_CONTAINS:
+    case PATH_CONTAINS:
+    case QUERY_CONTAINS:
+    case URL_PREFIX:
+    case URL_SUFFIX:
+    case URL_CONTAINS:
+    case URL_EQUALS:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+bool URLMatcherCondition::IsRegexCondition() const {
+  return IsRegexCriterion(criterion_);
+}
+
+bool URLMatcherCondition::IsMatch(
+    const std::set<StringPattern::ID>& matching_patterns,
+    const GURL& url) const {
+  DCHECK(string_pattern_);
+  if (!ContainsKey(matching_patterns, string_pattern_->id()))
+    return false;
+  // The criteria HOST_CONTAINS, PATH_CONTAINS, QUERY_CONTAINS are based on
+  // a substring match on the raw URL. In case of a match, we need to verify
+  // that the match was found in the correct component of the URL.
+  switch (criterion_) {
+    case HOST_CONTAINS:
+      return url.host().find(string_pattern_->pattern()) !=
+          std::string::npos;
+    case PATH_CONTAINS:
+      return url.path().find(string_pattern_->pattern()) !=
+          std::string::npos;
+    case QUERY_CONTAINS:
+      return url.query().find(string_pattern_->pattern()) !=
+          std::string::npos;
+    default:
+      break;
+  }
+  return true;
+}
+
+//
+// URLMatcherConditionFactory
+//
+
+namespace {
+// These are symbols that are not contained in 7-bit ASCII used in GURLs.
+const char kBeginningOfURL[] = {static_cast<char>(-1), 0};
+const char kEndOfDomain[] = {static_cast<char>(-2), 0};
+const char kEndOfPath[] = {static_cast<char>(-3), 0};
+const char kEndOfURL[] = {static_cast<char>(-4), 0};
+}  // namespace
+
+URLMatcherConditionFactory::URLMatcherConditionFactory() : id_counter_(0) {}
+
+URLMatcherConditionFactory::~URLMatcherConditionFactory() {
+  STLDeleteElements(&substring_pattern_singletons_);
+  STLDeleteElements(&regex_pattern_singletons_);
+}
+
+std::string URLMatcherConditionFactory::CanonicalizeURLForComponentSearches(
+    const GURL& url) {
+  return kBeginningOfURL + CanonicalizeHostname(url.host()) + kEndOfDomain +
+      url.path() + kEndOfPath + (url.has_query() ? "?" + url.query() : "") +
+      kEndOfURL;
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreateHostPrefixCondition(
+    const std::string& prefix) {
+  return CreateCondition(URLMatcherCondition::HOST_PREFIX,
+      kBeginningOfURL + CanonicalizeHostname(prefix));
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreateHostSuffixCondition(
+    const std::string& suffix) {
+  return CreateCondition(URLMatcherCondition::HOST_SUFFIX,
+      suffix + kEndOfDomain);
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreateHostContainsCondition(
+    const std::string& str) {
+  return CreateCondition(URLMatcherCondition::HOST_CONTAINS, str);
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreateHostEqualsCondition(
+    const std::string& str) {
+  return CreateCondition(URLMatcherCondition::HOST_EQUALS,
+      kBeginningOfURL + CanonicalizeHostname(str) + kEndOfDomain);
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreatePathPrefixCondition(
+    const std::string& prefix) {
+  return CreateCondition(URLMatcherCondition::PATH_PREFIX,
+      kEndOfDomain + prefix);
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreatePathSuffixCondition(
+    const std::string& suffix) {
+  return CreateCondition(URLMatcherCondition::PATH_SUFFIX, suffix + kEndOfPath);
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreatePathContainsCondition(
+    const std::string& str) {
+  return CreateCondition(URLMatcherCondition::PATH_CONTAINS, str);
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreatePathEqualsCondition(
+    const std::string& str) {
+  return CreateCondition(URLMatcherCondition::PATH_EQUALS,
+      kEndOfDomain + str + kEndOfPath);
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreateQueryPrefixCondition(
+    const std::string& prefix) {
+  return CreateCondition(URLMatcherCondition::QUERY_PREFIX,
+      kEndOfPath + prefix);
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreateQuerySuffixCondition(
+    const std::string& suffix) {
+  return CreateCondition(URLMatcherCondition::QUERY_SUFFIX, suffix + kEndOfURL);
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreateQueryContainsCondition(
+    const std::string& str) {
+  return CreateCondition(URLMatcherCondition::QUERY_CONTAINS, str);
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreateQueryEqualsCondition(
+    const std::string& str) {
+  return CreateCondition(URLMatcherCondition::QUERY_EQUALS,
+      kEndOfPath + str + kEndOfURL);
+}
+
+URLMatcherCondition
+    URLMatcherConditionFactory::CreateHostSuffixPathPrefixCondition(
+    const std::string& host_suffix,
+    const std::string& path_prefix) {
+  return CreateCondition(URLMatcherCondition::HOST_SUFFIX_PATH_PREFIX,
+      host_suffix + kEndOfDomain + path_prefix);
+}
+
+URLMatcherCondition
+URLMatcherConditionFactory::CreateHostEqualsPathPrefixCondition(
+    const std::string& host,
+    const std::string& path_prefix) {
+  return CreateCondition(URLMatcherCondition::HOST_EQUALS_PATH_PREFIX,
+      kBeginningOfURL + CanonicalizeHostname(host) + kEndOfDomain +
+      path_prefix);
+}
+
+std::string URLMatcherConditionFactory::CanonicalizeURLForFullSearches(
+    const GURL& url) {
+  GURL::Replacements replacements;
+  replacements.ClearPassword();
+  replacements.ClearUsername();
+  replacements.ClearRef();
+  // Clear port if it is implicit from scheme.
+  if (url.has_port()) {
+    const std::string& port = url.scheme();
+    if (url_canon::DefaultPortForScheme(port.c_str(), port.size()) ==
+            url.EffectiveIntPort()) {
+      replacements.ClearPort();
+    }
+  }
+  return kBeginningOfURL + url.ReplaceComponents(replacements).spec() +
+      kEndOfURL;
+}
+
+std::string URLMatcherConditionFactory::CanonicalizeURLForRegexSearches(
+    const GURL& url) {
+  GURL::Replacements replacements;
+  replacements.ClearPassword();
+  replacements.ClearUsername();
+  replacements.ClearRef();
+  // Clear port if it is implicit from scheme.
+  if (url.has_port()) {
+    const std::string& port = url.scheme();
+    if (url_canon::DefaultPortForScheme(port.c_str(), port.size()) ==
+            url.EffectiveIntPort()) {
+      replacements.ClearPort();
+    }
+  }
+  return url.ReplaceComponents(replacements).spec();
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreateURLPrefixCondition(
+    const std::string& prefix) {
+  return CreateCondition(URLMatcherCondition::URL_PREFIX,
+      kBeginningOfURL + prefix);
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreateURLSuffixCondition(
+    const std::string& suffix) {
+  return CreateCondition(URLMatcherCondition::URL_SUFFIX, suffix + kEndOfURL);
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreateURLContainsCondition(
+    const std::string& str) {
+  return CreateCondition(URLMatcherCondition::URL_CONTAINS, str);
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreateURLEqualsCondition(
+    const std::string& str) {
+  return CreateCondition(URLMatcherCondition::URL_EQUALS,
+      kBeginningOfURL + str + kEndOfURL);
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreateURLMatchesCondition(
+    const std::string& regex) {
+  return CreateCondition(URLMatcherCondition::URL_MATCHES, regex);
+}
+
+void URLMatcherConditionFactory::ForgetUnusedPatterns(
+      const std::set<StringPattern::ID>& used_patterns) {
+  PatternSingletons::iterator i = substring_pattern_singletons_.begin();
+  while (i != substring_pattern_singletons_.end()) {
+    if (used_patterns.find((*i)->id()) != used_patterns.end()) {
+      ++i;
+    } else {
+      delete *i;
+      substring_pattern_singletons_.erase(i++);
+    }
+  }
+  i = regex_pattern_singletons_.begin();
+  while (i != regex_pattern_singletons_.end()) {
+    if (used_patterns.find((*i)->id()) != used_patterns.end()) {
+      ++i;
+    } else {
+      delete *i;
+      regex_pattern_singletons_.erase(i++);
+    }
+  }
+}
+
+bool URLMatcherConditionFactory::IsEmpty() const {
+  return substring_pattern_singletons_.empty() &&
+      regex_pattern_singletons_.empty();
+}
+
+URLMatcherCondition URLMatcherConditionFactory::CreateCondition(
+    URLMatcherCondition::Criterion criterion,
+    const std::string& pattern) {
+  StringPattern search_pattern(pattern, 0);
+  PatternSingletons* pattern_singletons =
+      IsRegexCriterion(criterion) ? &regex_pattern_singletons_
+                                  : &substring_pattern_singletons_;
+
+  PatternSingletons::const_iterator iter =
+      pattern_singletons->find(&search_pattern);
+
+  if (iter != pattern_singletons->end()) {
+    return URLMatcherCondition(criterion, *iter);
+  } else {
+    StringPattern* new_pattern =
+        new StringPattern(pattern, id_counter_++);
+    pattern_singletons->insert(new_pattern);
+    return URLMatcherCondition(criterion, new_pattern);
+  }
+}
+
+std::string URLMatcherConditionFactory::CanonicalizeHostname(
+    const std::string& hostname) const {
+  if (!hostname.empty() && hostname[0] == '.')
+    return hostname;
+  else
+    return "." + hostname;
+}
+
+bool URLMatcherConditionFactory::StringPatternPointerCompare::operator()(
+    StringPattern* lhs,
+    StringPattern* rhs) const {
+  if (lhs == NULL && rhs != NULL) return true;
+  if (lhs != NULL && rhs != NULL)
+    return lhs->pattern() < rhs->pattern();
+  // Either both are NULL or only rhs is NULL.
+  return false;
+}
+
+//
+// URLMatcherSchemeFilter
+//
+
+URLMatcherSchemeFilter::URLMatcherSchemeFilter(const std::string& filter)
+    : filters_(1) {
+  filters_.push_back(filter);
+}
+
+URLMatcherSchemeFilter::URLMatcherSchemeFilter(
+    const std::vector<std::string>& filters)
+    : filters_(filters) {}
+
+URLMatcherSchemeFilter::~URLMatcherSchemeFilter() {}
+
+bool URLMatcherSchemeFilter::IsMatch(const GURL& url) const {
+  return std::find(filters_.begin(), filters_.end(), url.scheme()) !=
+      filters_.end();
+}
+
+//
+// URLMatcherPortFilter
+//
+
+URLMatcherPortFilter::URLMatcherPortFilter(
+    const std::vector<URLMatcherPortFilter::Range>& ranges)
+    : ranges_(ranges) {}
+
+URLMatcherPortFilter::~URLMatcherPortFilter() {}
+
+bool URLMatcherPortFilter::IsMatch(const GURL& url) const {
+  int port = url.EffectiveIntPort();
+  for (std::vector<Range>::const_iterator i = ranges_.begin();
+       i != ranges_.end(); ++i) {
+    if (i->first <= port && port <= i->second)
+      return true;
+  }
+  return false;
+}
+
+// static
+URLMatcherPortFilter::Range URLMatcherPortFilter::CreateRange(int from,
+                                                              int to) {
+  return Range(from, to);
+}
+
+// static
+URLMatcherPortFilter::Range URLMatcherPortFilter::CreateRange(int port) {
+  return Range(port, port);
+}
+
+//
+// URLMatcherConditionSet
+//
+
+URLMatcherConditionSet::~URLMatcherConditionSet() {}
+
+URLMatcherConditionSet::URLMatcherConditionSet(
+    ID id,
+    const Conditions& conditions)
+    : id_(id),
+      conditions_(conditions) {}
+
+URLMatcherConditionSet::URLMatcherConditionSet(
+    ID id,
+    const Conditions& conditions,
+    scoped_ptr<URLMatcherSchemeFilter> scheme_filter,
+    scoped_ptr<URLMatcherPortFilter> port_filter)
+    : id_(id),
+      conditions_(conditions),
+      scheme_filter_(scheme_filter.Pass()),
+      port_filter_(port_filter.Pass()) {}
+
+bool URLMatcherConditionSet::IsMatch(
+    const std::set<StringPattern::ID>& matching_patterns,
+    const GURL& url) const {
+  for (Conditions::const_iterator i = conditions_.begin();
+       i != conditions_.end(); ++i) {
+    if (!i->IsMatch(matching_patterns, url))
+      return false;
+  }
+  if (scheme_filter_.get() && !scheme_filter_->IsMatch(url))
+    return false;
+  if (port_filter_.get() && !port_filter_->IsMatch(url))
+    return false;
+  return true;
+}
+
+//
+// URLMatcher
+//
+
+URLMatcher::URLMatcher() {}
+
+URLMatcher::~URLMatcher() {}
+
+void URLMatcher::AddConditionSets(
+    const URLMatcherConditionSet::Vector& condition_sets) {
+  for (URLMatcherConditionSet::Vector::const_iterator i =
+       condition_sets.begin(); i != condition_sets.end(); ++i) {
+    DCHECK(url_matcher_condition_sets_.find((*i)->id()) ==
+        url_matcher_condition_sets_.end());
+    url_matcher_condition_sets_[(*i)->id()] = *i;
+  }
+  UpdateInternalDatastructures();
+}
+
+void URLMatcher::RemoveConditionSets(
+    const std::vector<URLMatcherConditionSet::ID>& condition_set_ids) {
+  for (std::vector<URLMatcherConditionSet::ID>::const_iterator i =
+       condition_set_ids.begin(); i != condition_set_ids.end(); ++i) {
+    DCHECK(url_matcher_condition_sets_.find(*i) !=
+        url_matcher_condition_sets_.end());
+    url_matcher_condition_sets_.erase(*i);
+  }
+  UpdateInternalDatastructures();
+}
+
+void URLMatcher::ClearUnusedConditionSets() {
+  UpdateConditionFactory();
+}
+
+std::set<URLMatcherConditionSet::ID> URLMatcher::MatchURL(const GURL& url) {
+  // Find all IDs of StringPatterns that match |url|.
+  // See URLMatcherConditionFactory for the canonicalization of URLs and the
+  // distinction between full url searches and url component searches.
+  std::set<StringPattern::ID> matches;
+  full_url_matcher_.Match(
+      condition_factory_.CanonicalizeURLForFullSearches(url), &matches);
+  url_component_matcher_.Match(
+      condition_factory_.CanonicalizeURLForComponentSearches(url), &matches);
+  regex_set_matcher_.Match(
+      condition_factory_.CanonicalizeURLForRegexSearches(url), &matches);
+
+  // Calculate all URLMatcherConditionSets for which all URLMatcherConditions
+  // were fulfilled.
+  std::set<URLMatcherConditionSet::ID> result;
+  for (std::set<StringPattern::ID>::const_iterator i = matches.begin();
+       i != matches.end(); ++i) {
+    // For each URLMatcherConditionSet there is exactly one condition
+    // registered in substring_match_triggers_. This means that the following
+    // logic tests each URLMatcherConditionSet exactly once if it can be
+    // completely fulfilled.
+    std::set<URLMatcherConditionSet::ID>& condition_sets =
+        substring_match_triggers_[*i];
+    for (std::set<URLMatcherConditionSet::ID>::const_iterator j =
+         condition_sets.begin(); j != condition_sets.end(); ++j) {
+      if (url_matcher_condition_sets_[*j]->IsMatch(matches, url))
+        result.insert(*j);
+    }
+  }
+
+  return result;
+}
+
+bool URLMatcher::IsEmpty() const {
+  return condition_factory_.IsEmpty() &&
+      url_matcher_condition_sets_.empty() &&
+      substring_match_triggers_.empty() &&
+      full_url_matcher_.IsEmpty() &&
+      url_component_matcher_.IsEmpty() &&
+      registered_full_url_patterns_.empty() &&
+      registered_url_component_patterns_.empty();
+}
+
+void URLMatcher::UpdateSubstringSetMatcher(bool full_url_conditions) {
+  // The purpose of |full_url_conditions| is just that we need to execute
+  // the same logic once for Full URL searches and once for URL Component
+  // searches (see URLMatcherConditionFactory).
+
+  // Determine which patterns need to be registered when this function
+  // terminates.
+  std::set<const StringPattern*> new_patterns;
+  for (URLMatcherConditionSets::const_iterator condition_set_iter =
+      url_matcher_condition_sets_.begin();
+      condition_set_iter != url_matcher_condition_sets_.end();
+      ++condition_set_iter) {
+    const URLMatcherConditionSet::Conditions& conditions =
+        condition_set_iter->second->conditions();
+    for (URLMatcherConditionSet::Conditions::const_iterator condition_iter =
+         conditions.begin(); condition_iter != conditions.end();
+         ++condition_iter) {
+      // If we are called to process Full URL searches, ignore others, and
+      // vice versa. (Regex conditions are updated in UpdateRegexSetMatcher.)
+      if (!condition_iter->IsRegexCondition() &&
+          full_url_conditions == condition_iter->IsFullURLCondition())
+        new_patterns.insert(condition_iter->string_pattern());
+    }
+  }
+
+  // This is the set of patterns that were registered before this function
+  // is called.
+  std::set<const StringPattern*>& registered_patterns =
+      full_url_conditions ? registered_full_url_patterns_
+                          : registered_url_component_patterns_;
+
+  // Add all patterns that are in new_patterns but not in registered_patterns.
+  std::vector<const StringPattern*> patterns_to_register;
+  std::set_difference(
+      new_patterns.begin(), new_patterns.end(),
+      registered_patterns.begin(), registered_patterns.end(),
+      std::back_inserter(patterns_to_register));
+
+  // Remove all patterns that are in registered_patterns but not in
+  // new_patterns.
+  std::vector<const StringPattern*> patterns_to_unregister;
+  std::set_difference(
+      registered_patterns.begin(), registered_patterns.end(),
+      new_patterns.begin(), new_patterns.end(),
+      std::back_inserter(patterns_to_unregister));
+
+  // Update the SubstringSetMatcher.
+  SubstringSetMatcher& url_matcher =
+      full_url_conditions ? full_url_matcher_ : url_component_matcher_;
+  url_matcher.RegisterAndUnregisterPatterns(patterns_to_register,
+                                            patterns_to_unregister);
+
+  // Update the set of registered_patterns for the next time this function
+  // is being called.
+  registered_patterns.swap(new_patterns);
+}
+
+void URLMatcher::UpdateRegexSetMatcher() {
+  std::vector<const StringPattern*> new_patterns;
+
+  for (URLMatcherConditionSets::const_iterator condition_set_iter =
+      url_matcher_condition_sets_.begin();
+      condition_set_iter != url_matcher_condition_sets_.end();
+      ++condition_set_iter) {
+    const URLMatcherConditionSet::Conditions& conditions =
+        condition_set_iter->second->conditions();
+    for (URLMatcherConditionSet::Conditions::const_iterator condition_iter =
+         conditions.begin(); condition_iter != conditions.end();
+         ++condition_iter) {
+      if (condition_iter->IsRegexCondition())
+        new_patterns.push_back(condition_iter->string_pattern());
+    }
+  }
+
+  // Start over from scratch. We can't really do better than this, since the
+  // FilteredRE2 backend doesn't support incremental updates.
+  regex_set_matcher_.ClearPatterns();
+  regex_set_matcher_.AddPatterns(new_patterns);
+}
+
+void URLMatcher::UpdateTriggers() {
+  // Count substring pattern frequencies.
+  std::map<StringPattern::ID, size_t> substring_pattern_frequencies;
+  for (URLMatcherConditionSets::const_iterator condition_set_iter =
+      url_matcher_condition_sets_.begin();
+      condition_set_iter != url_matcher_condition_sets_.end();
+      ++condition_set_iter) {
+    const URLMatcherConditionSet::Conditions& conditions =
+        condition_set_iter->second->conditions();
+    for (URLMatcherConditionSet::Conditions::const_iterator condition_iter =
+         conditions.begin(); condition_iter != conditions.end();
+         ++condition_iter) {
+      const StringPattern* pattern = condition_iter->string_pattern();
+      substring_pattern_frequencies[pattern->id()]++;
+    }
+  }
+
+  // Update trigger conditions: Determine for each URLMatcherConditionSet which
+  // URLMatcherCondition contains a StringPattern that occurs least
+  // frequently in this URLMatcher. We assume that this condition is very
+  // specific and occurs rarely in URLs. If a match occurs for this
+  // URLMatcherCondition, we want to test all other URLMatcherCondition in the
+  // respective URLMatcherConditionSet as well to see whether the entire
+  // URLMatcherConditionSet is considered matching.
+  substring_match_triggers_.clear();
+  for (URLMatcherConditionSets::const_iterator condition_set_iter =
+      url_matcher_condition_sets_.begin();
+      condition_set_iter != url_matcher_condition_sets_.end();
+      ++condition_set_iter) {
+    const URLMatcherConditionSet::Conditions& conditions =
+        condition_set_iter->second->conditions();
+    if (conditions.empty())
+      continue;
+    URLMatcherConditionSet::Conditions::const_iterator condition_iter =
+        conditions.begin();
+    StringPattern::ID trigger = condition_iter->string_pattern()->id();
+    // We skip the first element in the following loop.
+    ++condition_iter;
+    for (; condition_iter != conditions.end(); ++condition_iter) {
+      StringPattern::ID current_id =
+          condition_iter->string_pattern()->id();
+      if (substring_pattern_frequencies[trigger] >
+          substring_pattern_frequencies[current_id]) {
+        trigger = current_id;
+      }
+    }
+    substring_match_triggers_[trigger].insert(condition_set_iter->second->id());
+  }
+}
+
+void URLMatcher::UpdateConditionFactory() {
+  std::set<StringPattern::ID> used_patterns;
+  for (URLMatcherConditionSets::const_iterator condition_set_iter =
+      url_matcher_condition_sets_.begin();
+      condition_set_iter != url_matcher_condition_sets_.end();
+      ++condition_set_iter) {
+    const URLMatcherConditionSet::Conditions& conditions =
+        condition_set_iter->second->conditions();
+    for (URLMatcherConditionSet::Conditions::const_iterator condition_iter =
+         conditions.begin(); condition_iter != conditions.end();
+         ++condition_iter) {
+      used_patterns.insert(condition_iter->string_pattern()->id());
+    }
+  }
+  condition_factory_.ForgetUnusedPatterns(used_patterns);
+}
+
+void URLMatcher::UpdateInternalDatastructures() {
+  UpdateSubstringSetMatcher(false);
+  UpdateSubstringSetMatcher(true);
+  UpdateRegexSetMatcher();
+  UpdateTriggers();
+  UpdateConditionFactory();
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/matcher/url_matcher.h b/chrome/common/extensions/matcher/url_matcher.h
new file mode 100644
index 0000000..e868e27
--- /dev/null
+++ b/chrome/common/extensions/matcher/url_matcher.h
@@ -0,0 +1,342 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MATCHER_URL_MATCHER_H_
+#define CHROME_COMMON_EXTENSIONS_MATCHER_URL_MATCHER_H_
+
+#include <set>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "chrome/common/extensions/matcher/regex_set_matcher.h"
+#include "chrome/common/extensions/matcher/substring_set_matcher.h"
+
+class GURL;
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+
+// This class represents a single URL matching condition, e.g. a match on the
+// host suffix or the containment of a string in the query component of a GURL.
+//
+// The difference from a simple StringPattern is that this also supports
+// checking whether the {Host, Path, Query} of a URL contains a string. The
+// reduction of URL matching conditions to StringPatterns conducted by
+// URLMatcherConditionFactory is not capable of expressing that alone.
+//
+// Also supported is matching regular expressions against the URL (URL_MATCHES).
+class URLMatcherCondition {
+ public:
+  enum Criterion {
+    HOST_PREFIX,
+    HOST_SUFFIX,
+    HOST_CONTAINS,
+    HOST_EQUALS,
+    PATH_PREFIX,
+    PATH_SUFFIX,
+    PATH_CONTAINS,
+    PATH_EQUALS,
+    QUERY_PREFIX,
+    QUERY_SUFFIX,
+    QUERY_CONTAINS,
+    QUERY_EQUALS,
+    HOST_SUFFIX_PATH_PREFIX,
+    HOST_EQUALS_PATH_PREFIX,
+    URL_PREFIX,
+    URL_SUFFIX,
+    URL_CONTAINS,
+    URL_EQUALS,
+    URL_MATCHES,
+  };
+
+  URLMatcherCondition();
+  ~URLMatcherCondition();
+  URLMatcherCondition(Criterion criterion,
+                      const StringPattern* substring_pattern);
+  URLMatcherCondition(const URLMatcherCondition& rhs);
+  URLMatcherCondition& operator=(const URLMatcherCondition& rhs);
+  bool operator<(const URLMatcherCondition& rhs) const;
+
+  Criterion criterion() const { return criterion_; }
+  const StringPattern* string_pattern() const {
+    return string_pattern_;
+  }
+
+  // Returns whether this URLMatcherCondition needs to be executed on a
+  // full URL rather than the individual components (see
+  // URLMatcherConditionFactory).
+  bool IsFullURLCondition() const;
+
+  // Returns whether this URLMatcherCondition is a regular expression to be
+  // handled by a regex matcher instead of a substring matcher.
+  bool IsRegexCondition() const;
+
+  // Returns whether this condition is fulfilled according to
+  // |matching_patterns| and |url|.
+  bool IsMatch(const std::set<StringPattern::ID>& matching_patterns,
+               const GURL& url) const;
+
+ private:
+  // |criterion_| and |string_pattern_| describe together what property a URL
+  // needs to fulfill to be considered a match.
+  Criterion criterion_;
+
+  // This is the StringPattern that is used in a SubstringSetMatcher.
+  const StringPattern* string_pattern_;
+};
+
+// Class to map the problem of finding {host, path, query} {prefixes, suffixes,
+// containments, and equality} in GURLs to the substring matching problem.
+//
+// Say, you want to check whether the path of a URL starts with "/index.html".
+// This class preprocesses a URL like "www.google.com/index.html" into something
+// like "www.google.com|/index.html". After preprocessing, you can search for
+// "|/index.html" in the string and see that this candidate URL actually has
+// a path that starts with "/index.html". On the contrary,
+// "www.google.com/images/index.html" would be normalized to
+// "www.google.com|/images/index.html". It is easy to see that it contains
+// "/index.html" but the path of the URL does not start with "/index.html".
+//
+// This preprocessing is important if you want to match a URL against many
+// patterns because it reduces the matching to a "discover all substrings
+// of a dictionary in a text" problem, which can be solved very efficiently
+// by the Aho-Corasick algorithm.
+//
+// IMPORTANT: The URLMatcherConditionFactory owns the StringPattern
+// referenced by created URLMatcherConditions. Therefore, it must outlive
+// all created URLMatcherCondition and the SubstringSetMatcher.
+class URLMatcherConditionFactory {
+ public:
+  URLMatcherConditionFactory();
+  ~URLMatcherConditionFactory();
+
+  // Canonicalizes a URL for "Create{Host,Path,Query}*Condition" searches.
+  std::string CanonicalizeURLForComponentSearches(const GURL& url);
+
+  // Factory methods for various condition types.
+  //
+  // Note that these methods fill the pattern_singletons_. If you create
+  // conditions and don't register them to a URLMatcher, they will continue to
+  // consume memory. You need to call ForgetUnusedPatterns() or
+  // URLMatcher::ClearUnusedConditionSets() in this case.
+  URLMatcherCondition CreateHostPrefixCondition(const std::string& prefix);
+  URLMatcherCondition CreateHostSuffixCondition(const std::string& suffix);
+  URLMatcherCondition CreateHostContainsCondition(const std::string& str);
+  URLMatcherCondition CreateHostEqualsCondition(const std::string& str);
+
+  URLMatcherCondition CreatePathPrefixCondition(const std::string& prefix);
+  URLMatcherCondition CreatePathSuffixCondition(const std::string& suffix);
+  URLMatcherCondition CreatePathContainsCondition(const std::string& str);
+  URLMatcherCondition CreatePathEqualsCondition(const std::string& str);
+
+  URLMatcherCondition CreateQueryPrefixCondition(const std::string& prefix);
+  URLMatcherCondition CreateQuerySuffixCondition(const std::string& suffix);
+  URLMatcherCondition CreateQueryContainsCondition(const std::string& str);
+  URLMatcherCondition CreateQueryEqualsCondition(const std::string& str);
+
+  // This covers the common case, where you don't care whether a domain
+  // "foobar.com" is expressed as "foobar.com" or "www.foobar.com", and it
+  // should be followed by a given |path_prefix|.
+  URLMatcherCondition CreateHostSuffixPathPrefixCondition(
+      const std::string& host_suffix,
+      const std::string& path_prefix);
+  URLMatcherCondition CreateHostEqualsPathPrefixCondition(
+      const std::string& host,
+      const std::string& path_prefix);
+
+  // Canonicalizes a URL for "CreateURL*Condition" searches.
+  std::string CanonicalizeURLForFullSearches(const GURL& url);
+
+  // Canonicalizes a URL for "CreateURLMatchesCondition" searches.
+  std::string CanonicalizeURLForRegexSearches(const GURL& url);
+
+  URLMatcherCondition CreateURLPrefixCondition(const std::string& prefix);
+  URLMatcherCondition CreateURLSuffixCondition(const std::string& suffix);
+  URLMatcherCondition CreateURLContainsCondition(const std::string& str);
+  URLMatcherCondition CreateURLEqualsCondition(const std::string& str);
+
+  URLMatcherCondition CreateURLMatchesCondition(const std::string& regex);
+
+  // Removes all patterns from |pattern_singletons_| that are not listed in
+  // |used_patterns|. These patterns are not referenced any more and get
+  // freed.
+  void ForgetUnusedPatterns(
+      const std::set<StringPattern::ID>& used_patterns);
+
+  // Returns true if this object retains no allocated data. Only for debugging.
+  bool IsEmpty() const;
+
+ private:
+  // Creates a URLMatcherCondition according to the parameters passed.
+  // The URLMatcherCondition will refer to a StringPattern that is
+  // owned by |pattern_singletons_|.
+  URLMatcherCondition CreateCondition(URLMatcherCondition::Criterion criterion,
+                                      const std::string& pattern);
+
+  // Prepends a "." to the hostname if it does not start with one.
+  std::string CanonicalizeHostname(const std::string& hostname) const;
+
+  // Counter that ensures that all created StringPatterns have unique IDs.
+  // Note that substring patterns and regex patterns will use different IDs.
+  int id_counter_;
+
+  // This comparison considers only the pattern() value of the
+  // StringPatterns.
+  struct StringPatternPointerCompare {
+    bool operator()(StringPattern* lhs, StringPattern* rhs) const;
+  };
+  // Set to ensure that we generate only one StringPattern for each content
+  // of StringPattern::pattern().
+  typedef std::set<StringPattern*, StringPatternPointerCompare>
+      PatternSingletons;
+  PatternSingletons substring_pattern_singletons_;
+  PatternSingletons regex_pattern_singletons_;
+
+  DISALLOW_COPY_AND_ASSIGN(URLMatcherConditionFactory);
+};
+
+// This class represents a filter for the URL scheme to be hooked up into a
+// URLMatcherConditionSet.
+class URLMatcherSchemeFilter {
+ public:
+  explicit URLMatcherSchemeFilter(const std::string& filter);
+  explicit URLMatcherSchemeFilter(const std::vector<std::string>& filters);
+  ~URLMatcherSchemeFilter();
+  bool IsMatch(const GURL& url) const;
+
+ private:
+  std::vector<std::string> filters_;
+
+  DISALLOW_COPY_AND_ASSIGN(URLMatcherSchemeFilter);
+};
+
+// This class represents a filter for port numbers to be hooked up into a
+// URLMatcherConditionSet.
+class URLMatcherPortFilter {
+ public:
+  // Boundaries of a port range (both ends are included).
+  typedef std::pair<int, int> Range;
+  explicit URLMatcherPortFilter(const std::vector<Range>& ranges);
+  ~URLMatcherPortFilter();
+  bool IsMatch(const GURL& url) const;
+
+  // Creates a port range [from, to]; both ends are included.
+  static Range CreateRange(int from, int to);
+  // Creates a port range containing a single port.
+  static Range CreateRange(int port);
+
+ private:
+  std::vector<Range> ranges_;
+
+  DISALLOW_COPY_AND_ASSIGN(URLMatcherPortFilter);
+};
+
+// This class represents a set of conditions that all need to match on a
+// given URL in order to be considered a match.
+class URLMatcherConditionSet : public base::RefCounted<URLMatcherConditionSet> {
+ public:
+  typedef int ID;
+  typedef std::set<URLMatcherCondition> Conditions;
+  typedef std::vector<scoped_refptr<URLMatcherConditionSet> > Vector;
+
+  // Matches if all conditions in |conditions| are fulfilled.
+  URLMatcherConditionSet(ID id, const Conditions& conditions);
+
+  // Matches if all conditions in |conditions|, |scheme_filter| and
+  // |port_filter| are fulfilled. |scheme_filter| and |port_filter| may be NULL,
+  // in which case, no restrictions are imposed on the scheme/port of a URL.
+  URLMatcherConditionSet(ID id, const Conditions& conditions,
+                         scoped_ptr<URLMatcherSchemeFilter> scheme_filter,
+                         scoped_ptr<URLMatcherPortFilter> port_filter);
+
+  ID id() const { return id_; }
+  const Conditions& conditions() const { return conditions_; }
+
+  bool IsMatch(const std::set<StringPattern::ID>& matching_patterns,
+               const GURL& url) const;
+
+ private:
+  friend class base::RefCounted<URLMatcherConditionSet>;
+  ~URLMatcherConditionSet();
+  ID id_;
+  Conditions conditions_;
+  scoped_ptr<URLMatcherSchemeFilter> scheme_filter_;
+  scoped_ptr<URLMatcherPortFilter> port_filter_;
+
+  DISALLOW_COPY_AND_ASSIGN(URLMatcherConditionSet);
+};
+
+// This class allows matching one URL against a large set of
+// URLMatcherConditionSets at the same time.
+class URLMatcher {
+ public:
+  URLMatcher();
+  ~URLMatcher();
+
+  // Adds new URLMatcherConditionSet to this URL Matcher. Each condition set
+  // must have a unique ID.
+  // This is an expensive operation as it triggers pre-calculations on the
+  // currently registered condition sets. Do not call this operation many
+  // times with a single condition set in each call.
+  void AddConditionSets(const URLMatcherConditionSet::Vector& condition_sets);
+
+  // Removes the listed condition sets. All |condition_set_ids| must be
+  // currently registered. This function should be called with large batches
+  // of |condition_set_ids| at a time to improve performance.
+  void RemoveConditionSets(
+      const std::vector<URLMatcherConditionSet::ID>& condition_set_ids);
+
+  // Removes all unused condition sets from the ConditionFactory.
+  void ClearUnusedConditionSets();
+
+  // Returns the IDs of all URLMatcherConditionSet that match to this |url|.
+  std::set<URLMatcherConditionSet::ID> MatchURL(const GURL& url);
+
+  // Returns the URLMatcherConditionFactory that must be used to create
+  // URLMatcherConditionSets for this URLMatcher.
+  URLMatcherConditionFactory* condition_factory() {
+    return &condition_factory_;
+  }
+
+  // Returns true if this object retains no allocated data. Only for debugging.
+  bool IsEmpty() const;
+
+ private:
+  void UpdateSubstringSetMatcher(bool full_url_conditions);
+  void UpdateRegexSetMatcher();
+  void UpdateTriggers();
+  void UpdateConditionFactory();
+  void UpdateInternalDatastructures();
+
+  URLMatcherConditionFactory condition_factory_;
+
+  // Maps the ID of a URLMatcherConditionSet to the respective
+  // URLMatcherConditionSet.
+  typedef std::map<URLMatcherConditionSet::ID,
+                   scoped_refptr<URLMatcherConditionSet> >
+      URLMatcherConditionSets;
+  URLMatcherConditionSets url_matcher_condition_sets_;
+
+  // Maps a StringPattern ID to the URLMatcherConditions that need to
+  // be triggered in case of a StringPattern match.
+  std::map<StringPattern::ID, std::set<URLMatcherConditionSet::ID> >
+      substring_match_triggers_;
+
+  SubstringSetMatcher full_url_matcher_;
+  SubstringSetMatcher url_component_matcher_;
+  RegexSetMatcher regex_set_matcher_;
+  std::set<const StringPattern*> registered_full_url_patterns_;
+  std::set<const StringPattern*> registered_url_component_patterns_;
+
+  DISALLOW_COPY_AND_ASSIGN(URLMatcher);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_MATCHER_URL_MATCHER_H_
diff --git a/chrome/common/extensions/matcher/url_matcher_constants.cc b/chrome/common/extensions/matcher/url_matcher_constants.cc
new file mode 100644
index 0000000..a07cb1d
--- /dev/null
+++ b/chrome/common/extensions/matcher/url_matcher_constants.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/matcher/url_matcher_constants.h"
+
+namespace extensions {
+namespace url_matcher_constants {
+
+// Keys of dictionaries for URL constraints
+const char kPortsKey[] = "ports";
+const char kSchemesKey[] = "schemes";
+const char kHostContainsKey[] = "hostContains";
+const char kHostEqualsKey[] = "hostEquals";
+const char kHostPrefixKey[] = "hostPrefix";
+const char kHostSuffixKey[] = "hostSuffix";
+const char kHostSuffixPathPrefixKey[] = "hostSuffixPathPrefix";
+const char kPathContainsKey[] = "pathContains";
+const char kPathEqualsKey[] = "pathEquals";
+const char kPathPrefixKey[] = "pathPrefix";
+const char kPathSuffixKey[] = "pathSuffix";
+const char kQueryContainsKey[] = "queryContains";
+const char kQueryEqualsKey[] = "queryEquals";
+const char kQueryPrefixKey[] = "queryPrefix";
+const char kQuerySuffixKey[] = "querySuffix";
+const char kURLContainsKey[] = "urlContains";
+const char kURLEqualsKey[] = "urlEquals";
+const char kURLMatchesKey[] = "urlMatches";
+const char kURLPrefixKey[] = "urlPrefix";
+const char kURLSuffixKey[] = "urlSuffix";
+
+}  // namespace url_matcher_constants
+}  // namespace extensions
diff --git a/chrome/common/extensions/matcher/url_matcher_constants.h b/chrome/common/extensions/matcher/url_matcher_constants.h
new file mode 100644
index 0000000..59d0498
--- /dev/null
+++ b/chrome/common/extensions/matcher/url_matcher_constants.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 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.
+
+// Constants used for the URLMatcher component of the Declarative API.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MATCHER_URL_MATCHER_CONSTANTS_H_
+#define CHROME_COMMON_EXTENSIONS_MATCHER_URL_MATCHER_CONSTANTS_H_
+
+namespace extensions {
+namespace url_matcher_constants {
+
+// Keys of dictionaries for URL constraints
+extern const char kPortsKey[];
+extern const char kSchemesKey[];
+extern const char kHostContainsKey[];
+extern const char kHostEqualsKey[];
+extern const char kHostPrefixKey[];
+extern const char kHostSuffixKey[];
+extern const char kHostSuffixPathPrefixKey[];
+extern const char kPathContainsKey[];
+extern const char kPathEqualsKey[];
+extern const char kPathPrefixKey[];
+extern const char kPathSuffixKey[];
+extern const char kQueryContainsKey[];
+extern const char kQueryEqualsKey[];
+extern const char kQueryPrefixKey[];
+extern const char kQuerySuffixKey[];
+extern const char kURLContainsKey[];
+extern const char kURLEqualsKey[];
+extern const char kURLMatchesKey[];
+extern const char kURLPrefixKey[];
+extern const char kURLSuffixKey[];
+
+}  // namespace url_matcher_constants
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_MATCHER_URL_MATCHER_CONSTANTS_H_
diff --git a/chrome/common/extensions/matcher/url_matcher_factory.cc b/chrome/common/extensions/matcher/url_matcher_factory.cc
new file mode 100644
index 0000000..8a5273f
--- /dev/null
+++ b/chrome/common/extensions/matcher/url_matcher_factory.cc
@@ -0,0 +1,242 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/matcher/url_matcher_factory.h"
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/stringprintf.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/matcher/url_matcher_constants.h"
+#include "chrome/common/extensions/matcher/url_matcher_helpers.h"
+#include "third_party/re2/re2/re2.h"
+
+namespace helpers = extensions::url_matcher_helpers;
+namespace keys = extensions::url_matcher_constants;
+
+namespace {
+// Error messages:
+const char kInvalidPortRanges[] = "Invalid port ranges in UrlFilter.";
+const char kVectorOfStringsExpected[] =
+    "UrlFilter attribute '*' expected a vector of strings as parameter.";
+const char kUnknownURLFilterAttribute[] =
+    "Unknown attribute '*' in UrlFilter.";
+const char kAttributeExpectedString[] =
+    "UrlFilter attribute '*' expected a string value.";
+const char kUnparseableRegexString[] =
+    "Could not parse regular expression '*': *";
+
+// Registry for all factory methods of extensions::URLMatcherConditionFactory
+// that allows translating string literals from the extension API into
+// the corresponding factory method to be called.
+class URLMatcherConditionFactoryMethods {
+ public:
+  URLMatcherConditionFactoryMethods() {
+    typedef extensions::URLMatcherConditionFactory F;
+    factory_methods_[keys::kHostContainsKey] = &F::CreateHostContainsCondition;
+    factory_methods_[keys::kHostEqualsKey] = &F::CreateHostEqualsCondition;
+    factory_methods_[keys::kHostPrefixKey] = &F::CreateHostPrefixCondition;
+    factory_methods_[keys::kHostSuffixKey] = &F::CreateHostSuffixCondition;
+    factory_methods_[keys::kPathContainsKey] = &F::CreatePathContainsCondition;
+    factory_methods_[keys::kPathEqualsKey] = &F::CreatePathEqualsCondition;
+    factory_methods_[keys::kPathPrefixKey] = &F::CreatePathPrefixCondition;
+    factory_methods_[keys::kPathSuffixKey] = &F::CreatePathSuffixCondition;
+    factory_methods_[keys::kQueryContainsKey] =
+        &F::CreateQueryContainsCondition;
+    factory_methods_[keys::kQueryEqualsKey] = &F::CreateQueryEqualsCondition;
+    factory_methods_[keys::kQueryPrefixKey] = &F::CreateQueryPrefixCondition;
+    factory_methods_[keys::kQuerySuffixKey] = &F::CreateQuerySuffixCondition;
+    factory_methods_[keys::kURLContainsKey] = &F::CreateURLContainsCondition;
+    factory_methods_[keys::kURLEqualsKey] = &F::CreateURLEqualsCondition;
+    factory_methods_[keys::kURLPrefixKey] = &F::CreateURLPrefixCondition;
+    factory_methods_[keys::kURLSuffixKey] = &F::CreateURLSuffixCondition;
+    factory_methods_[keys::kURLMatchesKey] = &F::CreateURLMatchesCondition;
+  }
+
+  // Returns whether a factory method for the specified |pattern_type| (e.g.
+  // "host_suffix") is known.
+  bool Contains(const std::string& pattern_type) const {
+    return factory_methods_.find(pattern_type) != factory_methods_.end();
+  }
+
+  // Creates a URLMatcherCondition instance from |url_matcher_condition_factory|
+  // of the given |pattern_type| (e.g. "host_suffix") for the given
+  // |pattern_value| (e.g. "example.com").
+  // The |pattern_type| needs to be known to this class (see Contains()) or
+  // a CHECK is triggered.
+  extensions::URLMatcherCondition Call(
+      extensions::URLMatcherConditionFactory* url_matcher_condition_factory,
+      const std::string& pattern_type,
+      const std::string& pattern_value) const {
+    FactoryMethods::const_iterator i = factory_methods_.find(pattern_type);
+    CHECK(i != factory_methods_.end());
+    const FactoryMethod& method = i->second;
+    return (url_matcher_condition_factory->*method)(pattern_value);
+  }
+
+ private:
+  typedef extensions::URLMatcherCondition
+      (extensions::URLMatcherConditionFactory::* FactoryMethod)
+      (const std::string& prefix);
+  typedef std::map<std::string, FactoryMethod> FactoryMethods;
+
+  FactoryMethods factory_methods_;
+
+  DISALLOW_COPY_AND_ASSIGN(URLMatcherConditionFactoryMethods);
+};
+
+static base::LazyInstance<URLMatcherConditionFactoryMethods>
+    g_url_matcher_condition_factory_methods = LAZY_INSTANCE_INITIALIZER;
+
+}  // namespace
+
+namespace extensions {
+
+// static
+scoped_refptr<URLMatcherConditionSet>
+URLMatcherFactory::CreateFromURLFilterDictionary(
+    URLMatcherConditionFactory* url_matcher_condition_factory,
+    const base::DictionaryValue* url_filter_dict,
+    URLMatcherConditionSet::ID id,
+    std::string* error) {
+  scoped_ptr<URLMatcherSchemeFilter> url_matcher_schema_filter;
+  scoped_ptr<URLMatcherPortFilter> url_matcher_port_filter;
+  URLMatcherConditionSet::Conditions url_matcher_conditions;
+
+  for (base::DictionaryValue::Iterator iter(*url_filter_dict);
+       iter.HasNext(); iter.Advance()) {
+    const std::string& condition_attribute_name = iter.key();
+    const Value& condition_attribute_value = iter.value();
+    if (IsURLMatcherConditionAttribute(condition_attribute_name)) {
+      // Handle {host, path, ...}{Prefix, Suffix, Contains, Equals}.
+      URLMatcherCondition url_matcher_condition =
+          CreateURLMatcherCondition(
+              url_matcher_condition_factory,
+              condition_attribute_name,
+              &condition_attribute_value,
+              error);
+      if (!error->empty())
+        return scoped_refptr<URLMatcherConditionSet>(NULL);
+      url_matcher_conditions.insert(url_matcher_condition);
+    } else if (condition_attribute_name == keys::kSchemesKey) {
+      // Handle scheme.
+      url_matcher_schema_filter = CreateURLMatcherScheme(
+          &condition_attribute_value, error);
+      if (!error->empty())
+        return scoped_refptr<URLMatcherConditionSet>(NULL);
+    } else if (condition_attribute_name == keys::kPortsKey) {
+      // Handle ports.
+      url_matcher_port_filter = CreateURLMatcherPorts(
+          &condition_attribute_value, error);
+      if (!error->empty())
+        return scoped_refptr<URLMatcherConditionSet>(NULL);
+    } else {
+      // Handle unknown attributes.
+      *error = ExtensionErrorUtils::FormatErrorMessage(
+          kUnknownURLFilterAttribute,
+          condition_attribute_name);
+      return scoped_refptr<URLMatcherConditionSet>(NULL);
+    }
+  }
+
+  // As the URL is the preliminary matching criterion that triggers the tests
+  // for the remaining condition attributes, we insert an empty URL match if
+  // no other url match conditions were specified. Such an empty URL is always
+  // matched.
+  if (url_matcher_conditions.empty()) {
+    url_matcher_conditions.insert(
+        url_matcher_condition_factory->CreateHostPrefixCondition(""));
+  }
+
+  scoped_refptr<URLMatcherConditionSet> url_matcher_condition_set(
+      new URLMatcherConditionSet(id, url_matcher_conditions,
+          url_matcher_schema_filter.Pass(), url_matcher_port_filter.Pass()));
+  return url_matcher_condition_set;
+}
+
+// static
+bool URLMatcherFactory::IsURLMatcherConditionAttribute(
+    const std::string& condition_attribute_name) {
+  return g_url_matcher_condition_factory_methods.Get().Contains(
+      condition_attribute_name);
+}
+
+// static
+URLMatcherCondition URLMatcherFactory::CreateURLMatcherCondition(
+    URLMatcherConditionFactory* url_matcher_condition_factory,
+    const std::string& condition_attribute_name,
+    const base::Value* value,
+    std::string* error) {
+  std::string str_value;
+  if (!value->GetAsString(&str_value)) {
+    *error = ExtensionErrorUtils::FormatErrorMessage(kAttributeExpectedString,
+                                                     condition_attribute_name);
+    return URLMatcherCondition();
+  }
+  // Test regular expressions for validity.
+  if (condition_attribute_name == keys::kURLMatchesKey) {
+    re2::RE2 regex(str_value);
+    if (!regex.ok()) {
+      *error = ExtensionErrorUtils::FormatErrorMessage(kUnparseableRegexString,
+                                                       str_value,
+                                                       regex.error());
+      return URLMatcherCondition();
+    }
+  }
+  return g_url_matcher_condition_factory_methods.Get().Call(
+      url_matcher_condition_factory, condition_attribute_name, str_value);
+}
+
+// static
+scoped_ptr<URLMatcherSchemeFilter> URLMatcherFactory::CreateURLMatcherScheme(
+    const base::Value* value,
+    std::string* error) {
+  std::vector<std::string> schemas;
+  if (!helpers::GetAsStringVector(value, &schemas)) {
+    *error = ExtensionErrorUtils::FormatErrorMessage(kVectorOfStringsExpected,
+                                                     keys::kSchemesKey);
+    return scoped_ptr<URLMatcherSchemeFilter>(NULL);
+  }
+  return scoped_ptr<URLMatcherSchemeFilter>(
+      new URLMatcherSchemeFilter(schemas));
+}
+
+// static
+scoped_ptr<URLMatcherPortFilter> URLMatcherFactory::CreateURLMatcherPorts(
+    const base::Value* value,
+    std::string* error) {
+  std::vector<URLMatcherPortFilter::Range> ranges;
+  const base::ListValue* value_list = NULL;
+  if (!value->GetAsList(&value_list)) {
+    *error = kInvalidPortRanges;
+    return scoped_ptr<URLMatcherPortFilter>(NULL);
+  }
+
+  for (ListValue::const_iterator i = value_list->begin();
+       i != value_list->end(); ++i) {
+    Value* entry = *i;
+    int port = 0;
+    base::ListValue* range = NULL;
+    if (entry->GetAsInteger(&port)) {
+      ranges.push_back(URLMatcherPortFilter::CreateRange(port));
+    } else if (entry->GetAsList(&range)) {
+      int from = 0, to = 0;
+      if (range->GetSize() != 2u ||
+          !range->GetInteger(0, &from) ||
+          !range->GetInteger(1, &to)) {
+        *error = kInvalidPortRanges;
+        return scoped_ptr<URLMatcherPortFilter>(NULL);
+      }
+      ranges.push_back(URLMatcherPortFilter::CreateRange(from, to));
+    } else {
+      *error = kInvalidPortRanges;
+      return scoped_ptr<URLMatcherPortFilter>(NULL);
+    }
+  }
+
+  return scoped_ptr<URLMatcherPortFilter>(new URLMatcherPortFilter(ranges));
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/matcher/url_matcher_factory.h b/chrome/common/extensions/matcher/url_matcher_factory.h
new file mode 100644
index 0000000..7623e18
--- /dev/null
+++ b/chrome/common/extensions/matcher/url_matcher_factory.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MATCHER_URL_MATCHER_FACTORY_H_
+#define CHROME_COMMON_EXTENSIONS_MATCHER_URL_MATCHER_FACTORY_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "chrome/common/extensions/matcher/url_matcher.h"
+
+namespace base {
+class DictionaryValue;
+class Value;
+}
+
+namespace extensions {
+
+class URLMatcherFactory {
+ public:
+  // Creates a URLMatcherConditionSet from a UrlFilter dictionary as defined in
+  // the declarative API. |url_fetcher_dict| contains the dictionary passed
+  // by the extension, |id| is the identifier assigned to the created
+  // URLMatcherConditionSet. In case of an error, |error| is set to contain
+  // an error message.
+  //
+  // Note: In case this function fails or if you don't register the
+  // URLMatcherConditionSet to the URLMatcher, you need to call
+  // URLMatcher::ClearUnusedConditionSets() on the URLMatcher that owns this
+  // URLMatcherFactory. Otherwise you leak memory.
+  static scoped_refptr<URLMatcherConditionSet> CreateFromURLFilterDictionary(
+      URLMatcherConditionFactory* url_matcher_condition_factory,
+      const base::DictionaryValue* url_filter_dict,
+      URLMatcherConditionSet::ID id,
+      std::string* error);
+
+ private:
+  // Returns whether a condition attribute with name |condition_attribute_name|
+  // needs to be handled by the URLMatcher.
+  static bool IsURLMatcherConditionAttribute(
+      const std::string& condition_attribute_name);
+
+  // Factory method of for URLMatcherConditions.
+  static URLMatcherCondition CreateURLMatcherCondition(
+      URLMatcherConditionFactory* url_matcher_condition_factory,
+      const std::string& condition_attribute_name,
+      const base::Value* value,
+      std::string* error);
+
+  static scoped_ptr<URLMatcherSchemeFilter> CreateURLMatcherScheme(
+      const base::Value* value, std::string* error);
+
+  static scoped_ptr<URLMatcherPortFilter> CreateURLMatcherPorts(
+      const base::Value* value, std::string* error);
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(URLMatcherFactory);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_MATCHER_URL_MATCHER_FACTORY_H_
diff --git a/chrome/common/extensions/matcher/url_matcher_factory_unittest.cc b/chrome/common/extensions/matcher/url_matcher_factory_unittest.cc
new file mode 100644
index 0000000..aa2a76c
--- /dev/null
+++ b/chrome/common/extensions/matcher/url_matcher_factory_unittest.cc
@@ -0,0 +1,102 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/matcher/url_matcher_factory.h"
+
+#include "base/values.h"
+#include "chrome/common/extensions/matcher/url_matcher_constants.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace keys = url_matcher_constants;
+
+TEST(URLMatcherFactoryTest, CreateFromURLFilterDictionary) {
+  URLMatcher matcher;
+
+  std::string error;
+  scoped_refptr<URLMatcherConditionSet> result;
+
+  // Invalid key: {"invalid": "foobar"}
+  DictionaryValue invalid_condition;
+  invalid_condition.SetString("invalid", "foobar");
+
+  // Invalid value type: {"hostSuffix": []}
+  DictionaryValue invalid_condition2;
+  invalid_condition2.Set(keys::kHostSuffixKey, new ListValue);
+
+  // Invalid regex value: {"urlMatches": "*"}
+  DictionaryValue invalid_condition3;
+  invalid_condition3.SetString(keys::kURLMatchesKey, "*");
+
+  // Valid values:
+  // {
+  //   "port_range": [80, [1000, 1010]],
+  //   "schemes": ["http"],
+  //   "hostSuffix": "example.com"
+  //   "hostPrefix": "www"
+  // }
+
+  // Port range: Allow 80;1000-1010.
+  ListValue* port_range = new ListValue();
+  port_range->Append(Value::CreateIntegerValue(1000));
+  port_range->Append(Value::CreateIntegerValue(1010));
+  ListValue* port_ranges = new ListValue();
+  port_ranges->Append(Value::CreateIntegerValue(80));
+  port_ranges->Append(port_range);
+
+  ListValue* scheme_list = new ListValue();
+  scheme_list->Append(Value::CreateStringValue("http"));
+
+  DictionaryValue valid_condition;
+  valid_condition.SetString(keys::kHostSuffixKey, "example.com");
+  valid_condition.SetString(keys::kHostPrefixKey, "www");
+  valid_condition.Set(keys::kPortsKey, port_ranges);
+  valid_condition.Set(keys::kSchemesKey, scheme_list);
+
+  // Test wrong condition name passed.
+  error.clear();
+  result = URLMatcherFactory::CreateFromURLFilterDictionary(
+      matcher.condition_factory(), &invalid_condition, 1, &error);
+  EXPECT_FALSE(error.empty());
+  EXPECT_FALSE(result.get());
+
+  // Test wrong datatype in hostSuffix.
+  error.clear();
+  result = URLMatcherFactory::CreateFromURLFilterDictionary(
+      matcher.condition_factory(), &invalid_condition2, 2, &error);
+  EXPECT_FALSE(error.empty());
+  EXPECT_FALSE(result.get());
+
+  // Test invalid regex in urlMatches.
+  error.clear();
+  result = URLMatcherFactory::CreateFromURLFilterDictionary(
+      matcher.condition_factory(), &invalid_condition3, 3, &error);
+  EXPECT_FALSE(error.empty());
+  EXPECT_FALSE(result.get());
+
+  // Test success.
+  error.clear();
+  result = URLMatcherFactory::CreateFromURLFilterDictionary(
+      matcher.condition_factory(), &valid_condition, 100, &error);
+  EXPECT_EQ("", error);
+  ASSERT_TRUE(result.get());
+
+  URLMatcherConditionSet::Vector conditions;
+  conditions.push_back(result);
+  matcher.AddConditionSets(conditions);
+
+  EXPECT_EQ(1u, matcher.MatchURL(GURL("http://www.example.com")).size());
+  EXPECT_EQ(1u, matcher.MatchURL(GURL("http://www.example.com:80")).size());
+  EXPECT_EQ(1u, matcher.MatchURL(GURL("http://www.example.com:1000")).size());
+  // Wrong scheme.
+  EXPECT_EQ(0u, matcher.MatchURL(GURL("https://www.example.com:80")).size());
+  // Wrong port.
+  EXPECT_EQ(0u, matcher.MatchURL(GURL("http://www.example.com:81")).size());
+  // Unfulfilled host prefix.
+  EXPECT_EQ(0u, matcher.MatchURL(GURL("http://mail.example.com:81")).size());
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/matcher/url_matcher_helpers.cc b/chrome/common/extensions/matcher/url_matcher_helpers.cc
new file mode 100644
index 0000000..c9bec75
--- /dev/null
+++ b/chrome/common/extensions/matcher/url_matcher_helpers.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/matcher/url_matcher_helpers.h"
+
+#include "base/values.h"
+
+namespace extensions {
+namespace url_matcher_helpers {
+
+// Converts a ValueList |value| of strings into a vector. Returns true if
+// successful.
+bool GetAsStringVector(const base::Value* value,
+                       std::vector<std::string>* out) {
+  const ListValue* value_as_list = 0;
+  if (!value->GetAsList(&value_as_list))
+    return false;
+
+  size_t number_types = value_as_list->GetSize();
+  for (size_t i = 0; i < number_types; ++i) {
+    std::string item;
+    if (!value_as_list->GetString(i, &item))
+      return false;
+    out->push_back(item);
+  }
+  return true;
+}
+
+}  // namespace url_matcher_helpers
+}  // namespace extensions
diff --git a/chrome/common/extensions/matcher/url_matcher_helpers.h b/chrome/common/extensions/matcher/url_matcher_helpers.h
new file mode 100644
index 0000000..ebcb18b
--- /dev/null
+++ b/chrome/common/extensions/matcher/url_matcher_helpers.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 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.
+
+// Helper functions used for URLMatcher and Declarative APIs.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MATCHER_URL_MATCHER_HELPERS_H_
+#define CHROME_COMMON_EXTENSIONS_MATCHER_URL_MATCHER_HELPERS_H_
+
+#include <string>
+#include <vector>
+
+namespace base {
+class Value;
+}
+
+namespace extensions {
+namespace url_matcher_helpers {
+
+// Converts a ValueList |value| of strings into a vector. Returns true if
+// successful.
+bool GetAsStringVector(const base::Value* value, std::vector<std::string>* out);
+
+}  // namespace declarative_helpers
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_MATCHER_URL_MATCHER_HELPERS_H_
diff --git a/chrome/common/extensions/matcher/url_matcher_unittest.cc b/chrome/common/extensions/matcher/url_matcher_unittest.cc
new file mode 100644
index 0000000..661dc50
--- /dev/null
+++ b/chrome/common/extensions/matcher/url_matcher_unittest.cc
@@ -0,0 +1,565 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/matcher/url_matcher.h"
+
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+//
+// URLMatcherCondition
+//
+
+TEST(URLMatcherConditionTest, Constructors) {
+  StringPattern pattern("example.com", 1);
+  URLMatcherCondition m1(URLMatcherCondition::HOST_SUFFIX, &pattern);
+  EXPECT_EQ(URLMatcherCondition::HOST_SUFFIX, m1.criterion());
+  EXPECT_EQ(&pattern, m1.string_pattern());
+
+  URLMatcherCondition m2;
+  m2 = m1;
+  EXPECT_EQ(URLMatcherCondition::HOST_SUFFIX, m2.criterion());
+  EXPECT_EQ(&pattern, m2.string_pattern());
+
+  URLMatcherCondition m3(m1);
+  EXPECT_EQ(URLMatcherCondition::HOST_SUFFIX, m3.criterion());
+  EXPECT_EQ(&pattern, m3.string_pattern());
+}
+
+TEST(URLMatcherSchemeFilter, TestMatching) {
+  URLMatcherSchemeFilter filter1("https");
+  std::vector<std::string> filter2_content;
+  filter2_content.push_back("http");
+  filter2_content.push_back("https");
+  URLMatcherSchemeFilter filter2(filter2_content);
+
+  GURL matching_url("https://www.foobar.com");
+  GURL non_matching_url("http://www.foobar.com");
+  EXPECT_TRUE(filter1.IsMatch(matching_url));
+  EXPECT_FALSE(filter1.IsMatch(non_matching_url));
+  EXPECT_TRUE(filter2.IsMatch(matching_url));
+  EXPECT_TRUE(filter2.IsMatch(non_matching_url));
+}
+
+TEST(URLMatcherPortFilter, TestMatching) {
+  std::vector<URLMatcherPortFilter::Range> ranges;
+  ranges.push_back(URLMatcherPortFilter::CreateRange(80, 90));
+  ranges.push_back(URLMatcherPortFilter::CreateRange(8080));
+  URLMatcherPortFilter filter(ranges);
+  EXPECT_TRUE(filter.IsMatch(GURL("http://www.example.com")));
+  EXPECT_TRUE(filter.IsMatch(GURL("http://www.example.com:80")));
+  EXPECT_TRUE(filter.IsMatch(GURL("http://www.example.com:81")));
+  EXPECT_TRUE(filter.IsMatch(GURL("http://www.example.com:90")));
+  EXPECT_TRUE(filter.IsMatch(GURL("http://www.example.com:8080")));
+  EXPECT_FALSE(filter.IsMatch(GURL("http://www.example.com:79")));
+  EXPECT_FALSE(filter.IsMatch(GURL("http://www.example.com:91")));
+  EXPECT_FALSE(filter.IsMatch(GURL("https://www.example.com")));
+}
+
+TEST(URLMatcherConditionTest, IsFullURLCondition) {
+  StringPattern pattern("example.com", 1);
+  EXPECT_FALSE(URLMatcherCondition(URLMatcherCondition::HOST_SUFFIX,
+      &pattern).IsFullURLCondition());
+
+  EXPECT_TRUE(URLMatcherCondition(URLMatcherCondition::HOST_CONTAINS,
+      &pattern).IsFullURLCondition());
+  EXPECT_TRUE(URLMatcherCondition(URLMatcherCondition::PATH_CONTAINS,
+      &pattern).IsFullURLCondition());
+  EXPECT_TRUE(URLMatcherCondition(URLMatcherCondition::QUERY_CONTAINS,
+      &pattern).IsFullURLCondition());
+
+  EXPECT_TRUE(URLMatcherCondition(URLMatcherCondition::URL_PREFIX,
+      &pattern).IsFullURLCondition());
+  EXPECT_TRUE(URLMatcherCondition(URLMatcherCondition::URL_SUFFIX,
+      &pattern).IsFullURLCondition());
+  EXPECT_TRUE(URLMatcherCondition(URLMatcherCondition::URL_CONTAINS,
+      &pattern).IsFullURLCondition());
+  EXPECT_TRUE(URLMatcherCondition(URLMatcherCondition::URL_EQUALS,
+      &pattern).IsFullURLCondition());
+}
+
+TEST(URLMatcherConditionTest, IsMatch) {
+  GURL url1("http://www.example.com/www.foobar.com/index.html");
+  GURL url2("http://www.foobar.com/example.com/index.html");
+
+  StringPattern pattern("example.com", 1);
+  URLMatcherCondition m1(URLMatcherCondition::HOST_SUFFIX, &pattern);
+
+  std::set<StringPattern::ID> matching_patterns;
+
+  // matches = {0} --> matcher did not indicate that m1 was a match.
+  matching_patterns.insert(0);
+  EXPECT_FALSE(m1.IsMatch(matching_patterns, url1));
+
+  // matches = {0, 1} --> matcher did indicate that m1 was a match.
+  matching_patterns.insert(1);
+  EXPECT_TRUE(m1.IsMatch(matching_patterns, url1));
+
+  // For m2 we use a HOST_CONTAINS test, which requires a post-validation
+  // whether the match reported by the SubstringSetMatcher occurs really
+  // in the correct url component.
+  URLMatcherCondition m2(URLMatcherCondition::HOST_CONTAINS, &pattern);
+  EXPECT_TRUE(m2.IsMatch(matching_patterns, url1));
+  EXPECT_FALSE(m2.IsMatch(matching_patterns, url2));
+}
+
+TEST(URLMatcherConditionTest, Comparison) {
+  StringPattern p1("foobar.com", 1);
+  StringPattern p2("foobar.com", 2);
+  // The first component of each test is expected to be < than the second.
+  URLMatcherCondition test_smaller[][2] = {
+      {URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, &p1),
+       URLMatcherCondition(URLMatcherCondition::HOST_SUFFIX, &p1)},
+      {URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, &p1),
+       URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, &p2)},
+      {URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, NULL),
+       URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, &p2)},
+      {URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, &p1),
+       URLMatcherCondition(URLMatcherCondition::HOST_SUFFIX, NULL)},
+  };
+  for (size_t i = 0; i < arraysize(test_smaller); ++i) {
+    EXPECT_TRUE(test_smaller[i][0] < test_smaller[i][1])
+        << "Test " << i << " of test_smaller failed";
+    EXPECT_FALSE(test_smaller[i][1] < test_smaller[i][0])
+        << "Test " << i << " of test_smaller failed";
+  }
+  URLMatcherCondition test_equal[][2] = {
+      {URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, &p1),
+       URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, &p1)},
+      {URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, NULL),
+       URLMatcherCondition(URLMatcherCondition::HOST_PREFIX, NULL)},
+  };
+  for (size_t i = 0; i < arraysize(test_equal); ++i) {
+    EXPECT_FALSE(test_equal[i][0] < test_equal[i][1])
+        << "Test " << i << " of test_equal failed";
+    EXPECT_FALSE(test_equal[i][1] < test_equal[i][0])
+        << "Test " << i << " of test_equal failed";
+  }
+}
+
+//
+// URLMatcherConditionFactory
+//
+
+namespace {
+
+bool Matches(const URLMatcherCondition& condition, std::string text) {
+  return text.find(condition.string_pattern()->pattern()) !=
+      std::string::npos;
+}
+
+}  // namespace
+
+TEST(URLMatcherConditionFactoryTest, GURLCharacterSet) {
+  // GURL guarantees that neither domain, nor path, nor query may contain
+  // non ASCII-7 characters. We test this here, because a change to this
+  // guarantee breaks this implementation horribly.
+  GURL url("http://www.föö.com/föö?föö#föö");
+  EXPECT_TRUE(IsStringASCII(url.host()));
+  EXPECT_TRUE(IsStringASCII(url.path()));
+  EXPECT_TRUE(IsStringASCII(url.query()));
+  EXPECT_FALSE(IsStringASCII(url.ref()));
+}
+
+TEST(URLMatcherConditionFactoryTest, Criteria) {
+  URLMatcherConditionFactory factory;
+  EXPECT_EQ(URLMatcherCondition::HOST_PREFIX,
+            factory.CreateHostPrefixCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::HOST_SUFFIX,
+            factory.CreateHostSuffixCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::HOST_CONTAINS,
+            factory.CreateHostContainsCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::HOST_EQUALS,
+            factory.CreateHostEqualsCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::PATH_PREFIX,
+            factory.CreatePathPrefixCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::PATH_SUFFIX,
+            factory.CreatePathSuffixCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::PATH_CONTAINS,
+            factory.CreatePathContainsCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::PATH_EQUALS,
+            factory.CreatePathEqualsCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::QUERY_PREFIX,
+            factory.CreateQueryPrefixCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::QUERY_SUFFIX,
+            factory.CreateQuerySuffixCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::QUERY_CONTAINS,
+            factory.CreateQueryContainsCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::QUERY_EQUALS,
+            factory.CreateQueryEqualsCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::HOST_SUFFIX_PATH_PREFIX,
+            factory.CreateHostSuffixPathPrefixCondition("foo",
+                                                        "bar").criterion());
+  EXPECT_EQ(URLMatcherCondition::HOST_EQUALS_PATH_PREFIX,
+            factory.CreateHostEqualsPathPrefixCondition("foo",
+                                                        "bar").criterion());
+  EXPECT_EQ(URLMatcherCondition::URL_PREFIX,
+            factory.CreateURLPrefixCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::URL_SUFFIX,
+            factory.CreateURLSuffixCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::URL_CONTAINS,
+            factory.CreateURLContainsCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::URL_EQUALS,
+            factory.CreateURLEqualsCondition("foo").criterion());
+  EXPECT_EQ(URLMatcherCondition::URL_MATCHES,
+            factory.CreateURLMatchesCondition("foo").criterion());
+}
+
+TEST(URLMatcherConditionFactoryTest, TestSingletonProperty) {
+  URLMatcherConditionFactory factory;
+  URLMatcherCondition c1 = factory.CreateHostEqualsCondition("www.google.com");
+  URLMatcherCondition c2 = factory.CreateHostEqualsCondition("www.google.com");
+  EXPECT_EQ(c1.criterion(), c2.criterion());
+  EXPECT_EQ(c1.string_pattern(), c2.string_pattern());
+  URLMatcherCondition c3 = factory.CreateHostEqualsCondition("www.google.de");
+  EXPECT_EQ(c2.criterion(), c3.criterion());
+  EXPECT_NE(c2.string_pattern(), c3.string_pattern());
+  EXPECT_NE(c2.string_pattern()->id(), c3.string_pattern()->id());
+  EXPECT_NE(c2.string_pattern()->pattern(),
+            c3.string_pattern()->pattern());
+  URLMatcherCondition c4 = factory.CreateURLMatchesCondition("www.google.com");
+  URLMatcherCondition c5 = factory.CreateURLContainsCondition("www.google.com");
+  // Regex patterns and substring patterns do not share IDs.
+  EXPECT_EQ(c5.string_pattern()->pattern(), c4.string_pattern()->pattern());
+  EXPECT_NE(c5.string_pattern(), c4.string_pattern());
+  EXPECT_NE(c5.string_pattern()->id(), c4.string_pattern()->id());
+
+  // Check that all StringPattern singletons are freed if we call
+  // ForgetUnusedPatterns.
+  StringPattern::ID old_id_1 = c1.string_pattern()->id();
+  StringPattern::ID old_id_4 = c4.string_pattern()->id();
+  factory.ForgetUnusedPatterns(std::set<StringPattern::ID>());
+  EXPECT_TRUE(factory.IsEmpty());
+  URLMatcherCondition c6 = factory.CreateHostEqualsCondition("www.google.com");
+  EXPECT_NE(old_id_1, c6.string_pattern()->id());
+  URLMatcherCondition c7 = factory.CreateURLMatchesCondition("www.google.com");
+  EXPECT_NE(old_id_4, c7.string_pattern()->id());
+}
+
+TEST(URLMatcherConditionFactoryTest, TestComponentSearches) {
+  GURL gurl("https://www.google.com:1234/webhp?sourceid=chrome-instant&ie=UTF-8"
+      "&ion=1#hl=en&output=search&sclient=psy-ab&q=chrome%20is%20awesome");
+  URLMatcherConditionFactory factory;
+  std::string url = factory.CanonicalizeURLForComponentSearches(gurl);
+
+  // Test host component.
+  EXPECT_TRUE(Matches(factory.CreateHostPrefixCondition(""), url));
+  EXPECT_TRUE(Matches(factory.CreateHostPrefixCondition("www.goog"), url));
+  EXPECT_TRUE(
+      Matches(factory.CreateHostPrefixCondition("www.google.com"), url));
+  EXPECT_TRUE(
+      Matches(factory.CreateHostPrefixCondition(".www.google.com"), url));
+  EXPECT_FALSE(Matches(factory.CreateHostPrefixCondition("google.com"), url));
+  EXPECT_FALSE(
+      Matches(factory.CreateHostPrefixCondition("www.google.com/"), url));
+  EXPECT_FALSE(Matches(factory.CreateHostPrefixCondition("webhp"), url));
+
+  EXPECT_TRUE(Matches(factory.CreateHostSuffixCondition(""), url));
+  EXPECT_TRUE(Matches(factory.CreateHostSuffixCondition("com"), url));
+  EXPECT_TRUE(Matches(factory.CreateHostSuffixCondition(".com"), url));
+  EXPECT_TRUE(
+      Matches(factory.CreateHostSuffixCondition("www.google.com"), url));
+  EXPECT_TRUE(
+      Matches(factory.CreateHostSuffixCondition(".www.google.com"), url));
+  EXPECT_FALSE(Matches(factory.CreateHostSuffixCondition("www"), url));
+  EXPECT_FALSE(
+      Matches(factory.CreateHostSuffixCondition("www.google.com/"), url));
+  EXPECT_FALSE(Matches(factory.CreateHostSuffixCondition("webhp"), url));
+
+  EXPECT_FALSE(Matches(factory.CreateHostEqualsCondition(""), url));
+  EXPECT_FALSE(Matches(factory.CreateHostEqualsCondition("www"), url));
+  EXPECT_TRUE(
+      Matches(factory.CreateHostEqualsCondition("www.google.com"), url));
+  EXPECT_FALSE(
+      Matches(factory.CreateHostEqualsCondition("www.google.com/"), url));
+
+
+  // Test path component.
+  EXPECT_TRUE(Matches(factory.CreatePathPrefixCondition(""), url));
+  EXPECT_TRUE(Matches(factory.CreatePathPrefixCondition("/web"), url));
+  EXPECT_TRUE(Matches(factory.CreatePathPrefixCondition("/webhp"), url));
+  EXPECT_FALSE(Matches(factory.CreatePathPrefixCondition("webhp"), url));
+  EXPECT_FALSE(Matches(factory.CreatePathPrefixCondition("/webhp?"), url));
+
+  EXPECT_TRUE(Matches(factory.CreatePathSuffixCondition(""), url));
+  EXPECT_TRUE(Matches(factory.CreatePathSuffixCondition("webhp"), url));
+  EXPECT_TRUE(Matches(factory.CreatePathSuffixCondition("/webhp"), url));
+  EXPECT_FALSE(Matches(factory.CreatePathSuffixCondition("/web"), url));
+  EXPECT_FALSE(Matches(factory.CreatePathSuffixCondition("/webhp?"), url));
+
+  EXPECT_TRUE(Matches(factory.CreatePathEqualsCondition("/webhp"), url));
+  EXPECT_FALSE(Matches(factory.CreatePathEqualsCondition("webhp"), url));
+  EXPECT_FALSE(Matches(factory.CreatePathEqualsCondition("/webhp?"), url));
+  EXPECT_FALSE(
+      Matches(factory.CreatePathEqualsCondition("www.google.com"), url));
+
+
+  // Test query component.
+  EXPECT_TRUE(Matches(factory.CreateQueryPrefixCondition(""), url));
+  EXPECT_TRUE(Matches(factory.CreateQueryPrefixCondition("?sourceid"), url));
+  EXPECT_FALSE(Matches(factory.CreatePathPrefixCondition("sourceid"), url));
+
+  EXPECT_TRUE(Matches(factory.CreateQuerySuffixCondition(""), url));
+  EXPECT_TRUE(Matches(factory.CreateQuerySuffixCondition("ion=1"), url));
+  EXPECT_FALSE(Matches(factory.CreatePathPrefixCondition("?sourceid"), url));
+  EXPECT_FALSE(Matches(factory.CreateQuerySuffixCondition("www"), url));
+
+  EXPECT_TRUE(Matches(factory.CreateQueryEqualsCondition(
+      "?sourceid=chrome-instant&ie=UTF-8&ion=1"), url));
+  EXPECT_FALSE(Matches(factory.CreateQueryEqualsCondition(
+        "sourceid=chrome-instant&ie=UTF-8&ion="), url));
+  EXPECT_FALSE(
+      Matches(factory.CreateQueryEqualsCondition("www.google.com"), url));
+
+
+  // Test adjacent components
+  EXPECT_TRUE(Matches(factory.CreateHostSuffixPathPrefixCondition(
+      "google.com", "/webhp"), url));
+  EXPECT_TRUE(Matches(factory.CreateHostSuffixPathPrefixCondition(
+        "", "/webhp"), url));
+  EXPECT_TRUE(Matches(factory.CreateHostSuffixPathPrefixCondition(
+        "google.com", ""), url));
+  EXPECT_FALSE(Matches(factory.CreateHostSuffixPathPrefixCondition(
+        "www", ""), url));
+
+  EXPECT_TRUE(Matches(factory.CreateHostEqualsPathPrefixCondition(
+      "www.google.com", "/webhp"), url));
+  EXPECT_FALSE(Matches(factory.CreateHostEqualsPathPrefixCondition(
+        "", "/webhp"), url));
+  EXPECT_TRUE(Matches(factory.CreateHostEqualsPathPrefixCondition(
+        "www.google.com", ""), url));
+  EXPECT_FALSE(Matches(factory.CreateHostEqualsPathPrefixCondition(
+        "google.com", ""), url));
+}
+
+TEST(URLMatcherConditionFactoryTest, TestFullSearches) {
+  // The Port 443 is stripped because it is the default port for https.
+  GURL gurl("https://www.google.com:443/webhp?sourceid=chrome-instant&ie=UTF-8"
+      "&ion=1#hl=en&output=search&sclient=psy-ab&q=chrome%20is%20awesome");
+  URLMatcherConditionFactory factory;
+  std::string url = factory.CanonicalizeURLForFullSearches(gurl);
+
+  EXPECT_TRUE(Matches(factory.CreateURLPrefixCondition(""), url));
+  EXPECT_TRUE(Matches(factory.CreateURLPrefixCondition(
+      "https://www.goog"), url));
+  EXPECT_TRUE(Matches(factory.CreateURLPrefixCondition(
+      "https://www.google.com"), url));
+  EXPECT_TRUE(Matches(factory.CreateURLPrefixCondition(
+      "https://www.google.com/webhp?"), url));
+  EXPECT_FALSE(Matches(factory.CreateURLPrefixCondition(
+      "http://www.google.com"), url));
+  EXPECT_FALSE(Matches(factory.CreateURLPrefixCondition("webhp"), url));
+
+  EXPECT_TRUE(Matches(factory.CreateURLSuffixCondition(""), url));
+  EXPECT_TRUE(Matches(factory.CreateURLSuffixCondition("ion=1"), url));
+  EXPECT_FALSE(Matches(factory.CreateURLSuffixCondition("www"), url));
+
+  EXPECT_TRUE(Matches(factory.CreateURLContainsCondition(""), url));
+  EXPECT_TRUE(Matches(factory.CreateURLContainsCondition("www.goog"), url));
+  EXPECT_TRUE(Matches(factory.CreateURLContainsCondition("webhp"), url));
+  EXPECT_TRUE(Matches(factory.CreateURLContainsCondition("?"), url));
+  EXPECT_TRUE(Matches(factory.CreateURLContainsCondition("sourceid"), url));
+  EXPECT_TRUE(Matches(factory.CreateURLContainsCondition("ion=1"), url));
+  EXPECT_FALSE(Matches(factory.CreateURLContainsCondition(".www.goog"), url));
+  EXPECT_FALSE(Matches(factory.CreateURLContainsCondition("foobar"), url));
+  EXPECT_FALSE(Matches(factory.CreateURLContainsCondition("search"), url));
+  EXPECT_FALSE(Matches(factory.CreateURLContainsCondition(":443"), url));
+
+  EXPECT_TRUE(Matches(factory.CreateURLEqualsCondition(
+      "https://www.google.com/webhp?sourceid=chrome-instant&ie=UTF-8&ion=1"),
+      url));
+  EXPECT_FALSE(
+      Matches(factory.CreateURLEqualsCondition("https://www.google.com"), url));
+
+  // Same as above but this time with a non-standard port.
+  gurl = GURL("https://www.google.com:1234/webhp?sourceid=chrome-instant&"
+      "ie=UTF-8&ion=1#hl=en&output=search&sclient=psy-ab&q=chrome%20is%20"
+      "awesome");
+  url = factory.CanonicalizeURLForFullSearches(gurl);
+  EXPECT_TRUE(Matches(factory.CreateURLPrefixCondition(
+      "https://www.google.com:1234/webhp?"), url));
+  EXPECT_TRUE(Matches(factory.CreateURLContainsCondition(":1234"), url));
+}
+
+//
+// URLMatcherConditionSet
+//
+
+TEST(URLMatcherConditionSetTest, Constructor) {
+  URLMatcherConditionFactory factory;
+  URLMatcherCondition m1 = factory.CreateHostSuffixCondition("example.com");
+  URLMatcherCondition m2 = factory.CreatePathContainsCondition("foo");
+
+  std::set<URLMatcherCondition> conditions;
+  conditions.insert(m1);
+  conditions.insert(m2);
+
+  scoped_refptr<URLMatcherConditionSet> condition_set(
+      new URLMatcherConditionSet(1, conditions));
+  EXPECT_EQ(1, condition_set->id());
+  EXPECT_EQ(2u, condition_set->conditions().size());
+}
+
+TEST(URLMatcherConditionSetTest, Matching) {
+  GURL url1("http://www.example.com/foo?bar=1");
+  GURL url2("http://foo.example.com/index.html");
+  GURL url3("http://www.example.com:80/foo?bar=1");
+  GURL url4("http://www.example.com:8080/foo?bar=1");
+
+  URLMatcherConditionFactory factory;
+  URLMatcherCondition m1 = factory.CreateHostSuffixCondition("example.com");
+  URLMatcherCondition m2 = factory.CreatePathContainsCondition("foo");
+
+  std::set<URLMatcherCondition> conditions;
+  conditions.insert(m1);
+  conditions.insert(m2);
+
+  scoped_refptr<URLMatcherConditionSet> condition_set(
+      new URLMatcherConditionSet(1, conditions));
+  EXPECT_EQ(1, condition_set->id());
+  EXPECT_EQ(2u, condition_set->conditions().size());
+
+  std::set<StringPattern::ID> matching_patterns;
+  matching_patterns.insert(m1.string_pattern()->id());
+  EXPECT_FALSE(condition_set->IsMatch(matching_patterns, url1));
+
+  matching_patterns.insert(m2.string_pattern()->id());
+  EXPECT_TRUE(condition_set->IsMatch(matching_patterns, url1));
+  EXPECT_FALSE(condition_set->IsMatch(matching_patterns, url2));
+
+  // Test scheme filters.
+  scoped_refptr<URLMatcherConditionSet> condition_set2(
+      new URLMatcherConditionSet(1, conditions,
+          scoped_ptr<URLMatcherSchemeFilter>(
+              new URLMatcherSchemeFilter("https")),
+          scoped_ptr<URLMatcherPortFilter>(NULL)));
+  EXPECT_FALSE(condition_set2->IsMatch(matching_patterns, url1));
+  scoped_refptr<URLMatcherConditionSet> condition_set3(
+      new URLMatcherConditionSet(1, conditions,
+          scoped_ptr<URLMatcherSchemeFilter>(
+              new URLMatcherSchemeFilter("http")),
+          scoped_ptr<URLMatcherPortFilter>(NULL)));
+  EXPECT_TRUE(condition_set3->IsMatch(matching_patterns, url1));
+
+  // Test port filters.
+  std::vector<URLMatcherPortFilter::Range> ranges;
+  ranges.push_back(URLMatcherPortFilter::CreateRange(80));
+  scoped_ptr<URLMatcherPortFilter> filter(new URLMatcherPortFilter(ranges));
+  scoped_refptr<URLMatcherConditionSet> condition_set4(
+      new URLMatcherConditionSet(1, conditions,
+          scoped_ptr<URLMatcherSchemeFilter>(NULL), filter.Pass()));
+  EXPECT_TRUE(condition_set4->IsMatch(matching_patterns, url1));
+  EXPECT_TRUE(condition_set4->IsMatch(matching_patterns, url3));
+  EXPECT_FALSE(condition_set4->IsMatch(matching_patterns, url4));
+
+  // Test regex patterns.
+  matching_patterns.clear();
+  URLMatcherCondition r1 = factory.CreateURLMatchesCondition("/fo?oo");
+  std::set<URLMatcherCondition> regex_conditions;
+  regex_conditions.insert(r1);
+  scoped_refptr<URLMatcherConditionSet> condition_set5(
+      new URLMatcherConditionSet(1, regex_conditions));
+  EXPECT_FALSE(condition_set5->IsMatch(matching_patterns, url1));
+  matching_patterns.insert(r1.string_pattern()->id());
+  EXPECT_TRUE(condition_set5->IsMatch(matching_patterns, url1));
+
+  regex_conditions.insert(m1);
+  scoped_refptr<URLMatcherConditionSet> condition_set6(
+      new URLMatcherConditionSet(1, regex_conditions));
+  EXPECT_FALSE(condition_set6->IsMatch(matching_patterns, url1));
+  matching_patterns.insert(m1.string_pattern()->id());
+  EXPECT_TRUE(condition_set6->IsMatch(matching_patterns, url1));
+}
+
+
+//
+// URLMatcher
+//
+
+TEST(URLMatcherTest, FullTest) {
+  GURL url1("http://www.example.com/foo?bar=1");
+  GURL url2("http://foo.example.com/index.html");
+
+  URLMatcher matcher;
+  URLMatcherConditionFactory* factory = matcher.condition_factory();
+
+  // First insert.
+  URLMatcherConditionSet::Conditions conditions1;
+  conditions1.insert(factory->CreateHostSuffixCondition("example.com"));
+  conditions1.insert(factory->CreatePathContainsCondition("foo"));
+
+  const int kConditionSetId1 = 1;
+  URLMatcherConditionSet::Vector insert1;
+  insert1.push_back(make_scoped_refptr(
+      new URLMatcherConditionSet(kConditionSetId1, conditions1)));
+  matcher.AddConditionSets(insert1);
+  EXPECT_EQ(1u, matcher.MatchURL(url1).size());
+  EXPECT_EQ(0u, matcher.MatchURL(url2).size());
+
+  // Second insert.
+  URLMatcherConditionSet::Conditions conditions2;
+  conditions2.insert(factory->CreateHostSuffixCondition("example.com"));
+
+  const int kConditionSetId2 = 2;
+  URLMatcherConditionSet::Vector insert2;
+  insert2.push_back(make_scoped_refptr(
+      new URLMatcherConditionSet(kConditionSetId2, conditions2)));
+  matcher.AddConditionSets(insert2);
+  EXPECT_EQ(2u, matcher.MatchURL(url1).size());
+  EXPECT_EQ(1u, matcher.MatchURL(url2).size());
+
+  // This should be the cached singleton.
+  int patternId1 = factory->CreateHostSuffixCondition(
+      "example.com").string_pattern()->id();
+
+  // Third insert.
+  URLMatcherConditionSet::Conditions conditions3;
+  conditions3.insert(factory->CreateHostSuffixCondition("example.com"));
+  conditions3.insert(factory->CreateURLMatchesCondition("x.*[0-9]"));
+
+  const int kConditionSetId3 = 3;
+  URLMatcherConditionSet::Vector insert3;
+  insert3.push_back(make_scoped_refptr(
+      new URLMatcherConditionSet(kConditionSetId3, conditions3)));
+  matcher.AddConditionSets(insert3);
+  EXPECT_EQ(3u, matcher.MatchURL(url1).size());
+  EXPECT_EQ(1u, matcher.MatchURL(url2).size());
+
+  // Removal of third insert.
+  std::vector<URLMatcherConditionSet::ID> remove3;
+  remove3.push_back(kConditionSetId3);
+  matcher.RemoveConditionSets(remove3);
+  EXPECT_EQ(2u, matcher.MatchURL(url1).size());
+  EXPECT_EQ(1u, matcher.MatchURL(url2).size());
+
+  // Removal of second insert.
+  std::vector<URLMatcherConditionSet::ID> remove2;
+  remove2.push_back(kConditionSetId2);
+  matcher.RemoveConditionSets(remove2);
+  EXPECT_EQ(1u, matcher.MatchURL(url1).size());
+  EXPECT_EQ(0u, matcher.MatchURL(url2).size());
+
+  // Removal of first insert.
+  std::vector<URLMatcherConditionSet::ID> remove1;
+  remove1.push_back(kConditionSetId1);
+  matcher.RemoveConditionSets(remove1);
+  EXPECT_EQ(0u, matcher.MatchURL(url1).size());
+  EXPECT_EQ(0u, matcher.MatchURL(url2).size());
+
+  EXPECT_TRUE(matcher.IsEmpty());
+
+  // The cached singleton in matcher.condition_factory_ should be destroyed to
+  // free memory.
+  int patternId2 = factory->CreateHostSuffixCondition(
+      "example.com").string_pattern()->id();
+  // If patternId1 and patternId2 are different that indicates that
+  // matcher.condition_factory_ does not leak memory by holding onto
+  // unused patterns.
+  EXPECT_NE(patternId1, patternId2);
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/message_bundle.cc b/chrome/common/extensions/message_bundle.cc
new file mode 100644
index 0000000..9e39b98
--- /dev/null
+++ b/chrome/common/extensions/message_bundle.cc
@@ -0,0 +1,345 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/message_bundle.h"
+
+#include <string>
+#include <vector>
+
+#include "base/hash_tables.h"
+#include "base/i18n/rtl.h"
+#include "base/lazy_instance.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/extension_l10n_util.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace errors = extension_manifest_errors;
+
+namespace extensions {
+
+const char* MessageBundle::kContentKey = "content";
+const char* MessageBundle::kMessageKey = "message";
+const char* MessageBundle::kPlaceholdersKey = "placeholders";
+
+const char* MessageBundle::kPlaceholderBegin = "$";
+const char* MessageBundle::kPlaceholderEnd = "$";
+const char* MessageBundle::kMessageBegin = "__MSG_";
+const char* MessageBundle::kMessageEnd = "__";
+
+// Reserved messages names.
+const char* MessageBundle::kUILocaleKey = "@@ui_locale";
+const char* MessageBundle::kBidiDirectionKey = "@@bidi_dir";
+const char* MessageBundle::kBidiReversedDirectionKey =
+    "@@bidi_reversed_dir";
+const char* MessageBundle::kBidiStartEdgeKey = "@@bidi_start_edge";
+const char* MessageBundle::kBidiEndEdgeKey = "@@bidi_end_edge";
+const char* MessageBundle::kExtensionIdKey = "@@extension_id";
+
+// Reserved messages values.
+const char* MessageBundle::kBidiLeftEdgeValue = "left";
+const char* MessageBundle::kBidiRightEdgeValue = "right";
+
+// Formats message in case we encounter a bad formed key in the JSON object.
+// Returns false and sets |error| to actual error message.
+static bool BadKeyMessage(const std::string& name, std::string* error) {
+  *error = base::StringPrintf(
+      "Name of a key \"%s\" is invalid. Only ASCII [a-z], "
+      "[A-Z], [0-9] and \"_\" are allowed.",
+      name.c_str());
+  return false;
+}
+
+// static
+MessageBundle* MessageBundle::Create(const CatalogVector& locale_catalogs,
+                                     std::string* error) {
+  scoped_ptr<MessageBundle> message_bundle(new MessageBundle);
+  if (!message_bundle->Init(locale_catalogs, error))
+    return NULL;
+
+  return message_bundle.release();
+}
+
+bool MessageBundle::Init(const CatalogVector& locale_catalogs,
+                         std::string* error) {
+  dictionary_.clear();
+
+  for (CatalogVector::const_reverse_iterator it = locale_catalogs.rbegin();
+       it != locale_catalogs.rend(); ++it) {
+    DictionaryValue* catalog = (*it).get();
+    for (DictionaryValue::key_iterator key_it = catalog->begin_keys();
+         key_it != catalog->end_keys(); ++key_it) {
+      std::string key(StringToLowerASCII(*key_it));
+      if (!IsValidName(*key_it))
+        return BadKeyMessage(key, error);
+      std::string value;
+      if (!GetMessageValue(*key_it, *catalog, &value, error))
+        return false;
+      // Keys are not case-sensitive.
+      dictionary_[key] = value;
+    }
+  }
+
+  if (!AppendReservedMessagesForLocale(
+      extension_l10n_util::CurrentLocaleOrDefault(), error))
+    return false;
+
+  return true;
+}
+
+bool MessageBundle::AppendReservedMessagesForLocale(
+    const std::string& app_locale, std::string* error) {
+  SubstitutionMap append_messages;
+  append_messages[kUILocaleKey] = app_locale;
+
+  // Calling base::i18n::GetTextDirection on non-UI threads doesn't seems safe,
+  // so we use GetTextDirectionForLocale instead.
+  if (base::i18n::GetTextDirectionForLocale(app_locale.c_str()) ==
+      base::i18n::RIGHT_TO_LEFT) {
+    append_messages[kBidiDirectionKey] = "rtl";
+    append_messages[kBidiReversedDirectionKey] = "ltr";
+    append_messages[kBidiStartEdgeKey] = kBidiRightEdgeValue;
+    append_messages[kBidiEndEdgeKey] = kBidiLeftEdgeValue;
+  } else {
+    append_messages[kBidiDirectionKey] = "ltr";
+    append_messages[kBidiReversedDirectionKey] = "rtl";
+    append_messages[kBidiStartEdgeKey] = kBidiLeftEdgeValue;
+    append_messages[kBidiEndEdgeKey] = kBidiRightEdgeValue;
+  }
+
+  // Add all reserved messages to the dictionary, but check for collisions.
+  SubstitutionMap::iterator it = append_messages.begin();
+  for (; it != append_messages.end(); ++it) {
+    if (ContainsKey(dictionary_, it->first)) {
+      *error = ExtensionErrorUtils::FormatErrorMessage(
+          errors::kReservedMessageFound, it->first);
+      return false;
+    } else {
+      dictionary_[it->first] = it->second;
+    }
+  }
+
+  return true;
+}
+
+bool MessageBundle::GetMessageValue(const std::string& key,
+                                    const DictionaryValue& catalog,
+                                    std::string* value,
+                                    std::string* error) const {
+  // Get the top level tree for given key (name part).
+  const DictionaryValue* name_tree;
+  if (!catalog.GetDictionaryWithoutPathExpansion(key, &name_tree)) {
+    *error = base::StringPrintf("Not a valid tree for key %s.", key.c_str());
+    return false;
+  }
+  // Extract message from it.
+  if (!name_tree->GetString(kMessageKey, value)) {
+    *error = base::StringPrintf(
+        "There is no \"%s\" element for key %s.", kMessageKey, key.c_str());
+    return false;
+  }
+
+  SubstitutionMap placeholders;
+  if (!GetPlaceholders(*name_tree, key, &placeholders, error))
+    return false;
+
+  if (!ReplacePlaceholders(placeholders, value, error))
+    return false;
+
+  return true;
+}
+
+MessageBundle::MessageBundle() {
+}
+
+bool MessageBundle::GetPlaceholders(const DictionaryValue& name_tree,
+                                    const std::string& name_key,
+                                    SubstitutionMap* placeholders,
+                                    std::string* error) const {
+  if (!name_tree.HasKey(kPlaceholdersKey))
+    return true;
+
+  const DictionaryValue* placeholders_tree;
+  if (!name_tree.GetDictionary(kPlaceholdersKey, &placeholders_tree)) {
+    *error = base::StringPrintf("Not a valid \"%s\" element for key %s.",
+                                kPlaceholdersKey, name_key.c_str());
+    return false;
+  }
+
+  for (DictionaryValue::key_iterator key_it = placeholders_tree->begin_keys();
+       key_it != placeholders_tree->end_keys(); ++key_it) {
+    const DictionaryValue* placeholder;
+    const std::string& content_key(*key_it);
+    if (!IsValidName(content_key))
+      return BadKeyMessage(content_key, error);
+    if (!placeholders_tree->GetDictionaryWithoutPathExpansion(content_key,
+                                                              &placeholder)) {
+      *error = base::StringPrintf("Invalid placeholder %s for key %s",
+                                  content_key.c_str(),
+                                  name_key.c_str());
+      return false;
+    }
+    std::string content;
+    if (!placeholder->GetString(kContentKey, &content)) {
+      *error = base::StringPrintf("Invalid \"%s\" element for key %s.",
+                                  kContentKey, name_key.c_str());
+      return false;
+    }
+    (*placeholders)[StringToLowerASCII(content_key)] = content;
+  }
+
+  return true;
+}
+
+bool MessageBundle::ReplacePlaceholders(const SubstitutionMap& placeholders,
+                                        std::string* message,
+                                        std::string* error) const {
+  return ReplaceVariables(placeholders,
+                          kPlaceholderBegin,
+                          kPlaceholderEnd,
+                          message,
+                          error);
+}
+
+bool MessageBundle::ReplaceMessages(std::string* text,
+                                    std::string* error) const {
+  return ReplaceMessagesWithExternalDictionary(dictionary_, text, error);
+}
+
+MessageBundle::~MessageBundle() {
+}
+
+// static
+bool MessageBundle::ReplaceMessagesWithExternalDictionary(
+    const SubstitutionMap& dictionary, std::string* text, std::string* error) {
+  return ReplaceVariables(dictionary, kMessageBegin, kMessageEnd, text, error);
+}
+
+// static
+bool MessageBundle::ReplaceVariables(const SubstitutionMap& variables,
+                                     const std::string& var_begin_delimiter,
+                                     const std::string& var_end_delimiter,
+                                     std::string* message,
+                                     std::string* error) {
+  std::string::size_type beg_index = 0;
+  const std::string::size_type var_begin_delimiter_size =
+    var_begin_delimiter.size();
+  while (true) {
+    beg_index = message->find(var_begin_delimiter, beg_index);
+    if (beg_index == message->npos)
+      return true;
+
+    // Advance it immediately to the begining of possible variable name.
+    beg_index += var_begin_delimiter_size;
+    if (beg_index >= message->size())
+      return true;
+    std::string::size_type end_index =
+      message->find(var_end_delimiter, beg_index);
+    if (end_index == message->npos)
+      return true;
+
+    // Looking for 1 in substring of ...$1$....
+    const std::string& var_name =
+      message->substr(beg_index, end_index - beg_index);
+    if (!IsValidName(var_name))
+      continue;
+    SubstitutionMap::const_iterator it =
+      variables.find(StringToLowerASCII(var_name));
+    if (it == variables.end()) {
+      *error = base::StringPrintf("Variable %s%s%s used but not defined.",
+                                  var_begin_delimiter.c_str(),
+                                  var_name.c_str(),
+                                  var_end_delimiter.c_str());
+      return false;
+    }
+
+    // Replace variable with its value.
+    std::string value = it->second;
+    message->replace(beg_index - var_begin_delimiter_size,
+                     end_index - beg_index + var_begin_delimiter_size +
+                       var_end_delimiter.size(),
+                     value);
+
+    // And position pointer to after the replacement.
+    beg_index += value.size() - var_begin_delimiter_size;
+  }
+
+  return true;
+}
+
+// static
+bool MessageBundle::IsValidName(const std::string& name) {
+  if (name.empty())
+    return false;
+
+  std::string::const_iterator it = name.begin();
+  for (; it != name.end(); ++it) {
+    // Allow only ascii 0-9, a-z, A-Z, and _ in the name.
+    if (!IsAsciiAlpha(*it) && !IsAsciiDigit(*it) && *it != '_' && *it != '@')
+      return false;
+  }
+
+  return true;
+}
+
+// Dictionary interface.
+
+std::string MessageBundle::GetL10nMessage(const std::string& name) const {
+  return GetL10nMessage(name, dictionary_);
+}
+
+// static
+std::string MessageBundle::GetL10nMessage(const std::string& name,
+                                          const SubstitutionMap& dictionary) {
+  SubstitutionMap::const_iterator it =
+    dictionary.find(StringToLowerASCII(name));
+  if (it != dictionary.end()) {
+    return it->second;
+  }
+
+  return "";
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Renderer helper functions.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// Unique class for Singleton.
+struct ExtensionToMessagesMap {
+  ExtensionToMessagesMap();
+  ~ExtensionToMessagesMap();
+
+  // Maps extension ID to message map.
+  ExtensionToL10nMessagesMap messages_map;
+};
+
+static base::LazyInstance<ExtensionToMessagesMap> g_extension_to_messages_map =
+    LAZY_INSTANCE_INITIALIZER;
+
+ExtensionToMessagesMap::ExtensionToMessagesMap() {}
+
+ExtensionToMessagesMap::~ExtensionToMessagesMap() {}
+
+ExtensionToL10nMessagesMap* GetExtensionToL10nMessagesMap() {
+  return &g_extension_to_messages_map.Get().messages_map;
+}
+
+L10nMessagesMap* GetL10nMessagesMap(const std::string& extension_id) {
+  ExtensionToL10nMessagesMap::iterator it =
+      g_extension_to_messages_map.Get().messages_map.find(extension_id);
+  if (it != g_extension_to_messages_map.Get().messages_map.end())
+    return &(it->second);
+
+  return NULL;
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/message_bundle.h b/chrome/common/extensions/message_bundle.h
new file mode 100644
index 0000000..3a5b9f8
--- /dev/null
+++ b/chrome/common/extensions/message_bundle.h
@@ -0,0 +1,169 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_MESSAGE_BUNDLE_H_
+#define CHROME_COMMON_EXTENSIONS_MESSAGE_BUNDLE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/linked_ptr.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+
+// Contains localized extension messages for one locale. Any messages that the
+// locale does not provide are pulled from the default locale.
+class MessageBundle {
+ public:
+  typedef std::map<std::string, std::string> SubstitutionMap;
+  typedef std::vector<linked_ptr<base::DictionaryValue> > CatalogVector;
+
+  // JSON keys of interest for messages file.
+  static const char* kContentKey;
+  static const char* kMessageKey;
+  static const char* kPlaceholdersKey;
+
+  // Begin/end markers for placeholders and messages
+  static const char* kPlaceholderBegin;
+  static const char* kPlaceholderEnd;
+  static const char* kMessageBegin;
+  static const char* kMessageEnd;
+
+  // Reserved message names in the dictionary.
+  // Update i18n documentation when adding new reserved value.
+  static const char* kUILocaleKey;
+  // See http://code.google.com/apis/gadgets/docs/i18n.html#BIDI for
+  // description.
+  // TODO(cira): point to chrome docs once they are out.
+  static const char* kBidiDirectionKey;
+  static const char* kBidiReversedDirectionKey;
+  static const char* kBidiStartEdgeKey;
+  static const char* kBidiEndEdgeKey;
+  // Extension id gets added in the
+  // browser/renderer_host/resource_message_filter.cc to enable message
+  // replacement for non-localized extensions.
+  static const char* kExtensionIdKey;
+
+  // Values for some of the reserved messages.
+  static const char* kBidiLeftEdgeValue;
+  static const char* kBidiRightEdgeValue;
+
+  // Creates MessageBundle or returns NULL if there was an error. Expects
+  // locale_catalogs to be sorted from more specific to less specific, with
+  // default catalog at the end.
+  static MessageBundle* Create(const CatalogVector& locale_catalogs,
+                               std::string* error);
+
+  // Get message from the catalog with given key.
+  // Returned message has all of the internal placeholders resolved to their
+  // value (content).
+  // Returns empty string if it can't find a message.
+  // We don't use simple GetMessage name, since there is a global
+  // #define GetMessage GetMessageW override in Chrome code.
+  std::string GetL10nMessage(const std::string& name) const;
+
+  // Get message from the given catalog with given key.
+  static std::string GetL10nMessage(const std::string& name,
+                                    const SubstitutionMap& dictionary);
+
+  // Number of messages in the catalog.
+  // Used for unittesting only.
+  size_t size() const { return dictionary_.size(); }
+
+  // Replaces all __MSG_message__ with values from the catalog.
+  // Returns false if there is a message in text that's not defined in the
+  // dictionary.
+  bool ReplaceMessages(std::string* text, std::string* error) const;
+  // Static version that accepts dictionary.
+  static bool ReplaceMessagesWithExternalDictionary(
+      const SubstitutionMap& dictionary, std::string* text, std::string* error);
+
+  // Replaces each occurance of variable placeholder with its value.
+  // I.e. replaces __MSG_name__ with value from the catalog with the key "name".
+  // Returns false if for a valid message/placeholder name there is no matching
+  // replacement.
+  // Public for easier unittesting.
+  static bool ReplaceVariables(const SubstitutionMap& variables,
+                               const std::string& var_begin,
+                               const std::string& var_end,
+                               std::string* message,
+                               std::string* error);
+
+  // Allow only ascii 0-9, a-z, A-Z, and _ in the variable name.
+  // Returns false if the input is empty or if it has illegal characters.
+  static bool IsValidName(const std::string& name);
+
+  // Getter for dictionary_.
+  const SubstitutionMap* dictionary() const { return &dictionary_; }
+
+  ~MessageBundle();
+
+ private:
+  // Testing friend.
+  friend class MessageBundleTest;
+
+  // Use Create to create MessageBundle instance.
+  MessageBundle();
+
+  // Initializes the instance from the contents of vector of catalogs.
+  // If the key is not present in more specific catalog we fall back to next one
+  // (less specific).
+  // Returns false on error.
+  bool Init(const CatalogVector& locale_catalogs, std::string* error);
+
+  // Appends locale specific reserved messages to the dictionary.
+  // Returns false if there was a conflict with user defined messages.
+  bool AppendReservedMessagesForLocale(const std::string& application_locale,
+                                       std::string* error);
+
+  // Helper methods that navigate JSON tree and return simplified message.
+  // They replace all $PLACEHOLDERS$ with their value, and return just key/value
+  // of the message.
+  bool GetMessageValue(const std::string& key,
+                       const base::DictionaryValue& catalog,
+                       std::string* value,
+                       std::string* error) const;
+
+  // Get all placeholders for a given message from JSON subtree.
+  bool GetPlaceholders(const base::DictionaryValue& name_tree,
+                       const std::string& name_key,
+                       SubstitutionMap* placeholders,
+                       std::string* error) const;
+
+  // For a given message, replaces all placeholders with their actual value.
+  // Returns false if replacement failed (see ReplaceVariables).
+  bool ReplacePlaceholders(const SubstitutionMap& placeholders,
+                           std::string* message,
+                           std::string* error) const;
+
+  // Holds all messages for application locale.
+  SubstitutionMap dictionary_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Renderer helper typedefs and functions.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// A map of message name to message.
+typedef std::map<std::string, std::string> L10nMessagesMap;
+
+// A map of extension ID to l10n message map.
+typedef std::map<std::string, L10nMessagesMap > ExtensionToL10nMessagesMap;
+
+// Returns the extension_id to messages map.
+ExtensionToL10nMessagesMap* GetExtensionToL10nMessagesMap();
+
+// Returns message map that matches given extension_id, or NULL.
+L10nMessagesMap* GetL10nMessagesMap(const std::string& extension_id);
+
+}  // namsepace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_MESSAGE_BUNDLE_H_
diff --git a/chrome/common/extensions/message_bundle_unittest.cc b/chrome/common/extensions/message_bundle_unittest.cc
new file mode 100644
index 0000000..1d313a1
--- /dev/null
+++ b/chrome/common/extensions/message_bundle_unittest.cc
@@ -0,0 +1,429 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/message_bundle.h"
+
+#include <string>
+#include <vector>
+
+#include "base/i18n/rtl.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/extension_l10n_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace errors = extension_manifest_errors;
+
+namespace extensions {
+
+class MessageBundleTest : public testing::Test {
+ protected:
+  enum BadDictionary {
+    INVALID_NAME,
+    NAME_NOT_A_TREE,
+    EMPTY_NAME_TREE,
+    MISSING_MESSAGE,
+    PLACEHOLDER_NOT_A_TREE,
+    EMPTY_PLACEHOLDER_TREE,
+    CONTENT_MISSING,
+    MESSAGE_PLACEHOLDER_DOESNT_MATCH,
+  };
+
+  // Helper method for dictionary building.
+  void SetDictionary(const std::string& name,
+                     DictionaryValue* subtree,
+                     DictionaryValue* target) {
+    target->Set(name, static_cast<Value*>(subtree));
+  }
+
+  void CreateContentTree(const std::string& name,
+                         const std::string& content,
+                         DictionaryValue* dict) {
+    DictionaryValue* content_tree = new DictionaryValue;
+    content_tree->SetString(MessageBundle::kContentKey, content);
+    SetDictionary(name, content_tree, dict);
+  }
+
+  void CreatePlaceholdersTree(DictionaryValue* dict) {
+    DictionaryValue* placeholders_tree = new DictionaryValue;
+    CreateContentTree("a", "A", placeholders_tree);
+    CreateContentTree("b", "B", placeholders_tree);
+    CreateContentTree("c", "C", placeholders_tree);
+    SetDictionary(MessageBundle::kPlaceholdersKey,
+                  placeholders_tree,
+                  dict);
+  }
+
+  void CreateMessageTree(const std::string& name,
+                         const std::string& message,
+                         bool create_placeholder_subtree,
+                         DictionaryValue* dict) {
+    DictionaryValue* message_tree = new DictionaryValue;
+    if (create_placeholder_subtree)
+      CreatePlaceholdersTree(message_tree);
+    message_tree->SetString(MessageBundle::kMessageKey, message);
+    SetDictionary(name, message_tree, dict);
+  }
+
+  // Caller owns the memory.
+  DictionaryValue* CreateGoodDictionary() {
+    DictionaryValue* dict = new DictionaryValue;
+    CreateMessageTree("n1", "message1 $a$ $b$", true, dict);
+    CreateMessageTree("n2", "message2 $c$", true, dict);
+    CreateMessageTree("n3", "message3", false, dict);
+    return dict;
+  }
+
+  // Caller owns the memory.
+  DictionaryValue* CreateBadDictionary(enum BadDictionary what_is_bad) {
+    DictionaryValue* dict = CreateGoodDictionary();
+    // Now remove/break things.
+    switch (what_is_bad) {
+      case INVALID_NAME:
+        CreateMessageTree("n 5", "nevermind", false, dict);
+        break;
+      case NAME_NOT_A_TREE:
+        dict->SetString("n4", "whatever");
+        break;
+      case EMPTY_NAME_TREE: {
+          DictionaryValue* empty_tree = new DictionaryValue;
+          SetDictionary("n4", empty_tree, dict);
+        }
+        break;
+      case MISSING_MESSAGE:
+        dict->Remove("n1.message", NULL);
+        break;
+      case PLACEHOLDER_NOT_A_TREE:
+        dict->SetString("n1.placeholders", "whatever");
+        break;
+      case EMPTY_PLACEHOLDER_TREE: {
+          DictionaryValue* empty_tree = new DictionaryValue;
+          SetDictionary("n1.placeholders", empty_tree, dict);
+        }
+        break;
+      case CONTENT_MISSING:
+         dict->Remove("n1.placeholders.a.content", NULL);
+        break;
+      case MESSAGE_PLACEHOLDER_DOESNT_MATCH:
+        DictionaryValue* value;
+        dict->Remove("n1.placeholders.a", NULL);
+        dict->GetDictionary("n1.placeholders", &value);
+        CreateContentTree("x", "X", value);
+        break;
+    }
+
+    return dict;
+  }
+
+  unsigned int ReservedMessagesCount() {
+    // Update when adding new reserved messages.
+    return 5U;
+  }
+
+  void CheckReservedMessages(MessageBundle* handler) {
+    std::string ui_locale = extension_l10n_util::CurrentLocaleOrDefault();
+    EXPECT_EQ(ui_locale,
+              handler->GetL10nMessage(MessageBundle::kUILocaleKey));
+
+    std::string text_dir = "ltr";
+    if (base::i18n::GetTextDirectionForLocale(ui_locale.c_str()) ==
+        base::i18n::RIGHT_TO_LEFT)
+      text_dir = "rtl";
+
+    EXPECT_EQ(text_dir, handler->GetL10nMessage(
+        MessageBundle::kBidiDirectionKey));
+  }
+
+  bool AppendReservedMessages(const std::string& application_locale) {
+    std::string error;
+    return handler_->AppendReservedMessagesForLocale(
+        application_locale, &error);
+  }
+
+  std::string CreateMessageBundle() {
+    std::string error;
+    handler_.reset(MessageBundle::Create(catalogs_, &error));
+
+    return error;
+  }
+
+  void ClearDictionary() {
+    handler_->dictionary_.clear();
+  }
+
+  scoped_ptr<MessageBundle> handler_;
+  std::vector<linked_ptr<DictionaryValue> > catalogs_;
+};
+
+TEST_F(MessageBundleTest, ReservedMessagesCount) {
+  ASSERT_EQ(5U, ReservedMessagesCount());
+}
+
+TEST_F(MessageBundleTest, InitEmptyDictionaries) {
+  CreateMessageBundle();
+  EXPECT_TRUE(handler_.get() != NULL);
+  EXPECT_EQ(0U + ReservedMessagesCount(), handler_->size());
+  CheckReservedMessages(handler_.get());
+}
+
+TEST_F(MessageBundleTest, InitGoodDefaultDict) {
+  catalogs_.push_back(linked_ptr<DictionaryValue>(CreateGoodDictionary()));
+  CreateMessageBundle();
+
+  EXPECT_TRUE(handler_.get() != NULL);
+  EXPECT_EQ(3U + ReservedMessagesCount(), handler_->size());
+
+  EXPECT_EQ("message1 A B", handler_->GetL10nMessage("n1"));
+  EXPECT_EQ("message2 C", handler_->GetL10nMessage("n2"));
+  EXPECT_EQ("message3", handler_->GetL10nMessage("n3"));
+  CheckReservedMessages(handler_.get());
+}
+
+TEST_F(MessageBundleTest, InitAppDictConsultedFirst) {
+  catalogs_.push_back(linked_ptr<DictionaryValue>(CreateGoodDictionary()));
+  catalogs_.push_back(linked_ptr<DictionaryValue>(CreateGoodDictionary()));
+
+  DictionaryValue* app_dict = catalogs_[0].get();
+  // Flip placeholders in message of n1 tree.
+  app_dict->SetString("n1.message", "message1 $b$ $a$");
+  // Remove one message from app dict.
+  app_dict->Remove("n2", NULL);
+  // Replace n3 with N3.
+  app_dict->Remove("n3", NULL);
+  CreateMessageTree("N3", "message3_app_dict", false, app_dict);
+
+  CreateMessageBundle();
+
+  EXPECT_TRUE(handler_.get() != NULL);
+  EXPECT_EQ(3U + ReservedMessagesCount(), handler_->size());
+
+  EXPECT_EQ("message1 B A", handler_->GetL10nMessage("n1"));
+  EXPECT_EQ("message2 C", handler_->GetL10nMessage("n2"));
+  EXPECT_EQ("message3_app_dict", handler_->GetL10nMessage("n3"));
+  CheckReservedMessages(handler_.get());
+}
+
+TEST_F(MessageBundleTest, InitBadAppDict) {
+  catalogs_.push_back(
+      linked_ptr<DictionaryValue>(CreateBadDictionary(INVALID_NAME)));
+  catalogs_.push_back(linked_ptr<DictionaryValue>(CreateGoodDictionary()));
+
+  std::string error = CreateMessageBundle();
+
+  EXPECT_TRUE(handler_.get() == NULL);
+  EXPECT_EQ("Name of a key \"n 5\" is invalid. Only ASCII [a-z], "
+            "[A-Z], [0-9] and \"_\" are allowed.", error);
+
+  catalogs_[0].reset(CreateBadDictionary(NAME_NOT_A_TREE));
+  handler_.reset(MessageBundle::Create(catalogs_, &error));
+  EXPECT_TRUE(handler_.get() == NULL);
+  EXPECT_EQ("Not a valid tree for key n4.", error);
+
+  catalogs_[0].reset(CreateBadDictionary(EMPTY_NAME_TREE));
+  handler_.reset(MessageBundle::Create(catalogs_, &error));
+  EXPECT_TRUE(handler_.get() == NULL);
+  EXPECT_EQ("There is no \"message\" element for key n4.", error);
+
+  catalogs_[0].reset(CreateBadDictionary(MISSING_MESSAGE));
+  handler_.reset(MessageBundle::Create(catalogs_, &error));
+  EXPECT_TRUE(handler_.get() == NULL);
+  EXPECT_EQ("There is no \"message\" element for key n1.", error);
+
+  catalogs_[0].reset(CreateBadDictionary(PLACEHOLDER_NOT_A_TREE));
+  handler_.reset(MessageBundle::Create(catalogs_, &error));
+  EXPECT_TRUE(handler_.get() == NULL);
+  EXPECT_EQ("Not a valid \"placeholders\" element for key n1.", error);
+
+  catalogs_[0].reset(CreateBadDictionary(EMPTY_PLACEHOLDER_TREE));
+  handler_.reset(MessageBundle::Create(catalogs_, &error));
+  EXPECT_TRUE(handler_.get() == NULL);
+  EXPECT_EQ("Variable $a$ used but not defined.", error);
+
+  catalogs_[0].reset(CreateBadDictionary(CONTENT_MISSING));
+  handler_.reset(MessageBundle::Create(catalogs_, &error));
+  EXPECT_TRUE(handler_.get() == NULL);
+  EXPECT_EQ("Invalid \"content\" element for key n1.", error);
+
+  catalogs_[0].reset(CreateBadDictionary(MESSAGE_PLACEHOLDER_DOESNT_MATCH));
+  handler_.reset(MessageBundle::Create(catalogs_, &error));
+  EXPECT_TRUE(handler_.get() == NULL);
+  EXPECT_EQ("Variable $a$ used but not defined.", error);
+}
+
+TEST_F(MessageBundleTest, ReservedMessagesOverrideDeveloperMessages) {
+  catalogs_.push_back(linked_ptr<DictionaryValue>(CreateGoodDictionary()));
+
+  DictionaryValue* dict = catalogs_[0].get();
+  CreateMessageTree(MessageBundle::kUILocaleKey, "x", false, dict);
+
+  std::string error = CreateMessageBundle();
+
+  EXPECT_TRUE(handler_.get() == NULL);
+  std::string expected_error = ExtensionErrorUtils::FormatErrorMessage(
+      errors::kReservedMessageFound, MessageBundle::kUILocaleKey);
+  EXPECT_EQ(expected_error, error);
+}
+
+TEST_F(MessageBundleTest, AppendReservedMessagesForLTR) {
+  CreateMessageBundle();
+
+  ASSERT_TRUE(handler_.get() != NULL);
+  ClearDictionary();
+  ASSERT_TRUE(AppendReservedMessages("en_US"));
+
+  EXPECT_EQ("en_US",
+            handler_->GetL10nMessage(MessageBundle::kUILocaleKey));
+  EXPECT_EQ("ltr", handler_->GetL10nMessage(
+      MessageBundle::kBidiDirectionKey));
+  EXPECT_EQ("rtl", handler_->GetL10nMessage(
+      MessageBundle::kBidiReversedDirectionKey));
+  EXPECT_EQ("left", handler_->GetL10nMessage(
+      MessageBundle::kBidiStartEdgeKey));
+  EXPECT_EQ("right", handler_->GetL10nMessage(
+      MessageBundle::kBidiEndEdgeKey));
+}
+
+TEST_F(MessageBundleTest, AppendReservedMessagesForRTL) {
+  CreateMessageBundle();
+
+  ASSERT_TRUE(handler_.get() != NULL);
+  ClearDictionary();
+  ASSERT_TRUE(AppendReservedMessages("he"));
+
+  EXPECT_EQ("he",
+            handler_->GetL10nMessage(MessageBundle::kUILocaleKey));
+  EXPECT_EQ("rtl", handler_->GetL10nMessage(
+      MessageBundle::kBidiDirectionKey));
+  EXPECT_EQ("ltr", handler_->GetL10nMessage(
+      MessageBundle::kBidiReversedDirectionKey));
+  EXPECT_EQ("right", handler_->GetL10nMessage(
+      MessageBundle::kBidiStartEdgeKey));
+  EXPECT_EQ("left", handler_->GetL10nMessage(
+      MessageBundle::kBidiEndEdgeKey));
+}
+
+TEST_F(MessageBundleTest, IsValidNameCheckValidCharacters) {
+  EXPECT_TRUE(MessageBundle::IsValidName(std::string("a__BV_9")));
+  EXPECT_TRUE(MessageBundle::IsValidName(std::string("@@a__BV_9")));
+  EXPECT_FALSE(MessageBundle::IsValidName(std::string("$a__BV_9$")));
+  EXPECT_FALSE(MessageBundle::IsValidName(std::string("a-BV-9")));
+  EXPECT_FALSE(MessageBundle::IsValidName(std::string("a#BV!9")));
+  EXPECT_FALSE(MessageBundle::IsValidName(std::string("a<b")));
+}
+
+struct ReplaceVariables {
+  const char* original;
+  const char* result;
+  const char* error;
+  const char* begin_delimiter;
+  const char* end_delimiter;
+  bool pass;
+};
+
+TEST(MessageBundle, ReplaceMessagesInText) {
+  const char* kMessageBegin = MessageBundle::kMessageBegin;
+  const char* kMessageEnd = MessageBundle::kMessageEnd;
+  const char* kPlaceholderBegin = MessageBundle::kPlaceholderBegin;
+  const char* kPlaceholderEnd = MessageBundle::kPlaceholderEnd;
+
+  static ReplaceVariables test_cases[] = {
+    // Message replacement.
+    { "This is __MSG_siMPle__ message", "This is simple message",
+      "", kMessageBegin, kMessageEnd, true },
+    { "This is __MSG_", "This is __MSG_",
+      "", kMessageBegin, kMessageEnd, true },
+    { "This is __MSG__simple__ message", "This is __MSG__simple__ message",
+      "Variable __MSG__simple__ used but not defined.",
+      kMessageBegin, kMessageEnd, false },
+    { "__MSG_LoNg__", "A pretty long replacement",
+      "", kMessageBegin, kMessageEnd, true },
+    { "A __MSG_SimpLE__MSG_ a", "A simpleMSG_ a",
+      "", kMessageBegin, kMessageEnd, true },
+    { "A __MSG_simple__MSG_long__", "A simpleMSG_long__",
+      "", kMessageBegin, kMessageEnd, true },
+    { "A __MSG_simple____MSG_long__", "A simpleA pretty long replacement",
+      "", kMessageBegin, kMessageEnd, true },
+    { "__MSG_d1g1ts_are_ok__", "I are d1g1t",
+      "", kMessageBegin, kMessageEnd, true },
+    // Placeholder replacement.
+    { "This is $sImpLe$ message", "This is simple message",
+       "", kPlaceholderBegin, kPlaceholderEnd, true },
+    { "This is $", "This is $",
+       "", kPlaceholderBegin, kPlaceholderEnd, true },
+    { "This is $$sIMPle$ message", "This is $simple message",
+       "", kPlaceholderBegin, kPlaceholderEnd, true },
+    { "$LONG_V$", "A pretty long replacement",
+       "", kPlaceholderBegin, kPlaceholderEnd, true },
+    { "A $simple$$ a", "A simple$ a",
+       "", kPlaceholderBegin, kPlaceholderEnd, true },
+    { "A $simple$long_v$", "A simplelong_v$",
+       "", kPlaceholderBegin, kPlaceholderEnd, true },
+    { "A $simple$$long_v$", "A simpleA pretty long replacement",
+       "", kPlaceholderBegin, kPlaceholderEnd, true },
+    { "This is $bad name$", "This is $bad name$",
+       "", kPlaceholderBegin, kPlaceholderEnd, true },
+    { "This is $missing$", "This is $missing$",
+       "Variable $missing$ used but not defined.",
+       kPlaceholderBegin, kPlaceholderEnd, false },
+  };
+
+  MessageBundle::SubstitutionMap messages;
+  messages.insert(std::make_pair("simple", "simple"));
+  messages.insert(std::make_pair("long", "A pretty long replacement"));
+  messages.insert(std::make_pair("long_v", "A pretty long replacement"));
+  messages.insert(std::make_pair("bad name", "Doesn't matter"));
+  messages.insert(std::make_pair("d1g1ts_are_ok", "I are d1g1t"));
+
+  for (size_t i = 0; i < arraysize(test_cases); ++i) {
+    std::string text = test_cases[i].original;
+    std::string error;
+    EXPECT_EQ(test_cases[i].pass,
+              MessageBundle::ReplaceVariables(messages,
+                                              test_cases[i].begin_delimiter,
+                                              test_cases[i].end_delimiter,
+                                              &text,
+                                              &error));
+    EXPECT_EQ(test_cases[i].result, text);
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Renderer helper functions test.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(GetExtensionToL10nMessagesMapTest, ReturnsTheSameObject) {
+  ExtensionToL10nMessagesMap* map1 = GetExtensionToL10nMessagesMap();
+  ASSERT_TRUE(NULL != map1);
+
+  ExtensionToL10nMessagesMap* map2 = GetExtensionToL10nMessagesMap();
+  ASSERT_EQ(map1, map2);
+}
+
+TEST(GetExtensionToL10nMessagesMapTest, ReturnsNullForUnknownExtensionId) {
+  const std::string extension_id("some_unique_12334212314234_id");
+  L10nMessagesMap* map = GetL10nMessagesMap(extension_id);
+  EXPECT_TRUE(NULL == map);
+}
+
+TEST(GetExtensionToL10nMessagesMapTest, ReturnsMapForKnownExtensionId) {
+  const std::string extension_id("some_unique_121212121212121_id");
+  // Store a map for given id.
+  L10nMessagesMap messages;
+  messages.insert(std::make_pair("message_name", "message_value"));
+  (*GetExtensionToL10nMessagesMap())[extension_id] = messages;
+
+  L10nMessagesMap* map = GetL10nMessagesMap(extension_id);
+  ASSERT_TRUE(NULL != map);
+  EXPECT_EQ(1U, map->size());
+  EXPECT_EQ("message_value", (*map)["message_name"]);
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/permissions/api_permission.cc b/chrome/common/extensions/permissions/api_permission.cc
new file mode 100644
index 0000000..114f35d
--- /dev/null
+++ b/chrome/common/extensions/permissions/api_permission.cc
@@ -0,0 +1,345 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/permissions/api_permission.h"
+
+#include "chrome/common/extensions/permissions/permissions_info.h"
+#include "chrome/common/extensions/permissions/socket_permission.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+using extensions::APIPermission;
+using extensions::APIPermissionInfo;
+using extensions::PermissionMessage;
+using extensions::PermissionMessages;
+
+const char kOldUnlimitedStoragePermission[] = "unlimited_storage";
+const char kWindowsPermission[] = "windows";
+
+class SimpleAPIPermission : public APIPermission {
+ public:
+  explicit SimpleAPIPermission(const APIPermissionInfo* permission)
+    : APIPermission(permission) { }
+
+  virtual ~SimpleAPIPermission() { }
+
+  virtual bool HasMessages() const OVERRIDE {
+    return info()->message_id() > PermissionMessage::kNone;
+  }
+
+  virtual PermissionMessages GetMessages() const OVERRIDE {
+    DCHECK(HasMessages());
+    PermissionMessages result;
+    result.push_back(GetMessage_());
+    return result;
+  }
+
+  virtual bool Check(
+      const APIPermission::CheckParam* param) const OVERRIDE {
+    return !param;
+  }
+
+  virtual bool Contains(const APIPermission* rhs) const OVERRIDE {
+    CHECK(info() == rhs->info());
+    return true;
+  }
+
+  virtual bool Equal(const APIPermission* rhs) const OVERRIDE {
+    if (this == rhs)
+      return true;
+    CHECK(info() == rhs->info());
+    return true;
+  }
+
+  virtual bool FromValue(const base::Value* value) OVERRIDE {
+    if (value)
+      return false;
+    return true;
+  }
+
+  virtual void ToValue(base::Value** value) const OVERRIDE {
+    *value = NULL;
+  }
+
+  virtual APIPermission* Clone() const OVERRIDE {
+    return new SimpleAPIPermission(info());
+  }
+
+  virtual APIPermission* Diff(const APIPermission* rhs) const OVERRIDE {
+    CHECK(info() == rhs->info());
+    return NULL;
+  }
+
+  virtual APIPermission* Union(const APIPermission* rhs) const OVERRIDE {
+    CHECK(info() == rhs->info());
+    return new SimpleAPIPermission(info());
+  }
+
+  virtual APIPermission* Intersect(const APIPermission* rhs) const OVERRIDE {
+    CHECK(info() == rhs->info());
+    return new SimpleAPIPermission(info());
+  }
+
+  virtual void Write(IPC::Message* m) const OVERRIDE { }
+
+  virtual bool Read(const IPC::Message* m, PickleIterator* iter) OVERRIDE {
+    return true;
+  }
+
+  virtual void Log(std::string* log) const OVERRIDE { }
+};
+
+template<typename T>
+APIPermission* CreateAPIPermission(const APIPermissionInfo* permission) {
+  return new T(permission);
+}
+
+}  // namespace
+
+namespace extensions {
+
+APIPermission::APIPermission(const APIPermissionInfo* info)
+  : info_(info) {
+  DCHECK(info_);
+}
+
+APIPermission::~APIPermission() { }
+
+APIPermission::ID APIPermission::id() const {
+  return info()->id();
+}
+
+const char* APIPermission::name() const {
+  return info()->name();
+}
+
+PermissionMessage APIPermission::GetMessage_() const {
+  return info()->GetMessage_();
+}
+
+//
+// APIPermissionInfo
+//
+
+APIPermissionInfo::APIPermissionInfo(
+    APIPermission::ID id,
+    const char* name,
+    int l10n_message_id,
+    PermissionMessage::ID message_id,
+    int flags,
+    APIPermissionConstructor api_permission_constructor)
+    : id_(id),
+      name_(name),
+      flags_(flags),
+      l10n_message_id_(l10n_message_id),
+      message_id_(message_id),
+      api_permission_constructor_(api_permission_constructor) { }
+
+
+APIPermissionInfo::~APIPermissionInfo() { }
+
+APIPermission* APIPermissionInfo::CreateAPIPermission() const {
+  return api_permission_constructor_ ?
+    api_permission_constructor_(this) : new SimpleAPIPermission(this);
+}
+
+PermissionMessage APIPermissionInfo::GetMessage_() const {
+  return PermissionMessage(
+      message_id_, l10n_util::GetStringUTF16(l10n_message_id_));
+}
+
+// static
+void APIPermissionInfo::RegisterAllPermissions(
+    PermissionsInfo* info) {
+
+  struct PermissionRegistration {
+    APIPermission::ID id;
+    const char* name;
+    int flags;
+    int l10n_message_id;
+    PermissionMessage::ID message_id;
+    APIPermissionConstructor constructor;
+  } PermissionsToRegister[] = {
+    // Register permissions for all extension types.
+    { APIPermission::kBackground, "background" },
+    { APIPermission::kClipboardRead, "clipboardRead", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_CLIPBOARD,
+      PermissionMessage::kClipboard },
+    { APIPermission::kClipboardWrite, "clipboardWrite" },
+    { APIPermission::kDeclarativeWebRequest, "declarativeWebRequest" },
+    { APIPermission::kDownloads, "downloads", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_DOWNLOADS,
+      PermissionMessage::kDownloads },
+    { APIPermission::kExperimental, "experimental", kFlagCannotBeOptional },
+    { APIPermission::kGeolocation, "geolocation", kFlagCannotBeOptional,
+      IDS_EXTENSION_PROMPT_WARNING_GEOLOCATION,
+      PermissionMessage::kGeolocation },
+    { APIPermission::kNotification, "notifications" },
+    { APIPermission::kUnlimitedStorage, "unlimitedStorage",
+      kFlagCannotBeOptional },
+
+    // Register hosted and packaged app permissions.
+    { APIPermission::kAppNotifications, "appNotifications" },
+
+    // Register extension permissions.
+    { APIPermission::kActiveTab, "activeTab" },
+    { APIPermission::kAlarms, "alarms" },
+    { APIPermission::kBookmark, "bookmarks", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_BOOKMARKS,
+      PermissionMessage::kBookmarks },
+    { APIPermission::kBrowsingData, "browsingData" },
+    { APIPermission::kContentSettings, "contentSettings", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_CONTENT_SETTINGS,
+      PermissionMessage::kContentSettings },
+    { APIPermission::kContextMenus, "contextMenus" },
+    { APIPermission::kCookie, "cookies" },
+    { APIPermission::kFileBrowserHandler, "fileBrowserHandler",
+      kFlagCannotBeOptional },
+    { APIPermission::kFontSettings, "fontSettings", kFlagCannotBeOptional },
+    { APIPermission::kHistory, "history", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_BROWSING_HISTORY,
+      PermissionMessage::kBrowsingHistory },
+    { APIPermission::kIdle, "idle" },
+    { APIPermission::kInput, "input", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_INPUT,
+      PermissionMessage::kInput },
+    { APIPermission::kManagement, "management", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_MANAGEMENT,
+      PermissionMessage::kManagement },
+    { APIPermission::kPrivacy, "privacy", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_PRIVACY,
+      PermissionMessage::kPrivacy },
+    { APIPermission::kStorage, "storage" },
+    // TODO(kinuko): syncFileSystem permission should take the service name
+    // parameter.
+    { APIPermission::kSyncFileSystem, "syncFileSystem" },
+    { APIPermission::kTab, "tabs", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_TABS,
+      PermissionMessage::kTabs },
+    { APIPermission::kTopSites, "topSites", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_BROWSING_HISTORY,
+      PermissionMessage::kBrowsingHistory },
+    { APIPermission::kTts, "tts", 0, kFlagCannotBeOptional },
+    { APIPermission::kTtsEngine, "ttsEngine", kFlagCannotBeOptional,
+      IDS_EXTENSION_PROMPT_WARNING_TTS_ENGINE,
+      PermissionMessage::kTtsEngine },
+    { APIPermission::kWebNavigation, "webNavigation", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_TABS, PermissionMessage::kTabs },
+    { APIPermission::kWebRequest, "webRequest" },
+    { APIPermission::kWebRequestBlocking, "webRequestBlocking" },
+    { APIPermission::kWebView, "webview", kFlagCannotBeOptional },
+
+    // Register private permissions.
+    { APIPermission::kBookmarkManagerPrivate, "bookmarkManagerPrivate",
+      kFlagCannotBeOptional },
+    { APIPermission::kChromeosInfoPrivate, "chromeosInfoPrivate",
+      kFlagCannotBeOptional },
+    { APIPermission::kFileBrowserHandlerInternal, "fileBrowserHandlerInternal",
+      kFlagCannotBeOptional },
+    { APIPermission::kFileBrowserPrivate, "fileBrowserPrivate",
+      kFlagCannotBeOptional },
+    { APIPermission::kManagedModePrivate, "managedModePrivate",
+      kFlagCannotBeOptional },
+    { APIPermission::kMediaPlayerPrivate, "mediaPlayerPrivate",
+      kFlagCannotBeOptional },
+    { APIPermission::kMetricsPrivate, "metricsPrivate",
+      kFlagCannotBeOptional },
+    { APIPermission::kSystemPrivate, "systemPrivate",
+      kFlagCannotBeOptional },
+    { APIPermission::kCloudPrintPrivate, "cloudPrintPrivate",
+      kFlagCannotBeOptional },
+    { APIPermission::kInputMethodPrivate, "inputMethodPrivate",
+      kFlagCannotBeOptional },
+    { APIPermission::kEchoPrivate, "echoPrivate", kFlagCannotBeOptional },
+    { APIPermission::kRtcPrivate, "rtcPrivate", kFlagCannotBeOptional },
+    { APIPermission::kTerminalPrivate, "terminalPrivate",
+      kFlagCannotBeOptional },
+    { APIPermission::kWallpaperPrivate, "wallpaperPrivate",
+      kFlagCannotBeOptional },
+    { APIPermission::kWebRequestInternal, "webRequestInternal" },
+    { APIPermission::kWebSocketProxyPrivate, "webSocketProxyPrivate",
+      kFlagCannotBeOptional },
+    { APIPermission::kWebstorePrivate, "webstorePrivate",
+      kFlagCannotBeOptional },
+    { APIPermission::kMediaGalleriesPrivate, "mediaGalleriesPrivate",
+      kFlagCannotBeOptional },
+
+    // Full url access permissions.
+    { APIPermission::kDebugger, "debugger",
+      kFlagImpliesFullURLAccess | kFlagCannotBeOptional,
+      IDS_EXTENSION_PROMPT_WARNING_DEBUGGER,
+      PermissionMessage::kDebugger },
+    { APIPermission::kDevtools, "devtools",
+      kFlagImpliesFullURLAccess | kFlagCannotBeOptional },
+    { APIPermission::kPageCapture, "pageCapture",
+      kFlagImpliesFullURLAccess },
+    { APIPermission::kTabCapture, "tabCapture",
+      kFlagImpliesFullURLAccess },
+    { APIPermission::kPlugin, "plugin",
+      kFlagImpliesFullURLAccess | kFlagImpliesFullAccess |
+          kFlagCannotBeOptional,
+      IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS,
+      PermissionMessage::kFullAccess },
+    { APIPermission::kProxy, "proxy",
+      kFlagImpliesFullURLAccess | kFlagCannotBeOptional },
+
+    // Platform-app permissions.
+    { APIPermission::kSerial, "serial", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_SERIAL,
+      PermissionMessage::kSerial },
+    // Because warning messages for the "socket" permission vary based on the
+    // permissions parameters, no message ID or message text is specified here.
+    // The message ID and text used will be determined at run-time in the
+    // |SocketPermission| class.
+    { APIPermission::kSocket, "socket", kFlagCannotBeOptional, 0,
+      PermissionMessage::kNone, &::CreateAPIPermission<SocketPermission> },
+    { APIPermission::kAppCurrentWindowInternal, "app.currentWindowInternal" },
+    { APIPermission::kAppRuntime, "app.runtime" },
+    { APIPermission::kAppWindow, "app.window" },
+    { APIPermission::kAudioCapture, "audioCapture", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_AUDIO_CAPTURE,
+      PermissionMessage::kAudioCapture },
+    { APIPermission::kVideoCapture, "videoCapture", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_VIDEO_CAPTURE,
+      PermissionMessage::kVideoCapture },
+    // The permission string for "fileSystem" is only shown when "write" is
+    // present. Read-only access is only granted after the user has been shown
+    // a file chooser dialog and selected a file. Selecting the file is
+    // considered consent to read it.
+    { APIPermission::kFileSystem, "fileSystem" },
+    { APIPermission::kFileSystemWrite, "fileSystem.write", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_FILE_SYSTEM_WRITE,
+      PermissionMessage::kFileSystemWrite },
+    { APIPermission::kMediaGalleries, "mediaGalleries" },
+    { APIPermission::kMediaGalleriesRead, "mediaGalleries.read" },
+    { APIPermission::kMediaGalleriesAllAutoDetected,
+      "mediaGalleries.allAutoDetected", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_ALL_GALLERIES,
+      PermissionMessage::kMediaGalleriesAllGalleries },
+    { APIPermission::kPushMessaging, "pushMessaging", kFlagCannotBeOptional },
+    { APIPermission::kBluetooth, "bluetooth", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_BLUETOOTH,
+      PermissionMessage::kBluetooth },
+    { APIPermission::kUsb, "usb", kFlagNone,
+      IDS_EXTENSION_PROMPT_WARNING_USB,
+      PermissionMessage::kUsb },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(PermissionsToRegister); ++i) {
+    const PermissionRegistration& pr = PermissionsToRegister[i];
+    info->RegisterPermission(
+        pr.id, pr.name, pr.l10n_message_id,
+        pr.message_id ? pr.message_id : PermissionMessage::kNone,
+        pr.flags,
+        pr.constructor);
+  }
+
+  // Register aliases.
+  info->RegisterAlias("unlimitedStorage", kOldUnlimitedStoragePermission);
+  info->RegisterAlias("tabs", kWindowsPermission);
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/permissions/api_permission.h b/chrome/common/extensions/permissions/api_permission.h
new file mode 100644
index 0000000..07d0631
--- /dev/null
+++ b/chrome/common/extensions/permissions/api_permission.h
@@ -0,0 +1,275 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_PERMISSIONS_API_PERMISSION_H_
+#define CHROME_COMMON_EXTENSIONS_PERMISSIONS_API_PERMISSION_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "base/pickle.h"
+#include "chrome/common/extensions/permissions/permission_message.h"
+
+namespace base {
+class Value;
+}
+
+namespace IPC {
+class Message;
+}
+
+namespace extensions {
+
+class APIPermissionInfo;
+class PermissionsInfo;
+
+// APIPermission is for handling some complex permissions. Please refer to
+// extensions::SocketPermission as an example.
+// There is one instance per permission per loaded extension.
+class APIPermission {
+ public:
+  enum ID {
+    // Error codes.
+    kInvalid = -2,
+    kUnknown = -1,
+
+    // Real permissions.
+    kActiveTab,
+    kAlarms,
+    kAppCurrentWindowInternal,
+    kAppNotifications,
+    kAppRuntime,
+    kAppWindow,
+    kAudioCapture,
+    kBackground,
+    kBluetooth,
+    kBookmark,
+    kBookmarkManagerPrivate,
+    kBrowsingData,
+    kChromeosInfoPrivate,
+    kClipboardRead,
+    kClipboardWrite,
+    kCloudPrintPrivate,
+    kContentSettings,
+    kContextMenus,
+    kCookie,
+    kDebugger,
+    kDeclarative,
+    kDeclarativeWebRequest,
+    kDevtools,
+    kDownloads,
+    kEchoPrivate,
+    kExperimental,
+    kFileBrowserHandler,
+    kFileBrowserHandlerInternal,
+    kFileBrowserPrivate,
+    kFileSystem,
+    kFileSystemWrite,
+    kFontSettings,
+    kGeolocation,
+    kHistory,
+    kIdle,
+    kInput,
+    kInputMethodPrivate,
+    kManagedModePrivate,
+    kManagement,
+    kMediaGalleries,
+    kMediaGalleriesRead,
+    kMediaGalleriesAllAutoDetected,
+    kMediaGalleriesPrivate,
+    kMediaPlayerPrivate,
+    kMetricsPrivate,
+    kNotification,
+    kPageCapture,
+    kPlugin,
+    kPrivacy,
+    kProxy,
+    kPushMessaging,
+    kRtcPrivate,
+    kSerial,
+    kSocket,
+    kStorage,
+    kSyncFileSystem,
+    kSystemPrivate,
+    kTab,
+    kTabCapture,
+    kTerminalPrivate,
+    kTopSites,
+    kTts,
+    kTtsEngine,
+    kUnlimitedStorage,
+    kUsb,
+    kVideoCapture,
+    kWallpaperPrivate,
+    kWebNavigation,
+    kWebRequest,
+    kWebRequestBlocking,
+    kWebRequestInternal,
+    kWebSocketProxyPrivate,
+    kWebstorePrivate,
+    kWebView,
+    kEnumBoundary
+  };
+
+  struct CheckParam {
+  };
+
+  explicit APIPermission(const APIPermissionInfo* info);
+
+  virtual ~APIPermission();
+
+  // Returns the id of this permission.
+  ID id() const;
+
+  // Returns the name of this permission.
+  const char* name() const;
+
+  // Returns the APIPermission of this permission.
+  const APIPermissionInfo* info() const {
+    return info_;
+  }
+
+  // Returns true if this permission has any PermissionMessages.
+  virtual bool HasMessages() const = 0;
+
+  // Returns the localized permission messages of this permission.
+  virtual PermissionMessages GetMessages() const = 0;
+
+  // Returns true if the given permission is allowed.
+  virtual bool Check(const CheckParam* param) const = 0;
+
+  // Returns true if |rhs| is a subset of this.
+  virtual bool Contains(const APIPermission* rhs) const = 0;
+
+  // Returns true if |rhs| is equal to this.
+  virtual bool Equal(const APIPermission* rhs) const = 0;
+
+  // Parses the APIPermission from |value|. Returns false if error happens.
+  virtual bool FromValue(const base::Value* value) = 0;
+
+  // Stores this into a new created |value|.
+  virtual void ToValue(base::Value** value) const = 0;
+
+  // Clones this.
+  virtual APIPermission* Clone() const = 0;
+
+  // Returns a new API permission which equals this - |rhs|.
+  virtual APIPermission* Diff(const APIPermission* rhs) const = 0;
+
+  // Returns a new API permission which equals the union of this and |rhs|.
+  virtual APIPermission* Union(const APIPermission* rhs) const = 0;
+
+  // Returns a new API permission which equals the intersect of this and |rhs|.
+  virtual APIPermission* Intersect(const APIPermission* rhs) const = 0;
+
+  // IPC functions
+  // Writes this into the given IPC message |m|.
+  virtual void Write(IPC::Message* m) const = 0;
+
+  // Reads from the given IPC message |m|.
+  virtual bool Read(const IPC::Message* m, PickleIterator* iter) = 0;
+
+  // Logs this permission.
+  virtual void Log(std::string* log) const = 0;
+
+ protected:
+  // Returns the localized permission message associated with this api.
+  // Use GetMessage_ to avoid name conflict with macro GetMessage on Windows.
+  PermissionMessage GetMessage_() const;
+
+ private:
+  const APIPermissionInfo* const info_;
+};
+
+
+// The APIPermissionInfo is an immutable class that describes a single
+// named permission (API permission).
+// There is one instance per permission.
+class APIPermissionInfo {
+ public:
+  enum Flag {
+    kFlagNone = 0,
+
+    // Indicates if the permission implies full access (native code).
+    kFlagImpliesFullAccess = 1 << 0,
+
+    // Indicates if the permission implies full URL access.
+    kFlagImpliesFullURLAccess = 1 << 1,
+
+    // Indicates that extensions cannot specify the permission as optional.
+    kFlagCannotBeOptional = 1 << 3
+  };
+
+  typedef APIPermission* (*APIPermissionConstructor)(const APIPermissionInfo*);
+
+  typedef std::set<APIPermission::ID> IDSet;
+
+  ~APIPermissionInfo();
+
+  // Creates a APIPermission instance.
+  APIPermission* CreateAPIPermission() const;
+
+  int flags() const { return flags_; }
+
+  APIPermission::ID id() const { return id_; }
+
+  // Returns the message id associated with this permission.
+  PermissionMessage::ID message_id() const {
+    return message_id_;
+  }
+
+  // Returns the name of this permission.
+  const char* name() const { return name_; }
+
+  // Returns true if this permission implies full access (e.g., native code).
+  bool implies_full_access() const {
+    return (flags_ & kFlagImpliesFullAccess) != 0;
+  }
+
+  // Returns true if this permission implies full URL access.
+  bool implies_full_url_access() const {
+    return (flags_ & kFlagImpliesFullURLAccess) != 0;
+  }
+
+  // Returns true if this permission can be added and removed via the
+  // optional permissions extension API.
+  bool supports_optional() const {
+    return (flags_ & kFlagCannotBeOptional) == 0;
+  }
+
+ private:
+  // Instances should only be constructed from within PermissionsInfo.
+  friend class PermissionsInfo;
+  // Implementations of APIPermission will want to get the permission message,
+  // but this class's implementation should be hidden from everyone else.
+  friend class APIPermission;
+
+  explicit APIPermissionInfo(
+      APIPermission::ID id,
+      const char* name,
+      int l10n_message_id,
+      PermissionMessage::ID message_id,
+      int flags,
+      APIPermissionConstructor api_permission_constructor);
+
+  // Register ALL the permissions!
+  static void RegisterAllPermissions(PermissionsInfo* info);
+
+  // Returns the localized permission message associated with this api.
+  // Use GetMessage_ to avoid name conflict with macro GetMessage on Windows.
+  PermissionMessage GetMessage_() const;
+
+  const APIPermission::ID id_;
+  const char* const name_;
+  const int flags_;
+  const int l10n_message_id_;
+  const PermissionMessage::ID message_id_;
+  const APIPermissionConstructor api_permission_constructor_;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_PERMISSIONS_API_PERMISSION_H_
diff --git a/chrome/common/extensions/permissions/api_permission_set.cc b/chrome/common/extensions/permissions/api_permission_set.cc
new file mode 100644
index 0000000..0211e2a
--- /dev/null
+++ b/chrome/common/extensions/permissions/api_permission_set.cc
@@ -0,0 +1,324 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/permissions/api_permission_set.h"
+
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/permissions/permissions_info.h"
+
+namespace errors = extension_manifest_errors;
+
+namespace {
+
+using extensions::PermissionsInfo;
+using extensions::APIPermission;
+using extensions::APIPermissionInfo;
+using extensions::APIPermissionSet;
+
+bool CreateAPIPermission(
+    const std::string& permission_str,
+    const base::Value* permission_value,
+    APIPermissionSet* api_permissions,
+    string16* error,
+    std::vector<std::string>* unhandled_permissions) {
+  PermissionsInfo* info = PermissionsInfo::GetInstance();
+
+  const APIPermissionInfo* permission_info = info->GetByName(permission_str);
+  if (permission_info) {
+    scoped_ptr<APIPermission> permission(
+        permission_info->CreateAPIPermission());
+    if (!permission->FromValue(permission_value)) {
+      if (error) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidPermission, permission_info->name());
+        return false;
+      }
+      LOG(WARNING) << "Parse permission failed.";
+    } else {
+      api_permissions->insert(permission.release());
+    }
+    return true;
+  }
+
+  if (unhandled_permissions)
+    unhandled_permissions->push_back(permission_str);
+  else
+    LOG(WARNING) << "Unknown permission[" << permission_str << "].";
+
+  return true;
+}
+
+bool ParseChildPermissions(const std::string& base_name,
+                          const Value* permission_value,
+                          APIPermissionSet* api_permissions,
+                          string16* error,
+                          std::vector<std::string>* unhandled_permissions) {
+  if (permission_value) {
+    const ListValue* permissions;
+    if (!permission_value->GetAsList(&permissions)) {
+      if (error) {
+        *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+            errors::kInvalidPermission, base_name);
+        return false;
+      }
+      LOG(WARNING) << "Permission value is not a list.";
+      // Failed to parse, but since error is NULL, failures are not fatal so
+      // return true here anyway.
+      return true;
+    }
+
+    for (size_t i = 0; i < permissions->GetSize(); ++i) {
+      std::string permission_str;
+      if (!permissions->GetString(i, &permission_str)) {
+        // permission should be a string
+        if (error) {
+          *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+              errors::kInvalidPermission,
+              base_name + '.' + base::IntToString(i));
+          return false;
+        }
+        LOG(WARNING) << "Permission is not a string.";
+        continue;
+      }
+
+      if (!CreateAPIPermission(base_name + '.' + permission_str, NULL,
+          api_permissions, error, unhandled_permissions))
+        return false;
+    }
+  }
+
+  return CreateAPIPermission(base_name, NULL, api_permissions, error, NULL);
+}
+
+}  // namespace
+
+namespace extensions {
+
+APIPermissionSet::APIPermissionSet() {
+}
+
+APIPermissionSet::APIPermissionSet(const APIPermissionSet& set) {
+  this->operator=(set);
+}
+
+APIPermissionSet::~APIPermissionSet() {
+}
+
+APIPermissionSet::const_iterator::const_iterator(
+    const APIPermissionMap::const_iterator& it)
+  : it_(it) {
+}
+
+APIPermissionSet::const_iterator::const_iterator(
+    const const_iterator& ids_it)
+  : it_(ids_it.it_) {
+}
+
+APIPermissionSet& APIPermissionSet::operator=(const APIPermissionSet& rhs) {
+  const_iterator it = rhs.begin();
+  const const_iterator end = rhs.end();
+  while (it != end) {
+    insert(it->Clone());
+    ++it;
+  }
+  return *this;
+}
+
+bool APIPermissionSet::operator==(const APIPermissionSet& rhs) const {
+  const_iterator it = begin();
+  const_iterator rhs_it = rhs.begin();
+  const_iterator it_end = end();
+  const_iterator rhs_it_end = rhs.end();
+
+  while (it != it_end && rhs_it != rhs_it_end) {
+    if (!it->Equal(*rhs_it))
+      return false;
+    ++it;
+    ++rhs_it;
+  }
+  return it == it_end && rhs_it == rhs_it_end;
+}
+
+void APIPermissionSet::insert(APIPermission::ID id) {
+  const APIPermissionInfo* permission_info =
+    PermissionsInfo::GetInstance()->GetByID(id);
+  insert(permission_info->CreateAPIPermission());
+}
+
+void APIPermissionSet::insert(
+    APIPermission* permission) {
+  map_[permission->id()].reset(permission);
+}
+
+bool APIPermissionSet::Contains(const APIPermissionSet& rhs) const {
+  APIPermissionSet::const_iterator it1 = begin();
+  APIPermissionSet::const_iterator it2 = rhs.begin();
+  APIPermissionSet::const_iterator end1 = end();
+  APIPermissionSet::const_iterator end2 = rhs.end();
+
+  while (it1 != end1 && it2 != end2) {
+    if (it1->id() > it2->id()) {
+      return false;
+    } else if (it1->id() < it2->id()) {
+      ++it1;
+    } else {
+        if (!it1->Contains(*it2))
+          return false;
+      ++it1;
+      ++it2;
+    }
+  }
+
+  return it2 == end2;
+}
+
+void APIPermissionSet::Difference(
+    const APIPermissionSet& set1,
+    const APIPermissionSet& set2,
+    APIPermissionSet* set3) {
+  CHECK(set3);
+  set3->clear();
+
+  APIPermissionSet::const_iterator it1 = set1.begin();
+  APIPermissionSet::const_iterator it2 = set2.begin();
+  const APIPermissionSet::const_iterator end1 = set1.end();
+  const APIPermissionSet::const_iterator end2 = set2.end();
+
+  while (it1 != end1 && it2 != end2) {
+    if (it1->id() < it2->id()) {
+      set3->insert(it1->Clone());
+      ++it1;
+    } else if (it1->id() > it2->id()) {
+      ++it2;
+    } else {
+      APIPermission* p = it1->Diff(*it2);
+      if (p)
+        set3->insert(p);
+      ++it1;
+      ++it2;
+    }
+  }
+
+  while (it1 != end1) {
+    set3->insert(it1->Clone());
+    ++it1;
+  }
+}
+
+void APIPermissionSet::Intersection(
+    const APIPermissionSet& set1,
+    const APIPermissionSet& set2,
+    APIPermissionSet* set3) {
+  DCHECK(set3);
+  set3->clear();
+
+  APIPermissionSet::const_iterator it1 = set1.begin();
+  APIPermissionSet::const_iterator it2 = set2.begin();
+  const APIPermissionSet::const_iterator end1 = set1.end();
+  const APIPermissionSet::const_iterator end2 = set2.end();
+
+  while (it1 != end1 && it2 != end2) {
+    if (it1->id() < it2->id()) {
+      ++it1;
+    } else if (it1->id() > it2->id()) {
+      ++it2;
+    } else {
+      APIPermission* p = it1->Intersect(*it2);
+      if (p)
+        set3->insert(p);
+      ++it1;
+      ++it2;
+    }
+  }
+}
+
+void APIPermissionSet::Union(
+    const APIPermissionSet& set1,
+    const APIPermissionSet& set2,
+    APIPermissionSet* set3) {
+  DCHECK(set3);
+  set3->clear();
+
+  APIPermissionSet::const_iterator it1 = set1.begin();
+  APIPermissionSet::const_iterator it2 = set2.begin();
+  const APIPermissionSet::const_iterator end1 = set1.end();
+  const APIPermissionSet::const_iterator end2 = set2.end();
+
+  while (true) {
+    if (it1 == end1) {
+      while (it2 != end2) {
+        set3->insert(it2->Clone());
+        ++it2;
+      }
+      break;
+    }
+    if (it2 == end2) {
+      while (it1 != end1) {
+        set3->insert(it1->Clone());
+        ++it1;
+      }
+      break;
+    }
+    if (it1->id() < it2->id()) {
+      set3->insert(it1->Clone());
+      ++it1;
+    } else if (it1->id() > it2->id()) {
+      set3->insert(it2->Clone());
+      ++it2;
+    } else {
+      set3->insert(it1->Union(*it2));
+      ++it1;
+      ++it2;
+    }
+  }
+}
+
+// static
+bool APIPermissionSet::ParseFromJSON(
+    const ListValue* permissions,
+    APIPermissionSet* api_permissions,
+    string16* error,
+    std::vector<std::string>* unhandled_permissions) {
+  PermissionsInfo* info = PermissionsInfo::GetInstance();
+  for (size_t i = 0; i < permissions->GetSize(); ++i) {
+    std::string permission_str;
+    const base::Value* permission_value = NULL;
+    if (!permissions->GetString(i, &permission_str)) {
+      const base::DictionaryValue* dict = NULL;
+      // permission should be a string or a single key dict.
+      if (!permissions->GetDictionary(i, &dict) || dict->size() != 1) {
+        if (error) {
+          *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+              errors::kInvalidPermission, base::IntToString(i));
+          return false;
+        }
+        LOG(WARNING) << "Permission is not a string or single key dict.";
+        continue;
+      }
+      base::DictionaryValue::Iterator it(*dict);
+      permission_str = it.key();
+      permission_value = &it.value();
+    }
+
+    // Check if this permission is a special case where its value should
+    // be treated as a list of child permissions.
+    if (info->HasChildPermissions(permission_str)) {
+      if (!ParseChildPermissions(permission_str, permission_value,
+                                 api_permissions, error, unhandled_permissions))
+        return false;
+      continue;
+    }
+
+    if (!CreateAPIPermission(permission_str, permission_value,
+        api_permissions, error, unhandled_permissions))
+      return false;
+  }
+  return true;
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/permissions/api_permission_set.h b/chrome/common/extensions/permissions/api_permission_set.h
new file mode 100644
index 0000000..848484e
--- /dev/null
+++ b/chrome/common/extensions/permissions/api_permission_set.h
@@ -0,0 +1,158 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_PERMISSIONS_API_PERMISSION_SET_H_
+#define CHROME_COMMON_EXTENSIONS_PERMISSIONS_API_PERMISSION_SET_H_
+
+#include <iterator>
+#include <map>
+
+#include "base/memory/linked_ptr.h"
+#include "chrome/common/extensions/permissions/api_permission.h"
+
+namespace base {
+class ListValue;
+}  // namespace base
+
+namespace extensions {
+
+class Extension;
+
+typedef std::map<APIPermission::ID,
+  linked_ptr<APIPermission> > APIPermissionMap;
+
+class APIPermissionSet {
+ public:
+  class const_iterator :
+    public std::iterator<std::input_iterator_tag, const APIPermission*> {
+   public:
+    const_iterator(const APIPermissionMap::const_iterator& it);
+    const_iterator(const const_iterator& ids_it);
+
+    const_iterator& operator++() {
+      ++it_;
+      return *this;
+    }
+
+    const_iterator operator++(int) {
+      const_iterator tmp(it_++);
+      return tmp;
+    }
+
+    bool operator==(const const_iterator& rhs) const {
+      return it_ == rhs.it_;
+    }
+
+    bool operator!=(const const_iterator& rhs) const {
+      return it_ != rhs.it_;
+    }
+
+    const APIPermission* operator*() const {
+      return it_->second.get();
+    }
+
+    const APIPermission* operator->() const {
+      return it_->second.get();
+    }
+
+   private:
+    APIPermissionMap::const_iterator it_;
+  };
+
+  APIPermissionSet();
+
+  APIPermissionSet(const APIPermissionSet& set);
+
+  ~APIPermissionSet();
+
+  const_iterator begin() const {
+    return const_iterator(map().begin());
+  }
+
+  const_iterator end() const {
+    return map().end();
+  }
+
+  const_iterator find(APIPermission::ID id) const {
+    return map().find(id);
+  }
+
+  const APIPermissionMap& map() const {
+    return map_;
+  }
+
+  APIPermissionMap& map() {
+    return map_;
+  }
+
+  void clear() {
+    map_.clear();
+  }
+
+  size_t count(APIPermission::ID id) const {
+    return map().count(id);
+  }
+
+  bool empty() const {
+    return map().empty();
+  }
+
+  size_t erase(APIPermission::ID id) {
+    return map().erase(id);
+  }
+
+  size_t size() const {
+    return map().size();
+  }
+
+  APIPermissionSet& operator=(const APIPermissionSet& rhs);
+
+  bool operator==(const APIPermissionSet& rhs) const;
+
+  bool operator!=(const APIPermissionSet& rhs) const {
+    return !operator==(rhs);
+  }
+
+  void insert(APIPermission::ID id);
+
+  // Insert |permission| into the APIPermissionSet. The APIPermissionSet will
+  // take the ownership of |permission|,
+  void insert(APIPermission* permission);
+
+  bool Contains(const APIPermissionSet& rhs) const;
+
+  static void Difference(
+      const APIPermissionSet& set1,
+      const APIPermissionSet& set2,
+      APIPermissionSet* set3);
+
+  static void Intersection(
+      const APIPermissionSet& set1,
+      const APIPermissionSet& set2,
+      APIPermissionSet* set3);
+
+  static void Union(
+      const APIPermissionSet& set1,
+      const APIPermissionSet& set2,
+      APIPermissionSet* set3);
+
+  // Parses permissions from |permissions_data| and adds the parsed permissions
+  // to |api_permissions|. If |unhandled_permissions| is not NULL the names of
+  // all permissions that couldn't be parsed will be added to this vector.
+  // If |error| is NULL, parsing will continue with the next permission if
+  // invalid data is detected. If |error| is not NULL, it will be set to an
+  // error message and false is returned when an invalid permission is found.
+  static bool ParseFromJSON(
+      const base::ListValue* permissions_data,
+      APIPermissionSet* api_permissions,
+      string16* error,
+      std::vector<std::string>* unhandled_permissions);
+
+ private:
+  APIPermissionMap map_;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_PERMISSIONS_API_PERMISSION_SET_H_
diff --git a/chrome/common/extensions/permissions/api_permission_set_unittest.cc b/chrome/common/extensions/permissions/api_permission_set_unittest.cc
new file mode 100644
index 0000000..dcc7321
--- /dev/null
+++ b/chrome/common/extensions/permissions/api_permission_set_unittest.cc
@@ -0,0 +1,307 @@
+// Copyright (c) 2012 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.
+
+#include "base/pickle.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension_messages.h"
+#include "chrome/common/extensions/permissions/api_permission_set.h"
+#include "chrome/common/extensions/permissions/permissions_info.h"
+#include "ipc/ipc_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+class APIPermissionSetTest : public testing::Test {
+};
+
+TEST(APIPermissionSetTest, General) {
+  APIPermissionSet apis;
+  apis.insert(APIPermission::kTab);
+  apis.insert(APIPermission::kBackground);
+  apis.insert(APIPermission::kProxy);
+  apis.insert(APIPermission::kClipboardWrite);
+  apis.insert(APIPermission::kPlugin);
+
+  EXPECT_EQ(apis.find(APIPermission::kProxy)->id(), APIPermission::kProxy);
+  EXPECT_TRUE(apis.find(APIPermission::kSocket) == apis.end());
+
+  EXPECT_EQ(apis.size(), 5u);
+
+  EXPECT_EQ(apis.erase(APIPermission::kTab), 1u);
+  EXPECT_EQ(apis.size(), 4u);
+
+  EXPECT_EQ(apis.erase(APIPermission::kTab), 0u);
+  EXPECT_EQ(apis.size(), 4u);
+}
+
+TEST(APIPermissionSetTest, CreateUnion) {
+  APIPermission* permission = NULL;
+
+  APIPermissionSet apis1;
+  APIPermissionSet apis2;
+  APIPermissionSet expected_apis;
+  APIPermissionSet result;
+
+  const APIPermissionInfo* permission_info =
+    PermissionsInfo::GetInstance()->GetByID(APIPermission::kSocket);
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("tcp-connect:*.example.com:80"));
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+
+  // Union with an empty set.
+  apis1.insert(APIPermission::kTab);
+  apis1.insert(APIPermission::kBackground);
+  apis1.insert(permission->Clone());
+  expected_apis.insert(APIPermission::kTab);
+  expected_apis.insert(APIPermission::kBackground);
+  expected_apis.insert(permission);
+
+  APIPermissionSet::Union(apis1, apis2, &result);
+
+  EXPECT_TRUE(apis1.Contains(apis2));
+  EXPECT_TRUE(apis1.Contains(result));
+  EXPECT_FALSE(apis2.Contains(apis1));
+  EXPECT_FALSE(apis2.Contains(result));
+  EXPECT_TRUE(result.Contains(apis1));
+  EXPECT_TRUE(result.Contains(apis2));
+
+  EXPECT_EQ(expected_apis, result);
+
+  // Now use a real second set.
+  apis2.insert(APIPermission::kTab);
+  apis2.insert(APIPermission::kProxy);
+  apis2.insert(APIPermission::kClipboardWrite);
+  apis2.insert(APIPermission::kPlugin);
+
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("tcp-connect:*.example.com:80"));
+    value->Append(Value::CreateStringValue("udp-send-to::8899"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  apis2.insert(permission);
+
+  expected_apis.insert(APIPermission::kTab);
+  expected_apis.insert(APIPermission::kProxy);
+  expected_apis.insert(APIPermission::kClipboardWrite);
+  expected_apis.insert(APIPermission::kPlugin);
+
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("tcp-connect:*.example.com:80"));
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    value->Append(Value::CreateStringValue("udp-send-to::8899"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  // Insert a new socket permission which will replace the old one.
+  expected_apis.insert(permission);
+
+  APIPermissionSet::Union(apis1, apis2, &result);
+
+  EXPECT_FALSE(apis1.Contains(apis2));
+  EXPECT_FALSE(apis1.Contains(result));
+  EXPECT_FALSE(apis2.Contains(apis1));
+  EXPECT_FALSE(apis2.Contains(result));
+  EXPECT_TRUE(result.Contains(apis1));
+  EXPECT_TRUE(result.Contains(apis2));
+
+  EXPECT_EQ(expected_apis, result);
+}
+
+TEST(APIPermissionSetTest, CreateIntersection) {
+  APIPermission* permission = NULL;
+
+  APIPermissionSet apis1;
+  APIPermissionSet apis2;
+  APIPermissionSet expected_apis;
+  APIPermissionSet result;
+
+  const APIPermissionInfo* permission_info =
+    PermissionsInfo::GetInstance()->GetByID(APIPermission::kSocket);
+
+  // Intersection with an empty set.
+  apis1.insert(APIPermission::kTab);
+  apis1.insert(APIPermission::kBackground);
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("tcp-connect:*.example.com:80"));
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  apis1.insert(permission);
+
+  APIPermissionSet::Intersection(apis1, apis2, &result);
+  EXPECT_TRUE(apis1.Contains(result));
+  EXPECT_TRUE(apis2.Contains(result));
+  EXPECT_TRUE(apis1.Contains(apis2));
+  EXPECT_FALSE(apis2.Contains(apis1));
+  EXPECT_FALSE(result.Contains(apis1));
+  EXPECT_TRUE(result.Contains(apis2));
+
+  EXPECT_TRUE(result.empty());
+  EXPECT_EQ(expected_apis, result);
+
+  // Now use a real second set.
+  apis2.insert(APIPermission::kTab);
+  apis2.insert(APIPermission::kProxy);
+  apis2.insert(APIPermission::kClipboardWrite);
+  apis2.insert(APIPermission::kPlugin);
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    value->Append(Value::CreateStringValue("udp-send-to::8899"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  apis2.insert(permission);
+
+  expected_apis.insert(APIPermission::kTab);
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  expected_apis.insert(permission);
+
+  APIPermissionSet::Intersection(apis1, apis2, &result);
+
+  EXPECT_TRUE(apis1.Contains(result));
+  EXPECT_TRUE(apis2.Contains(result));
+  EXPECT_FALSE(apis1.Contains(apis2));
+  EXPECT_FALSE(apis2.Contains(apis1));
+  EXPECT_FALSE(result.Contains(apis1));
+  EXPECT_FALSE(result.Contains(apis2));
+
+  EXPECT_EQ(expected_apis, result);
+}
+
+TEST(APIPermissionSetTest, CreateDifference) {
+  APIPermission* permission = NULL;
+
+  APIPermissionSet apis1;
+  APIPermissionSet apis2;
+  APIPermissionSet expected_apis;
+  APIPermissionSet result;
+
+  const APIPermissionInfo* permission_info =
+    PermissionsInfo::GetInstance()->GetByID(APIPermission::kSocket);
+
+  // Difference with an empty set.
+  apis1.insert(APIPermission::kTab);
+  apis1.insert(APIPermission::kBackground);
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("tcp-connect:*.example.com:80"));
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  apis1.insert(permission);
+
+  APIPermissionSet::Difference(apis1, apis2, &result);
+
+  EXPECT_EQ(apis1, result);
+
+  // Now use a real second set.
+  apis2.insert(APIPermission::kTab);
+  apis2.insert(APIPermission::kProxy);
+  apis2.insert(APIPermission::kClipboardWrite);
+  apis2.insert(APIPermission::kPlugin);
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("tcp-connect:*.example.com:80"));
+    value->Append(Value::CreateStringValue("udp-send-to::8899"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  apis2.insert(permission);
+
+  expected_apis.insert(APIPermission::kBackground);
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  expected_apis.insert(permission);
+
+  APIPermissionSet::Difference(apis1, apis2, &result);
+
+  EXPECT_TRUE(apis1.Contains(result));
+  EXPECT_FALSE(apis2.Contains(result));
+
+  EXPECT_EQ(expected_apis, result);
+
+  // |result| = |apis1| - |apis2| --> |result| intersect |apis2| == empty_set
+  APIPermissionSet result2;
+  APIPermissionSet::Intersection(result, apis2, &result2);
+  EXPECT_TRUE(result2.empty());
+}
+
+TEST(APIPermissionSetTest, IPC) {
+  APIPermission* permission = NULL;
+
+  APIPermissionSet apis;
+  APIPermissionSet expected_apis;
+
+  const APIPermissionInfo* permission_info =
+    PermissionsInfo::GetInstance()->GetByID(APIPermission::kSocket);
+
+  apis.insert(APIPermission::kTab);
+  apis.insert(APIPermission::kBackground);
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("tcp-connect:*.example.com:80"));
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  apis.insert(permission);
+
+  EXPECT_NE(apis, expected_apis);
+
+  IPC::Message m;
+  WriteParam(&m, apis);
+  PickleIterator iter(m);
+  CHECK(ReadParam(&m, &iter, &expected_apis));
+  EXPECT_EQ(apis, expected_apis);
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/permissions/permission_message.cc b/chrome/common/extensions/permissions/permission_message.cc
new file mode 100644
index 0000000..0847f0a
--- /dev/null
+++ b/chrome/common/extensions/permissions/permission_message.cc
@@ -0,0 +1,64 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/permissions/permission_message.h"
+
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+//
+// PermissionMessage
+//
+
+// static
+PermissionMessage PermissionMessage::CreateFromHostList(
+    const std::set<std::string>& hosts) {
+  std::vector<std::string> host_list(hosts.begin(), hosts.end());
+  DCHECK_GT(host_list.size(), 0UL);
+  ID message_id;
+  string16 message;
+
+  switch (host_list.size()) {
+    case 1:
+      message_id = kHosts1;
+      message = l10n_util::GetStringFUTF16(IDS_EXTENSION_PROMPT_WARNING_1_HOST,
+                                           UTF8ToUTF16(host_list[0]));
+      break;
+    case 2:
+      message_id = kHosts2;
+      message = l10n_util::GetStringFUTF16(IDS_EXTENSION_PROMPT_WARNING_2_HOSTS,
+                                           UTF8ToUTF16(host_list[0]),
+                                           UTF8ToUTF16(host_list[1]));
+      break;
+    case 3:
+      message_id = kHosts3;
+      message = l10n_util::GetStringFUTF16(IDS_EXTENSION_PROMPT_WARNING_3_HOSTS,
+                                           UTF8ToUTF16(host_list[0]),
+                                           UTF8ToUTF16(host_list[1]),
+                                           UTF8ToUTF16(host_list[2]));
+      break;
+    default:
+      message_id = kHosts4OrMore;
+      message = l10n_util::GetStringFUTF16(
+          IDS_EXTENSION_PROMPT_WARNING_4_OR_MORE_HOSTS,
+          UTF8ToUTF16(host_list[0]),
+          UTF8ToUTF16(host_list[1]),
+          base::IntToString16(hosts.size() - 2));
+      break;
+  }
+
+  return PermissionMessage(message_id, message);
+}
+
+PermissionMessage::PermissionMessage(
+    PermissionMessage::ID id, const string16& message)
+  : id_(id), message_(message) {
+}
+
+PermissionMessage::~PermissionMessage() {}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/permissions/permission_message.h b/chrome/common/extensions/permissions/permission_message.h
new file mode 100644
index 0000000..743efa6
--- /dev/null
+++ b/chrome/common/extensions/permissions/permission_message.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_PERMISSIONS_PERMISSION_MESSAGE_H_
+#define CHROME_COMMON_EXTENSIONS_PERMISSIONS_PERMISSION_MESSAGE_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/string16.h"
+
+namespace extensions {
+
+// When prompting the user to install or approve permissions, we display
+// messages describing the effects of the permissions rather than listing the
+// permissions themselves. Each PermissionMessage represents one of the
+// messages shown to the user.
+class PermissionMessage {
+ public:
+  // Do not reorder this enumeration. If you need to add a new enum, add it just
+  // prior to kEnumBoundary.
+  enum ID {
+    kUnknown,
+    kNone,
+    kBookmarks,
+    kGeolocation,
+    kBrowsingHistory,
+    kTabs,
+    kManagement,
+    kDebugger,
+    kHosts1,
+    kHosts2,
+    kHosts3,
+    kHosts4OrMore,
+    kHostsAll,
+    kFullAccess,
+    kClipboard,
+    kTtsEngine,
+    kContentSettings,
+    kPrivacy,
+    kManagedMode,
+    kInput,
+    kAudioCapture,
+    kVideoCapture,
+    kDownloads,
+    kFileSystemWrite,
+    kMediaGalleriesAllGalleries,
+    kSerial,
+    kSocketAnyHost,
+    kSocketDomainHosts,
+    kSocketSpecificHosts,
+    kBluetooth,
+    kUsb,
+    kEnumBoundary
+  };
+
+  // Creates the corresponding permission message for a list of hosts. This is
+  // simply a convenience method around the constructor, since the messages
+  // change depending on what hosts are present.
+  static PermissionMessage CreateFromHostList(
+      const std::set<std::string>& hosts);
+
+  // Creates the corresponding permission message.
+  PermissionMessage(ID id, const string16& message);
+  ~PermissionMessage();
+
+  // Gets the id of the permission message, which can be used in UMA
+  // histograms.
+  ID id() const { return id_; }
+
+  // Gets a localized message describing this permission. Please note that
+  // the message will be empty for message types TYPE_NONE and TYPE_UNKNOWN.
+  const string16& message() const { return message_; }
+
+  // Comparator to work with std::set.
+  bool operator<(const PermissionMessage& that) const {
+    return id_ < that.id_;
+  }
+
+ private:
+  ID id_;
+  string16 message_;
+};
+
+typedef std::vector<PermissionMessage> PermissionMessages;
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_PERMISSIONS_PERMISSION_MESSAGE_H_
diff --git a/chrome/common/extensions/permissions/permission_set.cc b/chrome/common/extensions/permissions/permission_set.cc
new file mode 100644
index 0000000..cf7225b
--- /dev/null
+++ b/chrome/common/extensions/permissions/permission_set.cc
@@ -0,0 +1,596 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/permissions/permission_set.h"
+
+#include <algorithm>
+#include <iterator>
+#include <string>
+
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/permissions/permissions_info.h"
+#include "chrome/common/extensions/url_pattern.h"
+#include "chrome/common/extensions/url_pattern_set.h"
+#include "content/public/common/url_constants.h"
+#include "grit/generated_resources.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+// Helper for GetDistinctHosts(): com > net > org > everything else.
+bool RcdBetterThan(const std::string& a, const std::string& b) {
+  if (a == b)
+    return false;
+  if (a == "com")
+    return true;
+  if (a == "net")
+    return b != "com";
+  if (a == "org")
+    return b != "com" && b != "net";
+  return false;
+}
+
+// Names of API modules that can be used without listing it in the
+// permissions section of the manifest.
+const char* kNonPermissionModuleNames[] = {
+  "browserAction",
+  "commands",
+  "devtools",
+  "events",
+  "extension",
+  "i18n",
+  "omnibox",
+  "pageAction",
+  "pageActions",
+  "permissions",
+  "runtime",
+  "scriptBadge",
+  "tabs",
+  "test",
+  "types",
+  "windows"
+};
+const size_t kNumNonPermissionModuleNames =
+    arraysize(kNonPermissionModuleNames);
+
+// Names of functions (within modules requiring permissions) that can be used
+// without asking for the module permission. In other words, functions you can
+// use with no permissions specified.
+const char* kNonPermissionFunctionNames[] = {
+  "app.getDetails",
+  "app.getDetailsForFrame",
+  "app.getIsInstalled",
+  "app.install",
+  "app.installState",
+  "app.runningState",
+  "management.getPermissionWarningsByManifest",
+};
+const size_t kNumNonPermissionFunctionNames =
+    arraysize(kNonPermissionFunctionNames);
+
+void AddPatternsAndRemovePaths(const URLPatternSet& set, URLPatternSet* out) {
+  DCHECK(out);
+  for (URLPatternSet::const_iterator i = set.begin(); i != set.end(); ++i) {
+    URLPattern p = *i;
+    p.SetPath("/*");
+    out->AddPattern(p);
+  }
+}
+
+// Strips out the API name from a function or event name.
+// Functions will be of the form api_name.function
+// Events will be of the form api_name/id or api_name.optional.stuff
+std::string GetPermissionName(const std::string& function_name) {
+  size_t separator = function_name.find_first_of("./");
+  if (separator != std::string::npos)
+    return function_name.substr(0, separator);
+  else
+    return function_name;
+}
+
+}  // namespace
+
+namespace extensions {
+
+//
+// PermissionSet
+//
+
+PermissionSet::PermissionSet() {}
+
+PermissionSet::PermissionSet(
+    const extensions::Extension* extension,
+    const APIPermissionSet& apis,
+    const URLPatternSet& explicit_hosts)
+    : apis_(apis) {
+  DCHECK(extension);
+  AddPatternsAndRemovePaths(explicit_hosts, &explicit_hosts_);
+  InitImplicitExtensionPermissions(extension);
+  InitImplicitPermissions();
+  InitEffectiveHosts();
+}
+
+PermissionSet::PermissionSet(
+    const APIPermissionSet& apis,
+    const URLPatternSet& explicit_hosts,
+    const URLPatternSet& scriptable_hosts)
+    : apis_(apis),
+      scriptable_hosts_(scriptable_hosts) {
+  AddPatternsAndRemovePaths(explicit_hosts, &explicit_hosts_);
+  InitImplicitPermissions();
+  InitEffectiveHosts();
+}
+
+// static
+PermissionSet* PermissionSet::CreateDifference(
+    const PermissionSet* set1,
+    const PermissionSet* set2) {
+  scoped_refptr<PermissionSet> empty = new PermissionSet();
+  const PermissionSet* set1_safe = (set1 == NULL) ? empty : set1;
+  const PermissionSet* set2_safe = (set2 == NULL) ? empty : set2;
+
+  APIPermissionSet apis;
+  APIPermissionSet::Difference(set1_safe->apis(), set2_safe->apis(), &apis);
+
+  URLPatternSet explicit_hosts;
+  URLPatternSet::CreateDifference(set1_safe->explicit_hosts(),
+                                  set2_safe->explicit_hosts(),
+                                  &explicit_hosts);
+
+  URLPatternSet scriptable_hosts;
+  URLPatternSet::CreateDifference(set1_safe->scriptable_hosts(),
+                                  set2_safe->scriptable_hosts(),
+                                  &scriptable_hosts);
+
+  return new PermissionSet(apis, explicit_hosts, scriptable_hosts);
+}
+
+// static
+PermissionSet* PermissionSet::CreateIntersection(
+    const PermissionSet* set1,
+    const PermissionSet* set2) {
+  scoped_refptr<PermissionSet> empty = new PermissionSet();
+  const PermissionSet* set1_safe = (set1 == NULL) ? empty : set1;
+  const PermissionSet* set2_safe = (set2 == NULL) ? empty : set2;
+
+  APIPermissionSet apis;
+  APIPermissionSet::Intersection(set1_safe->apis(), set2_safe->apis(), &apis);
+
+  URLPatternSet explicit_hosts;
+  URLPatternSet::CreateIntersection(set1_safe->explicit_hosts(),
+                                    set2_safe->explicit_hosts(),
+                                    &explicit_hosts);
+
+  URLPatternSet scriptable_hosts;
+  URLPatternSet::CreateIntersection(set1_safe->scriptable_hosts(),
+                                    set2_safe->scriptable_hosts(),
+                                    &scriptable_hosts);
+
+  return new PermissionSet(apis, explicit_hosts, scriptable_hosts);
+}
+
+// static
+PermissionSet* PermissionSet::CreateUnion(
+    const PermissionSet* set1,
+    const PermissionSet* set2) {
+  scoped_refptr<PermissionSet> empty = new PermissionSet();
+  const PermissionSet* set1_safe = (set1 == NULL) ? empty : set1;
+  const PermissionSet* set2_safe = (set2 == NULL) ? empty : set2;
+
+  APIPermissionSet apis;
+  APIPermissionSet::Union(set1_safe->apis(), set2_safe->apis(), &apis);
+
+  URLPatternSet explicit_hosts;
+  URLPatternSet::CreateUnion(set1_safe->explicit_hosts(),
+                             set2_safe->explicit_hosts(),
+                             &explicit_hosts);
+
+  URLPatternSet scriptable_hosts;
+  URLPatternSet::CreateUnion(set1_safe->scriptable_hosts(),
+                             set2_safe->scriptable_hosts(),
+                             &scriptable_hosts);
+
+  return new PermissionSet(apis, explicit_hosts, scriptable_hosts);
+}
+
+bool PermissionSet::operator==(
+    const PermissionSet& rhs) const {
+  return apis_ == rhs.apis_ &&
+      scriptable_hosts_ == rhs.scriptable_hosts_ &&
+      explicit_hosts_ == rhs.explicit_hosts_;
+}
+
+bool PermissionSet::Contains(const PermissionSet& set) const {
+  // Every set includes the empty set.
+  if (set.IsEmpty())
+    return true;
+
+  if (!apis_.Contains(set.apis()))
+      return false;
+
+  if (!explicit_hosts().Contains(set.explicit_hosts()))
+    return false;
+
+  if (!scriptable_hosts().Contains(set.scriptable_hosts()))
+    return false;
+
+  return true;
+}
+
+std::set<std::string> PermissionSet::GetAPIsAsStrings() const {
+  std::set<std::string> apis_str;
+  for (APIPermissionSet::const_iterator i = apis_.begin();
+       i != apis_.end(); ++i) {
+    apis_str.insert(i->name());
+  }
+  return apis_str;
+}
+
+bool PermissionSet::HasAnyAccessToAPI(
+    const std::string& api_name) const {
+  if (HasAccessToFunction(api_name))
+    return true;
+
+  for (size_t i = 0; i < kNumNonPermissionFunctionNames; ++i) {
+    if (api_name == GetPermissionName(kNonPermissionFunctionNames[i]))
+      return true;
+  }
+
+  return false;
+}
+
+std::set<std::string>
+    PermissionSet::GetDistinctHostsForDisplay() const {
+  return GetDistinctHosts(effective_hosts_, true, true);
+}
+
+PermissionMessages PermissionSet::GetPermissionMessages(
+    Extension::Type extension_type) const {
+  PermissionMessages messages;
+
+  if (HasEffectiveFullAccess()) {
+    messages.push_back(PermissionMessage(
+        PermissionMessage::kFullAccess,
+        l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS)));
+    return messages;
+  }
+
+  // Since platform apps always use isolated storage, they can't (silently)
+  // access user data on other domains, so there's no need to prompt.
+  if (extension_type != Extension::TYPE_PLATFORM_APP) {
+    if (HasEffectiveAccessToAllHosts()) {
+      messages.push_back(PermissionMessage(
+          PermissionMessage::kHostsAll,
+          l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS)));
+    } else {
+      std::set<std::string> hosts = GetDistinctHostsForDisplay();
+      if (!hosts.empty())
+        messages.push_back(PermissionMessage::CreateFromHostList(hosts));
+    }
+  }
+
+  std::set<PermissionMessage> simple_msgs =
+      GetSimplePermissionMessages();
+  messages.insert(messages.end(), simple_msgs.begin(), simple_msgs.end());
+
+  return messages;
+}
+
+std::vector<string16> PermissionSet::GetWarningMessages(
+    Extension::Type extension_type) const {
+  std::vector<string16> messages;
+  PermissionMessages permissions = GetPermissionMessages(extension_type);
+
+  bool audio_capture = false;
+  bool video_capture = false;
+  for (PermissionMessages::const_iterator i = permissions.begin();
+       i != permissions.end(); ++i) {
+    if (i->id() == PermissionMessage::kAudioCapture)
+      audio_capture = true;
+    if (i->id() == PermissionMessage::kVideoCapture)
+      video_capture = true;
+  }
+
+  for (PermissionMessages::const_iterator i = permissions.begin();
+       i != permissions.end(); ++i) {
+    if (audio_capture && video_capture) {
+      if (i->id() == PermissionMessage::kAudioCapture) {
+        messages.push_back(l10n_util::GetStringUTF16(
+            IDS_EXTENSION_PROMPT_WARNING_AUDIO_AND_VIDEO_CAPTURE));
+        continue;
+      } else if (i->id() == PermissionMessage::kVideoCapture) {
+        // The combined message will be pushed above.
+        continue;
+      }
+    }
+
+    messages.push_back(i->message());
+  }
+
+  return messages;
+}
+
+bool PermissionSet::IsEmpty() const {
+  // Not default if any host permissions are present.
+  if (!(explicit_hosts().is_empty() && scriptable_hosts().is_empty()))
+    return false;
+
+  // Or if it has no api permissions.
+  return apis().empty();
+}
+
+bool PermissionSet::HasAPIPermission(
+    APIPermission::ID id) const {
+  return apis().find(id) != apis().end();
+}
+
+bool PermissionSet::CheckAPIPermission(APIPermission::ID permission) const {
+  return CheckAPIPermissionWithParam(permission, NULL);
+}
+
+bool PermissionSet::CheckAPIPermissionWithParam(
+    APIPermission::ID permission,
+    const APIPermission::CheckParam* param) const {
+  APIPermissionSet::const_iterator iter = apis().find(permission);
+  if (iter == apis().end())
+    return false;
+  return iter->Check(param);
+}
+
+bool PermissionSet::HasAccessToFunction(
+    const std::string& function_name) const {
+  // TODO(jstritar): Embed this information in each permission and add a method
+  // like GrantsAccess(function_name) to APIPermission. A "default"
+  // permission can then handle the modules and functions that everyone can
+  // access.
+  for (size_t i = 0; i < kNumNonPermissionFunctionNames; ++i) {
+    if (function_name == kNonPermissionFunctionNames[i])
+      return true;
+  }
+
+  // Search for increasingly smaller substrings of |function_name| to see if we
+  // find a matching permission or non-permission module name. E.g. for
+  // "a.b.c", we'll search on "a.b.c", then "a.b", and finally "a".
+  std::string name = function_name;
+  size_t lastdot;
+  do {
+    const APIPermissionInfo* permission =
+        PermissionsInfo::GetInstance()->GetByName(name);
+    if (permission && apis_.count(permission->id()))
+      return true;
+
+    for (size_t i = 0; i < kNumNonPermissionModuleNames; ++i) {
+      if (name == kNonPermissionModuleNames[i]) {
+        return true;
+      }
+    }
+    lastdot = name.find_last_of("./");
+    if (lastdot != std::string::npos)
+      name = std::string(name, 0, lastdot);
+  } while (lastdot != std::string::npos);
+
+  return false;
+}
+
+bool PermissionSet::HasExplicitAccessToOrigin(
+    const GURL& origin) const {
+  return explicit_hosts().MatchesURL(origin);
+}
+
+bool PermissionSet::HasScriptableAccessToURL(
+    const GURL& origin) const {
+  // We only need to check our host list to verify access. The host list should
+  // already reflect any special rules (such as chrome://favicon, all hosts
+  // access, etc.).
+  return scriptable_hosts().MatchesURL(origin);
+}
+
+bool PermissionSet::HasEffectiveAccessToAllHosts() const {
+  // There are two ways this set can have effective access to all hosts:
+  //  1) it has an <all_urls> URL pattern.
+  //  2) it has a named permission with implied full URL access.
+  for (URLPatternSet::const_iterator host = effective_hosts().begin();
+       host != effective_hosts().end(); ++host) {
+    if (host->match_all_urls() ||
+        (host->match_subdomains() && host->host().empty()))
+      return true;
+  }
+
+  for (APIPermissionSet::const_iterator i = apis().begin();
+       i != apis().end(); ++i) {
+    if (i->info()->implies_full_url_access())
+      return true;
+  }
+  return false;
+}
+
+bool PermissionSet::HasEffectiveAccessToURL(
+    const GURL& url) const {
+  return effective_hosts().MatchesURL(url);
+}
+
+bool PermissionSet::HasEffectiveFullAccess() const {
+  for (APIPermissionSet::const_iterator i = apis().begin();
+       i != apis().end(); ++i) {
+    if (i->info()->implies_full_access())
+      return true;
+  }
+  return false;
+}
+
+bool PermissionSet::HasLessPrivilegesThan(
+    const PermissionSet* permissions) const {
+  // Things can't get worse than native code access.
+  if (HasEffectiveFullAccess())
+    return false;
+
+  // Otherwise, it's a privilege increase if the new one has full access.
+  if (permissions->HasEffectiveFullAccess())
+    return true;
+
+  if (HasLessHostPrivilegesThan(permissions))
+    return true;
+
+  if (HasLessAPIPrivilegesThan(permissions))
+    return true;
+
+  return false;
+}
+
+PermissionSet::~PermissionSet() {}
+
+// static
+std::set<std::string> PermissionSet::GetDistinctHosts(
+    const URLPatternSet& host_patterns,
+    bool include_rcd,
+    bool exclude_file_scheme) {
+  // Use a vector to preserve order (also faster than a map on small sets).
+  // Each item is a host split into two parts: host without RCDs and
+  // current best RCD.
+  typedef std::vector<std::pair<std::string, std::string> > HostVector;
+  HostVector hosts_best_rcd;
+  for (URLPatternSet::const_iterator i = host_patterns.begin();
+       i != host_patterns.end(); ++i) {
+    if (exclude_file_scheme && i->scheme() == chrome::kFileScheme)
+      continue;
+
+    std::string host = i->host();
+
+    // Add the subdomain wildcard back to the host, if necessary.
+    if (i->match_subdomains())
+      host = "*." + host;
+
+    // If the host has an RCD, split it off so we can detect duplicates.
+    std::string rcd;
+    size_t reg_len = net::RegistryControlledDomainService::GetRegistryLength(
+        host, false);
+    if (reg_len && reg_len != std::string::npos) {
+      if (include_rcd)  // else leave rcd empty
+        rcd = host.substr(host.size() - reg_len);
+      host = host.substr(0, host.size() - reg_len);
+    }
+
+    // Check if we've already seen this host.
+    HostVector::iterator it = hosts_best_rcd.begin();
+    for (; it != hosts_best_rcd.end(); ++it) {
+      if (it->first == host)
+        break;
+    }
+    // If this host was found, replace the RCD if this one is better.
+    if (it != hosts_best_rcd.end()) {
+      if (include_rcd && RcdBetterThan(rcd, it->second))
+        it->second = rcd;
+    } else {  // Previously unseen host, append it.
+      hosts_best_rcd.push_back(std::make_pair(host, rcd));
+    }
+  }
+
+  // Build up the final vector by concatenating hosts and RCDs.
+  std::set<std::string> distinct_hosts;
+  for (HostVector::iterator it = hosts_best_rcd.begin();
+       it != hosts_best_rcd.end(); ++it)
+    distinct_hosts.insert(it->first + it->second);
+  return distinct_hosts;
+}
+
+void PermissionSet::InitImplicitPermissions() {
+  // The webRequest permission implies the internal version as well.
+  if (apis_.find(APIPermission::kWebRequest) != apis_.end())
+    apis_.insert(APIPermission::kWebRequestInternal);
+
+  // The fileBrowserHandler permission implies the internal version as well.
+  if (apis_.find(APIPermission::kFileBrowserHandler) != apis_.end())
+    apis_.insert(APIPermission::kFileBrowserHandlerInternal);
+}
+
+void PermissionSet::InitImplicitExtensionPermissions(
+    const extensions::Extension* extension) {
+  // Add the implied permissions.
+  if (!extension->plugins().empty())
+    apis_.insert(APIPermission::kPlugin);
+
+  if (!extension->devtools_url().is_empty())
+    apis_.insert(APIPermission::kDevtools);
+
+  // Add the scriptable hosts.
+  for (extensions::UserScriptList::const_iterator content_script =
+           extension->content_scripts().begin();
+       content_script != extension->content_scripts().end(); ++content_script) {
+    URLPatternSet::const_iterator pattern =
+        content_script->url_patterns().begin();
+    for (; pattern != content_script->url_patterns().end(); ++pattern)
+      scriptable_hosts_.AddPattern(*pattern);
+  }
+}
+
+void PermissionSet::InitEffectiveHosts() {
+  effective_hosts_.ClearPatterns();
+
+  URLPatternSet::CreateUnion(
+      explicit_hosts(), scriptable_hosts(), &effective_hosts_);
+}
+
+std::set<PermissionMessage>
+    PermissionSet::GetSimplePermissionMessages() const {
+  std::set<PermissionMessage> messages;
+  for (APIPermissionSet::const_iterator permission_it = apis_.begin();
+       permission_it != apis_.end(); ++permission_it) {
+    DCHECK_GT(PermissionMessage::kNone,
+              PermissionMessage::kUnknown);
+    if (permission_it->HasMessages()) {
+      PermissionMessages new_messages = permission_it->GetMessages();
+      messages.insert(new_messages.begin(), new_messages.end());
+    }
+  }
+  return messages;
+}
+
+bool PermissionSet::HasLessAPIPrivilegesThan(
+    const PermissionSet* permissions) const {
+  if (permissions == NULL)
+    return false;
+
+  std::set<PermissionMessage> current_warnings =
+      GetSimplePermissionMessages();
+  std::set<PermissionMessage> new_warnings =
+      permissions->GetSimplePermissionMessages();
+  std::set<PermissionMessage> delta_warnings;
+  std::set_difference(new_warnings.begin(), new_warnings.end(),
+                      current_warnings.begin(), current_warnings.end(),
+                      std::inserter(delta_warnings, delta_warnings.begin()));
+
+  // We have less privileges if there are additional warnings present.
+  return !delta_warnings.empty();
+}
+
+bool PermissionSet::HasLessHostPrivilegesThan(
+    const PermissionSet* permissions) const {
+  // If this permission set can access any host, then it can't be elevated.
+  if (HasEffectiveAccessToAllHosts())
+    return false;
+
+  // Likewise, if the other permission set has full host access, then it must be
+  // a privilege increase.
+  if (permissions->HasEffectiveAccessToAllHosts())
+    return true;
+
+  const URLPatternSet& old_list = effective_hosts();
+  const URLPatternSet& new_list = permissions->effective_hosts();
+
+  // TODO(jstritar): This is overly conservative with respect to subdomains.
+  // For example, going from *.google.com to www.google.com will be
+  // considered an elevation, even though it is not (http://crbug.com/65337).
+  std::set<std::string> new_hosts_set(GetDistinctHosts(new_list, false, false));
+  std::set<std::string> old_hosts_set(GetDistinctHosts(old_list, false, false));
+  std::set<std::string> new_hosts_only;
+
+  std::set_difference(new_hosts_set.begin(), new_hosts_set.end(),
+                      old_hosts_set.begin(), old_hosts_set.end(),
+                      std::inserter(new_hosts_only, new_hosts_only.begin()));
+
+  return !new_hosts_only.empty();
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/permissions/permission_set.h b/chrome/common/extensions/permissions/permission_set.h
new file mode 100644
index 0000000..f376932
--- /dev/null
+++ b/chrome/common/extensions/permissions/permission_set.h
@@ -0,0 +1,201 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_PERMISSIONS_PERMISSION_SET_H_
+#define CHROME_COMMON_EXTENSIONS_PERMISSIONS_PERMISSION_SET_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/singleton.h"
+#include "base/string16.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/permissions/api_permission.h"
+#include "chrome/common/extensions/permissions/api_permission_set.h"
+#include "chrome/common/extensions/permissions/permission_message.h"
+#include "chrome/common/extensions/url_pattern_set.h"
+
+namespace extensions {
+
+// The PermissionSet is an immutable class that encapsulates an
+// extension's permissions. The class exposes set operations for combining and
+// manipulating the permissions.
+class PermissionSet
+    : public base::RefCountedThreadSafe<PermissionSet> {
+ public:
+  // Creates an empty permission set (e.g. default permissions).
+  PermissionSet();
+
+  // Creates a new permission set based on the |extension| manifest data, and
+  // the api and host permissions (|apis| and |hosts|). The effective hosts
+  // of the newly created permission set will be inferred from the |extension|
+  // manifest, |apis| and |hosts|.
+  PermissionSet(const extensions::Extension* extension,
+                const APIPermissionSet& apis,
+                const URLPatternSet& explicit_hosts);
+
+  // Creates a new permission set based on the specified data.
+  PermissionSet(const APIPermissionSet& apis,
+                const URLPatternSet& explicit_hosts,
+                const URLPatternSet& scriptable_hosts);
+
+  // Creates a new permission set equal to |set1| - |set2|, passing ownership of
+  // the new set to the caller.
+  static PermissionSet* CreateDifference(
+      const PermissionSet* set1, const PermissionSet* set2);
+
+  // Creates a new permission set equal to the intersection of |set1| and
+  // |set2|, passing ownership of the new set to the caller.
+  static PermissionSet* CreateIntersection(
+      const PermissionSet* set1, const PermissionSet* set2);
+
+  // Creates a new permission set equal to the union of |set1| and |set2|.
+  // Passes ownership of the new set to the caller.
+  static PermissionSet* CreateUnion(
+      const PermissionSet* set1, const PermissionSet* set2);
+
+  bool operator==(const PermissionSet& rhs) const;
+
+  // Returns true if |set| is a subset of this.
+  bool Contains(const PermissionSet& set) const;
+
+  // Gets the API permissions in this set as a set of strings.
+  std::set<std::string> GetAPIsAsStrings() const;
+
+  // Returns whether this namespace has any functions which the extension has
+  // permission to use.  For example, even though the extension may not have
+  // the "tabs" permission, "tabs.create" requires no permissions so
+  // HasAnyAccessToAPI("tabs") will return true.
+  bool HasAnyAccessToAPI(const std::string& api_name) const;
+
+  // Gets the localized permission messages that represent this set.
+  // The set of permission messages shown varies by extension type.
+  PermissionMessages GetPermissionMessages(Extension::Type extension_type)
+      const;
+
+  // Gets the localized permission messages that represent this set (represented
+  // as strings). The set of permission messages shown varies by extension type.
+  std::vector<string16> GetWarningMessages(Extension::Type extension_type)
+      const;
+
+  // Returns true if this is an empty set (e.g., the default permission set).
+  bool IsEmpty() const;
+
+  // Returns true if the set has the specified API permission.
+  bool HasAPIPermission(APIPermission::ID permission) const;
+
+  // Returns true if the set allows the given permission with the default
+  // permission detal.
+  bool CheckAPIPermission(APIPermission::ID permission) const;
+
+  // Returns true if the set allows the given permission and permission param.
+  bool CheckAPIPermissionWithParam(APIPermission::ID permission,
+      const APIPermission::CheckParam* param) const;
+
+  // Returns true if the permissions in this set grant access to the specified
+  // |function_name|.
+  bool HasAccessToFunction(const std::string& function_name) const;
+
+  // Returns true if this includes permission to access |origin|.
+  bool HasExplicitAccessToOrigin(const GURL& origin) const;
+
+  // Returns true if this permission set includes access to script |url|.
+  bool HasScriptableAccessToURL(const GURL& url) const;
+
+  // Returns true if this permission set includes effective access to all
+  // origins.
+  bool HasEffectiveAccessToAllHosts() const;
+
+  // Returns true if this permission set includes effective access to |url|.
+  bool HasEffectiveAccessToURL(const GURL& url) const;
+
+  // Returns ture if this permission set effectively represents full access
+  // (e.g. native code).
+  bool HasEffectiveFullAccess() const;
+
+  // Returns true if |permissions| has a greater privilege level than this
+  // permission set (e.g., this permission set has less permissions).
+  bool HasLessPrivilegesThan(const PermissionSet* permissions) const;
+
+  const APIPermissionSet& apis() const { return apis_; }
+
+  const URLPatternSet& effective_hosts() const { return effective_hosts_; }
+
+  const URLPatternSet& explicit_hosts() const { return explicit_hosts_; }
+
+  const URLPatternSet& scriptable_hosts() const { return scriptable_hosts_; }
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(PermissionsTest, HasLessHostPrivilegesThan);
+  FRIEND_TEST_ALL_PREFIXES(PermissionsTest, GetWarningMessages_AudioVideo);
+  FRIEND_TEST_ALL_PREFIXES(PermissionsTest, GetDistinctHostsForDisplay);
+  FRIEND_TEST_ALL_PREFIXES(PermissionsTest,
+                           GetDistinctHostsForDisplay_ComIsBestRcd);
+  FRIEND_TEST_ALL_PREFIXES(PermissionsTest,
+                           GetDistinctHostsForDisplay_NetIs2ndBestRcd);
+  FRIEND_TEST_ALL_PREFIXES(PermissionsTest,
+                           GetDistinctHostsForDisplay_OrgIs3rdBestRcd);
+  FRIEND_TEST_ALL_PREFIXES(PermissionsTest,
+                           GetDistinctHostsForDisplay_FirstInListIs4thBestRcd);
+  friend class base::RefCountedThreadSafe<PermissionSet>;
+
+  ~PermissionSet();
+
+  void AddAPIPermission(APIPermission::ID id);
+
+  static std::set<std::string> GetDistinctHosts(
+      const URLPatternSet& host_patterns,
+      bool include_rcd,
+      bool exclude_file_scheme);
+
+  // Initializes the set based on |extension|'s manifest data.
+  void InitImplicitExtensionPermissions(const extensions::Extension* extension);
+
+  // Adds permissions implied independently of other context.
+  void InitImplicitPermissions();
+
+  // Initializes the effective host permission based on the data in this set.
+  void InitEffectiveHosts();
+
+  // Gets the permission messages for the API permissions.
+  std::set<PermissionMessage> GetSimplePermissionMessages() const;
+
+  // Returns true if |permissions| has an elevated API privilege level than
+  // this set.
+  bool HasLessAPIPrivilegesThan(
+      const PermissionSet* permissions) const;
+
+  // Returns true if |permissions| has more host permissions compared to this
+  // set.
+  bool HasLessHostPrivilegesThan(
+      const PermissionSet* permissions) const;
+
+  // Gets a list of the distinct hosts for displaying to the user.
+  // NOTE: do not use this for comparing permissions, since this disgards some
+  // information.
+  std::set<std::string> GetDistinctHostsForDisplay() const;
+
+  // The api list is used when deciding if an extension can access certain
+  // extension APIs and features.
+  APIPermissionSet apis_;
+
+  // The list of hosts that can be accessed directly from the extension.
+  // TODO(jstritar): Rename to "hosts_"?
+  URLPatternSet explicit_hosts_;
+
+  // The list of hosts that can be scripted by content scripts.
+  // TODO(jstritar): Rename to "user_script_hosts_"?
+  URLPatternSet scriptable_hosts_;
+
+  // The list of hosts this effectively grants access to.
+  URLPatternSet effective_hosts_;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_PERMISSIONS_PERMISSION_SET_H_
diff --git a/chrome/common/extensions/permissions/permission_set_unittest.cc b/chrome/common/extensions/permissions/permission_set_unittest.cc
new file mode 100644
index 0000000..59849d6
--- /dev/null
+++ b/chrome/common/extensions/permissions/permission_set_unittest.cc
@@ -0,0 +1,1350 @@
+// Copyright (c) 2012 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.
+
+#include "base/command_line.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/features/feature.h"
+#include "chrome/common/extensions/permissions/permission_set.h"
+#include "chrome/common/extensions/permissions/permissions_info.h"
+#include "chrome/common/extensions/permissions/socket_permission.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::Extension;
+
+namespace errors = extension_manifest_errors;
+namespace keys = extension_manifest_keys;
+namespace values = extension_manifest_values;
+namespace {
+
+scoped_refptr<Extension> LoadManifest(const std::string& dir,
+                                      const std::string& test_file,
+                                      int extra_flags) {
+  FilePath path;
+  PathService::Get(chrome::DIR_TEST_DATA, &path);
+  path = path.AppendASCII("extensions")
+             .AppendASCII(dir)
+             .AppendASCII(test_file);
+
+  JSONFileValueSerializer serializer(path);
+  std::string error;
+  scoped_ptr<Value> result(serializer.Deserialize(NULL, &error));
+  if (!result.get()) {
+    EXPECT_EQ("", error);
+    return NULL;
+  }
+
+  scoped_refptr<Extension> extension = Extension::Create(
+      path.DirName(), Extension::INVALID,
+      *static_cast<DictionaryValue*>(result.get()), extra_flags, &error);
+  EXPECT_TRUE(extension) << error;
+  return extension;
+}
+
+scoped_refptr<Extension> LoadManifest(const std::string& dir,
+                                      const std::string& test_file) {
+  return LoadManifest(dir, test_file, Extension::NO_FLAGS);
+}
+
+static void AddPattern(URLPatternSet* extent, const std::string& pattern) {
+  int schemes = URLPattern::SCHEME_ALL;
+  extent->AddPattern(URLPattern(schemes, pattern));
+}
+
+size_t IndexOf(const std::vector<string16>& warnings,
+               const std::string& warning) {
+  for (size_t i = 0; i < warnings.size(); ++i) {
+    if (warnings[i] == ASCIIToUTF16(warning))
+      return i;
+  }
+
+  return warnings.size();
+}
+
+bool Contains(const std::vector<string16>& warnings,
+              const std::string& warning) {
+  return IndexOf(warnings, warning) != warnings.size();
+}
+
+}  // namespace
+
+namespace extensions {
+
+class PermissionsTest : public testing::Test {
+};
+
+// Tests GetByID.
+TEST(PermissionsTest, GetByID) {
+  PermissionsInfo* info = PermissionsInfo::GetInstance();
+  APIPermissionSet apis = info->GetAll();
+  for (APIPermissionSet::const_iterator i = apis.begin();
+       i != apis.end(); ++i) {
+    EXPECT_EQ(i->id(), i->info()->id());
+  }
+}
+
+// Tests that GetByName works with normal permission names and aliases.
+TEST(PermissionsTest, GetByName) {
+  PermissionsInfo* info = PermissionsInfo::GetInstance();
+  EXPECT_EQ(APIPermission::kTab, info->GetByName("tabs")->id());
+  EXPECT_EQ(APIPermission::kManagement,
+            info->GetByName("management")->id());
+  EXPECT_FALSE(info->GetByName("alsdkfjasldkfj"));
+}
+
+TEST(PermissionsTest, GetAll) {
+  size_t count = 0;
+  PermissionsInfo* info = PermissionsInfo::GetInstance();
+  APIPermissionSet apis = info->GetAll();
+  for (APIPermissionSet::const_iterator api = apis.begin();
+       api != apis.end(); ++api) {
+    // Make sure only the valid permission IDs get returned.
+    EXPECT_NE(APIPermission::kInvalid, api->id());
+    EXPECT_NE(APIPermission::kUnknown, api->id());
+    count++;
+  }
+  EXPECT_EQ(count, info->get_permission_count());
+}
+
+TEST(PermissionsTest, GetAllByName) {
+  std::set<std::string> names;
+  names.insert("background");
+  names.insert("management");
+
+  // This is an alias of kTab
+  names.insert("windows");
+
+  // This unknown name should get dropped.
+  names.insert("sdlkfjasdlkfj");
+
+  APIPermissionSet expected;
+  expected.insert(APIPermission::kBackground);
+  expected.insert(APIPermission::kManagement);
+  expected.insert(APIPermission::kTab);
+
+  EXPECT_EQ(expected,
+            PermissionsInfo::GetInstance()->GetAllByName(names));
+}
+
+// Tests that the aliases are properly mapped.
+TEST(PermissionsTest, Aliases) {
+  PermissionsInfo* info = PermissionsInfo::GetInstance();
+  // tabs: tabs, windows
+  std::string tabs_name = "tabs";
+  EXPECT_EQ(tabs_name, info->GetByID(APIPermission::kTab)->name());
+  EXPECT_EQ(APIPermission::kTab, info->GetByName("tabs")->id());
+  EXPECT_EQ(APIPermission::kTab, info->GetByName("windows")->id());
+
+  // unlimitedStorage: unlimitedStorage, unlimited_storage
+  std::string storage_name = "unlimitedStorage";
+  EXPECT_EQ(storage_name, info->GetByID(
+      APIPermission::kUnlimitedStorage)->name());
+  EXPECT_EQ(APIPermission::kUnlimitedStorage,
+            info->GetByName("unlimitedStorage")->id());
+  EXPECT_EQ(APIPermission::kUnlimitedStorage,
+            info->GetByName("unlimited_storage")->id());
+}
+
+TEST(PermissionsTest, EffectiveHostPermissions) {
+  scoped_refptr<Extension> extension;
+  scoped_refptr<const PermissionSet> permissions;
+
+  extension = LoadManifest("effective_host_permissions", "empty.json");
+  permissions = extension->GetActivePermissions();
+  EXPECT_EQ(0u, extension->GetEffectiveHostPermissions().patterns().size());
+  EXPECT_FALSE(permissions->HasEffectiveAccessToURL(
+      GURL("http://www.google.com")));
+  EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions", "one_host.json");
+  permissions = extension->GetActivePermissions();
+  EXPECT_TRUE(permissions->HasEffectiveAccessToURL(
+      GURL("http://www.google.com")));
+  EXPECT_FALSE(permissions->HasEffectiveAccessToURL(
+      GURL("https://www.google.com")));
+  EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions",
+                           "one_host_wildcard.json");
+  permissions = extension->GetActivePermissions();
+  EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("http://google.com")));
+  EXPECT_TRUE(permissions->HasEffectiveAccessToURL(
+      GURL("http://foo.google.com")));
+  EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions", "two_hosts.json");
+  permissions = extension->GetActivePermissions();
+  EXPECT_TRUE(permissions->HasEffectiveAccessToURL(
+      GURL("http://www.google.com")));
+  EXPECT_TRUE(permissions->HasEffectiveAccessToURL(
+      GURL("http://www.reddit.com")));
+  EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions",
+                           "https_not_considered.json");
+  permissions = extension->GetActivePermissions();
+  EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("http://google.com")));
+  EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("https://google.com")));
+  EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions",
+                           "two_content_scripts.json");
+  permissions = extension->GetActivePermissions();
+  EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("http://google.com")));
+  EXPECT_TRUE(permissions->HasEffectiveAccessToURL(
+      GURL("http://www.reddit.com")));
+  EXPECT_TRUE(permissions->HasEffectiveAccessToURL(
+      GURL("http://news.ycombinator.com")));
+  EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions", "all_hosts.json");
+  permissions = extension->GetActivePermissions();
+  EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("http://test/")));
+  EXPECT_FALSE(permissions->HasEffectiveAccessToURL(GURL("https://test/")));
+  EXPECT_TRUE(
+      permissions->HasEffectiveAccessToURL(GURL("http://www.google.com")));
+  EXPECT_TRUE(permissions->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions", "all_hosts2.json");
+  permissions = extension->GetActivePermissions();
+  EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("http://test/")));
+  EXPECT_TRUE(
+      permissions->HasEffectiveAccessToURL(GURL("http://www.google.com")));
+  EXPECT_TRUE(permissions->HasEffectiveAccessToAllHosts());
+
+  extension = LoadManifest("effective_host_permissions", "all_hosts3.json");
+  permissions = extension->GetActivePermissions();
+  EXPECT_FALSE(permissions->HasEffectiveAccessToURL(GURL("http://test/")));
+  EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("https://test/")));
+  EXPECT_TRUE(
+      permissions->HasEffectiveAccessToURL(GURL("http://www.google.com")));
+  EXPECT_TRUE(permissions->HasEffectiveAccessToAllHosts());
+}
+
+TEST(PermissionsTest, ExplicitAccessToOrigin) {
+  APIPermissionSet apis;
+  URLPatternSet explicit_hosts;
+  URLPatternSet scriptable_hosts;
+
+  AddPattern(&explicit_hosts, "http://*.google.com/*");
+  // The explicit host paths should get set to /*.
+  AddPattern(&explicit_hosts, "http://www.example.com/a/particular/path/*");
+
+  scoped_refptr<PermissionSet> perm_set = new PermissionSet(
+      apis, explicit_hosts, scriptable_hosts);
+  ASSERT_TRUE(perm_set->HasExplicitAccessToOrigin(
+      GURL("http://www.google.com/")));
+  ASSERT_TRUE(perm_set->HasExplicitAccessToOrigin(
+      GURL("http://test.google.com/")));
+  ASSERT_TRUE(perm_set->HasExplicitAccessToOrigin(
+      GURL("http://www.example.com")));
+  ASSERT_TRUE(perm_set->HasEffectiveAccessToURL(
+      GURL("http://www.example.com")));
+  ASSERT_FALSE(perm_set->HasExplicitAccessToOrigin(
+      GURL("http://test.example.com")));
+}
+
+TEST(PermissionsTest, CreateUnion) {
+  APIPermission* permission = NULL;
+
+  APIPermissionSet apis1;
+  APIPermissionSet apis2;
+  APIPermissionSet expected_apis;
+
+  URLPatternSet explicit_hosts1;
+  URLPatternSet explicit_hosts2;
+  URLPatternSet expected_explicit_hosts;
+
+  URLPatternSet scriptable_hosts1;
+  URLPatternSet scriptable_hosts2;
+  URLPatternSet expected_scriptable_hosts;
+
+  URLPatternSet effective_hosts;
+
+  scoped_refptr<PermissionSet> set1;
+  scoped_refptr<PermissionSet> set2;
+  scoped_refptr<PermissionSet> union_set;
+
+  const APIPermissionInfo* permission_info =
+    PermissionsInfo::GetInstance()->GetByID(APIPermission::kSocket);
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("tcp-connect:*.example.com:80"));
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+
+  // Union with an empty set.
+  apis1.insert(APIPermission::kTab);
+  apis1.insert(APIPermission::kBackground);
+  apis1.insert(permission->Clone());
+  expected_apis.insert(APIPermission::kTab);
+  expected_apis.insert(APIPermission::kBackground);
+  expected_apis.insert(permission);
+
+  AddPattern(&explicit_hosts1, "http://*.google.com/*");
+  AddPattern(&expected_explicit_hosts, "http://*.google.com/*");
+  AddPattern(&effective_hosts, "http://*.google.com/*");
+
+  set1 = new PermissionSet(apis1, explicit_hosts1, scriptable_hosts1);
+  set2 = new PermissionSet(apis2, explicit_hosts2, scriptable_hosts2);
+  union_set = PermissionSet::CreateUnion(set1.get(), set2.get());
+  EXPECT_TRUE(set1->Contains(*set2));
+  EXPECT_TRUE(set1->Contains(*union_set));
+  EXPECT_FALSE(set2->Contains(*set1));
+  EXPECT_FALSE(set2->Contains(*union_set));
+  EXPECT_TRUE(union_set->Contains(*set1));
+  EXPECT_TRUE(union_set->Contains(*set2));
+
+  EXPECT_FALSE(union_set->HasEffectiveFullAccess());
+  EXPECT_EQ(expected_apis, union_set->apis());
+  EXPECT_EQ(expected_explicit_hosts, union_set->explicit_hosts());
+  EXPECT_EQ(expected_scriptable_hosts, union_set->scriptable_hosts());
+  EXPECT_EQ(expected_explicit_hosts, union_set->effective_hosts());
+
+  // Now use a real second set.
+  apis2.insert(APIPermission::kTab);
+  apis2.insert(APIPermission::kProxy);
+  apis2.insert(APIPermission::kClipboardWrite);
+  apis2.insert(APIPermission::kPlugin);
+
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("tcp-connect:*.example.com:80"));
+    value->Append(Value::CreateStringValue("udp-send-to::8899"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  apis2.insert(permission);
+
+  expected_apis.insert(APIPermission::kTab);
+  expected_apis.insert(APIPermission::kProxy);
+  expected_apis.insert(APIPermission::kClipboardWrite);
+  expected_apis.insert(APIPermission::kPlugin);
+
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("tcp-connect:*.example.com:80"));
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    value->Append(Value::CreateStringValue("udp-send-to::8899"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  // Insert a new permission socket permisssion which will replace the old one.
+  expected_apis.insert(permission);
+
+  AddPattern(&explicit_hosts2, "http://*.example.com/*");
+  AddPattern(&scriptable_hosts2, "http://*.google.com/*");
+  AddPattern(&expected_explicit_hosts, "http://*.example.com/*");
+  AddPattern(&expected_scriptable_hosts, "http://*.google.com/*");
+
+  URLPatternSet::CreateUnion(
+      explicit_hosts2, scriptable_hosts2, &effective_hosts);
+
+  set2 = new PermissionSet(apis2, explicit_hosts2, scriptable_hosts2);
+  union_set = PermissionSet::CreateUnion(set1.get(), set2.get());
+
+  EXPECT_FALSE(set1->Contains(*set2));
+  EXPECT_FALSE(set1->Contains(*union_set));
+  EXPECT_FALSE(set2->Contains(*set1));
+  EXPECT_FALSE(set2->Contains(*union_set));
+  EXPECT_TRUE(union_set->Contains(*set1));
+  EXPECT_TRUE(union_set->Contains(*set2));
+
+  EXPECT_TRUE(union_set->HasEffectiveFullAccess());
+  EXPECT_TRUE(union_set->HasEffectiveAccessToAllHosts());
+  EXPECT_EQ(expected_apis, union_set->apis());
+  EXPECT_EQ(expected_explicit_hosts, union_set->explicit_hosts());
+  EXPECT_EQ(expected_scriptable_hosts, union_set->scriptable_hosts());
+  EXPECT_EQ(effective_hosts, union_set->effective_hosts());
+}
+
+TEST(PermissionsTest, CreateIntersection) {
+  APIPermission* permission = NULL;
+
+  APIPermissionSet apis1;
+  APIPermissionSet apis2;
+  APIPermissionSet expected_apis;
+
+  URLPatternSet explicit_hosts1;
+  URLPatternSet explicit_hosts2;
+  URLPatternSet expected_explicit_hosts;
+
+  URLPatternSet scriptable_hosts1;
+  URLPatternSet scriptable_hosts2;
+  URLPatternSet expected_scriptable_hosts;
+
+  URLPatternSet effective_hosts;
+
+  scoped_refptr<PermissionSet> set1;
+  scoped_refptr<PermissionSet> set2;
+  scoped_refptr<PermissionSet> new_set;
+
+  const APIPermissionInfo* permission_info =
+    PermissionsInfo::GetInstance()->GetByID(APIPermission::kSocket);
+
+  // Intersection with an empty set.
+  apis1.insert(APIPermission::kTab);
+  apis1.insert(APIPermission::kBackground);
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("tcp-connect:*.example.com:80"));
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  apis1.insert(permission);
+
+  AddPattern(&explicit_hosts1, "http://*.google.com/*");
+  AddPattern(&scriptable_hosts1, "http://www.reddit.com/*");
+
+  set1 = new PermissionSet(apis1, explicit_hosts1, scriptable_hosts1);
+  set2 = new PermissionSet(apis2, explicit_hosts2, scriptable_hosts2);
+  new_set = PermissionSet::CreateIntersection(set1.get(), set2.get());
+  EXPECT_TRUE(set1->Contains(*new_set));
+  EXPECT_TRUE(set2->Contains(*new_set));
+  EXPECT_TRUE(set1->Contains(*set2));
+  EXPECT_FALSE(set2->Contains(*set1));
+  EXPECT_FALSE(new_set->Contains(*set1));
+  EXPECT_TRUE(new_set->Contains(*set2));
+
+  EXPECT_TRUE(new_set->IsEmpty());
+  EXPECT_FALSE(new_set->HasEffectiveFullAccess());
+  EXPECT_EQ(expected_apis, new_set->apis());
+  EXPECT_EQ(expected_explicit_hosts, new_set->explicit_hosts());
+  EXPECT_EQ(expected_scriptable_hosts, new_set->scriptable_hosts());
+  EXPECT_EQ(expected_explicit_hosts, new_set->effective_hosts());
+
+  // Now use a real second set.
+  apis2.insert(APIPermission::kTab);
+  apis2.insert(APIPermission::kProxy);
+  apis2.insert(APIPermission::kClipboardWrite);
+  apis2.insert(APIPermission::kPlugin);
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    value->Append(Value::CreateStringValue("udp-send-to::8899"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  apis2.insert(permission);
+
+  expected_apis.insert(APIPermission::kTab);
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  expected_apis.insert(permission);
+
+  AddPattern(&explicit_hosts2, "http://*.example.com/*");
+  AddPattern(&explicit_hosts2, "http://*.google.com/*");
+  AddPattern(&scriptable_hosts2, "http://*.google.com/*");
+  AddPattern(&expected_explicit_hosts, "http://*.google.com/*");
+
+  effective_hosts.ClearPatterns();
+  AddPattern(&effective_hosts, "http://*.google.com/*");
+
+  set2 = new PermissionSet(apis2, explicit_hosts2, scriptable_hosts2);
+  new_set = PermissionSet::CreateIntersection(set1.get(), set2.get());
+
+  EXPECT_TRUE(set1->Contains(*new_set));
+  EXPECT_TRUE(set2->Contains(*new_set));
+  EXPECT_FALSE(set1->Contains(*set2));
+  EXPECT_FALSE(set2->Contains(*set1));
+  EXPECT_FALSE(new_set->Contains(*set1));
+  EXPECT_FALSE(new_set->Contains(*set2));
+
+  EXPECT_FALSE(new_set->HasEffectiveFullAccess());
+  EXPECT_FALSE(new_set->HasEffectiveAccessToAllHosts());
+  EXPECT_EQ(expected_apis, new_set->apis());
+  EXPECT_EQ(expected_explicit_hosts, new_set->explicit_hosts());
+  EXPECT_EQ(expected_scriptable_hosts, new_set->scriptable_hosts());
+  EXPECT_EQ(effective_hosts, new_set->effective_hosts());
+}
+
+TEST(PermissionsTest, CreateDifference) {
+  APIPermission* permission = NULL;
+
+  APIPermissionSet apis1;
+  APIPermissionSet apis2;
+  APIPermissionSet expected_apis;
+
+  URLPatternSet explicit_hosts1;
+  URLPatternSet explicit_hosts2;
+  URLPatternSet expected_explicit_hosts;
+
+  URLPatternSet scriptable_hosts1;
+  URLPatternSet scriptable_hosts2;
+  URLPatternSet expected_scriptable_hosts;
+
+  URLPatternSet effective_hosts;
+
+  scoped_refptr<PermissionSet> set1;
+  scoped_refptr<PermissionSet> set2;
+  scoped_refptr<PermissionSet> new_set;
+
+  const APIPermissionInfo* permission_info =
+    PermissionsInfo::GetInstance()->GetByID(APIPermission::kSocket);
+
+  // Difference with an empty set.
+  apis1.insert(APIPermission::kTab);
+  apis1.insert(APIPermission::kBackground);
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("tcp-connect:*.example.com:80"));
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  apis1.insert(permission);
+
+  AddPattern(&explicit_hosts1, "http://*.google.com/*");
+  AddPattern(&scriptable_hosts1, "http://www.reddit.com/*");
+
+  set1 = new PermissionSet(apis1, explicit_hosts1, scriptable_hosts1);
+  set2 = new PermissionSet(apis2, explicit_hosts2, scriptable_hosts2);
+  new_set = PermissionSet::CreateDifference(set1.get(), set2.get());
+  EXPECT_EQ(*set1, *new_set);
+
+  // Now use a real second set.
+  apis2.insert(APIPermission::kTab);
+  apis2.insert(APIPermission::kProxy);
+  apis2.insert(APIPermission::kClipboardWrite);
+  apis2.insert(APIPermission::kPlugin);
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("tcp-connect:*.example.com:80"));
+    value->Append(Value::CreateStringValue("udp-send-to::8899"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  apis2.insert(permission);
+
+  expected_apis.insert(APIPermission::kBackground);
+  permission = permission_info->CreateAPIPermission();
+  {
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    if (!permission->FromValue(value.get())) {
+      NOTREACHED();
+    }
+  }
+  expected_apis.insert(permission);
+
+  AddPattern(&explicit_hosts2, "http://*.example.com/*");
+  AddPattern(&explicit_hosts2, "http://*.google.com/*");
+  AddPattern(&scriptable_hosts2, "http://*.google.com/*");
+  AddPattern(&expected_scriptable_hosts, "http://www.reddit.com/*");
+
+  effective_hosts.ClearPatterns();
+  AddPattern(&effective_hosts, "http://www.reddit.com/*");
+
+  set2 = new PermissionSet(apis2, explicit_hosts2, scriptable_hosts2);
+  new_set = PermissionSet::CreateDifference(set1.get(), set2.get());
+
+  EXPECT_TRUE(set1->Contains(*new_set));
+  EXPECT_FALSE(set2->Contains(*new_set));
+
+  EXPECT_FALSE(new_set->HasEffectiveFullAccess());
+  EXPECT_FALSE(new_set->HasEffectiveAccessToAllHosts());
+  EXPECT_EQ(expected_apis, new_set->apis());
+  EXPECT_EQ(expected_explicit_hosts, new_set->explicit_hosts());
+  EXPECT_EQ(expected_scriptable_hosts, new_set->scriptable_hosts());
+  EXPECT_EQ(effective_hosts, new_set->effective_hosts());
+
+  // |set3| = |set1| - |set2| --> |set3| intersect |set2| == empty_set
+  set1 = PermissionSet::CreateIntersection(new_set.get(), set2.get());
+  EXPECT_TRUE(set1->IsEmpty());
+}
+
+TEST(PermissionsTest, HasLessPrivilegesThan) {
+  const struct {
+    const char* base_name;
+    bool expect_increase;
+  } kTests[] = {
+    { "allhosts1", false },  // all -> all
+    { "allhosts2", false },  // all -> one
+    { "allhosts3", true },  // one -> all
+    { "hosts1", false },  // http://a,http://b -> http://a,http://b
+    { "hosts2", true },  // http://a,http://b -> https://a,http://*.b
+    { "hosts3", false },  // http://a,http://b -> http://a
+    { "hosts4", true },  // http://a -> http://a,http://b
+    { "hosts5", false },  // http://a,b,c -> http://a,b,c + https://a,b,c
+    { "hosts6", false },  // http://a.com -> http://a.com + http://a.co.uk
+    { "permissions1", false },  // tabs -> tabs
+    { "permissions2", true },  // tabs -> tabs,bookmarks
+    { "permissions3", true },  // http://a -> http://a,tabs
+    { "permissions5", true },  // bookmarks -> bookmarks,history
+    { "equivalent_warnings", false },  // tabs --> tabs, webNavigation
+#if !defined(OS_CHROMEOS)  // plugins aren't allowed in ChromeOS
+    { "permissions4", false },  // plugin -> plugin,tabs
+    { "plugin1", false },  // plugin -> plugin
+    { "plugin2", false },  // plugin -> none
+    { "plugin3", true },  // none -> plugin
+#endif
+    { "storage", false },  // none -> storage
+    { "notifications", false },  // none -> notifications
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) {
+    scoped_refptr<Extension> old_extension(
+        LoadManifest("allow_silent_upgrade",
+                     std::string(kTests[i].base_name) + "_old.json"));
+    scoped_refptr<Extension> new_extension(
+        LoadManifest("allow_silent_upgrade",
+                     std::string(kTests[i].base_name) + "_new.json"));
+
+    EXPECT_TRUE(new_extension.get()) << kTests[i].base_name << "_new.json";
+    if (!new_extension.get())
+      continue;
+
+    scoped_refptr<const PermissionSet> old_p(
+        old_extension->GetActivePermissions());
+    scoped_refptr<const PermissionSet> new_p(
+        new_extension->GetActivePermissions());
+
+    EXPECT_EQ(kTests[i].expect_increase,
+              old_p->HasLessPrivilegesThan(new_p)) << kTests[i].base_name;
+  }
+}
+
+TEST(PermissionsTest, PermissionMessages) {
+  // Ensure that all permissions that needs to show install UI actually have
+  // strings associated with them.
+  APIPermissionSet skip;
+
+  // These are considered "nuisance" or "trivial" permissions that don't need
+  // a prompt.
+  skip.insert(APIPermission::kActiveTab);
+  skip.insert(APIPermission::kAlarms);
+  skip.insert(APIPermission::kAppNotifications);
+  skip.insert(APIPermission::kAppRuntime);
+  skip.insert(APIPermission::kAppWindow);
+  skip.insert(APIPermission::kAppCurrentWindowInternal);
+  skip.insert(APIPermission::kBrowsingData);
+  skip.insert(APIPermission::kContextMenus);
+  skip.insert(APIPermission::kFontSettings);
+  skip.insert(APIPermission::kIdle);
+  skip.insert(APIPermission::kNotification);
+  skip.insert(APIPermission::kPushMessaging);
+  skip.insert(APIPermission::kUnlimitedStorage);
+  skip.insert(APIPermission::kStorage);
+  skip.insert(APIPermission::kTts);
+  skip.insert(APIPermission::kWebView);
+
+  // TODO(erikkay) add a string for this permission.
+  skip.insert(APIPermission::kBackground);
+
+  skip.insert(APIPermission::kClipboardWrite);
+
+  // The cookie permission does nothing unless you have associated host
+  // permissions.
+  skip.insert(APIPermission::kCookie);
+
+  // These are warned as part of host permission checks.
+  skip.insert(APIPermission::kPageCapture);
+  skip.insert(APIPermission::kProxy);
+  skip.insert(APIPermission::kWebRequest);
+  skip.insert(APIPermission::kWebRequestBlocking);
+  skip.insert(APIPermission::kDeclarativeWebRequest);
+  skip.insert(APIPermission::kTabCapture);
+
+  // This permission requires explicit user action (context menu handler)
+  // so we won't prompt for it for now.
+  skip.insert(APIPermission::kFileBrowserHandler);
+
+  // These permissions require explicit user action (configuration dialog)
+  // so we don't prompt for them at install time.
+  skip.insert(APIPermission::kMediaGalleries);
+  skip.insert(APIPermission::kMediaGalleriesRead);
+
+  // If you've turned on the experimental command-line flag, we don't need
+  // to warn you further.
+  skip.insert(APIPermission::kExperimental);
+
+  // These are private.
+  skip.insert(APIPermission::kBookmarkManagerPrivate);
+  skip.insert(APIPermission::kChromeosInfoPrivate);
+  skip.insert(APIPermission::kCloudPrintPrivate);
+  skip.insert(APIPermission::kEchoPrivate);
+  skip.insert(APIPermission::kFileBrowserHandlerInternal);
+  skip.insert(APIPermission::kFileBrowserPrivate);
+  skip.insert(APIPermission::kInputMethodPrivate);
+  skip.insert(APIPermission::kManagedModePrivate);
+  skip.insert(APIPermission::kMediaGalleriesPrivate);
+  skip.insert(APIPermission::kMediaPlayerPrivate);
+  skip.insert(APIPermission::kMetricsPrivate);
+  skip.insert(APIPermission::kRtcPrivate);
+  skip.insert(APIPermission::kSystemPrivate);
+  skip.insert(APIPermission::kTerminalPrivate);
+  skip.insert(APIPermission::kWallpaperPrivate);
+  skip.insert(APIPermission::kWebRequestInternal);
+  skip.insert(APIPermission::kWebSocketProxyPrivate);
+  skip.insert(APIPermission::kWebstorePrivate);
+
+  // Available only on trunk.
+  // TODO(kinuko) add a string for this permission.
+  skip.insert(APIPermission::kSyncFileSystem);
+
+  // Warned as part of host permissions.
+  skip.insert(APIPermission::kDevtools);
+
+  // Platform apps.
+  skip.insert(APIPermission::kFileSystem);
+  skip.insert(APIPermission::kSocket);
+
+  PermissionsInfo* info = PermissionsInfo::GetInstance();
+  APIPermissionSet permissions = info->GetAll();
+  for (APIPermissionSet::const_iterator i = permissions.begin();
+       i != permissions.end(); ++i) {
+    const APIPermissionInfo* permission_info = i->info();
+    EXPECT_TRUE(permission_info != NULL);
+    if (skip.count(i->id())) {
+      EXPECT_EQ(PermissionMessage::kNone, permission_info->message_id())
+          << "unexpected message_id for " << permission_info->name();
+    } else {
+      EXPECT_NE(PermissionMessage::kNone, permission_info->message_id())
+          << "missing message_id for " << permission_info->name();
+    }
+  }
+}
+
+// Tests the default permissions (empty API permission set).
+TEST(PermissionsTest, DefaultFunctionAccess) {
+  const struct {
+    const char* permission_name;
+    bool expect_success;
+  } kTests[] = {
+    // Negative test.
+    { "non_existing_permission", false },
+    // Test default module/package permission.
+    { "browserAction",  true },
+    { "devtools",       true },
+    { "extension",      true },
+    { "i18n",           true },
+    { "pageAction",     true },
+    { "pageActions",    true },
+    { "test",           true },
+    // Some negative tests.
+    { "bookmarks",      false },
+    { "cookies",        false },
+    { "history",        false },
+    // Make sure we find the module name after stripping '.' and '/'.
+    { "browserAction/abcd/onClick",  true },
+    { "browserAction.abcd.onClick",  true },
+    // Test Tabs functions.
+    { "tabs.create",      true},
+    { "tabs.duplicate",   true},
+    { "tabs.update",      true},
+    { "tabs.getSelected", true},
+    { "tabs.onUpdated",   true },
+  };
+
+  scoped_refptr<PermissionSet> empty = new PermissionSet();
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) {
+    EXPECT_EQ(kTests[i].expect_success,
+              empty->HasAccessToFunction(kTests[i].permission_name))
+                  << "Permission being tested: " << kTests[i].permission_name;
+  }
+}
+
+// Tests the default permissions (empty API permission set).
+TEST(PermissionsTest, DefaultAnyAPIAccess) {
+  const struct {
+    const char* api_name;
+    bool expect_success;
+  } kTests[] = {
+    // Negative test.
+    { "non_existing_permission", false },
+    // Test default module/package permission.
+    { "browserAction",  true },
+    { "devtools",       true },
+    { "extension",      true },
+    { "i18n",           true },
+    { "pageAction",     true },
+    { "pageActions",    true },
+    { "test",           true },
+    // Some negative tests.
+    { "bookmarks",      false },
+    { "cookies",        false },
+    { "history",        false },
+    // Negative APIs that have positive individual functions.
+    { "management",     true},
+    { "tabs",           true},
+  };
+
+  scoped_refptr<PermissionSet> empty = new PermissionSet();
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) {
+    EXPECT_EQ(kTests[i].expect_success,
+              empty->HasAnyAccessToAPI(kTests[i].api_name));
+  }
+}
+
+TEST(PermissionsTest, GetWarningMessages_ManyHosts) {
+  scoped_refptr<Extension> extension;
+
+  extension = LoadManifest("permissions", "many-hosts.json");
+  std::vector<string16> warnings = extension->GetPermissionMessageStrings();
+  ASSERT_EQ(1u, warnings.size());
+  EXPECT_EQ("Access your data on encrypted.google.com and www.google.com",
+            UTF16ToUTF8(warnings[0]));
+}
+
+TEST(PermissionsTest, GetWarningMessages_Plugins) {
+  scoped_refptr<Extension> extension;
+  scoped_refptr<PermissionSet> permissions;
+
+  extension = LoadManifest("permissions", "plugins.json");
+  std::vector<string16> warnings = extension->GetPermissionMessageStrings();
+  // We don't parse the plugins key on Chrome OS, so it should not ask for any
+  // permissions.
+#if defined(OS_CHROMEOS)
+  ASSERT_EQ(0u, warnings.size());
+#else
+  ASSERT_EQ(1u, warnings.size());
+  EXPECT_EQ("Access all data on your computer and the websites you visit",
+            UTF16ToUTF8(warnings[0]));
+#endif
+}
+
+TEST(PermissionsTest, GetWarningMessages_AudioVideo) {
+  // Both audio and video present.
+  scoped_refptr<Extension> extension =
+      LoadManifest("permissions", "audio-video.json");
+  PermissionSet* set =
+      const_cast<PermissionSet*>(
+          extension->GetActivePermissions().get());
+  std::vector<string16> warnings =
+      set->GetWarningMessages(extension->GetType());
+  EXPECT_FALSE(Contains(warnings, "Use your microphone"));
+  EXPECT_FALSE(Contains(warnings, "Use your camera"));
+  EXPECT_TRUE(Contains(warnings, "Use your microphone and camera"));
+  size_t combined_index = IndexOf(warnings, "Use your microphone and camera");
+  size_t combined_size = warnings.size();
+
+  // Just audio present.
+  set->apis_.erase(APIPermission::kVideoCapture);
+  warnings = set->GetWarningMessages(extension->GetType());
+  EXPECT_EQ(combined_size, warnings.size());
+  EXPECT_EQ(combined_index, IndexOf(warnings, "Use your microphone"));
+  EXPECT_FALSE(Contains(warnings, "Use your camera"));
+  EXPECT_FALSE(Contains(warnings, "Use your microphone and camera"));
+
+  // Just video present.
+  set->apis_.erase(APIPermission::kAudioCapture);
+  set->apis_.insert(APIPermission::kVideoCapture);
+  warnings = set->GetWarningMessages(extension->GetType());
+  EXPECT_EQ(combined_size, warnings.size());
+  EXPECT_FALSE(Contains(warnings, "Use your microphone"));
+  EXPECT_FALSE(Contains(warnings, "Use your microphone and camera"));
+  EXPECT_TRUE(Contains(warnings, "Use your camera"));
+}
+
+TEST(PermissionsTest, GetWarningMessages_Serial) {
+  scoped_refptr<Extension> extension =
+      LoadManifest("permissions", "serial.json");
+
+  EXPECT_TRUE(extension->is_platform_app());
+  EXPECT_TRUE(extension->HasAPIPermission(APIPermission::kSerial));
+  std::vector<string16> warnings = extension->GetPermissionMessageStrings();
+  EXPECT_TRUE(Contains(warnings,
+                       "Use serial devices attached to your computer"));
+  ASSERT_EQ(1u, warnings.size());
+}
+
+TEST(PermissionsTest, GetWarningMessages_Socket_AnyHost) {
+  extensions::Feature::ScopedCurrentChannel channel(
+      chrome::VersionInfo::CHANNEL_DEV);
+
+  scoped_refptr<Extension> extension =
+      LoadManifest("permissions", "socket_any_host.json");
+  EXPECT_TRUE(extension->is_platform_app());
+  EXPECT_TRUE(extension->HasAPIPermission(APIPermission::kSocket));
+  std::vector<string16> warnings = extension->GetPermissionMessageStrings();
+  EXPECT_EQ(1u, warnings.size());
+  EXPECT_TRUE(Contains(warnings, "Exchange data with any computer "
+                                 "on the local network or internet"));
+}
+
+TEST(PermissionsTest, GetWarningMessages_Socket_OneDomainTwoHostnames) {
+  extensions::Feature::ScopedCurrentChannel channel(
+      chrome::VersionInfo::CHANNEL_DEV);
+
+  scoped_refptr<Extension> extension =
+      LoadManifest("permissions", "socket_one_domain_two_hostnames.json");
+  EXPECT_TRUE(extension->is_platform_app());
+  EXPECT_TRUE(extension->HasAPIPermission(APIPermission::kSocket));
+  std::vector<string16> warnings = extension->GetPermissionMessageStrings();
+
+  // Verify the warnings, including support for unicode characters, the fact
+  // that domain host warnings come before specific host warnings, and the fact
+  // that domains and hostnames are in alphabetical order regardless of the
+  // order in the manifest file.
+  EXPECT_EQ(2u, warnings.size());
+  if (warnings.size() > 0)
+    EXPECT_EQ(warnings[0],
+              UTF8ToUTF16("Exchange data with any computer in the domain "
+                          "example.org"));
+  if (warnings.size() > 1)
+    EXPECT_EQ(warnings[1],
+              UTF8ToUTF16("Exchange data with the computers named: "
+                          "b\xC3\xA5r.example.com foo.example.com"));
+                          // "\xC3\xA5" = UTF-8 for lowercase A with ring above
+}
+
+TEST(PermissionsTest, GetWarningMessages_Socket_TwoDomainsOneHostname) {
+  extensions::Feature::ScopedCurrentChannel channel(
+      chrome::VersionInfo::CHANNEL_DEV);
+
+  scoped_refptr<Extension> extension =
+      LoadManifest("permissions", "socket_two_domains_one_hostname.json");
+  EXPECT_TRUE(extension->is_platform_app());
+  EXPECT_TRUE(extension->HasAPIPermission(APIPermission::kSocket));
+  std::vector<string16> warnings = extension->GetPermissionMessageStrings();
+
+  // Verify the warnings, including the fact that domain host warnings come
+  // before specific host warnings and the fact that domains and hostnames are
+  // in alphabetical order regardless of the order in the manifest file.
+  EXPECT_EQ(2u, warnings.size());
+  if (warnings.size() > 0)
+    EXPECT_EQ(warnings[0],
+              UTF8ToUTF16("Exchange data with any computer in the domains: "
+                           "example.com foo.example.org"));
+  if (warnings.size() > 1)
+    EXPECT_EQ(warnings[1],
+              UTF8ToUTF16("Exchange data with the computer named "
+                           "bar.example.org"));
+}
+
+TEST(PermissionsTest, GetWarningMessages_PlatformApppHosts) {
+  scoped_refptr<Extension> extension;
+
+  extension = LoadManifest("permissions", "platform_app_hosts.json");
+  EXPECT_TRUE(extension->is_platform_app());
+  std::vector<string16> warnings = extension->GetPermissionMessageStrings();
+  ASSERT_EQ(0u, warnings.size());
+
+  extension = LoadManifest("permissions", "platform_app_all_urls.json");
+  EXPECT_TRUE(extension->is_platform_app());
+  warnings = extension->GetPermissionMessageStrings();
+  ASSERT_EQ(0u, warnings.size());
+}
+
+TEST(PermissionsTest, GetDistinctHostsForDisplay) {
+  scoped_refptr<PermissionSet> perm_set;
+  APIPermissionSet empty_perms;
+  std::set<std::string> expected;
+  expected.insert("www.foo.com");
+  expected.insert("www.bar.com");
+  expected.insert("www.baz.com");
+  URLPatternSet explicit_hosts;
+  URLPatternSet scriptable_hosts;
+
+  {
+    SCOPED_TRACE("no dupes");
+
+    // Simple list with no dupes.
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/path"));
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path"));
+    perm_set = new PermissionSet(
+        empty_perms, explicit_hosts, scriptable_hosts);
+    EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay());
+  }
+
+  {
+    SCOPED_TRACE("two dupes");
+
+    // Add some dupes.
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path"));
+    perm_set = new PermissionSet(
+        empty_perms, explicit_hosts, scriptable_hosts);
+    EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay());
+  }
+
+  {
+    SCOPED_TRACE("schemes differ");
+
+    // Add a pattern that differs only by scheme. This should be filtered out.
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTPS, "https://www.bar.com/path"));
+    perm_set = new PermissionSet(
+        empty_perms, explicit_hosts, scriptable_hosts);
+    EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay());
+  }
+
+  {
+    SCOPED_TRACE("paths differ");
+
+    // Add some dupes by path.
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/pathypath"));
+    perm_set = new PermissionSet(
+        empty_perms, explicit_hosts, scriptable_hosts);
+    EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay());
+  }
+
+  {
+    SCOPED_TRACE("subdomains differ");
+
+    // We don't do anything special for subdomains.
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://monkey.www.bar.com/path"));
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://bar.com/path"));
+
+    expected.insert("monkey.www.bar.com");
+    expected.insert("bar.com");
+
+    perm_set = new PermissionSet(
+        empty_perms, explicit_hosts, scriptable_hosts);
+    EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay());
+  }
+
+  {
+    SCOPED_TRACE("RCDs differ");
+
+    // Now test for RCD uniquing.
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.de/path"));
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca.us/path"));
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path"));
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com.my/path"));
+
+    // This is an unknown RCD, which shouldn't be uniqued out.
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.xyzzy/path"));
+    // But it should only occur once.
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.xyzzy/path"));
+
+    expected.insert("www.foo.xyzzy");
+
+    perm_set = new PermissionSet(
+        empty_perms, explicit_hosts, scriptable_hosts);
+    EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay());
+  }
+
+  {
+    SCOPED_TRACE("wildcards");
+
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com/*"));
+
+    expected.insert("*.google.com");
+
+    perm_set = new PermissionSet(
+        empty_perms, explicit_hosts, scriptable_hosts);
+    EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay());
+  }
+
+  {
+    SCOPED_TRACE("scriptable hosts");
+    explicit_hosts.ClearPatterns();
+    scriptable_hosts.ClearPatterns();
+    expected.clear();
+
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com/*"));
+    scriptable_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_HTTP, "http://*.example.com/*"));
+
+    expected.insert("*.google.com");
+    expected.insert("*.example.com");
+
+    perm_set = new PermissionSet(
+        empty_perms, explicit_hosts, scriptable_hosts);
+    EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay());
+  }
+
+  {
+    // We don't display warnings for file URLs because they are off by default.
+    SCOPED_TRACE("file urls");
+    explicit_hosts.ClearPatterns();
+    scriptable_hosts.ClearPatterns();
+    expected.clear();
+
+    explicit_hosts.AddPattern(
+        URLPattern(URLPattern::SCHEME_FILE, "file:///*"));
+
+    perm_set = new PermissionSet(
+        empty_perms, explicit_hosts, scriptable_hosts);
+    EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay());
+  }
+}
+
+TEST(PermissionsTest, GetDistinctHostsForDisplay_ComIsBestRcd) {
+  scoped_refptr<PermissionSet> perm_set;
+  APIPermissionSet empty_perms;
+  URLPatternSet explicit_hosts;
+  URLPatternSet scriptable_hosts;
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path"));
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path"));
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));
+
+  std::set<std::string> expected;
+  expected.insert("www.foo.com");
+  perm_set = new PermissionSet(
+      empty_perms, explicit_hosts, scriptable_hosts);
+  EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay());
+}
+
+TEST(PermissionsTest, GetDistinctHostsForDisplay_NetIs2ndBestRcd) {
+  scoped_refptr<PermissionSet> perm_set;
+  APIPermissionSet empty_perms;
+  URLPatternSet explicit_hosts;
+  URLPatternSet scriptable_hosts;
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path"));
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path"));
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
+  // No http://www.foo.com/path
+
+  std::set<std::string> expected;
+  expected.insert("www.foo.net");
+  perm_set = new PermissionSet(
+      empty_perms, explicit_hosts, scriptable_hosts);
+  EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay());
+}
+
+TEST(PermissionsTest,
+     GetDistinctHostsForDisplay_OrgIs3rdBestRcd) {
+  scoped_refptr<PermissionSet> perm_set;
+  APIPermissionSet empty_perms;
+  URLPatternSet explicit_hosts;
+  URLPatternSet scriptable_hosts;
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path"));
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
+  // No http://www.foo.net/path
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
+  // No http://www.foo.com/path
+
+  std::set<std::string> expected;
+  expected.insert("www.foo.org");
+  perm_set = new PermissionSet(
+      empty_perms, explicit_hosts, scriptable_hosts);
+  EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay());
+}
+
+TEST(PermissionsTest,
+     GetDistinctHostsForDisplay_FirstInListIs4thBestRcd) {
+  scoped_refptr<PermissionSet> perm_set;
+  APIPermissionSet empty_perms;
+  URLPatternSet explicit_hosts;
+  URLPatternSet scriptable_hosts;
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
+  // No http://www.foo.org/path
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
+  // No http://www.foo.net/path
+  explicit_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
+  // No http://www.foo.com/path
+
+  std::set<std::string> expected;
+  expected.insert("www.foo.ca");
+  perm_set = new PermissionSet(
+      empty_perms, explicit_hosts, scriptable_hosts);
+  EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay());
+}
+
+TEST(PermissionsTest, HasLessHostPrivilegesThan) {
+  URLPatternSet elist1;
+  URLPatternSet elist2;
+  URLPatternSet slist1;
+  URLPatternSet slist2;
+  scoped_refptr<PermissionSet> set1;
+  scoped_refptr<PermissionSet> set2;
+  APIPermissionSet empty_perms;
+  elist1.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path"));
+  elist1.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path"));
+
+  // Test that the host order does not matter.
+  elist2.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path"));
+  elist2.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path"));
+
+  set1 = new PermissionSet(empty_perms, elist1, slist1);
+  set2 = new PermissionSet(empty_perms, elist2, slist2);
+
+  EXPECT_FALSE(set1->HasLessHostPrivilegesThan(set2.get()));
+  EXPECT_FALSE(set2->HasLessHostPrivilegesThan(set1.get()));
+
+  // Test that paths are ignored.
+  elist2.ClearPatterns();
+  elist2.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/*"));
+  set2 = new PermissionSet(empty_perms, elist2, slist2);
+  EXPECT_FALSE(set1->HasLessHostPrivilegesThan(set2.get()));
+  EXPECT_FALSE(set2->HasLessHostPrivilegesThan(set1.get()));
+
+  // Test that RCDs are ignored.
+  elist2.ClearPatterns();
+  elist2.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/*"));
+  set2 = new PermissionSet(empty_perms, elist2, slist2);
+  EXPECT_FALSE(set1->HasLessHostPrivilegesThan(set2.get()));
+  EXPECT_FALSE(set2->HasLessHostPrivilegesThan(set1.get()));
+
+  // Test that subdomain wildcards are handled properly.
+  elist2.ClearPatterns();
+  elist2.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com.hk/*"));
+  set2 = new PermissionSet(empty_perms, elist2, slist2);
+  EXPECT_TRUE(set1->HasLessHostPrivilegesThan(set2.get()));
+  // TODO(jstritar): Does not match subdomains properly. http://crbug.com/65337
+  // EXPECT_FALSE(set2->HasLessHostPrivilegesThan(set1.get()));
+
+  // Test that different domains count as different hosts.
+  elist2.ClearPatterns();
+  elist2.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path"));
+  elist2.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://www.example.org/path"));
+  set2 = new PermissionSet(empty_perms, elist2, slist2);
+  EXPECT_TRUE(set1->HasLessHostPrivilegesThan(set2.get()));
+  EXPECT_FALSE(set2->HasLessHostPrivilegesThan(set1.get()));
+
+  // Test that different subdomains count as different hosts.
+  elist2.ClearPatterns();
+  elist2.AddPattern(
+      URLPattern(URLPattern::SCHEME_HTTP, "http://mail.google.com/*"));
+  set2 = new PermissionSet(empty_perms, elist2, slist2);
+  EXPECT_TRUE(set1->HasLessHostPrivilegesThan(set2.get()));
+  EXPECT_TRUE(set2->HasLessHostPrivilegesThan(set1.get()));
+}
+
+TEST(PermissionsTest, GetAPIsAsStrings) {
+  APIPermissionSet apis;
+  URLPatternSet empty_set;
+
+  apis.insert(APIPermission::kProxy);
+  apis.insert(APIPermission::kBackground);
+  apis.insert(APIPermission::kNotification);
+  apis.insert(APIPermission::kTab);
+
+  scoped_refptr<PermissionSet> perm_set = new PermissionSet(
+      apis, empty_set, empty_set);
+  std::set<std::string> api_names = perm_set->GetAPIsAsStrings();
+
+  // The result is correct if it has the same number of elements
+  // and we can convert it back to the id set.
+  EXPECT_EQ(4u, api_names.size());
+  EXPECT_EQ(apis,
+            PermissionsInfo::GetInstance()->GetAllByName(api_names));
+}
+
+TEST(PermissionsTest, IsEmpty) {
+  APIPermissionSet empty_apis;
+  URLPatternSet empty_extent;
+
+  scoped_refptr<PermissionSet> empty = new PermissionSet();
+  EXPECT_TRUE(empty->IsEmpty());
+  scoped_refptr<PermissionSet> perm_set;
+
+  perm_set = new PermissionSet(empty_apis, empty_extent, empty_extent);
+  EXPECT_TRUE(perm_set->IsEmpty());
+
+  APIPermissionSet non_empty_apis;
+  non_empty_apis.insert(APIPermission::kBackground);
+  perm_set = new PermissionSet(
+      non_empty_apis, empty_extent, empty_extent);
+  EXPECT_FALSE(perm_set->IsEmpty());
+
+  // Try non standard host
+  URLPatternSet non_empty_extent;
+  AddPattern(&non_empty_extent, "http://www.google.com/*");
+
+  perm_set = new PermissionSet(
+      empty_apis, non_empty_extent, empty_extent);
+  EXPECT_FALSE(perm_set->IsEmpty());
+
+  perm_set = new PermissionSet(
+      empty_apis, empty_extent, non_empty_extent);
+  EXPECT_FALSE(perm_set->IsEmpty());
+}
+
+TEST(PermissionsTest, ImpliedPermissions) {
+  URLPatternSet empty_extent;
+  APIPermissionSet apis;
+  apis.insert(APIPermission::kWebRequest);
+  apis.insert(APIPermission::kFileBrowserHandler);
+  EXPECT_EQ(2U, apis.size());
+
+  scoped_refptr<PermissionSet> perm_set;
+  perm_set = new PermissionSet(apis, empty_extent, empty_extent);
+  EXPECT_EQ(4U, perm_set->apis().size());
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/permissions/permissions_info.cc b/chrome/common/extensions/permissions/permissions_info.cc
new file mode 100644
index 0000000..a7be3e1
--- /dev/null
+++ b/chrome/common/extensions/permissions/permissions_info.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/permissions/permissions_info.h"
+
+#include "base/logging.h"
+#include "base/string_util.h"
+
+namespace extensions {
+
+// static
+PermissionsInfo* PermissionsInfo::GetInstance() {
+  return Singleton<PermissionsInfo>::get();
+}
+
+const APIPermissionInfo* PermissionsInfo::GetByID(
+    APIPermission::ID id) const {
+  IDMap::const_iterator i = id_map_.find(id);
+  return (i == id_map_.end()) ? NULL : i->second;
+}
+
+const APIPermissionInfo* PermissionsInfo::GetByName(
+    const std::string& name) const {
+  NameMap::const_iterator i = name_map_.find(name);
+  return (i == name_map_.end()) ? NULL : i->second;
+}
+
+APIPermissionSet PermissionsInfo::GetAll() const {
+  APIPermissionSet permissions;
+  for (IDMap::const_iterator i = id_map_.begin(); i != id_map_.end(); ++i)
+    permissions.insert(i->second->id());
+  return permissions;
+}
+
+APIPermissionSet PermissionsInfo::GetAllByName(
+    const std::set<std::string>& permission_names) const {
+  APIPermissionSet permissions;
+  for (std::set<std::string>::const_iterator i = permission_names.begin();
+       i != permission_names.end(); ++i) {
+    const APIPermissionInfo* permission_info = GetByName(*i);
+    if (permission_info)
+      permissions.insert(permission_info->id());
+  }
+  return permissions;
+}
+
+bool PermissionsInfo::HasChildPermissions(const std::string& name) const {
+  NameMap::const_iterator i = name_map_.lower_bound(name + '.');
+  if (i == name_map_.end()) return false;
+  return StartsWithASCII(i->first, name + '.', true);
+}
+
+PermissionsInfo::~PermissionsInfo() {
+  for (IDMap::iterator i = id_map_.begin(); i != id_map_.end(); ++i)
+    delete i->second;
+}
+
+PermissionsInfo::PermissionsInfo()
+    : hosted_app_permission_count_(0),
+      permission_count_(0) {
+  APIPermissionInfo::RegisterAllPermissions(this);
+}
+
+void PermissionsInfo::RegisterAlias(
+    const char* name,
+    const char* alias) {
+  DCHECK(name_map_.find(name) != name_map_.end());
+  DCHECK(name_map_.find(alias) == name_map_.end());
+  name_map_[alias] = name_map_[name];
+}
+
+const APIPermissionInfo* PermissionsInfo::RegisterPermission(
+    APIPermission::ID id,
+    const char* name,
+    int l10n_message_id,
+    PermissionMessage::ID message_id,
+    int flags,
+    const APIPermissionInfo::APIPermissionConstructor constructor) {
+  DCHECK(id_map_.find(id) == id_map_.end());
+  DCHECK(name_map_.find(name) == name_map_.end());
+
+  APIPermissionInfo* permission = new APIPermissionInfo(
+      id, name, l10n_message_id, message_id, flags, constructor);
+
+  id_map_[id] = permission;
+  name_map_[name] = permission;
+
+  permission_count_++;
+
+  return permission;
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/permissions/permissions_info.h b/chrome/common/extensions/permissions/permissions_info.h
new file mode 100644
index 0000000..e79abb4
--- /dev/null
+++ b/chrome/common/extensions/permissions/permissions_info.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_PERMISSIONS_PERMISSIONS_INFO_H_
+#define CHROME_COMMON_EXTENSIONS_PERMISSIONS_PERMISSIONS_INFO_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/singleton.h"
+#include "chrome/common/extensions/permissions/api_permission.h"
+#include "chrome/common/extensions/permissions/api_permission_set.h"
+#include "chrome/common/extensions/permissions/permission_message.h"
+
+namespace extensions {
+
+// Singleton that holds the extension permission instances and provides static
+// methods for accessing them.
+class PermissionsInfo {
+ public:
+  // Returns a pointer to the singleton instance.
+  static PermissionsInfo* GetInstance();
+
+  // Returns the permission with the given |id|, and NULL if it doesn't exist.
+  const APIPermissionInfo* GetByID(APIPermission::ID id) const;
+
+  // Returns the permission with the given |name|, and NULL if none
+  // exists.
+  const APIPermissionInfo* GetByName(const std::string& name) const;
+
+  // Returns a set containing all valid api permission ids.
+  APIPermissionSet GetAll() const;
+
+  // Converts all the permission names in |permission_names| to permission ids.
+  APIPermissionSet GetAllByName(
+      const std::set<std::string>& permission_names) const;
+
+  // Checks if any permissions have names that start with |name| followed by a
+  // period.
+  bool HasChildPermissions(const std::string& name) const;
+
+  // Gets the total number of API permissions.
+  size_t get_permission_count() const { return permission_count_; }
+
+ private:
+  friend class APIPermissionInfo;
+
+  ~PermissionsInfo();
+  PermissionsInfo();
+
+  // Registers an |alias| for a given permission |name|.
+  void RegisterAlias(const char* name, const char* alias);
+
+  // Registers a permission with the specified attributes and flags.
+  const APIPermissionInfo* RegisterPermission(
+      APIPermission::ID id,
+      const char* name,
+      int l10n_message_id,
+      PermissionMessage::ID message_id,
+      int flags,
+      const APIPermissionInfo::APIPermissionConstructor constructor);
+
+  // Maps permission ids to permissions.
+  typedef std::map<APIPermission::ID, APIPermissionInfo*> IDMap;
+
+  // Maps names and aliases to permissions.
+  typedef std::map<std::string, APIPermissionInfo*> NameMap;
+
+  IDMap id_map_;
+  NameMap name_map_;
+
+  size_t hosted_app_permission_count_;
+  size_t permission_count_;
+
+  friend struct DefaultSingletonTraits<PermissionsInfo>;
+  DISALLOW_COPY_AND_ASSIGN(PermissionsInfo);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_PERMISSIONS_PERMISSIONS_INFO_H_
diff --git a/chrome/common/extensions/permissions/socket_permission.cc b/chrome/common/extensions/permissions/socket_permission.cc
new file mode 100644
index 0000000..5a087f5
--- /dev/null
+++ b/chrome/common/extensions/permissions/socket_permission.cc
@@ -0,0 +1,205 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/permissions/socket_permission.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension_messages.h"
+#include "chrome/common/extensions/permissions/permissions_info.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace extensions {
+
+SocketPermission::SocketPermission(const APIPermissionInfo* info)
+  : APIPermission(info) {
+}
+
+SocketPermission::~SocketPermission() {
+}
+
+bool SocketPermission::HasMessages() const {
+  return !data_set_.empty();
+}
+
+PermissionMessages SocketPermission::GetMessages() const {
+  DCHECK(HasMessages());
+  PermissionMessages result;
+  if (!AddAnyHostMessage(result)) {
+    AddSpecificHostMessage(result);
+    AddSubdomainHostMessage(result);
+  }
+  return result;
+}
+
+bool SocketPermission::AddAnyHostMessage(PermissionMessages& messages)
+    const {
+  std::set<SocketPermissionData>::const_iterator i;
+  for (i = data_set_.begin(); i != data_set_.end(); ++i) {
+    if (i->GetHostType() == SocketPermissionData::ANY_HOST) {
+      messages.push_back(PermissionMessage(
+            PermissionMessage::kSocketAnyHost,
+            l10n_util::GetStringUTF16(
+                IDS_EXTENSION_PROMPT_WARNING_SOCKET_ANY_HOST)));
+      return true;
+    }
+  }
+  return false;
+}
+
+void SocketPermission::AddSubdomainHostMessage(PermissionMessages& messages)
+    const {
+  std::set<string16> domains;
+  std::set<SocketPermissionData>::const_iterator i;
+  for (i = data_set_.begin(); i != data_set_.end(); ++i) {
+    if (i->GetHostType() == SocketPermissionData::HOSTS_IN_DOMAINS)
+      domains.insert(UTF8ToUTF16(i->GetHost()));
+  }
+  if (!domains.empty()) {
+    int id = (domains.size() == 1) ?
+             IDS_EXTENSION_PROMPT_WARNING_SOCKET_HOSTS_IN_DOMAIN :
+             IDS_EXTENSION_PROMPT_WARNING_SOCKET_HOSTS_IN_DOMAINS;
+    messages.push_back(PermissionMessage(
+          PermissionMessage::kSocketDomainHosts,
+          l10n_util::GetStringFUTF16(
+              id,
+              JoinString(
+                  std::vector<string16>(
+                      domains.begin(), domains.end()), ' '))));
+  }
+}
+
+void SocketPermission::AddSpecificHostMessage(PermissionMessages& messages)
+    const {
+  std::set<string16> hostnames;
+  std::set<SocketPermissionData>::const_iterator i;
+  for (i = data_set_.begin(); i != data_set_.end(); ++i) {
+    if (i->GetHostType() == SocketPermissionData::SPECIFIC_HOSTS)
+      hostnames.insert(UTF8ToUTF16(i->GetHost()));
+  }
+  if (!hostnames.empty()) {
+    int id = (hostnames.size() == 1) ?
+             IDS_EXTENSION_PROMPT_WARNING_SOCKET_SPECIFIC_HOST :
+             IDS_EXTENSION_PROMPT_WARNING_SOCKET_SPECIFIC_HOSTS;
+    messages.push_back(PermissionMessage(
+          PermissionMessage::kSocketSpecificHosts,
+          l10n_util::GetStringFUTF16(
+              id,
+              JoinString(
+                  std::vector<string16>(
+                      hostnames.begin(), hostnames.end()), ' '))));
+  }
+}
+
+bool SocketPermission::Check(
+    const APIPermission::CheckParam* param) const {
+  const CheckParam* socket_param = static_cast<const CheckParam*>(param);
+  std::set<SocketPermissionData>::const_iterator i;
+  for (i = data_set_.begin(); i != data_set_.end(); ++i) {
+    if (i->Match(socket_param->request))
+      return true;
+  }
+  return false;
+}
+
+bool SocketPermission::Contains(const APIPermission* rhs) const {
+  CHECK(rhs->info() == info());
+  const SocketPermission* perm = static_cast<const SocketPermission*>(rhs);
+  return std::includes(data_set_.begin(), data_set_.end(),
+                       perm->data_set_.begin(), perm->data_set_.end());
+}
+
+bool SocketPermission::Equal(const APIPermission* rhs) const {
+  CHECK(rhs->info() == info());
+  const SocketPermission* perm = static_cast<const SocketPermission*>(rhs);
+  return data_set_ == perm->data_set_;
+}
+
+bool SocketPermission::FromValue(const base::Value* value) {
+  data_set_.clear();
+  const base::ListValue* list = NULL;
+
+  if (!value)
+    return false;
+
+  if (!value->GetAsList(&list) || list->GetSize() == 0)
+    return false;
+
+  for (size_t i = 0; i < list->GetSize(); ++i) {
+    std::string str;
+    SocketPermissionData data;
+    if (!list->GetString(i, &str) || !data.Parse(str))
+      return false;
+    data_set_.insert(data);
+  }
+  return true;
+}
+
+void SocketPermission::ToValue(base::Value** value) const {
+  base::ListValue* list = new ListValue();
+  std::set<SocketPermissionData>::const_iterator i;
+  for (i = data_set_.begin(); i != data_set_.end(); ++i) {
+    list->Append(base::Value::CreateStringValue(i->GetAsString()));
+  }
+  *value = list;
+}
+
+APIPermission* SocketPermission::Clone() const {
+  SocketPermission* result = new SocketPermission(info());
+  result->data_set_ = data_set_;
+  return result;
+}
+
+APIPermission* SocketPermission::Diff(const APIPermission* rhs) const {
+  CHECK(rhs->info() == info());
+  const SocketPermission* perm = static_cast<const SocketPermission*>(rhs);
+  scoped_ptr<SocketPermission> result(new SocketPermission(info()));
+  std::set_difference(data_set_.begin(), data_set_.end(),
+                      perm->data_set_.begin(), perm->data_set_.end(),
+                      std::inserter<std::set<SocketPermissionData> >(
+                          result->data_set_, result->data_set_.begin()));
+  return result->data_set_.empty() ? NULL : result.release();
+}
+
+APIPermission* SocketPermission::Union(const APIPermission* rhs) const {
+  CHECK(rhs->info() == info());
+  const SocketPermission* perm = static_cast<const SocketPermission*>(rhs);
+  scoped_ptr<SocketPermission> result(new SocketPermission(info()));
+  std::set_union(data_set_.begin(), data_set_.end(),
+                 perm->data_set_.begin(), perm->data_set_.end(),
+                 std::inserter<std::set<SocketPermissionData> >(
+                     result->data_set_, result->data_set_.begin()));
+  return result->data_set_.empty() ? NULL : result.release();
+}
+
+APIPermission* SocketPermission::Intersect(const APIPermission* rhs) const {
+  CHECK(rhs->info() == info());
+  const SocketPermission* perm = static_cast<const SocketPermission*>(rhs);
+  scoped_ptr<SocketPermission> result(new SocketPermission(info()));
+  std::set_intersection(data_set_.begin(), data_set_.end(),
+                        perm->data_set_.begin(), perm->data_set_.end(),
+                        std::inserter<std::set<SocketPermissionData> >(
+                            result->data_set_, result->data_set_.begin()));
+  return result->data_set_.empty() ? NULL : result.release();
+}
+
+void SocketPermission::Write(IPC::Message* m) const {
+  IPC::WriteParam(m, data_set_);
+}
+
+bool SocketPermission::Read(const IPC::Message* m, PickleIterator* iter) {
+  return IPC::ReadParam(m, iter, &data_set_);
+}
+
+void SocketPermission::Log(std::string* log) const {
+  IPC::LogParam(data_set_, log);
+}
+
+}  // namespace extensions
+
diff --git a/chrome/common/extensions/permissions/socket_permission.h b/chrome/common/extensions/permissions/socket_permission.h
new file mode 100644
index 0000000..e148379
--- /dev/null
+++ b/chrome/common/extensions/permissions/socket_permission.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_PERMISSIONS_SOCKET_PERMISSION_H_
+#define CHROME_COMMON_EXTENSIONS_PERMISSIONS_SOCKET_PERMISSION_H_
+
+#include <set>
+#include <string>
+
+#include "chrome/common/extensions/permissions/api_permission.h"
+#include "chrome/common/extensions/permissions/socket_permission_data.h"
+
+namespace base {
+class Value;
+}
+
+namespace IPC {
+class Message;
+}
+
+namespace extensions {
+
+// There's room to share code with related classes, see http://crbug.com/147531
+
+class SocketPermission : public APIPermission {
+ public:
+  struct CheckParam : APIPermission::CheckParam {
+    CheckParam(content::SocketPermissionRequest::OperationType type,
+        const std::string& host,
+        int port)
+      : request(type, host, port) { }
+    content::SocketPermissionRequest request;
+  };
+
+  explicit SocketPermission(const APIPermissionInfo* info);
+
+  virtual ~SocketPermission();
+
+  // Returns true if this permission has PermissionMessages.
+  virtual bool HasMessages() const OVERRIDE;
+
+  // Returns the localized permission messages of this permission.
+  virtual PermissionMessages GetMessages() const OVERRIDE;
+
+  // Returns true if the given permission in param is allowed.
+  virtual bool Check(
+      const APIPermission::CheckParam* param) const OVERRIDE;
+
+  // Returns true if |rhs| is a subset of this.
+  virtual bool Contains(const APIPermission* rhs) const OVERRIDE;
+
+  // Returns true if |rhs| is equal to this.
+  virtual bool Equal(const APIPermission* rhs) const OVERRIDE;
+
+  // Parses the rhs from |value|. Returns false if error happens.
+  virtual bool FromValue(const base::Value* value) OVERRIDE;
+
+  // Stores this into a new created |value|.
+  virtual void ToValue(base::Value** value) const OVERRIDE;
+
+  // Clones this.
+  virtual APIPermission* Clone() const OVERRIDE;
+
+  // Returns a new API permission rhs which equals this - |rhs|.
+  virtual APIPermission* Diff(const APIPermission* rhs) const OVERRIDE;
+
+  // Returns a new API permission rhs which equals the union of this and
+  // |rhs|.
+  virtual APIPermission* Union(const APIPermission* rhs) const OVERRIDE;
+
+  // Returns a new API permission rhs which equals the intersect of this and
+  // |rhs|.
+  virtual APIPermission* Intersect(const APIPermission* rhs) const OVERRIDE;
+
+  // IPC functions
+  // Writes this into the given IPC message |m|.
+  virtual void Write(IPC::Message* m) const OVERRIDE;
+
+  // Reads from the given IPC message |m|.
+  virtual bool Read(const IPC::Message* m, PickleIterator* iter) OVERRIDE;
+
+  // Logs this rhs.
+  virtual void Log(std::string* log) const OVERRIDE;
+
+ private:
+  bool AddAnyHostMessage(PermissionMessages& messages) const;
+  void AddSubdomainHostMessage(PermissionMessages& messages) const;
+  void AddSpecificHostMessage(PermissionMessages& messages) const;
+
+  std::set<SocketPermissionData> data_set_;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_PERMISSIONS_SOCKET_PERMISSION_H_
diff --git a/chrome/common/extensions/permissions/socket_permission_data.cc b/chrome/common/extensions/permissions/socket_permission_data.cc
new file mode 100644
index 0000000..bd9f041
--- /dev/null
+++ b/chrome/common/extensions/permissions/socket_permission_data.cc
@@ -0,0 +1,243 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/permissions/socket_permission_data.h"
+
+#include <cstdlib>
+#include <sstream>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "googleurl/src/url_canon.h"
+
+namespace {
+
+using content::SocketPermissionRequest;
+using extensions::SocketPermissionData;
+
+const char kColon = ':';
+const char kDot = '.';
+const char kWildcard[] = "*";
+const char kInvalid[] = "invalid";
+const char kTCPConnect[] = "tcp-connect";
+const char kTCPListen[] = "tcp-listen";
+const char kUDPBind[] = "udp-bind";
+const char kUDPSendTo[] = "udp-send-to";
+const int kAnyPort = 0;
+const int kInvalidPort = -1;
+
+SocketPermissionRequest::OperationType StringToType(const std::string& s) {
+  if (s == kTCPConnect)
+    return SocketPermissionRequest::TCP_CONNECT;
+  if (s == kTCPListen)
+    return SocketPermissionRequest::TCP_LISTEN;
+  if (s == kUDPBind)
+    return SocketPermissionRequest::UDP_BIND;
+  if (s == kUDPSendTo)
+    return SocketPermissionRequest::UDP_SEND_TO;
+  return SocketPermissionRequest::NONE;
+}
+
+const char* TypeToString(SocketPermissionRequest::OperationType type) {
+  switch (type) {
+    case SocketPermissionRequest::TCP_CONNECT:
+      return kTCPConnect;
+    case SocketPermissionRequest::TCP_LISTEN:
+      return kTCPListen;
+    case SocketPermissionRequest::UDP_BIND:
+      return kUDPBind;
+    case SocketPermissionRequest::UDP_SEND_TO:
+      return kUDPSendTo;
+    default:
+      return kInvalid;
+  }
+}
+
+bool StartsOrEndsWithWhitespace(const std::string& str) {
+  if (str.find_first_not_of(kWhitespaceASCII) != 0)
+    return true;
+  if (str.find_last_not_of(kWhitespaceASCII) != str.length() - 1)
+    return true;
+  return false;
+}
+
+}  // namespace
+
+namespace extensions {
+
+SocketPermissionData::SocketPermissionData()
+  : pattern_(SocketPermissionRequest::NONE, std::string(), kInvalidPort) {
+  Reset();
+}
+
+SocketPermissionData::~SocketPermissionData() {
+}
+
+bool SocketPermissionData::operator<(const SocketPermissionData& rhs) const {
+  if (pattern_.type < rhs.pattern_.type)
+    return true;
+  if (pattern_.type > rhs.pattern_.type)
+    return false;
+
+  if (pattern_.host < rhs.pattern_.host)
+    return true;
+  if (pattern_.host > rhs.pattern_.host)
+    return false;
+
+  if (match_subdomains_ < rhs.match_subdomains_)
+    return true;
+  if (match_subdomains_ > rhs.match_subdomains_)
+    return false;
+
+  if (pattern_.port < rhs.pattern_.port)
+    return true;
+  return false;
+}
+
+bool SocketPermissionData::operator==(const SocketPermissionData& rhs) const {
+  return (pattern_.type == rhs.pattern_.type) &&
+         (pattern_.host == rhs.pattern_.host) &&
+         (match_subdomains_ == rhs.match_subdomains_) &&
+         (pattern_.port == rhs.pattern_.port);
+}
+
+bool SocketPermissionData::Match(SocketPermissionRequest request) const {
+  if (pattern_.type != request.type)
+    return false;
+
+  std::string lhost = StringToLowerASCII(request.host);
+  if (pattern_.host != lhost) {
+    if (!match_subdomains_)
+      return false;
+
+    if (!pattern_.host.empty()) {
+      // Do not wildcard part of IP address.
+      url_parse::Component component(0, lhost.length());
+      url_canon::RawCanonOutputT<char, 128> ignored_output;
+      url_canon::CanonHostInfo host_info;
+      url_canon::CanonicalizeIPAddress(lhost.c_str(), component,
+                                       &ignored_output, &host_info);
+      if (host_info.IsIPAddress())
+        return false;
+
+      // host should equal one or more chars + "." +  host_.
+      int i = lhost.length() - pattern_.host.length();
+      if (i < 2)
+        return false;
+
+      if (lhost.compare(i, pattern_.host.length(), pattern_.host) != 0)
+        return false;
+
+      if (lhost[i - 1] != kDot)
+        return false;
+    }
+  }
+
+  if (pattern_.port != request.port && pattern_.port != kAnyPort)
+    return false;
+
+  return true;
+}
+
+bool SocketPermissionData::Parse(const std::string& permission) {
+  do {
+    pattern_.host.clear();
+    match_subdomains_ = true;
+    pattern_.port = kAnyPort;
+    spec_.clear();
+
+    std::vector<std::string> tokens;
+    base::SplitStringDontTrim(permission, kColon, &tokens);
+
+    if (tokens.empty() || tokens.size() > 3)
+      break;
+
+    pattern_.type = StringToType(tokens[0]);
+    if (pattern_.type == SocketPermissionRequest::NONE)
+      break;
+
+    if (tokens.size() == 1)
+      return true;
+
+    pattern_.host = tokens[1];
+    if (!pattern_.host.empty()) {
+      if (StartsOrEndsWithWhitespace(pattern_.host))
+        break;
+      pattern_.host = StringToLowerASCII(pattern_.host);
+
+      // The first component can optionally be '*' to match all subdomains.
+      std::vector<std::string> host_components;
+      base::SplitString(pattern_.host, kDot, &host_components);
+      DCHECK(!host_components.empty());
+
+      if (host_components[0] == kWildcard || host_components[0].empty()) {
+        host_components.erase(host_components.begin(),
+                              host_components.begin() + 1);
+      } else {
+        match_subdomains_ = false;
+      }
+      pattern_.host = JoinString(host_components, kDot);
+    }
+
+    if (tokens.size() == 2 || tokens[2].empty() || tokens[2] == kWildcard)
+      return true;
+
+    if (StartsOrEndsWithWhitespace(tokens[2]))
+      break;
+
+    if (!base::StringToInt(tokens[2], &pattern_.port) ||
+        pattern_.port < 1 || pattern_.port > 65535)
+      break;
+    return true;
+  } while (false);
+
+  Reset();
+  return false;
+}
+
+SocketPermissionData::HostType SocketPermissionData::GetHostType() const {
+  return pattern_.host.empty() ? SocketPermissionData::ANY_HOST :
+         match_subdomains_     ? SocketPermissionData::HOSTS_IN_DOMAINS :
+                                 SocketPermissionData::SPECIFIC_HOSTS;
+}
+
+const std::string SocketPermissionData::GetHost() const {
+  return pattern_.host;
+}
+
+const std::string& SocketPermissionData::GetAsString() const {
+  if (!spec_.empty())
+    return spec_;
+
+  spec_.reserve(64);
+  spec_.append(TypeToString(pattern_.type));
+
+  if (match_subdomains_) {
+    spec_.append(1, kColon).append(kWildcard);
+    if (!pattern_.host.empty())
+      spec_.append(1, kDot).append(pattern_.host);
+  } else {
+     spec_.append(1, kColon).append(pattern_.host);
+  }
+
+  if (pattern_.port == kAnyPort)
+    spec_.append(1, kColon).append(kWildcard);
+  else
+    spec_.append(1, kColon).append(base::IntToString(pattern_.port));
+
+  return spec_;
+}
+
+void SocketPermissionData::Reset() {
+  pattern_.type = SocketPermissionRequest::NONE;
+  pattern_.host.clear();
+  match_subdomains_ = false;
+  pattern_.port = kInvalidPort;
+  spec_.clear();
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/permissions/socket_permission_data.h b/chrome/common/extensions/permissions/socket_permission_data.h
new file mode 100644
index 0000000..a98d41a
--- /dev/null
+++ b/chrome/common/extensions/permissions/socket_permission_data.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 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.
+#ifndef CHROME_COMMON_EXTENSIONS_PERMISSIONS_SOCKET_PERMISSION_DATA_H_
+#define CHROME_COMMON_EXTENSIONS_PERMISSIONS_SOCKET_PERMISSION_DATA_H_
+
+#include <string>
+
+#include "content/public/common/socket_permission_request.h"
+
+namespace extensions {
+
+// A pattern that can be used to match socket permission.
+//   <socket-permission-pattern>
+//          := <op> |
+//             <op> ':' <host> |
+//             <op> ':' ':' <port> |
+//             <op> ':' <host> ':' <port>
+//   <op>   := 'tcp-connect' |
+//             'tcp-listen' |
+//             'udp-bind' |
+//             'udp-send-to'
+//   <host> := '*' |
+//             '*.' <anychar except '/' and '*'>+ |
+//             <anychar except '/' and '*'>+
+//   <port> := '*' |
+//             <port number between 0 and 65535>)
+class SocketPermissionData {
+ public:
+  enum HostType {
+    ANY_HOST,
+    HOSTS_IN_DOMAINS,
+    SPECIFIC_HOSTS,
+  };
+
+  SocketPermissionData();
+  ~SocketPermissionData();
+
+  // operators <, == are needed by container std::set and algorithms
+  // std::set_includes and std::set_differences.
+  bool operator<(const SocketPermissionData& rhs) const;
+  bool operator==(const SocketPermissionData& rhs) const;
+
+  bool Match(content::SocketPermissionRequest request) const;
+
+  bool Parse(const std::string& permission);
+
+  HostType GetHostType() const;
+  const std::string GetHost() const;
+
+  const std::string& GetAsString() const;
+
+ private:
+  void Reset();
+
+  content::SocketPermissionRequest pattern_;
+  bool match_subdomains_;
+  mutable std::string spec_;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_PERMISSIONS_SOCKET_PERMISSION_DATA_H_
diff --git a/chrome/common/extensions/permissions/socket_permission_unittest.cc b/chrome/common/extensions/permissions/socket_permission_unittest.cc
new file mode 100644
index 0000000..7ed1a52
--- /dev/null
+++ b/chrome/common/extensions/permissions/socket_permission_unittest.cc
@@ -0,0 +1,267 @@
+// Copyright (c) 2012 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.
+
+#include <string>
+
+#include "base/pickle.h"
+#include "base/values.h"
+#include "chrome/common/extensions/permissions/permissions_info.h"
+#include "chrome/common/extensions/permissions/socket_permission.h"
+#include "chrome/common/extensions/permissions/socket_permission_data.h"
+#include "ipc/ipc_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::SocketPermissionRequest;
+using extensions::SocketPermissionData;
+
+namespace {
+
+std::string Parse(const std::string& permission) {
+  SocketPermissionData data;
+  CHECK(data.Parse(permission)) << "Parse permission \"" << permission
+    << "\" failed.";
+  return data.GetAsString();
+}
+
+}  // namespace
+
+namespace extensions {
+
+class SocketPermissionTest : public testing::Test {
+};
+
+TEST(SocketPermissionTest, General) {
+  SocketPermissionData data1, data2;
+
+  CHECK(data1.Parse("tcp-connect"));
+  CHECK(data2.Parse("tcp-connect"));
+
+  EXPECT_TRUE(data1 == data2);
+  EXPECT_FALSE(data1 < data2);
+
+  CHECK(data1.Parse("tcp-connect"));
+  CHECK(data2.Parse("tcp-connect:www.example.com"));
+
+  EXPECT_FALSE(data1 == data2);
+  EXPECT_TRUE(data1 < data2);
+}
+
+TEST(SocketPermissionTest, Parse) {
+  SocketPermissionData data;
+
+  EXPECT_FALSE(data.Parse(""));
+  EXPECT_FALSE(data.Parse("*"));
+  EXPECT_FALSE(data.Parse("\00\00*"));
+  EXPECT_FALSE(data.Parse("\01*"));
+  EXPECT_FALSE(data.Parse("tcp-connect:www.example.com:-1"));
+  EXPECT_FALSE(data.Parse("tcp-connect:www.example.com:65536"));
+  EXPECT_FALSE(data.Parse("tcp-connect:::"));
+  EXPECT_FALSE(data.Parse("tcp-connect::0"));
+  EXPECT_FALSE(data.Parse("tcp-connect:  www.exmaple.com:  99  "));
+  EXPECT_FALSE(data.Parse("tcp-connect:*.exmaple.com :99"));
+  EXPECT_FALSE(data.Parse("tcp-connect:*.exmaple.com: 99"));
+  EXPECT_FALSE(data.Parse("tcp-connect:*.exmaple.com:99 "));
+  EXPECT_FALSE(data.Parse("tcp-connect:\t*.exmaple.com:99"));
+  EXPECT_FALSE(data.Parse("tcp-connect:\n*.exmaple.com:99"));
+
+  EXPECT_EQ(Parse("tcp-connect"), "tcp-connect:*:*");
+  EXPECT_EQ(Parse("tcp-listen"), "tcp-listen:*:*");
+  EXPECT_EQ(Parse("udp-bind"), "udp-bind:*:*");
+  EXPECT_EQ(Parse("udp-send-to"), "udp-send-to:*:*");
+
+  EXPECT_EQ(Parse("tcp-connect:"), "tcp-connect:*:*");
+  EXPECT_EQ(Parse("tcp-listen:"), "tcp-listen:*:*");
+  EXPECT_EQ(Parse("udp-bind:"), "udp-bind:*:*");
+  EXPECT_EQ(Parse("udp-send-to:"), "udp-send-to:*:*");
+
+  EXPECT_EQ(Parse("tcp-connect::"), "tcp-connect:*:*");
+  EXPECT_EQ(Parse("tcp-listen::"), "tcp-listen:*:*");
+  EXPECT_EQ(Parse("udp-bind::"), "udp-bind:*:*");
+  EXPECT_EQ(Parse("udp-send-to::"), "udp-send-to:*:*");
+
+  EXPECT_EQ(Parse("tcp-connect:*"), "tcp-connect:*:*");
+  EXPECT_EQ(Parse("tcp-listen:*"), "tcp-listen:*:*");
+  EXPECT_EQ(Parse("udp-bind:*"), "udp-bind:*:*");
+  EXPECT_EQ(Parse("udp-send-to:*"), "udp-send-to:*:*");
+
+  EXPECT_EQ(Parse("tcp-connect:*:"), "tcp-connect:*:*");
+  EXPECT_EQ(Parse("tcp-listen:*:"), "tcp-listen:*:*");
+  EXPECT_EQ(Parse("udp-bind:*:"), "udp-bind:*:*");
+  EXPECT_EQ(Parse("udp-send-to:*:"), "udp-send-to:*:*");
+
+  EXPECT_EQ(Parse("tcp-connect::*"), "tcp-connect:*:*");
+  EXPECT_EQ(Parse("tcp-listen::*"), "tcp-listen:*:*");
+  EXPECT_EQ(Parse("udp-bind::*"), "udp-bind:*:*");
+  EXPECT_EQ(Parse("udp-send-to::*"), "udp-send-to:*:*");
+
+  EXPECT_EQ(Parse("tcp-connect:www.example.com"),
+      "tcp-connect:www.example.com:*");
+  EXPECT_EQ(Parse("tcp-listen:www.example.com"),
+      "tcp-listen:www.example.com:*");
+  EXPECT_EQ(Parse("udp-bind:www.example.com"),
+      "udp-bind:www.example.com:*");
+  EXPECT_EQ(Parse("udp-send-to:www.example.com"),
+      "udp-send-to:www.example.com:*");
+  EXPECT_EQ(Parse("udp-send-to:wWW.ExAmPlE.cOm"),
+      "udp-send-to:www.example.com:*");
+
+  EXPECT_EQ(Parse("tcp-connect:.example.com"),
+      "tcp-connect:*.example.com:*");
+  EXPECT_EQ(Parse("tcp-listen:.example.com"),
+      "tcp-listen:*.example.com:*");
+  EXPECT_EQ(Parse("udp-bind:.example.com"),
+      "udp-bind:*.example.com:*");
+  EXPECT_EQ(Parse("udp-send-to:.example.com"),
+      "udp-send-to:*.example.com:*");
+
+  EXPECT_EQ(Parse("tcp-connect:*.example.com"),
+      "tcp-connect:*.example.com:*");
+  EXPECT_EQ(Parse("tcp-listen:*.example.com"),
+      "tcp-listen:*.example.com:*");
+  EXPECT_EQ(Parse("udp-bind:*.example.com"),
+      "udp-bind:*.example.com:*");
+  EXPECT_EQ(Parse("udp-send-to:*.example.com"),
+      "udp-send-to:*.example.com:*");
+
+  EXPECT_EQ(Parse("tcp-connect::99"), "tcp-connect:*:99");
+  EXPECT_EQ(Parse("tcp-listen::99"), "tcp-listen:*:99");
+  EXPECT_EQ(Parse("udp-bind::99"), "udp-bind:*:99");
+  EXPECT_EQ(Parse("udp-send-to::99"), "udp-send-to:*:99");
+
+  EXPECT_EQ(Parse("tcp-connect:www.example.com"),
+      "tcp-connect:www.example.com:*");
+
+  EXPECT_EQ(Parse("tcp-connect:*.example.com:99"),
+      "tcp-connect:*.example.com:99");
+}
+
+TEST(SocketPermissionTest, Match) {
+  SocketPermissionData data;
+
+  CHECK(data.Parse("tcp-connect"));
+  EXPECT_TRUE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80)));
+  EXPECT_FALSE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::UDP_SEND_TO, "www.example.com", 80)));
+
+  CHECK(data.Parse("udp-send-to::8800"));
+  EXPECT_TRUE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::UDP_SEND_TO, "www.example.com", 8800)));
+  EXPECT_TRUE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::UDP_SEND_TO, "smtp.example.com", 8800)));
+  EXPECT_FALSE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80)));
+
+  CHECK(data.Parse("udp-send-to:*.example.com:8800"));
+  EXPECT_TRUE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::UDP_SEND_TO, "www.example.com", 8800)));
+  EXPECT_TRUE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::UDP_SEND_TO, "smtp.example.com", 8800)));
+  EXPECT_TRUE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::UDP_SEND_TO, "SMTP.example.com", 8800)));
+  EXPECT_FALSE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80)));
+  EXPECT_FALSE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::UDP_SEND_TO, "www.google.com", 8800)));
+  EXPECT_FALSE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::UDP_SEND_TO, "wwwexample.com", 8800)));
+
+  CHECK(data.Parse("udp-send-to:*.ExAmPlE.cOm:8800"));
+  EXPECT_TRUE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::UDP_SEND_TO, "www.example.com", 8800)));
+  EXPECT_TRUE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::UDP_SEND_TO, "smtp.example.com", 8800)));
+  EXPECT_TRUE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::UDP_SEND_TO, "SMTP.example.com", 8800)));
+  EXPECT_FALSE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80)));
+  EXPECT_FALSE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::UDP_SEND_TO, "www.google.com", 8800)));
+
+  CHECK(data.Parse("udp-bind::8800"));
+  EXPECT_TRUE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::UDP_BIND, "127.0.0.1", 8800)));
+  EXPECT_FALSE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::UDP_BIND, "127.0.0.1", 8888)));
+  EXPECT_FALSE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80)));
+  EXPECT_FALSE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::UDP_SEND_TO, "www.google.com", 8800)));
+
+  // Do not wildcard part of ip address.
+  CHECK(data.Parse("tcp-connect:*.168.0.1:8800"));
+  EXPECT_FALSE(data.Match(SocketPermissionRequest(
+        SocketPermissionRequest::TCP_CONNECT, "192.168.0.1", 8800)));
+}
+
+TEST(SocketPermissionTest, IPC) {
+  const APIPermissionInfo* permission_info =
+    PermissionsInfo::GetInstance()->GetByID(APIPermission::kSocket);
+
+  {
+    IPC::Message m;
+
+    scoped_ptr<APIPermission> permission1(
+        permission_info->CreateAPIPermission());
+    scoped_ptr<APIPermission> permission2(
+        permission_info->CreateAPIPermission());
+
+    permission1->Write(&m);
+    PickleIterator iter(m);
+    permission2->Read(&m, &iter);
+
+    EXPECT_TRUE(permission1->Equal(permission2.get()));
+  }
+
+
+  {
+    IPC::Message m;
+
+    scoped_ptr<APIPermission> permission1(
+        permission_info->CreateAPIPermission());
+    scoped_ptr<APIPermission> permission2(
+        permission_info->CreateAPIPermission());
+
+    scoped_ptr<ListValue> value(new ListValue());
+    value->Append(Value::CreateStringValue("tcp-connect:*.example.com:80"));
+    value->Append(Value::CreateStringValue("udp-bind::8080"));
+    value->Append(Value::CreateStringValue("udp-send-to::8888"));
+    CHECK(permission1->FromValue(value.get()));
+
+    EXPECT_FALSE(permission1->Equal(permission2.get()));
+
+    permission1->Write(&m);
+    PickleIterator iter(m);
+    permission2->Read(&m, &iter);
+    EXPECT_TRUE(permission1->Equal(permission2.get()));
+  }
+}
+
+TEST(SocketPermissionTest, Value) {
+  const APIPermissionInfo* permission_info =
+    PermissionsInfo::GetInstance()->GetByID(APIPermission::kSocket);
+
+  scoped_ptr<APIPermission> permission1(
+      permission_info->CreateAPIPermission());
+  scoped_ptr<APIPermission> permission2(
+      permission_info->CreateAPIPermission());
+
+  scoped_ptr<ListValue> value(new ListValue());
+  value->Append(Value::CreateStringValue("tcp-connect:*.example.com:80"));
+  value->Append(Value::CreateStringValue("udp-bind::8080"));
+  value->Append(Value::CreateStringValue("udp-send-to::8888"));
+  CHECK(permission1->FromValue(value.get()));
+
+  EXPECT_FALSE(permission1->Equal(permission2.get()));
+
+  base::Value* vtmp = NULL;
+  permission1->ToValue(&vtmp);
+  CHECK(vtmp);
+  CHECK(permission2->FromValue(vtmp));
+  EXPECT_TRUE(permission1->Equal(permission2.get()));
+
+  delete vtmp;
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/request_media_access_permission_helper.cc b/chrome/common/extensions/request_media_access_permission_helper.cc
new file mode 100644
index 0000000..e9296e9
--- /dev/null
+++ b/chrome/common/extensions/request_media_access_permission_helper.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/request_media_access_permission_helper.h"
+
+#include "chrome/common/extensions/feature_switch.h"
+
+namespace extensions {
+
+// static
+void RequestMediaAccessPermissionHelper::AuthorizeRequest(
+    const content::MediaStreamRequest* request,
+    const content::MediaResponseCallback& callback,
+    const extensions::Extension* extension,
+    bool is_packaged_app) {
+  content::MediaStreamDevices devices;
+
+  bool accepted_an_audio_device = false;
+  bool accepted_a_video_device = false;
+  for (content::MediaStreamDeviceMap::const_iterator it =
+       request->devices.begin(); it != request->devices.end(); ++it) {
+    if (!accepted_an_audio_device &&
+        content::IsAudioMediaType(it->first) &&
+        !it->second.empty()) {
+      // Require flag and tab capture permission for tab media.
+      // Require audio capture permission for packaged apps.
+      if ((it->first == content::MEDIA_TAB_AUDIO_CAPTURE &&
+           extensions::FeatureSwitch::tab_capture()->IsEnabled() &&
+           extension->HasAPIPermission(APIPermission::kTabCapture)) ||
+          (it->first == content::MEDIA_DEVICE_AUDIO_CAPTURE &&
+           is_packaged_app &&
+           extension->HasAPIPermission(APIPermission::kAudioCapture))) {
+        devices.push_back(it->second.front());
+        accepted_an_audio_device = true;
+      }
+    } else if (!accepted_a_video_device &&
+               content::IsVideoMediaType(it->first) &&
+               !it->second.empty()) {
+      // Require flag and tab capture permission for tab media.
+      // Require video capture permission for packaged apps.
+      if ((it->first == content::MEDIA_TAB_VIDEO_CAPTURE &&
+           extensions::FeatureSwitch::tab_capture()->IsEnabled() &&
+           extension->HasAPIPermission(APIPermission::kTabCapture)) ||
+          (it->first == content::MEDIA_DEVICE_VIDEO_CAPTURE &&
+           is_packaged_app &&
+           extension->HasAPIPermission(APIPermission::kVideoCapture))) {
+        devices.push_back(it->second.front());
+        accepted_a_video_device = true;
+      }
+    }
+  }
+
+  callback.Run(devices);
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/request_media_access_permission_helper.h b/chrome/common/extensions/request_media_access_permission_helper.h
new file mode 100644
index 0000000..7d8a768
--- /dev/null
+++ b/chrome/common/extensions/request_media_access_permission_helper.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_REQUEST_MEDIA_ACCESS_PERMISSION_HELPER_H_
+#define CHROME_COMMON_EXTENSIONS_REQUEST_MEDIA_ACCESS_PERMISSION_HELPER_H_
+
+#include "base/callback.h"
+#include "chrome/common/extensions/extension.h"
+#include "content/public/common/media_stream_request.h"
+
+namespace content{
+typedef base::Callback< void(const MediaStreamDevices&) > MediaResponseCallback;
+}  // namespace content
+
+namespace extensions {
+
+class RequestMediaAccessPermissionHelper {
+ public:
+  static void AuthorizeRequest(const content::MediaStreamRequest* request,
+                               const content::MediaResponseCallback& callback,
+                               const extensions::Extension* extension,
+                               bool is_packaged_app);
+
+ private:
+  RequestMediaAccessPermissionHelper();
+  ~RequestMediaAccessPermissionHelper();
+
+  DISALLOW_COPY_AND_ASSIGN(RequestMediaAccessPermissionHelper);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_REQUEST_MEDIA_ACCESS_PERMISSION_HELPER_H_
diff --git a/chrome/common/extensions/unpacker.cc b/chrome/common/extensions/unpacker.cc
new file mode 100644
index 0000000..001baa5
--- /dev/null
+++ b/chrome/common/extensions/unpacker.cc
@@ -0,0 +1,339 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/unpacker.h"
+
+#include <set>
+
+#include "base/file_util.h"
+#include "base/i18n/rtl.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/memory/scoped_handle.h"
+#include "base/scoped_temp_dir.h"
+#include "base/string_util.h"
+#include "base/threading/thread.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/extension_file_util.h"
+#include "chrome/common/extensions/extension_l10n_util.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/common/zip.h"
+#include "content/public/common/common_param_traits.h"
+#include "grit/generated_resources.h"
+#include "ipc/ipc_message_utils.h"
+#include "net/base/file_stream.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "webkit/glue/image_decoder.h"
+
+namespace errors = extension_manifest_errors;
+namespace keys = extension_manifest_keys;
+namespace filenames = extension_filenames;
+
+namespace {
+
+// A limit to stop us passing dangerously large canvases to the browser.
+const int kMaxImageCanvas = 4096 * 4096;
+
+SkBitmap DecodeImage(const FilePath& path) {
+  // Read the file from disk.
+  std::string file_contents;
+  if (!file_util::PathExists(path) ||
+      !file_util::ReadFileToString(path, &file_contents)) {
+    return SkBitmap();
+  }
+
+  // Decode the image using WebKit's image decoder.
+  const unsigned char* data =
+      reinterpret_cast<const unsigned char*>(file_contents.data());
+  webkit_glue::ImageDecoder decoder;
+  SkBitmap bitmap = decoder.Decode(data, file_contents.length());
+  Sk64 bitmap_size = bitmap.getSize64();
+  if (!bitmap_size.is32() || bitmap_size.get32() > kMaxImageCanvas)
+    return SkBitmap();
+  return bitmap;
+}
+
+bool PathContainsParentDirectory(const FilePath& path) {
+  const FilePath::StringType kSeparators(FilePath::kSeparators);
+  const FilePath::StringType kParentDirectory(FilePath::kParentDirectory);
+  const size_t npos = FilePath::StringType::npos;
+  const FilePath::StringType& value = path.value();
+
+  for (size_t i = 0; i < value.length(); ) {
+    i = value.find(kParentDirectory, i);
+    if (i != npos) {
+      if ((i == 0 || kSeparators.find(value[i-1]) == npos) &&
+          (i+1 < value.length() || kSeparators.find(value[i+1]) == npos)) {
+        return true;
+      }
+      ++i;
+    }
+  }
+
+  return false;
+}
+
+}  // namespace
+
+namespace extensions {
+
+Unpacker::Unpacker(const FilePath& extension_path,
+                   const std::string& extension_id,
+                   Extension::Location location,
+                   int creation_flags)
+    : extension_path_(extension_path),
+      extension_id_(extension_id),
+      location_(location),
+      creation_flags_(creation_flags) {
+}
+
+Unpacker::~Unpacker() {
+}
+
+DictionaryValue* Unpacker::ReadManifest() {
+  FilePath manifest_path =
+      temp_install_dir_.Append(Extension::kManifestFilename);
+  if (!file_util::PathExists(manifest_path)) {
+    SetError(errors::kInvalidManifest);
+    return NULL;
+  }
+
+  JSONFileValueSerializer serializer(manifest_path);
+  std::string error;
+  scoped_ptr<Value> root(serializer.Deserialize(NULL, &error));
+  if (!root.get()) {
+    SetError(error);
+    return NULL;
+  }
+
+  if (!root->IsType(Value::TYPE_DICTIONARY)) {
+    SetError(errors::kInvalidManifest);
+    return NULL;
+  }
+
+  return static_cast<DictionaryValue*>(root.release());
+}
+
+bool Unpacker::ReadAllMessageCatalogs(const std::string& default_locale) {
+  FilePath locales_path =
+    temp_install_dir_.Append(Extension::kLocaleFolder);
+
+  // Not all folders under _locales have to be valid locales.
+  file_util::FileEnumerator locales(locales_path,
+                                    false,
+                                    file_util::FileEnumerator::DIRECTORIES);
+
+  std::set<std::string> all_locales;
+  extension_l10n_util::GetAllLocales(&all_locales);
+  FilePath locale_path;
+  while (!(locale_path = locales.Next()).empty()) {
+    if (extension_l10n_util::ShouldSkipValidation(locales_path, locale_path,
+                                                  all_locales))
+      continue;
+
+    FilePath messages_path =
+      locale_path.Append(Extension::kMessagesFilename);
+
+    if (!ReadMessageCatalog(messages_path))
+      return false;
+  }
+
+  return true;
+}
+
+bool Unpacker::Run() {
+  DVLOG(1) << "Installing extension " << extension_path_.value();
+
+  // <profile>/Extensions/CRX_INSTALL
+  temp_install_dir_ =
+      extension_path_.DirName().AppendASCII(filenames::kTempExtensionName);
+
+  if (!file_util::CreateDirectory(temp_install_dir_)) {
+    SetUTF16Error(
+        l10n_util::GetStringFUTF16(
+            IDS_EXTENSION_PACKAGE_DIRECTORY_ERROR,
+            base::i18n::GetDisplayStringInLTRDirectionality(
+                temp_install_dir_.LossyDisplayName())));
+    return false;
+  }
+
+  if (!zip::Unzip(extension_path_, temp_install_dir_)) {
+    SetUTF16Error(l10n_util::GetStringUTF16(IDS_EXTENSION_PACKAGE_UNZIP_ERROR));
+    return false;
+  }
+
+  // Parse the manifest.
+  parsed_manifest_.reset(ReadManifest());
+  if (!parsed_manifest_.get())
+    return false;  // Error was already reported.
+
+  std::string error;
+  scoped_refptr<Extension> extension(Extension::Create(
+      temp_install_dir_,
+      location_,
+      *parsed_manifest_,
+      creation_flags_,
+      extension_id_,
+      &error));
+  if (!extension.get()) {
+    SetError(error);
+    return false;
+  }
+
+  Extension::InstallWarningVector warnings;
+  if (!extension_file_util::ValidateExtension(extension.get(),
+                                              &error, &warnings)) {
+    SetError(error);
+    return false;
+  }
+  extension->AddInstallWarnings(warnings);
+
+  // Decode any images that the browser needs to display.
+  std::set<FilePath> image_paths = extension->GetBrowserImages();
+  for (std::set<FilePath>::iterator it = image_paths.begin();
+       it != image_paths.end(); ++it) {
+    if (!AddDecodedImage(*it))
+      return false;  // Error was already reported.
+  }
+
+  // Parse all message catalogs (if any).
+  parsed_catalogs_.reset(new DictionaryValue);
+  if (!extension->default_locale().empty()) {
+    if (!ReadAllMessageCatalogs(extension->default_locale()))
+      return false;  // Error was already reported.
+  }
+
+  return true;
+}
+
+bool Unpacker::DumpImagesToFile() {
+  IPC::Message pickle;  // We use a Message so we can use WriteParam.
+  IPC::WriteParam(&pickle, decoded_images_);
+
+  FilePath path = extension_path_.DirName().AppendASCII(
+      filenames::kDecodedImagesFilename);
+  if (!file_util::WriteFile(path, static_cast<const char*>(pickle.data()),
+                            pickle.size())) {
+    SetError("Could not write image data to disk.");
+    return false;
+  }
+
+  return true;
+}
+
+bool Unpacker::DumpMessageCatalogsToFile() {
+  IPC::Message pickle;
+  IPC::WriteParam(&pickle, *parsed_catalogs_.get());
+
+  FilePath path = extension_path_.DirName().AppendASCII(
+      filenames::kDecodedMessageCatalogsFilename);
+  if (!file_util::WriteFile(path, static_cast<const char*>(pickle.data()),
+                            pickle.size())) {
+    SetError("Could not write message catalogs to disk.");
+    return false;
+  }
+
+  return true;
+}
+
+// static
+bool Unpacker::ReadImagesFromFile(const FilePath& extension_path,
+                                  DecodedImages* images) {
+  FilePath path = extension_path.AppendASCII(filenames::kDecodedImagesFilename);
+  std::string file_str;
+  if (!file_util::ReadFileToString(path, &file_str))
+    return false;
+
+  IPC::Message pickle(file_str.data(), file_str.size());
+  PickleIterator iter(pickle);
+  return IPC::ReadParam(&pickle, &iter, images);
+}
+
+// static
+bool Unpacker::ReadMessageCatalogsFromFile(const FilePath& extension_path,
+                                           DictionaryValue* catalogs) {
+  FilePath path = extension_path.AppendASCII(
+      filenames::kDecodedMessageCatalogsFilename);
+  std::string file_str;
+  if (!file_util::ReadFileToString(path, &file_str))
+    return false;
+
+  IPC::Message pickle(file_str.data(), file_str.size());
+  PickleIterator iter(pickle);
+  return IPC::ReadParam(&pickle, &iter, catalogs);
+}
+
+bool Unpacker::AddDecodedImage(const FilePath& path) {
+  // Make sure it's not referencing a file outside the extension's subdir.
+  if (path.IsAbsolute() || PathContainsParentDirectory(path)) {
+    SetUTF16Error(
+        l10n_util::GetStringFUTF16(
+            IDS_EXTENSION_PACKAGE_IMAGE_PATH_ERROR,
+            base::i18n::GetDisplayStringInLTRDirectionality(
+                path.LossyDisplayName())));
+    return false;
+  }
+
+  SkBitmap image_bitmap = DecodeImage(temp_install_dir_.Append(path));
+  if (image_bitmap.isNull()) {
+    SetUTF16Error(
+        l10n_util::GetStringFUTF16(
+            IDS_EXTENSION_PACKAGE_IMAGE_ERROR,
+            base::i18n::GetDisplayStringInLTRDirectionality(
+                path.BaseName().LossyDisplayName())));
+    return false;
+  }
+
+  decoded_images_.push_back(MakeTuple(image_bitmap, path));
+  return true;
+}
+
+bool Unpacker::ReadMessageCatalog(const FilePath& message_path) {
+  std::string error;
+  JSONFileValueSerializer serializer(message_path);
+  scoped_ptr<DictionaryValue> root(
+      static_cast<DictionaryValue*>(serializer.Deserialize(NULL, &error)));
+  if (!root.get()) {
+    string16 messages_file = message_path.LossyDisplayName();
+    if (error.empty()) {
+      // If file is missing, Deserialize will fail with empty error.
+      SetError(base::StringPrintf("%s %s", errors::kLocalesMessagesFileMissing,
+                                  UTF16ToUTF8(messages_file).c_str()));
+    } else {
+      SetError(base::StringPrintf("%s: %s",
+                                  UTF16ToUTF8(messages_file).c_str(),
+                                  error.c_str()));
+    }
+    return false;
+  }
+
+  FilePath relative_path;
+  // message_path was created from temp_install_dir. This should never fail.
+  if (!temp_install_dir_.AppendRelativePath(message_path, &relative_path)) {
+    NOTREACHED();
+    return false;
+  }
+
+  std::string dir_name = relative_path.DirName().MaybeAsASCII();
+  if (dir_name.empty()) {
+    NOTREACHED();
+    return false;
+  }
+  parsed_catalogs_->Set(dir_name, root.release());
+
+  return true;
+}
+
+void Unpacker::SetError(const std::string &error) {
+  SetUTF16Error(UTF8ToUTF16(error));
+}
+
+void Unpacker::SetUTF16Error(const string16 &error) {
+  error_message_ = error;
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/unpacker.h b/chrome/common/extensions/unpacker.h
new file mode 100644
index 0000000..d312914
--- /dev/null
+++ b/chrome/common/extensions/unpacker.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_UNPACKER_H_
+#define CHROME_COMMON_EXTENSIONS_UNPACKER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/tuple.h"
+#include "chrome/common/extensions/extension.h"
+
+class SkBitmap;
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+
+// This class unpacks an extension.  It is designed to be used in a sandboxed
+// child process.  We unpack and parse various bits of the extension, then
+// report back to the browser process, who then transcodes the pre-parsed bits
+// and writes them back out to disk for later use.
+class Unpacker {
+ public:
+  typedef std::vector< Tuple2<SkBitmap, FilePath> > DecodedImages;
+
+  Unpacker(const FilePath& extension_path,
+           const std::string& extension_id,
+           Extension::Location location,
+           int creation_flags);
+  ~Unpacker();
+
+  // Install the extension file at |extension_path|.  Returns true on success.
+  // Otherwise, error_message will contain a string explaining what went wrong.
+  bool Run();
+
+  // Write the decoded images to kDecodedImagesFilename.  We do this instead
+  // of sending them over IPC, since they are so large.  Returns true on
+  // success.
+  bool DumpImagesToFile();
+
+  // Write the decoded messages to kDecodedMessageCatalogsFilename.  We do this
+  // instead of sending them over IPC, since they are so large.  Returns true on
+  // success.
+  bool DumpMessageCatalogsToFile();
+
+  // Read the decoded images back from the file we saved them to.
+  // |extension_path| is the path to the extension we unpacked that wrote the
+  // data. Returns true on success.
+  static bool ReadImagesFromFile(const FilePath& extension_path,
+                                 DecodedImages* images);
+
+  // Read the decoded message catalogs back from the file we saved them to.
+  // |extension_path| is the path to the extension we unpacked that wrote the
+  // data. Returns true on success.
+  static bool ReadMessageCatalogsFromFile(const FilePath& extension_path,
+                                          base::DictionaryValue* catalogs);
+
+  const string16& error_message() { return error_message_; }
+  base::DictionaryValue* parsed_manifest() {
+    return parsed_manifest_.get();
+  }
+  const DecodedImages& decoded_images() { return decoded_images_; }
+  base::DictionaryValue* parsed_catalogs() { return parsed_catalogs_.get(); }
+
+ private:
+  // Parse the manifest.json file inside the extension (not in the header).
+  // Caller takes ownership of return value.
+  base::DictionaryValue* ReadManifest();
+
+  // Parse all _locales/*/messages.json files inside the extension.
+  bool ReadAllMessageCatalogs(const std::string& default_locale);
+
+  // Decodes the image at the given path and puts it in our list of decoded
+  // images.
+  bool AddDecodedImage(const FilePath& path);
+
+  // Parses the catalog at the given path and puts it in our list of parsed
+  // catalogs.
+  bool ReadMessageCatalog(const FilePath& message_path);
+
+  // Set the error message.
+  void SetError(const std::string& error);
+  void SetUTF16Error(const string16& error);
+
+  // The extension to unpack.
+  FilePath extension_path_;
+
+  // The extension ID if known.
+  std::string extension_id_;
+
+  // The location to use for the created extension.
+  Extension::Location location_;
+
+  // The creation flags to use with the created extension.
+  int creation_flags_;
+
+  // The place we unpacked the extension to.
+  FilePath temp_install_dir_;
+
+  // The parsed version of the manifest JSON contained in the extension.
+  scoped_ptr<base::DictionaryValue> parsed_manifest_;
+
+  // A list of decoded images and the paths where those images came from.  Paths
+  // are relative to the manifest file.
+  DecodedImages decoded_images_;
+
+  // Dictionary of relative paths and catalogs per path. Paths are in the form
+  // of _locales/locale, without messages.json base part.
+  scoped_ptr<base::DictionaryValue> parsed_catalogs_;
+
+  // The last error message that was set.  Empty if there were no errors.
+  string16 error_message_;
+
+  DISALLOW_COPY_AND_ASSIGN(Unpacker);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_UNPACKER_H_
diff --git a/chrome/common/extensions/unpacker_unittest.cc b/chrome/common/extensions/unpacker_unittest.cc
new file mode 100644
index 0000000..5f69ebb
--- /dev/null
+++ b/chrome/common/extensions/unpacker_unittest.cc
@@ -0,0 +1,271 @@
+// Copyright (c) 2012 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.
+
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/scoped_temp_dir.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "chrome/common/extensions/unpacker.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace errors = extension_manifest_errors;
+namespace filenames = extension_filenames;
+namespace keys = extension_manifest_keys;
+
+namespace extensions {
+
+class UnpackerTest : public testing::Test {
+public:
+  ~UnpackerTest() {
+    LOG(WARNING) << "Deleting temp dir: "
+                 << temp_dir_.path().LossyDisplayName();
+    LOG(WARNING) << temp_dir_.Delete();
+  }
+
+  void SetupUnpacker(const std::string& crx_name) {
+    FilePath original_path;
+    ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &original_path));
+    original_path = original_path.AppendASCII("extensions")
+        .AppendASCII("unpacker")
+        .AppendASCII(crx_name);
+    ASSERT_TRUE(file_util::PathExists(original_path)) << original_path.value();
+
+    // Try bots won't let us write into DIR_TEST_DATA, so we have to create
+    // a temp folder to play in.
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+    FilePath crx_path = temp_dir_.path().AppendASCII(crx_name);
+    ASSERT_TRUE(file_util::CopyFile(original_path, crx_path)) <<
+        "Original path " << original_path.value() <<
+        ", Crx path " << crx_path.value();
+
+    unpacker_.reset(new Unpacker(crx_path,
+                                 std::string(),
+                                 Extension::INTERNAL,
+                                 Extension::NO_FLAGS));
+  }
+
+ protected:
+  ScopedTempDir temp_dir_;
+  scoped_ptr<Unpacker> unpacker_;
+};
+
+// Crashes intermittently on Windows, see http://crbug.com/109238
+#if defined(OS_WIN)
+#define MAYBE_EmptyDefaultLocale DISABLED_EmptyDefaultLocale
+#else
+#define MAYBE_EmptyDefaultLocale EmptyDefaultLocale
+#endif
+TEST_F(UnpackerTest, MAYBE_EmptyDefaultLocale) {
+  SetupUnpacker("empty_default_locale.crx");
+  EXPECT_FALSE(unpacker_->Run());
+  EXPECT_EQ(ASCIIToUTF16(errors::kInvalidDefaultLocale),
+            unpacker_->error_message());
+}
+
+// Crashes intermittently on Vista, see http://crbug.com/109385
+#if defined(OS_WIN)
+#define MAYBE_HasDefaultLocaleMissingLocalesFolder \
+  DISABLED_HasDefaultLocaleMissingLocalesFolder
+#else
+#define MAYBE_HasDefaultLocaleMissingLocalesFolder \
+  HasDefaultLocaleMissingLocalesFolder
+#endif
+TEST_F(UnpackerTest, MAYBE_HasDefaultLocaleMissingLocalesFolder) {
+  SetupUnpacker("has_default_missing_locales.crx");
+  EXPECT_FALSE(unpacker_->Run());
+  EXPECT_EQ(ASCIIToUTF16(errors::kLocalesTreeMissing),
+            unpacker_->error_message());
+}
+
+// Crashes intermittently on Windows, see http://crbug.com/109238
+#if defined(OS_WIN)
+#define MAYBE_InvalidDefaultLocale DISABLED_InvalidDefaultLocale
+#else
+#define MAYBE_InvalidDefaultLocale InvalidDefaultLocale
+#endif
+TEST_F(UnpackerTest, MAYBE_InvalidDefaultLocale) {
+  SetupUnpacker("invalid_default_locale.crx");
+  EXPECT_FALSE(unpacker_->Run());
+  EXPECT_EQ(ASCIIToUTF16(errors::kInvalidDefaultLocale),
+            unpacker_->error_message());
+}
+
+// Crashes intermittently on Windows, see http://crbug.com/109738
+#if defined(OS_WIN)
+#define MAYBE_InvalidMessagesFile DISABLED_InvalidMessagesFile
+#else
+#define MAYBE_InvalidMessagesFile InvalidMessagesFile
+#endif
+TEST_F(UnpackerTest, MAYBE_InvalidMessagesFile) {
+  SetupUnpacker("invalid_messages_file.crx");
+  EXPECT_FALSE(unpacker_->Run());
+  EXPECT_TRUE(MatchPattern(unpacker_->error_message(),
+    ASCIIToUTF16("*_locales?en_US?messages.json: Line: 2, column: 11,"
+        " Syntax error."))) << unpacker_->error_message();
+}
+
+// Crashes intermittently on Vista, see http://crbug.com/109238
+#if defined(OS_WIN)
+#define MAYBE_MissingDefaultData DISABLED_MissingDefaultData
+#else
+#define MAYBE_MissingDefaultData MissingDefaultData
+#endif
+TEST_F(UnpackerTest, MAYBE_MissingDefaultData) {
+  SetupUnpacker("missing_default_data.crx");
+  EXPECT_FALSE(unpacker_->Run());
+  EXPECT_EQ(ASCIIToUTF16(errors::kLocalesNoDefaultMessages),
+            unpacker_->error_message());
+}
+
+// Crashes intermittently on Vista, see http://crbug.com/109238
+#if defined(OS_WIN)
+#define MAYBE_MissingDefaultLocaleHasLocalesFolder \
+  DISABLED_MissingDefaultLocaleHasLocalesFolder
+#else
+#define MAYBE_MissingDefaultLocaleHasLocalesFolder \
+  MissingDefaultLocaleHasLocalesFolder
+#endif
+TEST_F(UnpackerTest, MAYBE_MissingDefaultLocaleHasLocalesFolder) {
+  SetupUnpacker("missing_default_has_locales.crx");
+  EXPECT_FALSE(unpacker_->Run());
+  EXPECT_EQ(ASCIIToUTF16(errors::kLocalesNoDefaultLocaleSpecified),
+            unpacker_->error_message());
+}
+
+// Crashes intermittently on Vista, see http://crbug.com/109238
+#if defined(OS_WIN)
+#define MAYBE_MissingMessagesFile DISABLED_MissingMessagesFile
+#else
+#define MAYBE_MissingMessagesFile MissingMessagesFile
+#endif
+TEST_F(UnpackerTest, MAYBE_MissingMessagesFile) {
+  SetupUnpacker("missing_messages_file.crx");
+  EXPECT_FALSE(unpacker_->Run());
+  EXPECT_TRUE(MatchPattern(unpacker_->error_message(),
+    ASCIIToUTF16(errors::kLocalesMessagesFileMissing) +
+    ASCIIToUTF16("*_locales?en_US?messages.json")));
+}
+
+// Crashes intermittently on Vista, see http://crbug.com/109238
+#if defined(OS_WIN)
+#define MAYBE_NoLocaleData DISABLED_NoLocaleData
+#else
+#define MAYBE_NoLocaleData NoLocaleData
+#endif
+TEST_F(UnpackerTest, MAYBE_NoLocaleData) {
+  SetupUnpacker("no_locale_data.crx");
+  EXPECT_FALSE(unpacker_->Run());
+  EXPECT_EQ(ASCIIToUTF16(errors::kLocalesNoDefaultMessages),
+            unpacker_->error_message());
+}
+
+// Crashes intermittently on Vista, see http://crbug.com/109238
+#if defined(OS_WIN)
+#define MAYBE_GoodL10n DISABLED_GoodL10n
+#else
+#define MAYBE_GoodL10n GoodL10n
+#endif
+TEST_F(UnpackerTest, MAYBE_GoodL10n) {
+  SetupUnpacker("good_l10n.crx");
+  EXPECT_TRUE(unpacker_->Run());
+  EXPECT_TRUE(unpacker_->error_message().empty());
+  ASSERT_EQ(2U, unpacker_->parsed_catalogs()->size());
+}
+
+// Crashes intermittently on Vista, see http://crbug.com/109238
+#if defined(OS_WIN)
+#define MAYBE_NoL10n DISABLED_NoL10n
+#else
+#define MAYBE_NoL10n NoL10n
+#endif
+TEST_F(UnpackerTest, MAYBE_NoL10n) {
+  SetupUnpacker("no_l10n.crx");
+  EXPECT_TRUE(unpacker_->Run());
+  EXPECT_TRUE(unpacker_->error_message().empty());
+  EXPECT_EQ(0U, unpacker_->parsed_catalogs()->size());
+}
+
+// Disabled on Windows because it probably crashes intermittently as described
+// in <http://crbug.com/109238>. However, because the logic being testing here
+// is platform-independant, this test should still provide good coverage.
+#if defined(OS_WIN)
+#define MAYBE_UnzipDirectoryError DISABLED_UnzipDirectoryError
+#else
+#define MAYBE_UnzipDirectoryError UnzipDirectoryError
+#endif
+TEST_F(UnpackerTest, MAYBE_UnzipDirectoryError) {
+  const char* kExpected = "Could not create directory for unzipping: ";
+  SetupUnpacker("good_package.crx");
+  FilePath path = temp_dir_.path().AppendASCII(filenames::kTempExtensionName);
+  ASSERT_TRUE(file_util::WriteFile(path, "foo", 3));
+  EXPECT_FALSE(unpacker_->Run());
+  EXPECT_TRUE(StartsWith(unpacker_->error_message(),
+              ASCIIToUTF16(kExpected),
+              false)) << "Expected prefix: \"" << kExpected
+                      << "\", actual error: \"" << unpacker_->error_message()
+                      << "\"";
+}
+
+// Disabled on Windows because it probably crashes intermittently as described
+// in <http://crbug.com/109238>. However, because the logic being testing here
+// is platform-independant, this test should still provide good coverage.
+#if defined(OS_WIN)
+#define MAYBE_UnzipError DISABLED_UnzipError
+#else
+#define MAYBE_UnzipError UnzipError
+#endif
+TEST_F(UnpackerTest, MAYBE_UnzipError) {
+  const char* kExpected = "Could not unzip extension";
+  SetupUnpacker("bad_zip.crx");
+  EXPECT_FALSE(unpacker_->Run());
+  EXPECT_EQ(ASCIIToUTF16(kExpected), unpacker_->error_message());
+}
+
+// Disabled on Windows because it probably crashes intermittently as described
+// in <http://crbug.com/109238>. However, because the logic being testing here
+// is platform-independant, this test should still provide good coverage.
+#if defined(OS_WIN)
+#define MAYBE_BadPathError DISABLED_BadPathError
+#else
+#define MAYBE_BadPathError BadPathError
+#endif
+TEST_F(UnpackerTest, MAYBE_BadPathError) {
+  const char* kExpected = "Illegal path (absolute or relative with '..'): ";
+  SetupUnpacker("bad_path.crx");
+  EXPECT_FALSE(unpacker_->Run());
+  EXPECT_TRUE(StartsWith(unpacker_->error_message(),
+              ASCIIToUTF16(kExpected),
+              false)) << "Expected prefix: \"" << kExpected
+                      << "\", actual error: \"" << unpacker_->error_message()
+                      << "\"";
+}
+
+
+// Disabled on Windows because it probably crashes intermittently as described
+// in <http://crbug.com/109238>. However, because the logic being testing here
+// is platform-independant, this test should still provide good coverage.
+#if defined(OS_WIN)
+#define MAYBE_ImageDecodingError DISABLED_ImageDecodingError
+#else
+#define MAYBE_ImageDecodingError ImageDecodingError
+#endif
+TEST_F(UnpackerTest, MAYBE_ImageDecodingError) {
+  const char* kExpected = "Could not decode image: ";
+  SetupUnpacker("bad_image.crx");
+  EXPECT_FALSE(unpacker_->Run());
+  EXPECT_TRUE(StartsWith(unpacker_->error_message(),
+              ASCIIToUTF16(kExpected),
+              false)) << "Expected prefix: \"" << kExpected
+                      << "\", actual error: \"" << unpacker_->error_message()
+                      << "\"";
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/update_manifest.cc b/chrome/common/extensions/update_manifest.cc
new file mode 100644
index 0000000..85947ca
--- /dev/null
+++ b/chrome/common/extensions/update_manifest.cc
@@ -0,0 +1,266 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/update_manifest.h"
+
+#include <algorithm>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/string_util.h"
+#include "base/string_number_conversions.h"
+#include "base/stringprintf.h"
+#include "base/version.h"
+#include "libxml/tree.h"
+#include "third_party/libxml/chromium/libxml_utils.h"
+
+static const char* kExpectedGupdateProtocol = "2.0";
+static const char* kExpectedGupdateXmlns =
+    "http://www.google.com/update2/response";
+
+UpdateManifest::Result::Result() {}
+
+UpdateManifest::Result::~Result() {}
+
+UpdateManifest::Results::Results() : daystart_elapsed_seconds(kNoDaystart) {}
+
+UpdateManifest::Results::~Results() {}
+
+UpdateManifest::UpdateManifest() {
+}
+
+UpdateManifest::~UpdateManifest() {}
+
+void UpdateManifest::ParseError(const char* details, ...) {
+  va_list args;
+  va_start(args, details);
+
+  if (errors_.length() > 0) {
+    // TODO(asargent) make a platform abstracted newline?
+    errors_ += "\r\n";
+  }
+  base::StringAppendV(&errors_, details, args);
+  va_end(args);
+}
+
+// Checks whether a given node's name matches |expected_name| and
+// |expected_namespace|.
+static bool TagNameEquals(const xmlNode* node, const char* expected_name,
+                          const xmlNs* expected_namespace) {
+  if (node->ns != expected_namespace) {
+    return false;
+  }
+  return 0 == strcmp(expected_name, reinterpret_cast<const char*>(node->name));
+}
+
+// Returns child nodes of |root| with name |name| in namespace |xml_namespace|.
+static std::vector<xmlNode*> GetChildren(xmlNode* root, xmlNs* xml_namespace,
+                                         const char* name) {
+  std::vector<xmlNode*> result;
+  for (xmlNode* child = root->children; child != NULL; child = child->next) {
+    if (!TagNameEquals(child, name, xml_namespace)) {
+      continue;
+    }
+    result.push_back(child);
+  }
+  return result;
+}
+
+// Returns the value of a named attribute, or the empty string.
+static std::string GetAttribute(xmlNode* node, const char* attribute_name) {
+  const xmlChar* name = reinterpret_cast<const xmlChar*>(attribute_name);
+  for (xmlAttr* attr = node->properties; attr != NULL; attr = attr->next) {
+    if (!xmlStrcmp(attr->name, name) && attr->children &&
+        attr->children->content) {
+      return std::string(reinterpret_cast<const char*>(
+          attr->children->content));
+    }
+  }
+  return std::string();
+}
+
+// This is used for the xml parser to report errors. This assumes the context
+// is a pointer to a std::string where the error message should be appended.
+static void XmlErrorFunc(void *context, const char *message, ...) {
+  va_list args;
+  va_start(args, message);
+  std::string* error = static_cast<std::string*>(context);
+  base::StringAppendV(error, message, args);
+  va_end(args);
+}
+
+// Utility class for cleaning up the xml document when leaving a scope.
+class ScopedXmlDocument {
+ public:
+  explicit ScopedXmlDocument(xmlDocPtr document) : document_(document) {}
+  ~ScopedXmlDocument() {
+    if (document_)
+      xmlFreeDoc(document_);
+  }
+
+  xmlDocPtr get() {
+    return document_;
+  }
+
+ private:
+  xmlDocPtr document_;
+};
+
+// Returns a pointer to the xmlNs on |node| with the |expected_href|, or
+// NULL if there isn't one with that href.
+static xmlNs* GetNamespace(xmlNode* node, const char* expected_href) {
+  const xmlChar* href = reinterpret_cast<const xmlChar*>(expected_href);
+  for (xmlNs* ns = node->ns; ns != NULL; ns = ns->next) {
+    if (ns->href && !xmlStrcmp(ns->href, href)) {
+      return ns;
+    }
+  }
+  return NULL;
+}
+
+
+// Helper function that reads in values for a single <app> tag. It returns a
+// boolean indicating success or failure. On failure, it writes a error message
+// into |error_detail|.
+static bool ParseSingleAppTag(xmlNode* app_node, xmlNs* xml_namespace,
+                              UpdateManifest::Result* result,
+                              std::string *error_detail) {
+  // Read the extension id.
+  result->extension_id = GetAttribute(app_node, "appid");
+  if (result->extension_id.length() == 0) {
+    *error_detail = "Missing appid on app node";
+    return false;
+  }
+
+  // Get the updatecheck node.
+  std::vector<xmlNode*> updates = GetChildren(app_node, xml_namespace,
+                                              "updatecheck");
+  if (updates.size() > 1) {
+    *error_detail = "Too many updatecheck tags on app (expecting only 1).";
+    return false;
+  }
+  if (updates.empty()) {
+    *error_detail = "Missing updatecheck on app.";
+    return false;
+  }
+  xmlNode *updatecheck = updates[0];
+
+  if (GetAttribute(updatecheck, "status") == "noupdate") {
+    return true;
+  }
+
+  // Find the url to the crx file.
+  result->crx_url = GURL(GetAttribute(updatecheck, "codebase"));
+  if (!result->crx_url.is_valid()) {
+    *error_detail = "Invalid codebase url: '";
+    *error_detail += GetAttribute(updatecheck, "codebase");
+    *error_detail += "'.";
+    return false;
+  }
+
+  // Get the version.
+  result->version = GetAttribute(updatecheck, "version");
+  if (result->version.length() == 0) {
+    *error_detail = "Missing version for updatecheck.";
+    return false;
+  }
+  Version version(result->version);
+  if (!version.IsValid()) {
+    *error_detail = "Invalid version: '";
+    *error_detail += result->version;
+    *error_detail += "'.";
+    return false;
+  }
+
+  // Get the minimum browser version (not required).
+  result->browser_min_version = GetAttribute(updatecheck, "prodversionmin");
+  if (result->browser_min_version.length()) {
+    Version browser_min_version(result->browser_min_version);
+    if (!browser_min_version.IsValid()) {
+      *error_detail = "Invalid prodversionmin: '";
+      *error_detail += result->browser_min_version;
+      *error_detail += "'.";
+      return false;
+    }
+  }
+
+  // package_hash is optional. It is only required for blacklist. It is a
+  // sha256 hash of the package in hex format.
+  result->package_hash = GetAttribute(updatecheck, "hash");
+
+  return true;
+}
+
+
+bool UpdateManifest::Parse(const std::string& manifest_xml) {
+  results_.list.resize(0);
+  results_.daystart_elapsed_seconds = kNoDaystart;
+  errors_ = "";
+
+  if (manifest_xml.length() < 1) {
+     ParseError("Empty xml");
+    return false;
+  }
+
+  std::string xml_errors;
+  ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc);
+
+  // Start up the xml parser with the manifest_xml contents.
+  ScopedXmlDocument document(xmlParseDoc(
+      reinterpret_cast<const xmlChar*>(manifest_xml.c_str())));
+  if (!document.get()) {
+    ParseError("%s", xml_errors.c_str());
+    return false;
+  }
+
+  xmlNode *root = xmlDocGetRootElement(document.get());
+  if (!root) {
+    ParseError("Missing root node");
+    return false;
+  }
+
+  // Look for the required namespace declaration.
+  xmlNs* gupdate_ns = GetNamespace(root, kExpectedGupdateXmlns);
+  if (!gupdate_ns) {
+    ParseError("Missing or incorrect xmlns on gupdate tag");
+    return false;
+  }
+
+  if (!TagNameEquals(root, "gupdate", gupdate_ns)) {
+    ParseError("Missing gupdate tag");
+    return false;
+  }
+
+  // Check for the gupdate "protocol" attribute.
+  if (GetAttribute(root, "protocol") != kExpectedGupdateProtocol) {
+    ParseError("Missing/incorrect protocol on gupdate tag "
+        "(expected '%s')", kExpectedGupdateProtocol);
+    return false;
+  }
+
+  // Parse the first <daystart> if it's present.
+  std::vector<xmlNode*> daystarts = GetChildren(root, gupdate_ns, "daystart");
+  if (!daystarts.empty()) {
+    xmlNode* first = daystarts[0];
+    std::string elapsed_seconds = GetAttribute(first, "elapsed_seconds");
+    int parsed_elapsed = kNoDaystart;
+    if (base::StringToInt(elapsed_seconds, &parsed_elapsed)) {
+      results_.daystart_elapsed_seconds = parsed_elapsed;
+    }
+  }
+
+  // Parse each of the <app> tags.
+  std::vector<xmlNode*> apps = GetChildren(root, gupdate_ns, "app");
+  for (unsigned int i = 0; i < apps.size(); i++) {
+    Result current;
+    std::string error;
+    if (!ParseSingleAppTag(apps[i], gupdate_ns, &current, &error)) {
+      ParseError("%s", error.c_str());
+    } else {
+      results_.list.push_back(current);
+    }
+  }
+
+  return true;
+}
diff --git a/chrome/common/extensions/update_manifest.h b/chrome/common/extensions/update_manifest.h
new file mode 100644
index 0000000..d847743
--- /dev/null
+++ b/chrome/common/extensions/update_manifest.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2010 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_UPDATE_MANIFEST_H_
+#define CHROME_COMMON_EXTENSIONS_UPDATE_MANIFEST_H_
+
+#include <string>
+#include <vector>
+
+#include "googleurl/src/gurl.h"
+
+class UpdateManifest {
+ public:
+
+  // An update manifest looks like this:
+  //
+  // <?xml version='1.0' encoding='UTF-8'?>
+  // <gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
+  //  <daystart elapsed_seconds='300' />
+  //  <app appid='12345'>
+  //   <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'
+  //                version='1.2.3.4' prodversionmin='2.0.143.0'
+  //                hash="12345"/>
+  //  </app>
+  // </gupdate>
+  //
+  // The <daystart> tag contains a "elapsed_seconds" attribute which refers to
+  // the server's notion of how many seconds it has been since midnight.
+  //
+  // The "appid" attribute of the <app> tag refers to the unique id of the
+  // extension. The "codebase" attribute of the <updatecheck> tag is the url to
+  // fetch the updated crx file, and the "prodversionmin" attribute refers to
+  // the minimum version of the chrome browser that the update applies to.
+
+  // The result of parsing one <app> tag in an xml update check manifest.
+  struct Result {
+    Result();
+    ~Result();
+
+    std::string extension_id;
+    std::string version;
+    std::string browser_min_version;
+    std::string package_hash;
+    GURL crx_url;
+  };
+
+  static const int kNoDaystart = -1;
+  struct Results {
+    Results();
+    ~Results();
+
+    std::vector<Result> list;
+    // This will be >= 0, or kNoDaystart if the <daystart> tag was not present.
+    int daystart_elapsed_seconds;
+  };
+
+  UpdateManifest();
+  ~UpdateManifest();
+
+  // Parses an update manifest xml string into Result data. Returns a bool
+  // indicating success or failure. On success, the results are available by
+  // calling results(). The details for any failures are available by calling
+  // errors().
+  bool Parse(const std::string& manifest_xml);
+
+  const Results& results() { return results_; }
+  const std::string& errors() { return errors_; }
+
+ private:
+  Results results_;
+  std::string errors_;
+
+  // Helper function that adds parse error details to our errors_ string.
+  void ParseError(const char* details, ...);
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateManifest);
+};
+
+#endif  // CHROME_COMMON_EXTENSIONS_UPDATE_MANIFEST_H_
diff --git a/chrome/common/extensions/update_manifest_unittest.cc b/chrome/common/extensions/update_manifest_unittest.cc
new file mode 100644
index 0000000..9af11df
--- /dev/null
+++ b/chrome/common/extensions/update_manifest_unittest.cc
@@ -0,0 +1,187 @@
+// 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.
+
+#include "base/memory/scoped_vector.h"
+#include "chrome/common/extensions/update_manifest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "libxml/globals.h"
+
+static const char* kValidXml =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345'>"
+"  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+"               version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </app>"
+"</gupdate>";
+
+const char *valid_xml_with_hash =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345'>"
+"  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+"               version='1.2.3.4' prodversionmin='2.0.143.0' "
+"               hash='1234'/>"
+" </app>"
+"</gupdate>";
+
+static const char*  kMissingAppId =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app>"
+"  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+"               version='1.2.3.4' />"
+" </app>"
+"</gupdate>";
+
+static const char*  kInvalidCodebase =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345' status='ok'>"
+"  <updatecheck codebase='example.com/extension_1.2.3.4.crx'"
+"               version='1.2.3.4' />"
+" </app>"
+"</gupdate>";
+
+static const char*  kMissingVersion =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345' status='ok'>"
+"  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' />"
+" </app>"
+"</gupdate>";
+
+static const char*  kInvalidVersion =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345' status='ok'>"
+"  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' "
+"               version='1.2.3.a'/>"
+" </app>"
+"</gupdate>";
+
+static const char* kUsesNamespacePrefix =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<g:gupdate xmlns:g='http://www.google.com/update2/response' protocol='2.0'>"
+" <g:app appid='12345'>"
+"  <g:updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+"               version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </g:app>"
+"</g:gupdate>";
+
+// Includes unrelated <app> tags from other xml namespaces - this should
+// not cause problems.
+static const char* kSimilarTagnames =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response'"
+"         xmlns:a='http://a' protocol='2.0'>"
+" <a:app/>"
+" <b:app xmlns:b='http://b' />"
+" <app appid='12345'>"
+"  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+"               version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </app>"
+"</gupdate>";
+
+// Includes a <daystart> tag.
+static const char* kWithDaystart =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <daystart elapsed_seconds='456' />"
+" <app appid='12345'>"
+"  <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+"               version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </app>"
+"</gupdate>";
+
+// Indicates no updates available - this should not be a parse error.
+static const char* kNoUpdate =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345'>"
+"  <updatecheck status='noupdate' />"
+" </app>"
+"</gupdate>";
+
+// Includes two <app> tags, one with an error.
+static const char* kTwoAppsOneError =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='aaaaaaaa' status='error-unknownApplication'>"
+"  <updatecheck status='error-unknownapplication'/>"
+" </app>"
+" <app appid='bbbbbbbb'>"
+"  <updatecheck codebase='http://example.com/b_3.1.crx' version='3.1'/>"
+" </app>"
+"</gupdate>";
+
+TEST(ExtensionUpdateManifestTest, TestUpdateManifest) {
+  UpdateManifest parser;
+
+  // Test parsing of a number of invalid xml cases
+  EXPECT_FALSE(parser.Parse(""));
+  EXPECT_FALSE(parser.errors().empty());
+
+  EXPECT_TRUE(parser.Parse(kMissingAppId));
+  EXPECT_TRUE(parser.results().list.empty());
+  EXPECT_FALSE(parser.errors().empty());
+
+  EXPECT_TRUE(parser.Parse(kInvalidCodebase));
+  EXPECT_TRUE(parser.results().list.empty());
+  EXPECT_FALSE(parser.errors().empty());
+
+  EXPECT_TRUE(parser.Parse(kMissingVersion));
+  EXPECT_TRUE(parser.results().list.empty());
+  EXPECT_FALSE(parser.errors().empty());
+
+  EXPECT_TRUE(parser.Parse(kInvalidVersion));
+  EXPECT_TRUE(parser.results().list.empty());
+  EXPECT_FALSE(parser.errors().empty());
+
+  // Parse some valid XML, and check that all params came out as expected
+  EXPECT_TRUE(parser.Parse(kValidXml));
+  EXPECT_TRUE(parser.errors().empty());
+  EXPECT_FALSE(parser.results().list.empty());
+  const UpdateManifest::Result* firstResult = &parser.results().list.at(0);
+  EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
+            firstResult->crx_url);
+
+  EXPECT_EQ("1.2.3.4", firstResult->version);
+  EXPECT_EQ("2.0.143.0", firstResult->browser_min_version);
+
+  // Parse some xml that uses namespace prefixes.
+  EXPECT_TRUE(parser.Parse(kUsesNamespacePrefix));
+  EXPECT_TRUE(parser.errors().empty());
+  EXPECT_TRUE(parser.Parse(kSimilarTagnames));
+  EXPECT_TRUE(parser.errors().empty());
+  xmlCleanupGlobals();
+
+  // Parse xml with hash value
+  EXPECT_TRUE(parser.Parse(valid_xml_with_hash));
+  EXPECT_TRUE(parser.errors().empty());
+  EXPECT_FALSE(parser.results().list.empty());
+  firstResult = &parser.results().list.at(0);
+  EXPECT_EQ("1234", firstResult->package_hash);
+
+  // Parse xml with a <daystart> element.
+  EXPECT_TRUE(parser.Parse(kWithDaystart));
+  EXPECT_TRUE(parser.errors().empty());
+  EXPECT_FALSE(parser.results().list.empty());
+  EXPECT_EQ(parser.results().daystart_elapsed_seconds, 456);
+
+  // Parse a no-update response.
+  EXPECT_TRUE(parser.Parse(kNoUpdate));
+  EXPECT_TRUE(parser.errors().empty());
+  EXPECT_FALSE(parser.results().list.empty());
+  firstResult = &parser.results().list.at(0);
+  EXPECT_EQ(firstResult->extension_id, "12345");
+  EXPECT_EQ(firstResult->version, "");
+
+  // Parse xml with one error and one success <app> tag.
+  EXPECT_TRUE(parser.Parse(kTwoAppsOneError));
+  EXPECT_FALSE(parser.errors().empty());
+  EXPECT_EQ(1u, parser.results().list.size());
+  firstResult = &parser.results().list.at(0);
+  EXPECT_EQ(firstResult->extension_id, "bbbbbbbb");
+}
diff --git a/chrome/common/extensions/url_pattern.cc b/chrome/common/extensions/url_pattern.cc
new file mode 100644
index 0000000..34ec6f8
--- /dev/null
+++ b/chrome/common/extensions/url_pattern.cc
@@ -0,0 +1,523 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/url_pattern.h"
+
+#include "base/string_number_conversions.h"
+#include "base/string_piece.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "chrome/common/url_constants.h"
+#include "googleurl/src/gurl.h"
+#include "googleurl/src/url_util.h"
+
+const char URLPattern::kAllUrlsPattern[] = "<all_urls>";
+
+namespace {
+
+// TODO(aa): What about more obscure schemes like data: and javascript: ?
+// Note: keep this array in sync with kValidSchemeMasks.
+const char* kValidSchemes[] = {
+  chrome::kHttpScheme,
+  chrome::kHttpsScheme,
+  chrome::kFileScheme,
+  chrome::kFtpScheme,
+  chrome::kChromeUIScheme,
+  chrome::kExtensionScheme,
+  chrome::kFileSystemScheme,
+};
+
+const int kValidSchemeMasks[] = {
+  URLPattern::SCHEME_HTTP,
+  URLPattern::SCHEME_HTTPS,
+  URLPattern::SCHEME_FILE,
+  URLPattern::SCHEME_FTP,
+  URLPattern::SCHEME_CHROMEUI,
+  URLPattern::SCHEME_EXTENSION,
+  URLPattern::SCHEME_FILESYSTEM,
+};
+
+COMPILE_ASSERT(arraysize(kValidSchemes) == arraysize(kValidSchemeMasks),
+               must_keep_these_arrays_in_sync);
+
+const char kParseSuccess[] = "Success.";
+const char kParseErrorMissingSchemeSeparator[] = "Missing scheme separator.";
+const char kParseErrorInvalidScheme[] = "Invalid scheme.";
+const char kParseErrorWrongSchemeType[] = "Wrong scheme type.";
+const char kParseErrorEmptyHost[] = "Host can not be empty.";
+const char kParseErrorInvalidHostWildcard[] = "Invalid host wildcard.";
+const char kParseErrorEmptyPath[] = "Empty path.";
+const char kParseErrorInvalidPort[] = "Invalid port.";
+
+// Message explaining each URLPattern::ParseResult.
+const char* const kParseResultMessages[] = {
+  kParseSuccess,
+  kParseErrorMissingSchemeSeparator,
+  kParseErrorInvalidScheme,
+  kParseErrorWrongSchemeType,
+  kParseErrorEmptyHost,
+  kParseErrorInvalidHostWildcard,
+  kParseErrorEmptyPath,
+  kParseErrorInvalidPort,
+};
+
+COMPILE_ASSERT(URLPattern::NUM_PARSE_RESULTS == arraysize(kParseResultMessages),
+               must_add_message_for_each_parse_result);
+
+const char kPathSeparator[] = "/";
+
+bool IsStandardScheme(const std::string& scheme) {
+  // "*" gets the same treatment as a standard scheme.
+  if (scheme == "*")
+    return true;
+
+  return url_util::IsStandard(scheme.c_str(),
+      url_parse::Component(0, static_cast<int>(scheme.length())));
+}
+
+bool IsValidPortForScheme(const std::string& scheme, const std::string& port) {
+  if (port == "*")
+    return true;
+
+  // Only accept non-wildcard ports if the scheme uses ports.
+  if (url_canon::DefaultPortForScheme(scheme.c_str(), scheme.length()) ==
+      url_parse::PORT_UNSPECIFIED) {
+    return false;
+  }
+
+  int parsed_port = url_parse::PORT_UNSPECIFIED;
+  if (!base::StringToInt(port, &parsed_port))
+    return false;
+  return (parsed_port >= 0) && (parsed_port < 65536);
+}
+
+}  // namespace
+
+URLPattern::URLPattern()
+    : valid_schemes_(SCHEME_NONE),
+      match_all_urls_(false),
+      match_subdomains_(false),
+      port_("*") {}
+
+URLPattern::URLPattern(int valid_schemes)
+    : valid_schemes_(valid_schemes),
+      match_all_urls_(false),
+      match_subdomains_(false),
+      port_("*") {}
+
+URLPattern::URLPattern(int valid_schemes, const std::string& pattern)
+    // Strict error checking is used, because this constructor is only
+    // appropriate when we know |pattern| is valid.
+    : valid_schemes_(valid_schemes),
+      match_all_urls_(false),
+      match_subdomains_(false),
+      port_("*") {
+  if (PARSE_SUCCESS != Parse(pattern))
+    NOTREACHED() << "URLPattern is invalid: " << pattern;
+}
+
+URLPattern::~URLPattern() {
+}
+
+bool URLPattern::operator<(const URLPattern& other) const {
+  return GetAsString() < other.GetAsString();
+}
+
+bool URLPattern::operator==(const URLPattern& other) const {
+  return GetAsString() == other.GetAsString();
+}
+
+URLPattern::ParseResult URLPattern::Parse(const std::string& pattern) {
+  spec_.clear();
+  SetMatchAllURLs(false);
+  SetMatchSubdomains(false);
+  SetPort("*");
+
+  // Special case pattern to match every valid URL.
+  if (pattern == kAllUrlsPattern) {
+    SetMatchAllURLs(true);
+    return PARSE_SUCCESS;
+  }
+
+  // Parse out the scheme.
+  size_t scheme_end_pos = pattern.find(content::kStandardSchemeSeparator);
+  bool has_standard_scheme_separator = true;
+
+  // Some urls also use ':' alone as the scheme separator.
+  if (scheme_end_pos == std::string::npos) {
+    scheme_end_pos = pattern.find(':');
+    has_standard_scheme_separator = false;
+  }
+
+  if (scheme_end_pos == std::string::npos)
+    return PARSE_ERROR_MISSING_SCHEME_SEPARATOR;
+
+  if (!SetScheme(pattern.substr(0, scheme_end_pos)))
+    return PARSE_ERROR_INVALID_SCHEME;
+
+  bool standard_scheme = IsStandardScheme(scheme_);
+  if (standard_scheme != has_standard_scheme_separator)
+    return PARSE_ERROR_WRONG_SCHEME_SEPARATOR;
+
+  // Advance past the scheme separator.
+  scheme_end_pos +=
+      (standard_scheme ? strlen(content::kStandardSchemeSeparator) : 1);
+  if (scheme_end_pos >= pattern.size())
+    return PARSE_ERROR_EMPTY_HOST;
+
+  // Parse out the host and path.
+  size_t host_start_pos = scheme_end_pos;
+  size_t path_start_pos = 0;
+
+  if (!standard_scheme) {
+    path_start_pos = host_start_pos;
+  } else if (scheme_ == chrome::kFileScheme) {
+    size_t host_end_pos = pattern.find(kPathSeparator, host_start_pos);
+    if (host_end_pos == std::string::npos) {
+      // Allow hostname omission.
+      // e.g. file://* is interpreted as file:///*,
+      // file://foo* is interpreted as file:///foo*.
+      path_start_pos = host_start_pos - 1;
+    } else {
+      // Ignore hostname if scheme is file://.
+      // e.g. file://localhost/foo is equal to file:///foo.
+      path_start_pos = host_end_pos;
+    }
+  } else {
+    size_t host_end_pos = pattern.find(kPathSeparator, host_start_pos);
+
+    // Host is required.
+    if (host_start_pos == host_end_pos)
+      return PARSE_ERROR_EMPTY_HOST;
+
+    if (host_end_pos == std::string::npos)
+      return PARSE_ERROR_EMPTY_PATH;
+
+    host_ = pattern.substr(host_start_pos, host_end_pos - host_start_pos);
+
+    // The first component can optionally be '*' to match all subdomains.
+    std::vector<std::string> host_components;
+    base::SplitString(host_, '.', &host_components);
+    if (host_components[0] == "*") {
+      match_subdomains_ = true;
+      host_components.erase(host_components.begin(),
+                            host_components.begin() + 1);
+    }
+    host_ = JoinString(host_components, '.');
+
+    path_start_pos = host_end_pos;
+  }
+
+  SetPath(pattern.substr(path_start_pos));
+
+  size_t port_pos = host_.find(':');
+  if (port_pos != std::string::npos) {
+    if (!SetPort(host_.substr(port_pos + 1)))
+      return PARSE_ERROR_INVALID_PORT;
+    host_ = host_.substr(0, port_pos);
+  }
+
+  // No other '*' can occur in the host, though. This isn't necessary, but is
+  // done as a convenience to developers who might otherwise be confused and
+  // think '*' works as a glob in the host.
+  if (host_.find('*') != std::string::npos)
+    return PARSE_ERROR_INVALID_HOST_WILDCARD;
+
+  return PARSE_SUCCESS;
+}
+
+void URLPattern::SetValidSchemes(int valid_schemes) {
+  spec_.clear();
+  valid_schemes_ = valid_schemes;
+}
+
+void URLPattern::SetHost(const std::string& host) {
+  spec_.clear();
+  host_ = host;
+}
+
+void URLPattern::SetMatchAllURLs(bool val) {
+  spec_.clear();
+  match_all_urls_ = val;
+
+  if (val) {
+    match_subdomains_ = true;
+    scheme_ = "*";
+    host_.clear();
+    SetPath("/*");
+  }
+}
+
+void URLPattern::SetMatchSubdomains(bool val) {
+  spec_.clear();
+  match_subdomains_ = val;
+}
+
+bool URLPattern::SetScheme(const std::string& scheme) {
+  spec_.clear();
+  scheme_ = scheme;
+  if (scheme_ == "*") {
+    valid_schemes_ &= (SCHEME_HTTP | SCHEME_HTTPS);
+  } else if (!IsValidScheme(scheme_)) {
+    return false;
+  }
+  return true;
+}
+
+bool URLPattern::IsValidScheme(const std::string& scheme) const {
+  if (valid_schemes_ == SCHEME_ALL)
+    return true;
+
+  for (size_t i = 0; i < arraysize(kValidSchemes); ++i) {
+    if (scheme == kValidSchemes[i] && (valid_schemes_ & kValidSchemeMasks[i]))
+      return true;
+  }
+
+  return false;
+}
+
+void URLPattern::SetPath(const std::string& path) {
+  spec_.clear();
+  path_ = path;
+  path_escaped_ = path_;
+  ReplaceSubstringsAfterOffset(&path_escaped_, 0, "\\", "\\\\");
+  ReplaceSubstringsAfterOffset(&path_escaped_, 0, "?", "\\?");
+}
+
+bool URLPattern::SetPort(const std::string& port) {
+  spec_.clear();
+  if (IsValidPortForScheme(scheme_, port)) {
+    port_ = port;
+    return true;
+  }
+  return false;
+}
+
+bool URLPattern::MatchesURL(const GURL& test) const {
+  const GURL* test_url = &test;
+  bool has_inner_url = test.inner_url() != NULL;
+
+  if (has_inner_url) {
+    if (!test.SchemeIsFileSystem())
+      return false;  // The only nested URLs we handle are filesystem URLs.
+    test_url = test.inner_url();
+  }
+
+  if (!MatchesScheme(test_url->scheme()))
+    return false;
+
+  if (match_all_urls_)
+    return true;
+
+  std::string path_for_request = test.PathForRequest();
+  if (has_inner_url)
+    path_for_request = test_url->path() + path_for_request;
+
+  return MatchesSecurityOriginHelper(*test_url) &&
+         MatchesPath(path_for_request);
+}
+
+bool URLPattern::MatchesSecurityOrigin(const GURL& test) const {
+  const GURL* test_url = &test;
+  bool has_inner_url = test.inner_url() != NULL;
+
+  if (has_inner_url) {
+    if (!test.SchemeIsFileSystem())
+      return false;  // The only nested URLs we handle are filesystem URLs.
+    test_url = test.inner_url();
+  }
+
+  if (!MatchesScheme(test_url->scheme()))
+    return false;
+
+  if (match_all_urls_)
+    return true;
+
+  return MatchesSecurityOriginHelper(*test_url);
+}
+
+bool URLPattern::MatchesScheme(const std::string& test) const {
+  if (!IsValidScheme(test))
+    return false;
+
+  return scheme_ == "*" || test == scheme_;
+}
+
+bool URLPattern::MatchesHost(const std::string& host) const {
+  std::string test(chrome::kHttpScheme);
+  test += content::kStandardSchemeSeparator;
+  test += host;
+  test += "/";
+  return MatchesHost(GURL(test));
+}
+
+bool URLPattern::MatchesHost(const GURL& test) const {
+  // If the hosts are exactly equal, we have a match.
+  if (test.host() == host_)
+    return true;
+
+  // If we're matching subdomains, and we have no host in the match pattern,
+  // that means that we're matching all hosts, which means we have a match no
+  // matter what the test host is.
+  if (match_subdomains_ && host_.empty())
+    return true;
+
+  // Otherwise, we can only match if our match pattern matches subdomains.
+  if (!match_subdomains_)
+    return false;
+
+  // We don't do subdomain matching against IP addresses, so we can give up now
+  // if the test host is an IP address.
+  if (test.HostIsIPAddress())
+    return false;
+
+  // Check if the test host is a subdomain of our host.
+  if (test.host().length() <= (host_.length() + 1))
+    return false;
+
+  if (test.host().compare(test.host().length() - host_.length(),
+                          host_.length(), host_) != 0)
+    return false;
+
+  return test.host()[test.host().length() - host_.length() - 1] == '.';
+}
+
+bool URLPattern::MatchesPath(const std::string& test) const {
+  if (!MatchPattern(test, path_escaped_))
+    return false;
+
+  return true;
+}
+
+bool URLPattern::MatchesPort(int port) const {
+  if (port == url_parse::PORT_INVALID)
+    return false;
+
+  return port_ == "*" || port_ == base::IntToString(port);
+}
+
+
+const std::string& URLPattern::GetAsString() const {
+  if (!spec_.empty())
+    return spec_;
+
+  if (match_all_urls_) {
+    spec_ = kAllUrlsPattern;
+    return spec_;
+  }
+
+  bool standard_scheme = IsStandardScheme(scheme_);
+
+  std::string spec = scheme_ +
+      (standard_scheme ? content::kStandardSchemeSeparator : ":");
+
+  if (scheme_ != chrome::kFileScheme && standard_scheme) {
+    if (match_subdomains_) {
+      spec += "*";
+      if (!host_.empty())
+        spec += ".";
+    }
+
+    if (!host_.empty())
+      spec += host_;
+
+    if (port_ != "*") {
+      spec += ":";
+      spec += port_;
+    }
+  }
+
+  if (!path_.empty())
+    spec += path_;
+
+  spec_ = spec;
+  return spec_;
+}
+
+bool URLPattern::OverlapsWith(const URLPattern& other) const {
+  if (!MatchesAnyScheme(other.GetExplicitSchemes()) &&
+      !other.MatchesAnyScheme(GetExplicitSchemes())) {
+    return false;
+  }
+
+  if (!MatchesHost(other.host()) && !other.MatchesHost(host_))
+    return false;
+
+  if (port_ != "*" && other.port() != "*" && port_ != other.port())
+    return false;
+
+  // We currently only use OverlapsWith() for the patterns inside
+  // URLPatternSet. In those cases, we know that the path will have only a
+  // single wildcard at the end. This makes figuring out overlap much easier. It
+  // seems like there is probably a computer-sciency way to solve the general
+  // case, but we don't need that yet.
+  DCHECK(path_.find('*') == path_.size() - 1);
+  DCHECK(other.path().find('*') == other.path().size() - 1);
+
+  if (!MatchesPath(other.path().substr(0, other.path().size() - 1)) &&
+      !other.MatchesPath(path_.substr(0, path_.size() - 1)))
+    return false;
+
+  return true;
+}
+
+bool URLPattern::MatchesAnyScheme(
+    const std::vector<std::string>& schemes) const {
+  for (std::vector<std::string>::const_iterator i = schemes.begin();
+       i != schemes.end(); ++i) {
+    if (MatchesScheme(*i))
+      return true;
+  }
+
+  return false;
+}
+
+bool URLPattern::MatchesSecurityOriginHelper(const GURL& test) const {
+  // Ignore hostname if scheme is file://.
+  if (scheme_ != chrome::kFileScheme && !MatchesHost(test))
+    return false;
+
+  if (!MatchesPort(test.EffectiveIntPort()))
+    return false;
+
+  return true;
+}
+
+std::vector<std::string> URLPattern::GetExplicitSchemes() const {
+  std::vector<std::string> result;
+
+  if (scheme_ != "*" && !match_all_urls_ && IsValidScheme(scheme_)) {
+    result.push_back(scheme_);
+    return result;
+  }
+
+  for (size_t i = 0; i < arraysize(kValidSchemes); ++i) {
+    if (MatchesScheme(kValidSchemes[i])) {
+      result.push_back(kValidSchemes[i]);
+    }
+  }
+
+  return result;
+}
+
+std::vector<URLPattern> URLPattern::ConvertToExplicitSchemes() const {
+  std::vector<std::string> explicit_schemes = GetExplicitSchemes();
+  std::vector<URLPattern> result;
+
+  for (std::vector<std::string>::const_iterator i = explicit_schemes.begin();
+       i != explicit_schemes.end(); ++i) {
+    URLPattern temp = *this;
+    temp.SetScheme(*i);
+    temp.SetMatchAllURLs(false);
+    result.push_back(temp);
+  }
+
+  return result;
+}
+
+// static
+const char* URLPattern::GetParseResultString(
+    URLPattern::ParseResult parse_result) {
+  return kParseResultMessages[parse_result];
+}
diff --git a/chrome/common/extensions/url_pattern.h b/chrome/common/extensions/url_pattern.h
new file mode 100644
index 0000000..6968133
--- /dev/null
+++ b/chrome/common/extensions/url_pattern.h
@@ -0,0 +1,236 @@
+// Copyright (c) 2012 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.
+#ifndef CHROME_COMMON_EXTENSIONS_URL_PATTERN_H_
+#define CHROME_COMMON_EXTENSIONS_URL_PATTERN_H_
+
+#include <functional>
+#include <string>
+#include <vector>
+
+class GURL;
+
+// A pattern that can be used to match URLs. A URLPattern is a very restricted
+// subset of URL syntax:
+//
+// <url-pattern> := <scheme>://<host><port><path> | '<all_urls>'
+// <scheme> := '*' | 'http' | 'https' | 'file' | 'ftp' | 'chrome' |
+//             'chrome-extension' | 'filesystem'
+// <host> := '*' | '*.' <anychar except '/' and '*'>+
+// <port> := [':' ('*' | <port number between 0 and 65535>)]
+// <path> := '/' <any chars>
+//
+// * Host is not used when the scheme is 'file'.
+// * The path can have embedded '*' characters which act as glob wildcards.
+// * '<all_urls>' is a special pattern that matches any URL that contains a
+//   valid scheme (as specified by valid_schemes_).
+// * The '*' scheme pattern excludes file URLs.
+//
+// Examples of valid patterns:
+// - http://*/*
+// - http://*/foo*
+// - https://*.google.com/foo*bar
+// - file://monkey*
+// - http://127.0.0.1/*
+//
+// Examples of invalid patterns:
+// - http://* -- path not specified
+// - http://*foo/bar -- * not allowed as substring of host component
+// - http://foo.*.bar/baz -- * must be first component
+// - http:/bar -- scheme separator not found
+// - foo://* -- invalid scheme
+// - chrome:// -- we don't support chrome internal URLs
+class URLPattern {
+ public:
+  // A collection of scheme bitmasks for use with valid_schemes.
+  enum SchemeMasks {
+    SCHEME_NONE       = 0,
+    SCHEME_HTTP       = 1 << 0,
+    SCHEME_HTTPS      = 1 << 1,
+    SCHEME_FILE       = 1 << 2,
+    SCHEME_FTP        = 1 << 3,
+    SCHEME_CHROMEUI   = 1 << 4,
+    SCHEME_EXTENSION  = 1 << 5,
+    SCHEME_FILESYSTEM = 1 << 6,
+
+    // IMPORTANT!
+    // SCHEME_ALL will match every scheme, including chrome://, chrome-
+    // extension://, about:, etc. Because this has lots of security
+    // implications, third-party extensions should usually not be able to get
+    // access to URL patterns initialized this way. If there is a reason
+    // for violating this general rule, document why this it safe.
+    SCHEME_ALL      = -1,
+  };
+
+  // Error codes returned from Parse().
+  enum ParseResult {
+    PARSE_SUCCESS = 0,
+    PARSE_ERROR_MISSING_SCHEME_SEPARATOR,
+    PARSE_ERROR_INVALID_SCHEME,
+    PARSE_ERROR_WRONG_SCHEME_SEPARATOR,
+    PARSE_ERROR_EMPTY_HOST,
+    PARSE_ERROR_INVALID_HOST_WILDCARD,
+    PARSE_ERROR_EMPTY_PATH,
+    PARSE_ERROR_INVALID_PORT,
+    NUM_PARSE_RESULTS
+  };
+
+  // The <all_urls> string pattern.
+  static const char kAllUrlsPattern[];
+
+  explicit URLPattern(int valid_schemes);
+
+  // Convenience to construct a URLPattern from a string. If the string is not
+  // known ahead of time, use Parse() instead, which returns success or failure.
+  URLPattern(int valid_schemes, const std::string& pattern);
+
+  URLPattern();
+  ~URLPattern();
+
+  bool operator<(const URLPattern& other) const;
+  bool operator==(const URLPattern& other) const;
+
+  // Initializes this instance by parsing the provided string. Returns
+  // URLPattern::PARSE_SUCCESS on success, or an error code otherwise. On
+  // failure, this instance will have some intermediate values and is in an
+  // invalid state.
+  ParseResult Parse(const std::string& pattern_str);
+
+  // Gets the bitmask of valid schemes.
+  int valid_schemes() const { return valid_schemes_; }
+  void SetValidSchemes(int valid_schemes);
+
+  // Gets the host the pattern matches. This can be an empty string if the
+  // pattern matches all hosts (the input was <scheme>://*/<whatever>).
+  const std::string& host() const { return host_; }
+  void SetHost(const std::string& host);
+
+  // Gets whether to match subdomains of host().
+  bool match_subdomains() const { return match_subdomains_; }
+  void SetMatchSubdomains(bool val);
+
+  // Gets the path the pattern matches with the leading slash. This can have
+  // embedded asterisks which are interpreted using glob rules.
+  const std::string& path() const { return path_; }
+  void SetPath(const std::string& path);
+
+  // Returns true if this pattern matches all urls.
+  bool match_all_urls() const { return match_all_urls_; }
+  void SetMatchAllURLs(bool val);
+
+  // Sets the scheme for pattern matches. This can be a single '*' if the
+  // pattern matches all valid schemes (as defined by the valid_schemes_
+  // property). Returns false on failure (if the scheme is not valid).
+  bool SetScheme(const std::string& scheme);
+  // Note: You should use MatchesScheme() instead of this getter unless you
+  // absolutely need the exact scheme. This is exposed for testing.
+  const std::string& scheme() const { return scheme_; }
+
+  // Returns true if the specified scheme can be used in this URL pattern, and
+  // false otherwise. Uses valid_schemes_ to determine validity.
+  bool IsValidScheme(const std::string& scheme) const;
+
+  // Returns true if this instance matches the specified URL.
+  bool MatchesURL(const GURL& test) const;
+
+  // Returns true if this instance matches the specified security origin.
+  bool MatchesSecurityOrigin(const GURL& test) const;
+
+  // Returns true if |test| matches our scheme.
+  // Note that if test is "filesystem", this may fail whereas MatchesURL
+  // may succeed.  MatchesURL is smart enough to look at the inner_url instead
+  // of the outer "filesystem:" part.
+  bool MatchesScheme(const std::string& test) const;
+
+  // Returns true if |test| matches our host.
+  bool MatchesHost(const std::string& test) const;
+  bool MatchesHost(const GURL& test) const;
+
+  // Returns true if |test| matches our path.
+  bool MatchesPath(const std::string& test) const;
+
+  // Returns true if |port| matches our port.
+  bool MatchesPort(int port) const;
+
+  // Sets the port. Returns false if the port is invalid.
+  bool SetPort(const std::string& port);
+  const std::string& port() const { return port_; }
+
+  // Returns a string representing this instance.
+  const std::string& GetAsString() const;
+
+  // Determine whether there is a URL that would match this instance and another
+  // instance. This method is symmetrical: Calling other.OverlapsWith(this)
+  // would result in the same answer.
+  bool OverlapsWith(const URLPattern& other) const;
+
+  // Convert this URLPattern into an equivalent set of URLPatterns that don't
+  // use a wildcard in the scheme component. If this URLPattern doesn't use a
+  // wildcard scheme, then the returned set will contain one element that is
+  // equivalent to this instance.
+  std::vector<URLPattern> ConvertToExplicitSchemes() const;
+
+  static bool EffectiveHostCompare(const URLPattern& a, const URLPattern& b) {
+    if (a.match_all_urls_ && b.match_all_urls_)
+      return false;
+    return a.host_.compare(b.host_) < 0;
+  };
+
+  // Used for origin comparisons in a std::set.
+  class EffectiveHostCompareFunctor {
+   public:
+    bool operator()(const URLPattern& a, const URLPattern& b) const {
+      return EffectiveHostCompare(a, b);
+    };
+  };
+
+  // Get an error string for a ParseResult.
+  static const char* GetParseResultString(URLPattern::ParseResult parse_result);
+
+ private:
+  // Returns true if any of the |schemes| items matches our scheme.
+  bool MatchesAnyScheme(const std::vector<std::string>& schemes) const;
+
+  bool MatchesSecurityOriginHelper(const GURL& test) const;
+
+  // If the URLPattern contains a wildcard scheme, returns a list of
+  // equivalent literal schemes, otherwise returns the current scheme.
+  std::vector<std::string> GetExplicitSchemes() const;
+
+  // A bitmask containing the schemes which are considered valid for this
+  // pattern. Parse() uses this to decide whether a pattern contains a valid
+  // scheme. MatchesScheme uses this to decide whether a wildcard scheme_
+  // matches a given test scheme.
+  int valid_schemes_;
+
+  // True if this is a special-case "<all_urls>" pattern.
+  bool match_all_urls_;
+
+  // The scheme for the pattern.
+  std::string scheme_;
+
+  // The host without any leading "*" components.
+  std::string host_;
+
+  // Whether we should match subdomains of the host. This is true if the first
+  // component of the pattern's host was "*".
+  bool match_subdomains_;
+
+  // The port.
+  std::string port_;
+
+  // The path to match. This is everything after the host of the URL, or
+  // everything after the scheme in the case of file:// URLs.
+  std::string path_;
+
+  // The path with "?" and "\" characters escaped for use with the
+  // MatchPattern() function.
+  std::string path_escaped_;
+
+  // A string representing this URLPattern.
+  mutable std::string spec_;
+};
+
+typedef std::vector<URLPattern> URLPatternList;
+
+#endif  // CHROME_COMMON_EXTENSIONS_URL_PATTERN_H_
diff --git a/chrome/common/extensions/url_pattern_set.cc b/chrome/common/extensions/url_pattern_set.cc
new file mode 100644
index 0000000..1bc256c
--- /dev/null
+++ b/chrome/common/extensions/url_pattern_set.cc
@@ -0,0 +1,215 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/url_pattern_set.h"
+
+#include <algorithm>
+#include <iterator>
+
+#include "base/logging.h"
+#include "base/memory/linked_ptr.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/extensions/url_pattern.h"
+#include "content/public/common/url_constants.h"
+#include "googleurl/src/gurl.h"
+
+namespace {
+
+const char kInvalidURLPatternError[] = "Invalid url pattern '*'";
+
+}  // namespace
+
+// static
+void URLPatternSet::CreateDifference(const URLPatternSet& set1,
+                                     const URLPatternSet& set2,
+                                     URLPatternSet* out) {
+  out->ClearPatterns();
+  std::set_difference(set1.patterns_.begin(), set1.patterns_.end(),
+                      set2.patterns_.begin(), set2.patterns_.end(),
+                      std::inserter<std::set<URLPattern> >(
+                          out->patterns_, out->patterns_.begin()));
+}
+
+// static
+void URLPatternSet::CreateIntersection(const URLPatternSet& set1,
+                                       const URLPatternSet& set2,
+                                       URLPatternSet* out) {
+  out->ClearPatterns();
+  std::set_intersection(set1.patterns_.begin(), set1.patterns_.end(),
+                        set2.patterns_.begin(), set2.patterns_.end(),
+                        std::inserter<std::set<URLPattern> >(
+                            out->patterns_, out->patterns_.begin()));
+}
+
+// static
+void URLPatternSet::CreateUnion(const URLPatternSet& set1,
+                                const URLPatternSet& set2,
+                                URLPatternSet* out) {
+  out->ClearPatterns();
+  std::set_union(set1.patterns_.begin(), set1.patterns_.end(),
+                 set2.patterns_.begin(), set2.patterns_.end(),
+                 std::inserter<std::set<URLPattern> >(
+                     out->patterns_, out->patterns_.begin()));
+}
+
+// static
+void URLPatternSet::CreateUnion(const std::vector<URLPatternSet>& sets,
+                                URLPatternSet* out) {
+  out->ClearPatterns();
+  if (sets.empty())
+    return;
+
+  // N-way union algorithm is basic O(nlog(n)) merge algorithm.
+  //
+  // Do the first merge step into a working set so that we don't mutate any of
+  // the input.
+  std::vector<URLPatternSet> working;
+  for (size_t i = 0; i < sets.size(); i += 2) {
+    if (i + 1 < sets.size()) {
+      URLPatternSet u;
+      URLPatternSet::CreateUnion(sets[i], sets[i + 1], &u);
+      working.push_back(u);
+    } else {
+      working.push_back(sets[i]);
+    }
+  }
+
+  for (size_t skip = 1; skip < working.size(); skip *= 2) {
+    for (size_t i = 0; i < (working.size() - skip); i += skip) {
+      URLPatternSet u;
+      URLPatternSet::CreateUnion(working[i], working[i + skip], &u);
+      working[i].patterns_.swap(u.patterns_);
+    }
+  }
+
+  out->patterns_.swap(working[0].patterns_);
+}
+
+URLPatternSet::URLPatternSet() {}
+
+URLPatternSet::URLPatternSet(const URLPatternSet& rhs)
+    : patterns_(rhs.patterns_) {}
+
+URLPatternSet::URLPatternSet(const std::set<URLPattern>& patterns)
+    : patterns_(patterns) {}
+
+URLPatternSet::~URLPatternSet() {}
+
+URLPatternSet& URLPatternSet::operator=(const URLPatternSet& rhs) {
+  patterns_ = rhs.patterns_;
+  return *this;
+}
+
+bool URLPatternSet::operator==(const URLPatternSet& other) const {
+  return patterns_ == other.patterns_;
+}
+
+bool URLPatternSet::is_empty() const {
+  return patterns_.empty();
+}
+
+size_t URLPatternSet::size() const {
+  return patterns_.size();
+}
+
+bool URLPatternSet::AddPattern(const URLPattern& pattern) {
+  return patterns_.insert(pattern).second;
+}
+
+void URLPatternSet::AddPatterns(const URLPatternSet& set) {
+  patterns_.insert(set.patterns().begin(),
+                   set.patterns().end());
+}
+
+void URLPatternSet::ClearPatterns() {
+  patterns_.clear();
+}
+
+bool URLPatternSet::Contains(const URLPatternSet& set) const {
+  return std::includes(patterns_.begin(), patterns_.end(),
+                       set.patterns_.begin(), set.patterns_.end());
+}
+
+bool URLPatternSet::MatchesURL(const GURL& url) const {
+  for (URLPatternSet::const_iterator pattern = patterns_.begin();
+       pattern != patterns_.end(); ++pattern) {
+    if (pattern->MatchesURL(url))
+      return true;
+  }
+
+  return false;
+}
+
+bool URLPatternSet::MatchesSecurityOrigin(const GURL& origin) const {
+  for (URLPatternSet::const_iterator pattern = patterns_.begin();
+       pattern != patterns_.end(); ++pattern) {
+    if (pattern->MatchesSecurityOrigin(origin))
+      return true;
+  }
+
+  return false;
+}
+
+bool URLPatternSet::OverlapsWith(const URLPatternSet& other) const {
+  // Two extension extents overlap if there is any one URL that would match at
+  // least one pattern in each of the extents.
+  for (URLPatternSet::const_iterator i = patterns_.begin();
+       i != patterns_.end(); ++i) {
+    for (URLPatternSet::const_iterator j = other.patterns().begin();
+         j != other.patterns().end(); ++j) {
+      if (i->OverlapsWith(*j))
+        return true;
+    }
+  }
+
+  return false;
+}
+
+scoped_ptr<base::ListValue> URLPatternSet::ToValue() const {
+  scoped_ptr<ListValue> value(new ListValue);
+  for (URLPatternSet::const_iterator i = patterns_.begin();
+       i != patterns_.end(); ++i)
+    value->AppendIfNotPresent(Value::CreateStringValue(i->GetAsString()));
+  return value.Pass();
+}
+
+bool URLPatternSet::Populate(const std::vector<std::string>& patterns,
+                             int valid_schemes,
+                             bool allow_file_access,
+                             std::string* error) {
+  ClearPatterns();
+  for (size_t i = 0; i < patterns.size(); ++i) {
+    URLPattern pattern(valid_schemes);
+    if (pattern.Parse(patterns[i]) != URLPattern::PARSE_SUCCESS) {
+      if (error) {
+        *error = ExtensionErrorUtils::FormatErrorMessage(
+            kInvalidURLPatternError, patterns[i]);
+      } else {
+        LOG(ERROR) << "Invalid url pattern: " << patterns[i];
+      }
+      return false;
+    }
+    if (!allow_file_access && pattern.MatchesScheme(chrome::kFileScheme)) {
+      pattern.SetValidSchemes(
+          pattern.valid_schemes() & ~URLPattern::SCHEME_FILE);
+    }
+    AddPattern(pattern);
+  }
+  return true;
+}
+
+bool URLPatternSet::Populate(const base::ListValue& value,
+                             int valid_schemes,
+                             bool allow_file_access,
+                             std::string* error) {
+  std::vector<std::string> patterns;
+  for (size_t i = 0; i < value.GetSize(); ++i) {
+    std::string item;
+    if (!value.GetString(i, &item))
+      return false;
+    patterns.push_back(item);
+  }
+  return Populate(patterns, valid_schemes, allow_file_access, error);
+}
diff --git a/chrome/common/extensions/url_pattern_set.h b/chrome/common/extensions/url_pattern_set.h
new file mode 100644
index 0000000..7347f09
--- /dev/null
+++ b/chrome/common/extensions/url_pattern_set.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_URL_PATTERN_SET_H_
+#define CHROME_COMMON_EXTENSIONS_URL_PATTERN_SET_H_
+
+#include <set>
+
+#include "base/memory/scoped_ptr.h"
+#include "chrome/common/extensions/url_pattern.h"
+
+class GURL;
+
+namespace base {
+class ListValue;
+class Value;
+}
+
+// Represents the set of URLs an extension uses for web content.
+class URLPatternSet {
+ public:
+  typedef std::set<URLPattern>::const_iterator const_iterator;
+  typedef std::set<URLPattern>::iterator iterator;
+
+  // Clears |out| and populates the set with |set1| - |set2|.
+  static void CreateDifference(const URLPatternSet& set1,
+                               const URLPatternSet& set2,
+                               URLPatternSet* out);
+
+  // Clears |out| and populates the set with the intersection of |set1|
+  // and |set2|.
+  static void CreateIntersection(const URLPatternSet& set1,
+                                 const URLPatternSet& set2,
+                                 URLPatternSet* out);
+
+  // Clears |out| and populates the set with the union of |set1| and |set2|.
+  static void CreateUnion(const URLPatternSet& set1,
+                          const URLPatternSet& set2,
+                          URLPatternSet* out);
+
+  // Clears |out| and populates it with the union of all sets in |sets|.
+  static void CreateUnion(const std::vector<URLPatternSet>& sets,
+                          URLPatternSet* out);
+
+  URLPatternSet();
+  URLPatternSet(const URLPatternSet& rhs);
+  explicit URLPatternSet(const std::set<URLPattern>& patterns);
+  ~URLPatternSet();
+
+  URLPatternSet& operator=(const URLPatternSet& rhs);
+  bool operator==(const URLPatternSet& rhs) const;
+
+  bool is_empty() const;
+  size_t size() const;
+  const std::set<URLPattern>& patterns() const { return patterns_; }
+  const_iterator begin() const { return patterns_.begin(); }
+  const_iterator end() const { return patterns_.end(); }
+
+  // Adds a pattern to the set. Returns true if a new pattern was inserted,
+  // false if the pattern was already in the set.
+  bool AddPattern(const URLPattern& pattern);
+
+  // Adds all patterns from |set| into this.
+  void AddPatterns(const URLPatternSet& set);
+
+  void ClearPatterns();
+
+  // Returns true if the permission |set| is a subset of this.
+  bool Contains(const URLPatternSet& set) const;
+
+  // Test if the extent contains a URL.
+  bool MatchesURL(const GURL& url) const;
+
+  bool MatchesSecurityOrigin(const GURL& origin) const;
+
+  // Returns true if there is a single URL that would be in two extents.
+  bool OverlapsWith(const URLPatternSet& other) const;
+
+  // Converts to and from Value for serialization to preferences.
+  scoped_ptr<base::ListValue> ToValue() const;
+  bool Populate(const base::ListValue& value,
+                int valid_schemes,
+                bool allow_file_access,
+                std::string* error);
+
+  bool Populate(const std::vector<std::string>& patterns,
+                int valid_schemes,
+                bool allow_file_access,
+                std::string* error);
+
+ private:
+  // The list of URL patterns that comprise the extent.
+  std::set<URLPattern> patterns_;
+};
+
+#endif  // CHROME_COMMON_EXTENSIONS_URL_PATTERN_SET_H_
diff --git a/chrome/common/extensions/url_pattern_set_unittest.cc b/chrome/common/extensions/url_pattern_set_unittest.cc
new file mode 100644
index 0000000..56dc518
--- /dev/null
+++ b/chrome/common/extensions/url_pattern_set_unittest.cc
@@ -0,0 +1,392 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/extension.h"
+
+#include "base/values.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void AddPattern(URLPatternSet* set, const std::string& pattern) {
+  int schemes = URLPattern::SCHEME_ALL;
+  set->AddPattern(URLPattern(schemes, pattern));
+}
+
+URLPatternSet Patterns(const std::string& pattern) {
+  URLPatternSet set;
+  AddPattern(&set, pattern);
+  return set;
+}
+
+URLPatternSet Patterns(const std::string& pattern1,
+                       const std::string& pattern2) {
+  URLPatternSet set;
+  AddPattern(&set, pattern1);
+  AddPattern(&set, pattern2);
+  return set;
+}
+
+}
+
+TEST(URLPatternSetTest, Empty) {
+  URLPatternSet set;
+  EXPECT_FALSE(set.MatchesURL(GURL("http://www.foo.com/bar")));
+  EXPECT_FALSE(set.MatchesURL(GURL()));
+  EXPECT_FALSE(set.MatchesURL(GURL("invalid")));
+}
+
+TEST(URLPatternSetTest, One) {
+  URLPatternSet set;
+  AddPattern(&set, "http://www.google.com/*");
+
+  EXPECT_TRUE(set.MatchesURL(GURL("http://www.google.com/")));
+  EXPECT_TRUE(set.MatchesURL(GURL("http://www.google.com/monkey")));
+  EXPECT_FALSE(set.MatchesURL(GURL("https://www.google.com/")));
+  EXPECT_FALSE(set.MatchesURL(GURL("https://www.microsoft.com/")));
+}
+
+TEST(URLPatternSetTest, Two) {
+  URLPatternSet set;
+  AddPattern(&set, "http://www.google.com/*");
+  AddPattern(&set, "http://www.yahoo.com/*");
+
+  EXPECT_TRUE(set.MatchesURL(GURL("http://www.google.com/monkey")));
+  EXPECT_TRUE(set.MatchesURL(GURL("http://www.yahoo.com/monkey")));
+  EXPECT_FALSE(set.MatchesURL(GURL("https://www.apple.com/monkey")));
+}
+
+TEST(URLPatternSetTest, OverlapsWith) {
+  URLPatternSet set1;
+  AddPattern(&set1, "http://www.google.com/f*");
+  AddPattern(&set1, "http://www.yahoo.com/b*");
+
+  URLPatternSet set2;
+  AddPattern(&set2, "http://www.reddit.com/f*");
+  AddPattern(&set2, "http://www.yahoo.com/z*");
+
+  URLPatternSet set3;
+  AddPattern(&set3, "http://www.google.com/q/*");
+  AddPattern(&set3, "http://www.yahoo.com/b/*");
+
+  EXPECT_FALSE(set1.OverlapsWith(set2));
+  EXPECT_FALSE(set2.OverlapsWith(set1));
+
+  EXPECT_TRUE(set1.OverlapsWith(set3));
+  EXPECT_TRUE(set3.OverlapsWith(set1));
+}
+
+TEST(URLPatternSetTest, CreateDifference) {
+  URLPatternSet expected;
+  URLPatternSet set1;
+  URLPatternSet set2;
+  AddPattern(&set1, "http://www.google.com/f*");
+  AddPattern(&set1, "http://www.yahoo.com/b*");
+
+  // Subtract an empty set.
+  URLPatternSet result;
+  URLPatternSet::CreateDifference(set1, set2, &result);
+  EXPECT_EQ(set1, result);
+
+  // Subtract a real set.
+  AddPattern(&set2, "http://www.reddit.com/f*");
+  AddPattern(&set2, "http://www.yahoo.com/z*");
+  AddPattern(&set2, "http://www.google.com/f*");
+
+  AddPattern(&expected, "http://www.yahoo.com/b*");
+
+  result.ClearPatterns();
+  URLPatternSet::CreateDifference(set1, set2, &result);
+  EXPECT_EQ(expected, result);
+  EXPECT_FALSE(result.is_empty());
+  EXPECT_TRUE(set1.Contains(result));
+  EXPECT_FALSE(result.Contains(set2));
+  EXPECT_FALSE(set2.Contains(result));
+
+  URLPatternSet intersection;
+  URLPatternSet::CreateIntersection(result, set2, &intersection);
+  EXPECT_TRUE(intersection.is_empty());
+}
+
+TEST(URLPatternSetTest, CreateIntersection) {
+  URLPatternSet empty_set;
+  URLPatternSet expected;
+  URLPatternSet set1;
+  AddPattern(&set1, "http://www.google.com/f*");
+  AddPattern(&set1, "http://www.yahoo.com/b*");
+
+  // Intersection with an empty set.
+  URLPatternSet result;
+  URLPatternSet::CreateIntersection(set1, empty_set, &result);
+  EXPECT_EQ(expected, result);
+  EXPECT_TRUE(result.is_empty());
+  EXPECT_TRUE(empty_set.Contains(result));
+  EXPECT_TRUE(result.Contains(empty_set));
+  EXPECT_TRUE(set1.Contains(result));
+
+  // Intersection with a real set.
+  URLPatternSet set2;
+  AddPattern(&set2, "http://www.reddit.com/f*");
+  AddPattern(&set2, "http://www.yahoo.com/z*");
+  AddPattern(&set2, "http://www.google.com/f*");
+
+  AddPattern(&expected, "http://www.google.com/f*");
+
+  result.ClearPatterns();
+  URLPatternSet::CreateIntersection(set1, set2, &result);
+  EXPECT_EQ(expected, result);
+  EXPECT_FALSE(result.is_empty());
+  EXPECT_TRUE(set1.Contains(result));
+  EXPECT_TRUE(set2.Contains(result));
+}
+
+TEST(URLPatternSetTest, CreateUnion) {
+  URLPatternSet empty_set;
+
+  URLPatternSet set1;
+  AddPattern(&set1, "http://www.google.com/f*");
+  AddPattern(&set1, "http://www.yahoo.com/b*");
+
+  URLPatternSet expected;
+  AddPattern(&expected, "http://www.google.com/f*");
+  AddPattern(&expected, "http://www.yahoo.com/b*");
+
+  // Union with an empty set.
+  URLPatternSet result;
+  URLPatternSet::CreateUnion(set1, empty_set, &result);
+  EXPECT_EQ(expected, result);
+
+  // Union with a real set.
+  URLPatternSet set2;
+  AddPattern(&set2, "http://www.reddit.com/f*");
+  AddPattern(&set2, "http://www.yahoo.com/z*");
+  AddPattern(&set2, "http://www.google.com/f*");
+
+  AddPattern(&expected, "http://www.reddit.com/f*");
+  AddPattern(&expected, "http://www.yahoo.com/z*");
+
+  result.ClearPatterns();
+  URLPatternSet::CreateUnion(set1, set2, &result);
+  EXPECT_EQ(expected, result);
+}
+
+TEST(URLPatternSetTest, Contains) {
+  URLPatternSet set1;
+  URLPatternSet set2;
+  URLPatternSet empty_set;
+
+  AddPattern(&set1, "http://www.google.com/*");
+  AddPattern(&set1, "http://www.yahoo.com/*");
+
+  AddPattern(&set2, "http://www.reddit.com/*");
+
+  EXPECT_FALSE(set1.Contains(set2));
+  EXPECT_TRUE(set1.Contains(empty_set));
+  EXPECT_FALSE(empty_set.Contains(set1));
+
+  AddPattern(&set2, "http://www.yahoo.com/*");
+
+  EXPECT_FALSE(set1.Contains(set2));
+  EXPECT_FALSE(set2.Contains(set1));
+
+  AddPattern(&set2, "http://www.google.com/*");
+
+  EXPECT_FALSE(set1.Contains(set2));
+  EXPECT_TRUE(set2.Contains(set1));
+
+  // Note that this just checks pattern equality, and not if individual patterns
+  // contain other patterns. For example:
+  AddPattern(&set1, "http://*.reddit.com/*");
+  EXPECT_FALSE(set1.Contains(set2));
+  EXPECT_FALSE(set2.Contains(set1));
+}
+
+TEST(URLPatternSetTest, Duplicates) {
+  URLPatternSet set1;
+  URLPatternSet set2;
+
+  AddPattern(&set1, "http://www.google.com/*");
+  AddPattern(&set2, "http://www.google.com/*");
+
+  AddPattern(&set1, "http://www.google.com/*");
+
+  // The sets should still be equal after adding a duplicate.
+  EXPECT_EQ(set2, set1);
+}
+
+TEST(URLPatternSetTest, ToValueAndPopulate) {
+  URLPatternSet set1;
+  URLPatternSet set2;
+
+  std::vector<std::string> patterns;
+  patterns.push_back("http://www.google.com/*");
+  patterns.push_back("http://www.yahoo.com/*");
+
+  for (size_t i = 0; i < patterns.size(); ++i)
+    AddPattern(&set1, patterns[i]);
+
+  std::string error;
+  bool allow_file_access = false;
+  scoped_ptr<base::ListValue> value(set1.ToValue());
+  set2.Populate(*value, URLPattern::SCHEME_ALL, allow_file_access, &error);
+  EXPECT_EQ(set1, set2);
+
+  set2.ClearPatterns();
+  set2.Populate(patterns, URLPattern::SCHEME_ALL, allow_file_access, &error);
+  EXPECT_EQ(set1, set2);
+}
+
+TEST(URLPatternSetTest, NwayUnion) {
+  std::string google_a = "http://www.google.com/a*";
+  std::string google_b = "http://www.google.com/b*";
+  std::string google_c = "http://www.google.com/c*";
+  std::string yahoo_a = "http://www.yahoo.com/a*";
+  std::string yahoo_b = "http://www.yahoo.com/b*";
+  std::string yahoo_c = "http://www.yahoo.com/c*";
+  std::string reddit_a = "http://www.reddit.com/a*";
+  std::string reddit_b = "http://www.reddit.com/b*";
+  std::string reddit_c = "http://www.reddit.com/c*";
+
+  // Empty list.
+  {
+    std::vector<URLPatternSet> empty;
+
+    URLPatternSet result;
+    URLPatternSet::CreateUnion(empty, &result);
+
+    URLPatternSet expected;
+    EXPECT_EQ(expected, result);
+  }
+
+  // Singleton list.
+  {
+    std::vector<URLPatternSet> test;
+    test.push_back(Patterns(google_a));
+
+    URLPatternSet result;
+    URLPatternSet::CreateUnion(test, &result);
+
+    URLPatternSet expected = Patterns(google_a);
+    EXPECT_EQ(expected, result);
+  }
+
+  // List with 2 elements.
+  {
+
+    std::vector<URLPatternSet> test;
+    test.push_back(Patterns(google_a, google_b));
+    test.push_back(Patterns(google_b, google_c));
+
+    URLPatternSet result;
+    URLPatternSet::CreateUnion(test, &result);
+
+    URLPatternSet expected;
+    AddPattern(&expected, google_a);
+    AddPattern(&expected, google_b);
+    AddPattern(&expected, google_c);
+    EXPECT_EQ(expected, result);
+  }
+
+  // List with 3 elements.
+  {
+    std::vector<URLPatternSet> test;
+    test.push_back(Patterns(google_a, google_b));
+    test.push_back(Patterns(google_b, google_c));
+    test.push_back(Patterns(yahoo_a, yahoo_b));
+
+    URLPatternSet result;
+    URLPatternSet::CreateUnion(test, &result);
+
+    URLPatternSet expected;
+    AddPattern(&expected, google_a);
+    AddPattern(&expected, google_b);
+    AddPattern(&expected, google_c);
+    AddPattern(&expected, yahoo_a);
+    AddPattern(&expected, yahoo_b);
+    EXPECT_EQ(expected, result);
+  }
+
+  // List with 7 elements.
+  {
+    std::vector<URLPatternSet> test;
+    test.push_back(Patterns(google_a));
+    test.push_back(Patterns(google_b));
+    test.push_back(Patterns(google_c));
+    test.push_back(Patterns(yahoo_a));
+    test.push_back(Patterns(yahoo_b));
+    test.push_back(Patterns(yahoo_c));
+    test.push_back(Patterns(reddit_a));
+
+    URLPatternSet result;
+    URLPatternSet::CreateUnion(test, &result);
+
+    URLPatternSet expected;
+    AddPattern(&expected, google_a);
+    AddPattern(&expected, google_b);
+    AddPattern(&expected, google_c);
+    AddPattern(&expected, yahoo_a);
+    AddPattern(&expected, yahoo_b);
+    AddPattern(&expected, yahoo_c);
+    AddPattern(&expected, reddit_a);
+    EXPECT_EQ(expected, result);
+  }
+
+  // List with 8 elements.
+  {
+    std::vector<URLPatternSet> test;
+    test.push_back(Patterns(google_a));
+    test.push_back(Patterns(google_b));
+    test.push_back(Patterns(google_c));
+    test.push_back(Patterns(yahoo_a));
+    test.push_back(Patterns(yahoo_b));
+    test.push_back(Patterns(yahoo_c));
+    test.push_back(Patterns(reddit_a));
+    test.push_back(Patterns(reddit_b));
+
+    URLPatternSet result;
+    URLPatternSet::CreateUnion(test, &result);
+
+    URLPatternSet expected;
+    AddPattern(&expected, google_a);
+    AddPattern(&expected, google_b);
+    AddPattern(&expected, google_c);
+    AddPattern(&expected, yahoo_a);
+    AddPattern(&expected, yahoo_b);
+    AddPattern(&expected, yahoo_c);
+    AddPattern(&expected, reddit_a);
+    AddPattern(&expected, reddit_b);
+    EXPECT_EQ(expected, result);
+  }
+
+  // List with 9 elements.
+  {
+
+    std::vector<URLPatternSet> test;
+    test.push_back(Patterns(google_a));
+    test.push_back(Patterns(google_b));
+    test.push_back(Patterns(google_c));
+    test.push_back(Patterns(yahoo_a));
+    test.push_back(Patterns(yahoo_b));
+    test.push_back(Patterns(yahoo_c));
+    test.push_back(Patterns(reddit_a));
+    test.push_back(Patterns(reddit_b));
+    test.push_back(Patterns(reddit_c));
+
+    URLPatternSet result;
+    URLPatternSet::CreateUnion(test, &result);
+
+    URLPatternSet expected;
+    AddPattern(&expected, google_a);
+    AddPattern(&expected, google_b);
+    AddPattern(&expected, google_c);
+    AddPattern(&expected, yahoo_a);
+    AddPattern(&expected, yahoo_b);
+    AddPattern(&expected, yahoo_c);
+    AddPattern(&expected, reddit_a);
+    AddPattern(&expected, reddit_b);
+    AddPattern(&expected, reddit_c);
+    EXPECT_EQ(expected, result);
+  }
+}
diff --git a/chrome/common/extensions/url_pattern_unittest.cc b/chrome/common/extensions/url_pattern_unittest.cc
new file mode 100644
index 0000000..d19d72d
--- /dev/null
+++ b/chrome/common/extensions/url_pattern_unittest.cc
@@ -0,0 +1,659 @@
+// Copyright (c) 2012 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.
+
+#include "base/memory/scoped_ptr.h"
+#include "chrome/common/extensions/url_pattern.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "googleurl/src/gurl.h"
+
+// See url_pattern.h for examples of valid and invalid patterns.
+
+static const int kAllSchemes =
+    URLPattern::SCHEME_HTTP |
+    URLPattern::SCHEME_HTTPS |
+    URLPattern::SCHEME_FILE |
+    URLPattern::SCHEME_FTP |
+    URLPattern::SCHEME_CHROMEUI |
+    URLPattern::SCHEME_EXTENSION |
+    URLPattern::SCHEME_FILESYSTEM;
+
+TEST(ExtensionURLPatternTest, ParseInvalid) {
+  const struct {
+    const char* pattern;
+    URLPattern::ParseResult expected_result;
+  } kInvalidPatterns[] = {
+    { "http", URLPattern::PARSE_ERROR_MISSING_SCHEME_SEPARATOR },
+    { "http:", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
+    { "http:/", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
+    { "about://", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
+    { "http://", URLPattern::PARSE_ERROR_EMPTY_HOST },
+    { "http:///", URLPattern::PARSE_ERROR_EMPTY_HOST },
+    { "http://*foo/bar", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD },
+    { "http://foo.*.bar/baz", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD },
+    { "http://fo.*.ba:123/baz", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD },
+    { "http:/bar", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
+    { "http://bar", URLPattern::PARSE_ERROR_EMPTY_PATH },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kInvalidPatterns); ++i) {
+    URLPattern pattern(URLPattern::SCHEME_ALL);
+    EXPECT_EQ(kInvalidPatterns[i].expected_result,
+              pattern.Parse(kInvalidPatterns[i].pattern))
+        << kInvalidPatterns[i].pattern;
+  }
+};
+
+TEST(ExtensionURLPatternTest, Ports) {
+  const struct {
+    const char* pattern;
+    URLPattern::ParseResult expected_result;
+    const char* expected_port;
+  } kTestPatterns[] = {
+    { "http://foo:1234/", URLPattern::PARSE_SUCCESS, "1234" },
+    { "http://foo:1234/bar", URLPattern::PARSE_SUCCESS, "1234" },
+    { "http://*.foo:1234/", URLPattern::PARSE_SUCCESS, "1234" },
+    { "http://*.foo:1234/bar", URLPattern::PARSE_SUCCESS,"1234" },
+    { "http://:1234/", URLPattern::PARSE_SUCCESS, "1234" },
+    { "http://foo:/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
+    { "http://foo:*/", URLPattern::PARSE_SUCCESS, "*" },
+    { "http://*.foo:/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
+    { "http://foo:com/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
+    { "http://foo:123456/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
+    { "http://foo:80:80/monkey", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
+    { "file://foo:1234/bar", URLPattern::PARSE_SUCCESS, "*" },
+    { "chrome://foo:1234/bar", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
+
+    // Port-like strings in the path should not trigger a warning.
+    { "http://*/:1234", URLPattern::PARSE_SUCCESS, "*" },
+    { "http://*.foo/bar:1234", URLPattern::PARSE_SUCCESS, "*" },
+    { "http://foo/bar:1234/path", URLPattern::PARSE_SUCCESS,"*" },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestPatterns); ++i) {
+    URLPattern pattern(URLPattern::SCHEME_ALL);
+    EXPECT_EQ(kTestPatterns[i].expected_result,
+              pattern.Parse(kTestPatterns[i].pattern))
+        << "Got unexpected result for URL pattern: "
+        << kTestPatterns[i].pattern;
+    EXPECT_EQ(kTestPatterns[i].expected_port, pattern.port())
+        << "Got unexpected port for URL pattern: " << kTestPatterns[i].pattern;
+  }
+};
+
+// all pages for a given scheme
+TEST(ExtensionURLPatternTest, Match1) {
+  URLPattern pattern(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*/*"));
+  EXPECT_EQ("http", pattern.scheme());
+  EXPECT_EQ("", pattern.host());
+  EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/*", pattern.path());
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://yahoo.com")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com/foo")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("https://google.com")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://74.125.127.100/search")));
+}
+
+// all domains
+TEST(ExtensionURLPatternTest, Match2) {
+  URLPattern pattern(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("https://*/foo*"));
+  EXPECT_EQ("https", pattern.scheme());
+  EXPECT_EQ("", pattern.host());
+  EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/foo*", pattern.path());
+  EXPECT_TRUE(pattern.MatchesURL(GURL("https://www.google.com/foo")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("https://www.google.com/foobar")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("http://www.google.com/foo")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("https://www.google.com/")));
+  EXPECT_TRUE(pattern.MatchesURL(
+      GURL("filesystem:https://www.google.com/foobar/")));
+}
+
+// subdomains
+TEST(URLPatternTest, Match3) {
+  URLPattern pattern(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+            pattern.Parse("http://*.google.com/foo*bar"));
+  EXPECT_EQ("http", pattern.scheme());
+  EXPECT_EQ("google.com", pattern.host());
+  EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/foo*bar", pattern.path());
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com/foobar")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.google.com/foo?bar")));
+  EXPECT_TRUE(pattern.MatchesURL(
+      GURL("http://monkey.images.google.com/foooobar")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("http://yahoo.com/foobar")));
+  EXPECT_TRUE(pattern.MatchesURL(
+      GURL("filesystem:http://google.com/foo/bar")));
+  EXPECT_FALSE(pattern.MatchesURL(
+      GURL("filesystem:http://google.com/temporary/foobar")));
+}
+
+// glob escaping
+TEST(ExtensionURLPatternTest, Match5) {
+  URLPattern pattern(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("file:///foo?bar\\*baz"));
+  EXPECT_EQ("file", pattern.scheme());
+  EXPECT_EQ("", pattern.host());
+  EXPECT_FALSE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/foo?bar\\*baz", pattern.path());
+  EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo?bar\\hellobaz")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("file:///fooXbar\\hellobaz")));
+}
+
+// ip addresses
+TEST(ExtensionURLPatternTest, Match6) {
+  URLPattern pattern(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://127.0.0.1/*"));
+  EXPECT_EQ("http", pattern.scheme());
+  EXPECT_EQ("127.0.0.1", pattern.host());
+  EXPECT_FALSE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/*", pattern.path());
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1")));
+}
+
+// subdomain matching with ip addresses
+TEST(ExtensionURLPatternTest, Match7) {
+  URLPattern pattern(kAllSchemes);
+  // allowed, but useless
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*.0.0.1/*"));
+  EXPECT_EQ("http", pattern.scheme());
+  EXPECT_EQ("0.0.1", pattern.host());
+  EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/*", pattern.path());
+  // Subdomain matching is never done if the argument has an IP address host.
+  EXPECT_FALSE(pattern.MatchesURL(GURL("http://127.0.0.1")));
+};
+
+// unicode
+TEST(ExtensionURLPatternTest, Match8) {
+  URLPattern pattern(kAllSchemes);
+  // The below is the ASCII encoding of the following URL:
+  // http://*.\xe1\x80\xbf/a\xc2\x81\xe1*
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+            pattern.Parse("http://*.xn--gkd/a%C2%81%E1*"));
+  EXPECT_EQ("http", pattern.scheme());
+  EXPECT_EQ("xn--gkd", pattern.host());
+  EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/a%C2%81%E1*", pattern.path());
+  EXPECT_TRUE(pattern.MatchesURL(
+      GURL("http://abc.\xe1\x80\xbf/a\xc2\x81\xe1xyz")));
+  EXPECT_TRUE(pattern.MatchesURL(
+      GURL("http://\xe1\x80\xbf/a\xc2\x81\xe1\xe1")));
+};
+
+// chrome://
+TEST(ExtensionURLPatternTest, Match9) {
+  URLPattern pattern(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("chrome://favicon/*"));
+  EXPECT_EQ("chrome", pattern.scheme());
+  EXPECT_EQ("favicon", pattern.host());
+  EXPECT_FALSE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/*", pattern.path());
+  EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/https://google.com")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("chrome://history")));
+};
+
+// *://
+TEST(ExtensionURLPatternTest, Match10) {
+  URLPattern pattern(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("*://*/*"));
+  EXPECT_TRUE(pattern.MatchesScheme("http"));
+  EXPECT_TRUE(pattern.MatchesScheme("https"));
+  EXPECT_FALSE(pattern.MatchesScheme("chrome"));
+  EXPECT_FALSE(pattern.MatchesScheme("file"));
+  EXPECT_FALSE(pattern.MatchesScheme("ftp"));
+  EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/*", pattern.path());
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("file:///foo/bar")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("file://localhost/foo/bar")));
+};
+
+// <all_urls>
+TEST(ExtensionURLPatternTest, Match11) {
+  URLPattern pattern(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("<all_urls>"));
+  EXPECT_TRUE(pattern.MatchesScheme("chrome"));
+  EXPECT_TRUE(pattern.MatchesScheme("http"));
+  EXPECT_TRUE(pattern.MatchesScheme("https"));
+  EXPECT_TRUE(pattern.MatchesScheme("file"));
+  EXPECT_TRUE(pattern.MatchesScheme("filesystem"));
+  EXPECT_TRUE(pattern.MatchesScheme("chrome-extension"));
+  EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_TRUE(pattern.match_all_urls());
+  EXPECT_EQ("/*", pattern.path());
+  EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo/bar")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo/bar")));
+
+  // Make sure the properties are the same when creating an <all_urls> pattern
+  // via SetMatchAllURLs and by parsing <all_urls>.
+  URLPattern pattern2(kAllSchemes);
+  pattern2.SetMatchAllURLs(true);
+
+  EXPECT_EQ(pattern.valid_schemes(), pattern2.valid_schemes());
+  EXPECT_EQ(pattern.match_subdomains(), pattern2.match_subdomains());
+  EXPECT_EQ(pattern.path(), pattern2.path());
+  EXPECT_EQ(pattern.match_all_urls(), pattern2.match_all_urls());
+  EXPECT_EQ(pattern.scheme(), pattern2.scheme());
+  EXPECT_EQ(pattern.port(), pattern2.port());
+  EXPECT_EQ(pattern.GetAsString(), pattern2.GetAsString());
+};
+
+// SCHEME_ALL matches all schemes.
+TEST(ExtensionURLPatternTest, Match12) {
+  URLPattern pattern(URLPattern::SCHEME_ALL);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("<all_urls>"));
+  EXPECT_TRUE(pattern.MatchesScheme("chrome"));
+  EXPECT_TRUE(pattern.MatchesScheme("http"));
+  EXPECT_TRUE(pattern.MatchesScheme("https"));
+  EXPECT_TRUE(pattern.MatchesScheme("file"));
+  EXPECT_TRUE(pattern.MatchesScheme("filesystem"));
+  EXPECT_TRUE(pattern.MatchesScheme("javascript"));
+  EXPECT_TRUE(pattern.MatchesScheme("data"));
+  EXPECT_TRUE(pattern.MatchesScheme("about"));
+  EXPECT_TRUE(pattern.MatchesScheme("chrome-extension"));
+  EXPECT_TRUE(pattern.match_subdomains());
+  EXPECT_TRUE(pattern.match_all_urls());
+  EXPECT_EQ("/*", pattern.path());
+  EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo/bar")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo/bar")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://newtab")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("about:blank")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("about:version")));
+  EXPECT_TRUE(pattern.MatchesURL(
+      GURL("data:text/html;charset=utf-8,<html>asdf</html>")));
+};
+
+static const struct MatchPatterns {
+  const char* pattern;
+  const char* matches;
+} kMatch13UrlPatternTestCases[] = {
+  {"about:*", "about:blank"},
+  {"about:blank", "about:blank"},
+  {"about:*", "about:version"},
+  {"chrome-extension://*/*", "chrome-extension://FTW"},
+  {"data:*", "data:monkey"},
+  {"javascript:*", "javascript:atemyhomework"},
+};
+
+// SCHEME_ALL and specific schemes.
+TEST(ExtensionURLPatternTest, Match13) {
+  for (size_t i = 0; i < arraysize(kMatch13UrlPatternTestCases); ++i) {
+    URLPattern pattern(URLPattern::SCHEME_ALL);
+    EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+              pattern.Parse(kMatch13UrlPatternTestCases[i].pattern))
+        << " while parsing " << kMatch13UrlPatternTestCases[i].pattern;
+    EXPECT_TRUE(pattern.MatchesURL(
+        GURL(kMatch13UrlPatternTestCases[i].matches)))
+        << " while matching " << kMatch13UrlPatternTestCases[i].matches;
+  }
+
+  // Negative test.
+  URLPattern pattern(URLPattern::SCHEME_ALL);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("data:*"));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("about:blank")));
+};
+
+// file scheme with empty hostname
+TEST(ExtensionURLPatternTest, Match14) {
+  URLPattern pattern(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("file:///foo*"));
+  EXPECT_EQ("file", pattern.scheme());
+  EXPECT_EQ("", pattern.host());
+  EXPECT_FALSE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/foo*", pattern.path());
+  EXPECT_FALSE(pattern.MatchesURL(GURL("file://foo")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("file://foobar")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foobar")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo")));
+}
+
+// file scheme without hostname part
+TEST(ExtensionURLPatternTest, Match15) {
+  URLPattern pattern(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("file://foo*"));
+  EXPECT_EQ("file", pattern.scheme());
+  EXPECT_EQ("", pattern.host());
+  EXPECT_FALSE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/foo*", pattern.path());
+  EXPECT_FALSE(pattern.MatchesURL(GURL("file://foo")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("file://foobar")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foobar")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo")));
+}
+
+// file scheme with hostname
+TEST(ExtensionURLPatternTest, Match16) {
+  URLPattern pattern(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("file://localhost/foo*"));
+  EXPECT_EQ("file", pattern.scheme());
+  // Since hostname is ignored for file://.
+  EXPECT_EQ("", pattern.host());
+  EXPECT_FALSE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/foo*", pattern.path());
+  EXPECT_FALSE(pattern.MatchesURL(GURL("file://foo")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("file://foobar")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foobar")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo")));
+}
+
+// Specific port
+TEST(ExtensionURLPatternTest, Match17) {
+  URLPattern pattern(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+            pattern.Parse("http://www.example.com:80/foo"));
+  EXPECT_EQ("http", pattern.scheme());
+  EXPECT_EQ("www.example.com", pattern.host());
+  EXPECT_FALSE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/foo", pattern.path());
+  EXPECT_EQ("80", pattern.port());
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.example.com:80/foo")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.example.com/foo")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("http://www.example.com:8080/foo")));
+  EXPECT_FALSE(pattern.MatchesURL(
+      GURL("filesystem:http://www.example.com:8080/foo/")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("filesystem:http://www.example.com/f/foo")));
+}
+
+// Explicit port wildcard
+TEST(ExtensionURLPatternTest, Match18) {
+  URLPattern pattern(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+            pattern.Parse("http://www.example.com:*/foo"));
+  EXPECT_EQ("http", pattern.scheme());
+  EXPECT_EQ("www.example.com", pattern.host());
+  EXPECT_FALSE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/foo", pattern.path());
+  EXPECT_EQ("*", pattern.port());
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.example.com:80/foo")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.example.com/foo")));
+  EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.example.com:8080/foo")));
+  EXPECT_FALSE(pattern.MatchesURL(
+      GURL("filesystem:http://www.example.com:8080/foo/")));
+}
+
+// chrome-extension://
+TEST(ExtensionURLPatternTest, Match19) {
+  URLPattern pattern(URLPattern::SCHEME_EXTENSION);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+            pattern.Parse("chrome-extension://ftw/*"));
+  EXPECT_EQ("chrome-extension", pattern.scheme());
+  EXPECT_EQ("ftw", pattern.host());
+  EXPECT_FALSE(pattern.match_subdomains());
+  EXPECT_FALSE(pattern.match_all_urls());
+  EXPECT_EQ("/*", pattern.path());
+  EXPECT_TRUE(pattern.MatchesURL(GURL("chrome-extension://ftw")));
+  EXPECT_TRUE(pattern.MatchesURL(
+      GURL("chrome-extension://ftw/http://google.com")));
+  EXPECT_TRUE(pattern.MatchesURL(
+      GURL("chrome-extension://ftw/https://google.com")));
+  EXPECT_FALSE(pattern.MatchesURL(GURL("chrome-extension://foobar")));
+  EXPECT_TRUE(pattern.MatchesURL(
+      GURL("filesystem:chrome-extension://ftw/t/file.txt")));
+};
+
+static const struct GetAsStringPatterns {
+  const char* pattern;
+} kGetAsStringTestCases[] = {
+  { "http://www/" },
+  { "http://*/*" },
+  { "chrome://*/*" },
+  { "chrome://newtab/" },
+  { "about:*" },
+  { "about:blank" },
+  { "chrome-extension://*/*" },
+  { "chrome-extension://FTW/" },
+  { "data:*" },
+  { "data:monkey" },
+  { "javascript:*" },
+  { "javascript:atemyhomework" },
+  { "http://www.example.com:8080/foo" },
+};
+
+TEST(ExtensionURLPatternTest, GetAsString) {
+  for (size_t i = 0; i < arraysize(kGetAsStringTestCases); ++i) {
+    URLPattern pattern(URLPattern::SCHEME_ALL);
+    EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+              pattern.Parse(kGetAsStringTestCases[i].pattern))
+        << "Error parsing " << kGetAsStringTestCases[i].pattern;
+    EXPECT_EQ(kGetAsStringTestCases[i].pattern,
+              pattern.GetAsString());
+  }
+}
+
+void TestPatternOverlap(const URLPattern& pattern1, const URLPattern& pattern2,
+                        bool expect_overlap) {
+  EXPECT_EQ(expect_overlap, pattern1.OverlapsWith(pattern2))
+      << pattern1.GetAsString() << ", " << pattern2.GetAsString();
+  EXPECT_EQ(expect_overlap, pattern2.OverlapsWith(pattern1))
+      << pattern2.GetAsString() << ", " << pattern1.GetAsString();
+}
+
+TEST(ExtensionURLPatternTest, OverlapsWith) {
+  URLPattern pattern1(kAllSchemes, "http://www.google.com/foo/*");
+  URLPattern pattern2(kAllSchemes, "https://www.google.com/foo/*");
+  URLPattern pattern3(kAllSchemes, "http://*.google.com/foo/*");
+  URLPattern pattern4(kAllSchemes, "http://*.yahooo.com/foo/*");
+  URLPattern pattern5(kAllSchemes, "http://www.yahooo.com/bar/*");
+  URLPattern pattern6(kAllSchemes,
+                      "http://www.yahooo.com/bar/baz/*");
+  URLPattern pattern7(kAllSchemes, "file:///*");
+  URLPattern pattern8(kAllSchemes, "*://*/*");
+  URLPattern pattern9(URLPattern::SCHEME_HTTPS, "*://*/*");
+  URLPattern pattern10(kAllSchemes, "<all_urls>");
+
+  TestPatternOverlap(pattern1, pattern1, true);
+  TestPatternOverlap(pattern1, pattern2, false);
+  TestPatternOverlap(pattern1, pattern3, true);
+  TestPatternOverlap(pattern1, pattern4, false);
+  TestPatternOverlap(pattern3, pattern4, false);
+  TestPatternOverlap(pattern4, pattern5, false);
+  TestPatternOverlap(pattern5, pattern6, true);
+
+  // Test that scheme restrictions work.
+  TestPatternOverlap(pattern1, pattern8, true);
+  TestPatternOverlap(pattern1, pattern9, false);
+  TestPatternOverlap(pattern1, pattern10, true);
+
+  // Test that '<all_urls>' includes file URLs, while scheme '*' does not.
+  TestPatternOverlap(pattern7, pattern8, false);
+  TestPatternOverlap(pattern7, pattern10, true);
+
+  // Test that wildcard schemes are handled correctly, especially when compared
+  // to each-other.
+  URLPattern pattern11(kAllSchemes, "http://example.com/*");
+  URLPattern pattern12(kAllSchemes, "*://example.com/*");
+  URLPattern pattern13(kAllSchemes, "*://example.com/foo/*");
+  URLPattern pattern14(kAllSchemes, "*://google.com/*");
+  TestPatternOverlap(pattern8, pattern12, true);
+  TestPatternOverlap(pattern9, pattern12, true);
+  TestPatternOverlap(pattern10, pattern12, true);
+  TestPatternOverlap(pattern11, pattern12, true);
+  TestPatternOverlap(pattern12, pattern13, true);
+  TestPatternOverlap(pattern11, pattern13, true);
+  TestPatternOverlap(pattern14, pattern12, false);
+  TestPatternOverlap(pattern14, pattern13, false);
+}
+
+TEST(ExtensionURLPatternTest, ConvertToExplicitSchemes) {
+  URLPatternList all_urls(URLPattern(
+      kAllSchemes,
+      "<all_urls>").ConvertToExplicitSchemes());
+
+  URLPatternList all_schemes(URLPattern(
+      kAllSchemes,
+      "*://google.com/foo").ConvertToExplicitSchemes());
+
+  URLPatternList monkey(URLPattern(
+      URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS |
+      URLPattern::SCHEME_FTP,
+      "http://google.com/monkey").ConvertToExplicitSchemes());
+
+  ASSERT_EQ(7u, all_urls.size());
+  ASSERT_EQ(2u, all_schemes.size());
+  ASSERT_EQ(1u, monkey.size());
+
+  EXPECT_EQ("http://*/*", all_urls[0].GetAsString());
+  EXPECT_EQ("https://*/*", all_urls[1].GetAsString());
+  EXPECT_EQ("file:///*", all_urls[2].GetAsString());
+  EXPECT_EQ("ftp://*/*", all_urls[3].GetAsString());
+  EXPECT_EQ("chrome://*/*", all_urls[4].GetAsString());
+
+  EXPECT_EQ("http://google.com/foo", all_schemes[0].GetAsString());
+  EXPECT_EQ("https://google.com/foo", all_schemes[1].GetAsString());
+
+  EXPECT_EQ("http://google.com/monkey", monkey[0].GetAsString());
+}
+
+TEST(ExtensionURLPatternTest, IgnorePorts) {
+  std::string pattern_str = "http://www.example.com:8080/foo";
+  GURL url("http://www.example.com:1234/foo");
+
+  URLPattern pattern(kAllSchemes);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse(pattern_str));
+
+  EXPECT_EQ(pattern_str, pattern.GetAsString());
+  EXPECT_FALSE(pattern.MatchesURL(url));
+}
+
+TEST(ExtensionURLPatternTest, Equals) {
+  const struct {
+    const char* pattern1;
+    const char* pattern2;
+    bool expected_equal;
+  } kEqualsTestCases[] = {
+    // schemes
+    { "http://en.google.com/blah/*/foo",
+      "https://en.google.com/blah/*/foo",
+      false
+    },
+    { "https://en.google.com/blah/*/foo",
+      "https://en.google.com/blah/*/foo",
+      true
+    },
+    { "https://en.google.com/blah/*/foo",
+      "ftp://en.google.com/blah/*/foo",
+      false
+    },
+
+    // subdomains
+    { "https://en.google.com/blah/*/foo",
+      "https://fr.google.com/blah/*/foo",
+      false
+    },
+    { "https://www.google.com/blah/*/foo",
+      "https://*.google.com/blah/*/foo",
+      false
+    },
+    { "https://*.google.com/blah/*/foo",
+      "https://*.google.com/blah/*/foo",
+      true
+    },
+
+    // domains
+    { "http://en.example.com/blah/*/foo",
+      "http://en.google.com/blah/*/foo",
+      false
+    },
+
+    // ports
+    { "http://en.google.com:8000/blah/*/foo",
+      "http://en.google.com/blah/*/foo",
+      false
+    },
+    { "http://fr.google.com:8000/blah/*/foo",
+      "http://fr.google.com:8000/blah/*/foo",
+      true
+    },
+    { "http://en.google.com:8000/blah/*/foo",
+      "http://en.google.com:8080/blah/*/foo",
+      false
+    },
+
+    // paths
+    { "http://en.google.com/blah/*/foo",
+      "http://en.google.com/blah/*",
+      false
+    },
+    { "http://en.google.com/*",
+      "http://en.google.com/",
+      false
+    },
+    { "http://en.google.com/*",
+      "http://en.google.com/*",
+      true
+    },
+
+    // all_urls
+    { "<all_urls>",
+      "<all_urls>",
+      true
+    },
+    { "<all_urls>",
+      "http://*/*",
+      false
+    }
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kEqualsTestCases); ++i) {
+    std::string message = kEqualsTestCases[i].pattern1;
+    message += " ";
+    message += kEqualsTestCases[i].pattern2;
+
+    URLPattern pattern1(URLPattern::SCHEME_ALL);
+    URLPattern pattern2(URLPattern::SCHEME_ALL);
+
+    pattern1.Parse(kEqualsTestCases[i].pattern1);
+    pattern2.Parse(kEqualsTestCases[i].pattern2);
+    EXPECT_EQ(kEqualsTestCases[i].expected_equal, pattern1 == pattern2)
+        << message;
+  }
+}
+
+TEST(ExtensionURLPatternTest, CanReusePatternWithParse) {
+  URLPattern pattern1(URLPattern::SCHEME_ALL);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern1.Parse("http://aa.com/*"));
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern1.Parse("http://bb.com/*"));
+
+  EXPECT_TRUE(pattern1.MatchesURL(GURL("http://bb.com/path")));
+  EXPECT_FALSE(pattern1.MatchesURL(GURL("http://aa.com/path")));
+
+  URLPattern pattern2(URLPattern::SCHEME_ALL, URLPattern::kAllUrlsPattern);
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern2.Parse("http://aa.com/*"));
+
+  EXPECT_FALSE(pattern2.MatchesURL(GURL("http://bb.com/path")));
+  EXPECT_TRUE(pattern2.MatchesURL(GURL("http://aa.com/path")));
+  EXPECT_FALSE(pattern2.MatchesURL(GURL("http://sub.aa.com/path")));
+
+  URLPattern pattern3(URLPattern::SCHEME_ALL, "http://aa.com/*");
+  EXPECT_EQ(URLPattern::PARSE_SUCCESS, pattern3.Parse("http://aa.com:88/*"));
+  EXPECT_FALSE(pattern3.MatchesURL(GURL("http://aa.com/path")));
+  EXPECT_TRUE(pattern3.MatchesURL(GURL("http://aa.com:88/path")));
+}
diff --git a/chrome/common/extensions/user_script.cc b/chrome/common/extensions/user_script.cc
new file mode 100644
index 0000000..0334e58
--- /dev/null
+++ b/chrome/common/extensions/user_script.cc
@@ -0,0 +1,217 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/user_script.h"
+
+#include "base/pickle.h"
+#include "base/string_util.h"
+
+namespace {
+
+bool UrlMatchesGlobs(const std::vector<std::string>* globs,
+                     const GURL& url) {
+  for (std::vector<std::string>::const_iterator glob = globs->begin();
+       glob != globs->end(); ++glob) {
+    if (MatchPattern(url.spec(), *glob))
+      return true;
+  }
+
+  return false;
+}
+
+}  // namespace
+
+namespace extensions {
+
+// static
+const char UserScript::kFileExtension[] = ".user.js";
+
+bool UserScript::IsURLUserScript(const GURL& url,
+                                 const std::string& mime_type) {
+  return EndsWith(url.ExtractFileName(), kFileExtension, false) &&
+      mime_type != "text/html";
+}
+
+UserScript::File::File(const FilePath& extension_root,
+                       const FilePath& relative_path,
+                       const GURL& url)
+    : extension_root_(extension_root),
+      relative_path_(relative_path),
+      url_(url) {
+}
+
+UserScript::File::File() {}
+
+UserScript::File::~File() {}
+
+UserScript::UserScript()
+    : run_location_(DOCUMENT_IDLE), emulate_greasemonkey_(false),
+      match_all_frames_(false), incognito_enabled_(false) {
+}
+
+UserScript::~UserScript() {
+}
+
+void UserScript::add_url_pattern(const URLPattern& pattern) {
+  url_set_.AddPattern(pattern);
+}
+
+void UserScript::add_exclude_url_pattern(const URLPattern& pattern) {
+  exclude_url_set_.AddPattern(pattern);
+}
+
+bool UserScript::MatchesURL(const GURL& url) const {
+  if (!url_set_.is_empty()) {
+    if (!url_set_.MatchesURL(url))
+      return false;
+  }
+
+  if (!exclude_url_set_.is_empty()) {
+    if (exclude_url_set_.MatchesURL(url))
+      return false;
+  }
+
+  if (!globs_.empty()) {
+    if (!UrlMatchesGlobs(&globs_, url))
+      return false;
+  }
+
+  if (!exclude_globs_.empty()) {
+    if (UrlMatchesGlobs(&exclude_globs_, url))
+      return false;
+  }
+
+  return true;
+}
+
+void UserScript::File::Pickle(::Pickle* pickle) const {
+  pickle->WriteString(url_.spec());
+  // Do not write path. It's not needed in the renderer.
+  // Do not write content. It will be serialized by other means.
+}
+
+void UserScript::File::Unpickle(const ::Pickle& pickle, PickleIterator* iter) {
+  // Read the url from the pickle.
+  std::string url;
+  CHECK(pickle.ReadString(iter, &url));
+  set_url(GURL(url));
+}
+
+void UserScript::Pickle(::Pickle* pickle) const {
+  // Write the simple types to the pickle.
+  pickle->WriteInt(run_location());
+  pickle->WriteString(extension_id());
+  pickle->WriteBool(emulate_greasemonkey());
+  pickle->WriteBool(match_all_frames());
+  pickle->WriteBool(is_incognito_enabled());
+
+  PickleGlobs(pickle, globs_);
+  PickleGlobs(pickle, exclude_globs_);
+  PickleURLPatternSet(pickle, url_set_);
+  PickleURLPatternSet(pickle, exclude_url_set_);
+  PickleScripts(pickle, js_scripts_);
+  PickleScripts(pickle, css_scripts_);
+}
+
+void UserScript::PickleGlobs(::Pickle* pickle,
+                             const std::vector<std::string>& globs) const {
+  pickle->WriteUInt64(globs.size());
+  for (std::vector<std::string>::const_iterator glob = globs.begin();
+       glob != globs.end(); ++glob) {
+    pickle->WriteString(*glob);
+  }
+}
+
+void UserScript::PickleURLPatternSet(::Pickle* pickle,
+                                     const URLPatternSet& pattern_list) const {
+  pickle->WriteUInt64(pattern_list.patterns().size());
+  for (URLPatternSet::const_iterator pattern = pattern_list.begin();
+       pattern != pattern_list.end(); ++pattern) {
+    pickle->WriteInt(pattern->valid_schemes());
+    pickle->WriteString(pattern->GetAsString());
+  }
+}
+
+void UserScript::PickleScripts(::Pickle* pickle,
+                               const FileList& scripts) const {
+  pickle->WriteUInt64(scripts.size());
+  for (FileList::const_iterator file = scripts.begin();
+       file != scripts.end(); ++file) {
+    file->Pickle(pickle);
+  }
+}
+
+void UserScript::Unpickle(const ::Pickle& pickle, PickleIterator* iter) {
+  // Read the run location.
+  int run_location = 0;
+  CHECK(pickle.ReadInt(iter, &run_location));
+  CHECK(run_location >= 0 && run_location < RUN_LOCATION_LAST);
+  run_location_ = static_cast<RunLocation>(run_location);
+
+  CHECK(pickle.ReadString(iter, &extension_id_));
+  CHECK(pickle.ReadBool(iter, &emulate_greasemonkey_));
+  CHECK(pickle.ReadBool(iter, &match_all_frames_));
+  CHECK(pickle.ReadBool(iter, &incognito_enabled_));
+
+  UnpickleGlobs(pickle, iter, &globs_);
+  UnpickleGlobs(pickle, iter, &exclude_globs_);
+  UnpickleURLPatternSet(pickle, iter, &url_set_);
+  UnpickleURLPatternSet(pickle, iter, &exclude_url_set_);
+  UnpickleScripts(pickle, iter, &js_scripts_);
+  UnpickleScripts(pickle, iter, &css_scripts_);
+}
+
+void UserScript::UnpickleGlobs(const ::Pickle& pickle, PickleIterator* iter,
+                               std::vector<std::string>* globs) {
+  uint64 num_globs = 0;
+  CHECK(pickle.ReadUInt64(iter, &num_globs));
+  globs->clear();
+  for (uint64 i = 0; i < num_globs; ++i) {
+    std::string glob;
+    CHECK(pickle.ReadString(iter, &glob));
+    globs->push_back(glob);
+  }
+}
+
+void UserScript::UnpickleURLPatternSet(const ::Pickle& pickle,
+                                       PickleIterator* iter,
+                                       URLPatternSet* pattern_list) {
+  uint64 num_patterns = 0;
+  CHECK(pickle.ReadUInt64(iter, &num_patterns));
+
+  pattern_list->ClearPatterns();
+  for (uint64 i = 0; i < num_patterns; ++i) {
+    int valid_schemes;
+    CHECK(pickle.ReadInt(iter, &valid_schemes));
+    std::string pattern_str;
+    URLPattern pattern(valid_schemes);
+    CHECK(pickle.ReadString(iter, &pattern_str));
+
+    // We remove the file scheme if it's not actually allowed (see Extension::
+    // LoadUserScriptHelper), but we need it temporarily while loading the
+    // pattern so that it's valid.
+    bool had_file_scheme = (valid_schemes & URLPattern::SCHEME_FILE) != 0;
+    if (!had_file_scheme)
+      pattern.SetValidSchemes(valid_schemes | URLPattern::SCHEME_FILE);
+    CHECK(URLPattern::PARSE_SUCCESS == pattern.Parse(pattern_str));
+    if (!had_file_scheme)
+      pattern.SetValidSchemes(valid_schemes);
+
+    pattern_list->AddPattern(pattern);
+  }
+}
+
+void UserScript::UnpickleScripts(const ::Pickle& pickle, PickleIterator* iter,
+                                 FileList* scripts) {
+  uint64 num_files = 0;
+  CHECK(pickle.ReadUInt64(iter, &num_files));
+  scripts->clear();
+  for (uint64 i = 0; i < num_files; ++i) {
+    File file;
+    file.Unpickle(pickle, iter);
+    scripts->push_back(file);
+  }
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/user_script.h b/chrome/common/extensions/user_script.h
new file mode 100644
index 0000000..e7caad6
--- /dev/null
+++ b/chrome/common/extensions/user_script.h
@@ -0,0 +1,263 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_USER_SCRIPT_H_
+#define CHROME_COMMON_EXTENSIONS_USER_SCRIPT_H_
+
+#include <string>
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/string_piece.h"
+#include "googleurl/src/gurl.h"
+#include "chrome/common/extensions/url_pattern.h"
+#include "chrome/common/extensions/url_pattern_set.h"
+
+class Pickle;
+class PickleIterator;
+
+namespace extensions {
+
+// Represents a user script, either a standalone one, or one that is part of an
+// extension.
+class UserScript {
+ public:
+  // The file extension for standalone user scripts.
+  static const char kFileExtension[];
+
+  // The bitmask for valid user script injectable schemes used by URLPattern.
+  enum {
+    kValidUserScriptSchemes = URLPattern::SCHEME_HTTP |
+                              URLPattern::SCHEME_HTTPS |
+                              URLPattern::SCHEME_FILE |
+                              URLPattern::SCHEME_FTP
+  };
+
+  // Check if a URL should be treated as a user script and converted to an
+  // extension.
+  static bool IsURLUserScript(const GURL& url, const std::string& mime_type);
+
+  // Locations that user scripts can be run inside the document.
+  enum RunLocation {
+    UNDEFINED,
+    DOCUMENT_START,  // After the documentElemnet is created, but before
+                     // anything else happens.
+    DOCUMENT_END,  // After the entire document is parsed. Same as
+                   // DOMContentLoaded.
+    DOCUMENT_IDLE,  // Sometime after DOMContentLoaded, as soon as the document
+                    // is "idle". Currently this uses the simple heuristic of:
+                    // min(DOM_CONTENT_LOADED + TIMEOUT, ONLOAD), but no
+                    // particular injection point is guaranteed.
+    RUN_LOCATION_LAST  // Leave this as the last item.
+  };
+
+  // Holds actual script file info.
+  class File {
+   public:
+    File(const FilePath& extension_root, const FilePath& relative_path,
+         const GURL& url);
+    File();
+    ~File();
+
+    const FilePath& extension_root() const { return extension_root_; }
+    const FilePath& relative_path() const { return relative_path_; }
+
+    const GURL& url() const { return url_; }
+    void set_url(const GURL& url) { url_ = url; }
+
+    // If external_content_ is set returns it as content otherwise it returns
+    // content_
+    const base::StringPiece GetContent() const {
+      if (external_content_.data())
+        return external_content_;
+      else
+        return content_;
+    }
+    void set_external_content(const base::StringPiece& content) {
+      external_content_ = content;
+    }
+    void set_content(const base::StringPiece& content) {
+      content_.assign(content.begin(), content.end());
+    }
+
+    // Serialization support. The content and FilePath members will not be
+    // serialized!
+    void Pickle(::Pickle* pickle) const;
+    void Unpickle(const ::Pickle& pickle, PickleIterator* iter);
+
+   private:
+    // Where the script file lives on the disk. We keep the path split so that
+    // it can be localized at will.
+    FilePath extension_root_;
+    FilePath relative_path_;
+
+    // The url to this scipt file.
+    GURL url_;
+
+    // The script content. It can be set to either loaded_content_ or
+    // externally allocated string.
+    base::StringPiece external_content_;
+
+    // Set when the content is loaded by LoadContent
+    std::string content_;
+  };
+
+  typedef std::vector<File> FileList;
+
+  // Constructor. Default the run location to document end, which is like
+  // Greasemonkey and probably more useful for typical scripts.
+  UserScript();
+  ~UserScript();
+
+  const std::string& name_space() const { return name_space_; }
+  void set_name_space(const std::string& name_space) {
+    name_space_ = name_space;
+  }
+
+  const std::string& name() const { return name_; }
+  void set_name(const std::string& name) { name_ = name; }
+
+  const std::string& version() const { return version_; }
+  void set_version(const std::string& version) {
+    version_ = version;
+  }
+
+  const std::string& description() const { return description_; }
+  void set_description(const std::string& description) {
+    description_ = description;
+  }
+
+  // The place in the document to run the script.
+  RunLocation run_location() const { return run_location_; }
+  void set_run_location(RunLocation location) { run_location_ = location; }
+
+  // Whether to emulate greasemonkey when running this script.
+  bool emulate_greasemonkey() const { return emulate_greasemonkey_; }
+  void set_emulate_greasemonkey(bool val) { emulate_greasemonkey_ = val; }
+
+  // Whether to match all frames, or only the top one.
+  bool match_all_frames() const { return match_all_frames_; }
+  void set_match_all_frames(bool val) { match_all_frames_ = val; }
+
+  // The globs, if any, that determine which pages this script runs against.
+  // These are only used with "standalone" Greasemonkey-like user scripts.
+  const std::vector<std::string>& globs() const { return globs_; }
+  void add_glob(const std::string& glob) { globs_.push_back(glob); }
+  void clear_globs() { globs_.clear(); }
+  const std::vector<std::string>& exclude_globs() const {
+    return exclude_globs_;
+  }
+  void add_exclude_glob(const std::string& glob) {
+    exclude_globs_.push_back(glob);
+  }
+  void clear_exclude_globs() { exclude_globs_.clear(); }
+
+  // The URLPatterns, if any, that determine which pages this script runs
+  // against.
+  const URLPatternSet& url_patterns() const { return url_set_; }
+  void add_url_pattern(const URLPattern& pattern);
+  const URLPatternSet& exclude_url_patterns() const {
+    return exclude_url_set_;
+  }
+  void add_exclude_url_pattern(const URLPattern& pattern);
+
+  // List of js scripts for this user script
+  FileList& js_scripts() { return js_scripts_; }
+  const FileList& js_scripts() const { return js_scripts_; }
+
+  // List of css scripts for this user script
+  FileList& css_scripts() { return css_scripts_; }
+  const FileList& css_scripts() const { return css_scripts_; }
+
+  const std::string& extension_id() const { return extension_id_; }
+  void set_extension_id(const std::string& id) { extension_id_ = id; }
+
+  bool is_incognito_enabled() const { return incognito_enabled_; }
+  void set_incognito_enabled(bool enabled) { incognito_enabled_ = enabled; }
+
+  bool is_standalone() const { return extension_id_.empty(); }
+
+  // Returns true if the script should be applied to the specified URL, false
+  // otherwise.
+  bool MatchesURL(const GURL& url) const;
+
+  // Serialize the UserScript into a pickle. The content of the scripts and
+  // paths to UserScript::Files will not be serialized!
+  void Pickle(::Pickle* pickle) const;
+
+  // Deserialize the script from a pickle. Note that this always succeeds
+  // because presumably we were the one that pickled it, and we did it
+  // correctly.
+  void Unpickle(const ::Pickle& pickle, PickleIterator* iter);
+
+ private:
+  // Pickle helper functions used to pickle the individual types of components.
+  void PickleGlobs(::Pickle* pickle,
+                   const std::vector<std::string>& globs) const;
+  void PickleURLPatternSet(::Pickle* pickle,
+                           const URLPatternSet& pattern_list) const;
+  void PickleScripts(::Pickle* pickle, const FileList& scripts) const;
+
+  // Unpickle helper functions used to unpickle individual types of components.
+  void UnpickleGlobs(const ::Pickle& pickle, PickleIterator* iter,
+                     std::vector<std::string>* globs);
+  void UnpickleURLPatternSet(const ::Pickle& pickle, PickleIterator* iter,
+                             URLPatternSet* pattern_list);
+  void UnpickleScripts(const ::Pickle& pickle, PickleIterator* iter,
+                       FileList* scripts);
+
+  // The location to run the script inside the document.
+  RunLocation run_location_;
+
+  // The namespace of the script. This is used by Greasemonkey in the same way
+  // as XML namespaces. Only used when parsing Greasemonkey-style scripts.
+  std::string name_space_;
+
+  // The script's name. Only used when parsing Greasemonkey-style scripts.
+  std::string name_;
+
+  // A longer description. Only used when parsing Greasemonkey-style scripts.
+  std::string description_;
+
+  // A version number of the script. Only used when parsing Greasemonkey-style
+  // scripts.
+  std::string version_;
+
+  // Greasemonkey-style globs that determine pages to inject the script into.
+  // These are only used with standalone scripts.
+  std::vector<std::string> globs_;
+  std::vector<std::string> exclude_globs_;
+
+  // URLPatterns that determine pages to inject the script into. These are
+  // only used with scripts that are part of extensions.
+  URLPatternSet url_set_;
+  URLPatternSet exclude_url_set_;
+
+  // List of js scripts defined in content_scripts
+  FileList js_scripts_;
+
+  // List of css scripts defined in content_scripts
+  FileList css_scripts_;
+
+  // The ID of the extension this script is a part of, if any. Can be empty if
+  // the script is a "standlone" user script.
+  std::string extension_id_;
+
+  // Whether we should try to emulate Greasemonkey's APIs when running this
+  // script.
+  bool emulate_greasemonkey_;
+
+  // Whether the user script should run in all frames, or only just the top one.
+  // Defaults to false.
+  bool match_all_frames_;
+
+  // True if the script should be injected into an incognito tab.
+  bool incognito_enabled_;
+};
+
+typedef std::vector<UserScript> UserScriptList;
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_USER_SCRIPT_H_
diff --git a/chrome/common/extensions/user_script_unittest.cc b/chrome/common/extensions/user_script_unittest.cc
new file mode 100644
index 0000000..323f573
--- /dev/null
+++ b/chrome/common/extensions/user_script_unittest.cc
@@ -0,0 +1,220 @@
+// Copyright (c) 2012 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.
+
+#include "base/file_path.h"
+#include "base/pickle.h"
+#include "chrome/common/extensions/user_script.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+static const int kAllSchemes =
+    URLPattern::SCHEME_HTTP |
+    URLPattern::SCHEME_HTTPS |
+    URLPattern::SCHEME_FILE |
+    URLPattern::SCHEME_FTP |
+    URLPattern::SCHEME_CHROMEUI;
+
+TEST(ExtensionUserScriptTest, Glob_HostString) {
+  UserScript script;
+  script.add_glob("*mail.google.com*");
+  script.add_glob("*mail.yahoo.com*");
+  script.add_glob("*mail.msn.com*");
+  EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com")));
+  EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com/foo")));
+  EXPECT_TRUE(script.MatchesURL(GURL("https://mail.google.com/foo")));
+  EXPECT_TRUE(script.MatchesURL(GURL("ftp://mail.google.com/foo")));
+  EXPECT_TRUE(script.MatchesURL(GURL("http://woo.mail.google.com/foo")));
+  EXPECT_TRUE(script.MatchesURL(GURL("http://mail.yahoo.com/bar")));
+  EXPECT_TRUE(script.MatchesURL(GURL("http://mail.msn.com/baz")));
+  EXPECT_FALSE(script.MatchesURL(GURL("http://www.hotmail.com")));
+
+  script.add_exclude_glob("*foo*");
+  EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com")));
+  EXPECT_FALSE(script.MatchesURL(GURL("http://mail.google.com/foo")));
+}
+
+TEST(ExtensionUserScriptTest, Glob_TrailingSlash) {
+  UserScript script;
+  script.add_glob("*mail.google.com/");
+  // GURL normalizes the URL to have a trailing "/"
+  EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com")));
+  EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com/")));
+  EXPECT_FALSE(script.MatchesURL(GURL("http://mail.google.com/foo")));
+}
+
+TEST(ExtensionUserScriptTest, Glob_TrailingSlashStar) {
+  UserScript script;
+  script.add_glob("http://mail.google.com/*");
+  // GURL normalizes the URL to have a trailing "/"
+  EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com")));
+  EXPECT_TRUE(script.MatchesURL(GURL("http://mail.google.com/foo")));
+  EXPECT_FALSE(script.MatchesURL(GURL("https://mail.google.com/foo")));
+}
+
+TEST(ExtensionUserScriptTest, Glob_Star) {
+  UserScript script;
+  script.add_glob("*");
+  EXPECT_TRUE(script.MatchesURL(GURL("http://foo.com/bar")));
+  EXPECT_TRUE(script.MatchesURL(GURL("http://hot.com/dog")));
+  EXPECT_TRUE(script.MatchesURL(GURL("https://hot.com/dog")));
+  EXPECT_TRUE(script.MatchesURL(GURL("file:///foo/bar")));
+  EXPECT_TRUE(script.MatchesURL(GURL("file://localhost/foo/bar")));
+}
+
+TEST(ExtensionUserScriptTest, Glob_StringAnywhere) {
+  UserScript script;
+  script.add_glob("*foo*");
+  EXPECT_TRUE(script.MatchesURL(GURL("http://foo.com/bar")));
+  EXPECT_TRUE(script.MatchesURL(GURL("http://baz.org/foo/bar")));
+  EXPECT_FALSE(script.MatchesURL(GURL("http://baz.org")));
+}
+
+TEST(ExtensionUserScriptTest, UrlPattern) {
+  URLPattern pattern(kAllSchemes);
+  ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*/foo*"));
+
+  UserScript script;
+  script.add_url_pattern(pattern);
+  EXPECT_TRUE(script.MatchesURL(GURL("http://monkey.com/foobar")));
+  EXPECT_FALSE(script.MatchesURL(GURL("http://monkey.com/hotdog")));
+
+  // NOTE: URLPattern is tested more extensively in url_pattern_unittest.cc.
+}
+
+TEST(ExtensionUserScriptTest, ExcludeUrlPattern) {
+  UserScript script;
+
+  URLPattern pattern(kAllSchemes);
+  ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*.nytimes.com/*"));
+  script.add_url_pattern(pattern);
+
+  URLPattern exclude(kAllSchemes);
+  ASSERT_EQ(URLPattern::PARSE_SUCCESS, exclude.Parse("*://*/*business*"));
+  script.add_exclude_url_pattern(exclude);
+
+  EXPECT_TRUE(script.MatchesURL(GURL("http://www.nytimes.com/health")));
+  EXPECT_FALSE(script.MatchesURL(GURL("http://www.nytimes.com/business")));
+  EXPECT_TRUE(script.MatchesURL(GURL("http://business.nytimes.com")));
+}
+
+TEST(ExtensionUserScriptTest, UrlPatternAndIncludeGlobs) {
+  UserScript script;
+
+  URLPattern pattern(kAllSchemes);
+  ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*.nytimes.com/*"));
+  script.add_url_pattern(pattern);
+
+  script.add_glob("*nytimes.com/???s/*");
+
+  EXPECT_TRUE(script.MatchesURL(GURL("http://www.nytimes.com/arts/1.html")));
+  EXPECT_TRUE(script.MatchesURL(GURL("http://www.nytimes.com/jobs/1.html")));
+  EXPECT_FALSE(script.MatchesURL(GURL("http://www.nytimes.com/sports/1.html")));
+}
+
+TEST(ExtensionUserScriptTest, UrlPatternAndExcludeGlobs) {
+  UserScript script;
+
+  URLPattern pattern(kAllSchemes);
+  ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern.Parse("http://*.nytimes.com/*"));
+  script.add_url_pattern(pattern);
+
+  script.add_exclude_glob("*science*");
+
+  EXPECT_TRUE(script.MatchesURL(GURL("http://www.nytimes.com")));
+  EXPECT_FALSE(script.MatchesURL(GURL("http://science.nytimes.com")));
+  EXPECT_FALSE(script.MatchesURL(GURL("http://www.nytimes.com/science")));
+}
+
+TEST(ExtensionUserScriptTest, UrlPatternGlobInteraction) {
+  // If there are both, match intersection(union(globs), union(urlpatterns)).
+  UserScript script;
+
+  URLPattern pattern(kAllSchemes);
+  ASSERT_EQ(URLPattern::PARSE_SUCCESS,pattern.Parse("http://www.google.com/*"));
+  script.add_url_pattern(pattern);
+
+  script.add_glob("*bar*");
+
+  // No match, because it doesn't match the glob.
+  EXPECT_FALSE(script.MatchesURL(GURL("http://www.google.com/foo")));
+
+  script.add_exclude_glob("*baz*");
+
+  // No match, because it matches the exclude glob.
+  EXPECT_FALSE(script.MatchesURL(GURL("http://www.google.com/baz")));
+
+  // Match, because it matches the glob, doesn't match the exclude glob.
+  EXPECT_TRUE(script.MatchesURL(GURL("http://www.google.com/bar")));
+
+  // Try with just a single exclude glob.
+  script.clear_globs();
+  EXPECT_TRUE(script.MatchesURL(GURL("http://www.google.com/foo")));
+
+  // Try with no globs or exclude globs.
+  script.clear_exclude_globs();
+  EXPECT_TRUE(script.MatchesURL(GURL("http://www.google.com/foo")));
+}
+
+TEST(ExtensionUserScriptTest, Pickle) {
+  URLPattern pattern1(kAllSchemes);
+  URLPattern pattern2(kAllSchemes);
+  URLPattern exclude1(kAllSchemes);
+  URLPattern exclude2(kAllSchemes);
+  ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern1.Parse("http://*/foo*"));
+  ASSERT_EQ(URLPattern::PARSE_SUCCESS, pattern2.Parse("http://bar/baz*"));
+  ASSERT_EQ(URLPattern::PARSE_SUCCESS, exclude1.Parse("*://*/*bar"));
+  ASSERT_EQ(URLPattern::PARSE_SUCCESS, exclude2.Parse("https://*/*"));
+
+  UserScript script1;
+  script1.js_scripts().push_back(UserScript::File(
+      FilePath(FILE_PATH_LITERAL("c:\\foo\\")),
+      FilePath(FILE_PATH_LITERAL("foo.user.js")),
+      GURL("chrome-extension://abc/foo.user.js")));
+  script1.css_scripts().push_back(UserScript::File(
+      FilePath(FILE_PATH_LITERAL("c:\\foo\\")),
+      FilePath(FILE_PATH_LITERAL("foo.user.css")),
+      GURL("chrome-extension://abc/foo.user.css")));
+  script1.css_scripts().push_back(UserScript::File(
+      FilePath(FILE_PATH_LITERAL("c:\\foo\\")),
+      FilePath(FILE_PATH_LITERAL("foo2.user.css")),
+      GURL("chrome-extension://abc/foo2.user.css")));
+  script1.set_run_location(UserScript::DOCUMENT_START);
+
+  script1.add_url_pattern(pattern1);
+  script1.add_url_pattern(pattern2);
+  script1.add_exclude_url_pattern(exclude1);
+  script1.add_exclude_url_pattern(exclude2);
+
+  Pickle pickle;
+  script1.Pickle(&pickle);
+
+  PickleIterator iter(pickle);
+  UserScript script2;
+  script2.Unpickle(pickle, &iter);
+
+  EXPECT_EQ(1U, script2.js_scripts().size());
+  EXPECT_EQ(script1.js_scripts()[0].url(), script2.js_scripts()[0].url());
+
+  EXPECT_EQ(2U, script2.css_scripts().size());
+  for (size_t i = 0; i < script2.js_scripts().size(); ++i) {
+    EXPECT_EQ(script1.css_scripts()[i].url(), script2.css_scripts()[i].url());
+  }
+
+  ASSERT_EQ(script1.globs().size(), script2.globs().size());
+  for (size_t i = 0; i < script1.globs().size(); ++i) {
+    EXPECT_EQ(script1.globs()[i], script2.globs()[i]);
+  }
+
+  ASSERT_EQ(script1.url_patterns(), script2.url_patterns());
+  ASSERT_EQ(script1.exclude_url_patterns(), script2.exclude_url_patterns());
+}
+
+TEST(ExtensionUserScriptTest, Defaults) {
+  UserScript script;
+  ASSERT_EQ(UserScript::DOCUMENT_IDLE, script.run_location());
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/value_builder.cc b/chrome/common/extensions/value_builder.cc
new file mode 100644
index 0000000..bf57d22
--- /dev/null
+++ b/chrome/common/extensions/value_builder.cc
@@ -0,0 +1,104 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/value_builder.h"
+
+using base::DictionaryValue;
+using base::ListValue;
+
+namespace extensions {
+
+// DictionaryBuilder
+
+DictionaryBuilder::DictionaryBuilder() : dict_(new DictionaryValue) {}
+
+DictionaryBuilder::DictionaryBuilder(const DictionaryValue& init)
+    : dict_(init.DeepCopy()) {}
+
+DictionaryBuilder::~DictionaryBuilder() {}
+
+DictionaryBuilder& DictionaryBuilder::Set(const std::string& path,
+                                          int in_value) {
+  dict_->SetWithoutPathExpansion(path, Value::CreateIntegerValue(in_value));
+  return *this;
+}
+
+DictionaryBuilder& DictionaryBuilder::Set(const std::string& path,
+                                          double in_value) {
+  dict_->SetWithoutPathExpansion(path, Value::CreateDoubleValue(in_value));
+  return *this;
+}
+
+DictionaryBuilder& DictionaryBuilder::Set(const std::string& path,
+                                          const std::string& in_value) {
+  dict_->SetWithoutPathExpansion(path, Value::CreateStringValue(in_value));
+  return *this;
+}
+
+DictionaryBuilder& DictionaryBuilder::Set(const std::string& path,
+                                          const string16& in_value) {
+  dict_->SetWithoutPathExpansion(path, Value::CreateStringValue(in_value));
+  return *this;
+}
+
+DictionaryBuilder& DictionaryBuilder::Set(const std::string& path,
+                                          DictionaryBuilder& in_value) {
+  dict_->SetWithoutPathExpansion(path, in_value.Build().release());
+  return *this;
+}
+
+DictionaryBuilder& DictionaryBuilder::Set(const std::string& path,
+                                          ListBuilder& in_value) {
+  dict_->SetWithoutPathExpansion(path, in_value.Build().release());
+  return *this;
+}
+
+DictionaryBuilder& DictionaryBuilder::SetBoolean(
+    const std::string& path, bool in_value) {
+  dict_->SetWithoutPathExpansion(path, Value::CreateBooleanValue(in_value));
+  return *this;
+}
+
+// ListBuilder
+
+ListBuilder::ListBuilder() : list_(new ListValue) {}
+ListBuilder::ListBuilder(const ListValue& init) : list_(init.DeepCopy()) {}
+ListBuilder::~ListBuilder() {}
+
+ListBuilder& ListBuilder::Append(int in_value) {
+  list_->Append(Value::CreateIntegerValue(in_value));
+  return *this;
+}
+
+ListBuilder& ListBuilder::Append(double in_value) {
+  list_->Append(Value::CreateDoubleValue(in_value));
+  return *this;
+}
+
+ListBuilder& ListBuilder::Append(const std::string& in_value) {
+  list_->Append(Value::CreateStringValue(in_value));
+  return *this;
+}
+
+ListBuilder& ListBuilder::Append(const string16& in_value) {
+  list_->Append(Value::CreateStringValue(in_value));
+  return *this;
+}
+
+ListBuilder& ListBuilder::Append(DictionaryBuilder& in_value) {
+  list_->Append(in_value.Build().release());
+  return *this;
+}
+
+ListBuilder& ListBuilder::Append(ListBuilder& in_value) {
+  list_->Append(in_value.Build().release());
+  return *this;
+}
+
+ListBuilder& ListBuilder::AppendBoolean(bool in_value) {
+  list_->Append(Value::CreateBooleanValue(in_value));
+  return *this;
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/value_builder.h b/chrome/common/extensions/value_builder.h
new file mode 100644
index 0000000..2ca7693
--- /dev/null
+++ b/chrome/common/extensions/value_builder.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 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.
+
+// This file provides a builders for DictionaryValue and ListValue.  These
+// aren't specific to extensions and could move up to base/ if there's interest
+// from other sub-projects.
+//
+// The general pattern is to write:
+//
+//  scoped_ptr<BuiltType> result(FooBuilder()
+//                               .Set(args)
+//                               .Set(args)
+//                               .Build());
+//
+// For methods that take other built types, you can pass the builder directly
+// to the setter without calling Build():
+//
+// DictionaryBuilder().Set("key", ListBuilder()
+//                         .Append("foo").Append("bar") /* No .Build() */);
+//
+// Because of limitations in C++03, and to avoid extra copies, you can't pass a
+// just-constructed Builder into another Builder's method without setting at
+// least 1 field.
+//
+// The Build() method invalidates its builder, and returns ownership of the
+// built value.
+//
+// These objects are intended to be used as temporaries rather than stored
+// anywhere, so the use of non-const reference parameters is likely to cause
+// less confusion than usual.
+
+#ifndef CHROME_COMMON_EXTENSIONS_VALUE_BUILDER_H_
+#define CHROME_COMMON_EXTENSIONS_VALUE_BUILDER_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/string16.h"
+#include "base/values.h"
+
+namespace extensions {
+
+class ListBuilder;
+
+class DictionaryBuilder {
+ public:
+  DictionaryBuilder();
+  explicit DictionaryBuilder(const base::DictionaryValue& init);
+  ~DictionaryBuilder();
+
+  // Can only be called once, after which it's invalid to use the builder.
+  scoped_ptr<base::DictionaryValue> Build() { return dict_.Pass(); }
+
+  DictionaryBuilder& Set(const std::string& path, int in_value);
+  DictionaryBuilder& Set(const std::string& path, double in_value);
+  DictionaryBuilder& Set(const std::string& path, const std::string& in_value);
+  DictionaryBuilder& Set(const std::string& path, const string16& in_value);
+  DictionaryBuilder& Set(const std::string& path, DictionaryBuilder& in_value);
+  DictionaryBuilder& Set(const std::string& path, ListBuilder& in_value);
+
+  // Named differently because overload resolution is too eager to
+  // convert implicitly to bool.
+  DictionaryBuilder& SetBoolean(const std::string& path, bool in_value);
+
+ private:
+  scoped_ptr<base::DictionaryValue> dict_;
+};
+
+class ListBuilder {
+ public:
+  ListBuilder();
+  explicit ListBuilder(const base::ListValue& init);
+  ~ListBuilder();
+
+  // Can only be called once, after which it's invalid to use the builder.
+  scoped_ptr<base::ListValue> Build() { return list_.Pass(); }
+
+  ListBuilder& Append(int in_value);
+  ListBuilder& Append(double in_value);
+  ListBuilder& Append(const std::string& in_value);
+  ListBuilder& Append(const string16& in_value);
+  ListBuilder& Append(DictionaryBuilder& in_value);
+  ListBuilder& Append(ListBuilder& in_value);
+
+  // Named differently because overload resolution is too eager to
+  // convert implicitly to bool.
+  ListBuilder& AppendBoolean(bool in_value);
+
+ private:
+  scoped_ptr<base::ListValue> list_;
+};
+
+} // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_VALUE_BUILDER_H_
diff --git a/chrome/common/extensions/value_counter.cc b/chrome/common/extensions/value_counter.cc
new file mode 100644
index 0000000..156e3ad
--- /dev/null
+++ b/chrome/common/extensions/value_counter.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/extensions/value_counter.h"
+
+#include "base/values.h"
+
+#include <algorithm>
+
+namespace extensions {
+
+ValueCounter::ValueCounter() {
+}
+
+ValueCounter::~ValueCounter() {
+}
+
+ValueCounter::Entry::Entry(const base::Value& value)
+    : value_(value.DeepCopy()),
+      count_(1) {
+}
+
+ValueCounter::Entry::~Entry() {
+}
+
+int ValueCounter::Entry::Increment() {
+  return ++count_;
+}
+
+int ValueCounter::Entry::Decrement() {
+  return --count_;
+}
+
+int ValueCounter::Add(const base::Value& value) {
+  return AddImpl(value, true);
+}
+
+int ValueCounter::Remove(const base::Value& value) {
+  for (EntryList::iterator it = entries_.begin(); it != entries_.end(); it++) {
+    (*it)->value()->GetType();
+    if ((*it)->value()->Equals(&value)) {
+      int remaining = (*it)->Decrement();
+      if (remaining == 0) {
+        std::swap(*it, entries_.back());
+        entries_.pop_back();
+      }
+      return remaining;
+    }
+  }
+  return 0;
+}
+
+int ValueCounter::AddIfMissing(const base::Value& value) {
+  return AddImpl(value, false);
+}
+
+int ValueCounter::AddImpl(const base::Value& value, bool increment) {
+  for (EntryList::iterator it = entries_.begin(); it != entries_.end(); it++) {
+    if ((*it)->value()->Equals(&value))
+      return increment ? (*it)->Increment() : (*it)->count();
+  }
+  entries_.push_back(linked_ptr<Entry>(new Entry(value)));
+  return 1;
+}
+
+}  // namespace extensions
diff --git a/chrome/common/extensions/value_counter.h b/chrome/common/extensions/value_counter.h
new file mode 100644
index 0000000..84fe06c
--- /dev/null
+++ b/chrome/common/extensions/value_counter.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_VALUE_COUNTER_H_
+#define CHROME_COMMON_EXTENSIONS_VALUE_COUNTER_H_
+
+#include "base/memory/linked_ptr.h"
+
+#include <vector>
+
+namespace base {
+class Value;
+}
+
+namespace extensions {
+
+// Keeps a running count of Values, like map<Value, int>. Adding / removing
+// values increments / decrements the count associated with a given Value.
+//
+// Add() and Remove() are linear in the number of Values in the ValueCounter,
+// because there is no operator<() defined on Value, so we must iterate to find
+// whether a Value is equal to an existing one.
+class ValueCounter {
+ public:
+  ValueCounter();
+  ~ValueCounter();
+
+  // Adds |value| to the set and returns how many equal values are in the set
+  // after. Does not take ownership of |value|. In the case where a Value equal
+  // to |value| doesn't already exist in this map, this function makes a
+  // DeepCopy() of |value|.
+  int Add(const base::Value& value);
+
+  // Removes |value| from the set and returns how many equal values are in
+  // the set after.
+  int Remove(const base::Value& value);
+
+  // Same as Add() but only performs the add if the value isn't present.
+  int AddIfMissing(const base::Value& value);
+
+ private:
+  class Entry {
+   public:
+    explicit Entry(const base::Value& value);
+    ~Entry();
+
+    int Increment();
+    int Decrement();
+
+    const base::Value* value() const { return value_.get(); }
+    int count() const { return count_; }
+
+   private:
+    linked_ptr<base::Value> value_;
+    int count_;
+
+    DISALLOW_COPY_AND_ASSIGN(Entry);
+  };
+  typedef std::vector<linked_ptr<Entry> > EntryList;
+
+  int AddImpl(const base::Value& value, bool increment);
+
+  EntryList entries_;
+
+  DISALLOW_COPY_AND_ASSIGN(ValueCounter);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_COMMON_EXTENSIONS_VALUE_COUNTER_H_
diff --git a/chrome/common/extensions/value_counter_unittest.cc b/chrome/common/extensions/value_counter_unittest.cc
new file mode 100644
index 0000000..6ad7454
--- /dev/null
+++ b/chrome/common/extensions/value_counter_unittest.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 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.
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "chrome/common/extensions/value_counter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class ValueCounterUnittest : public testing::Test {
+};
+
+TEST_F(ValueCounterUnittest, TestAddingSameValue) {
+  extensions::ValueCounter vc;
+  base::ListValue value;
+  ASSERT_EQ(1, vc.Add(value));
+  ASSERT_EQ(2, vc.Add(value));
+}
+
+TEST_F(ValueCounterUnittest, TestAddingDifferentValue) {
+  extensions::ValueCounter vc;
+  base::ListValue value1;
+  base::DictionaryValue value2;
+  ASSERT_EQ(1, vc.Add(value1));
+  ASSERT_EQ(1, vc.Add(value2));
+}
+
+TEST_F(ValueCounterUnittest, TestRemovingValue) {
+  extensions::ValueCounter vc;
+  base::ListValue value;
+  ASSERT_EQ(1, vc.Add(value));
+  ASSERT_EQ(2, vc.Add(value));
+  ASSERT_EQ(1, vc.Remove(value));
+  ASSERT_EQ(0, vc.Remove(value));
+}
+
+TEST_F(ValueCounterUnittest, TestAddIfMissing) {
+  extensions::ValueCounter vc;
+  base::ListValue value;
+  ASSERT_EQ(1, vc.AddIfMissing(value));
+  ASSERT_EQ(1, vc.AddIfMissing(value));
+}
diff --git a/chrome/common/extensions_api_resources.grd b/chrome/common/extensions_api_resources.grd
new file mode 100644
index 0000000..273139a
--- /dev/null
+++ b/chrome/common/extensions_api_resources.grd
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1">
+  <outputs>
+    <output filename="grit/extensions_api_resources.h" type="rc_header">
+      <emit emit_type='prepend'></emit>
+    </output>
+    <output filename="extensions_api_resources.pak" type="data_package" />
+    <output filename="extensions_api_resources.rc" type="rc_all" />
+  </outputs>
+  <release seq="1">
+    <includes>
+      <include name="IDR_EXTENSION_API_JSON_APP" file="extensions\api\app.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_BOOKMARKS" file="extensions\api\bookmarks.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_BOOKMARKMANAGERPRIVATE" file="extensions\api\bookmark_manager_private.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_BROWSERACTION" file="extensions\api\browser_action.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_BROWSINGDATA" file="extensions\api\browsing_data.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_CHROMEOSINFOPRIVATE" file="extensions\api\chromeos_info_private.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_CLOUDPRINTPRIVATE" file="extensions\api\cloud_print_private.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_COMMANDS" file="extensions\api\commands.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_CONTENTSETTINGS" file="extensions\api\content_settings.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_CONTEXTMENUS" file="extensions\api\context_menus.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_COOKIES" file="extensions\api\cookies.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_DEBUGGER" file="extensions\api\debugger.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_DECLARATIVE_WEBREQUEST" file="extensions\api\declarative_web_request.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_DEVTOOLS" file="extensions\api\devtools.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_ECHOPRIVATE" file="extensions\api\echo_private.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_EVENTS" file="extensions\api\events.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_ACCESSIBILITY" file="extensions\api\experimental_accessibility.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_APP" file="extensions\api\experimental_app.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_HISTORY" file="extensions\api\experimental_history.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_INFOBARS" file="extensions\api\experimental_infobars.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_INPUT_VIRTUALKEYBOARD" file="extensions\api\experimental_input_virtual_keyboard.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_OFFSCREENTABS" file="extensions\api\experimental_offscreen_tabs.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_POWER" file="extensions\api\experimental_power.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_PROCESSES" file="extensions\api\experimental_processes.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_RECORD" file="extensions\api\experimental_record.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_RLZ" file="extensions\api\experimental_rlz.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_SPEECHINPUT" file="extensions\api\experimental_speech_input.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_EXTENSION" file="extensions\api\extension.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_FILEBROWSERHANDLER" file="extensions\api\file_browser_handler.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_FILEBROWSERHANDLERINTERNAL" file="extensions\api\file_browser_handler_internal.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_FILEBROWSERPRIVATE" file="extensions\api\file_browser_private.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_FONTSSETTINGS" file="extensions\api\font_settings.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_HISTORY" file="extensions\api\history.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_I18N" file="extensions\api\i18n.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_INPUT_IME" file="extensions\api\input_ime.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_IDLE" file="extensions\api\idle.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_INPUTMETHODPRIVATE" file="extensions\api\input_method_private.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_MANAGEDMODEPRIVATE" file="extensions\api\managed_mode_private.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_MANAGEMENT" file="extensions\api\management.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_MEDIAPLAYERPRIVATE" file="extensions\api\media_player_private.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_METRICSPRIVATE" file="extensions\api\metrics_private.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_OMNIBOX" file="extensions\api\omnibox.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_PAGEACTION" file="extensions\api\page_action.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_PAGEACTIONS" file="extensions\api\page_actions.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_PAGECAPTURE" file="extensions\api\page_capture.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_PERMISSIONS" file="extensions\api\permissions.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_PRIVACY" file="extensions\api\privacy.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_PROXY" file="extensions\api\proxy.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_RUNTIME" file="extensions\api\runtime.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_SCRIPTBADGE" file="extensions\api\script_badge.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_STORAGE" file="extensions\api\storage.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_SYSTEMPRIVATE" file="extensions\api\system_private.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_TABS" file="extensions\api\tabs.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_TERMINALPRIVATE" file="extensions\api\terminal_private.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_TEST" file="extensions\api\test.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_TOPSITES" file="extensions\api\top_sites.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_TTS" file="extensions\api\tts.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_TTSENGINE" file="extensions\api\tts_engine.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_TYPES" file="extensions\api\types.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_WALLPAPERPRIVATE" file="extensions\api\wallpaper_private.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_WEBNAVIGATION" file="extensions\api\web_navigation.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_WEBREQUEST" file="extensions\api\web_request.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_WEBSOCKETPROXYPRIVATE" file="extensions\api\web_socket_proxy_private.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_WEBREQUESTINTERNAL" file="extensions\api\web_request_internal.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_WEBSTORE" file="extensions\api\webstore.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_WEBSTOREPRIVATE" file="extensions\api\webstore_private.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_WINDOWS" file="extensions\api\windows.json" type="BINDATA" />
+    </includes>
+  </release>
+</grit>
diff --git a/chrome/common/external_ipc_fuzzer.cc b/chrome/common/external_ipc_fuzzer.cc
new file mode 100644
index 0000000..9e125cc
--- /dev/null
+++ b/chrome/common/external_ipc_fuzzer.cc
@@ -0,0 +1,40 @@
+// 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.
+
+#include "chrome/common/external_ipc_fuzzer.h"
+
+#if defined(OS_LINUX)
+#include <dlfcn.h>
+#endif
+
+typedef IPC::ChannelProxy::OutgoingMessageFilter *(*GetFuzzerFunction)();
+const char kFuzzLibraryName[] = "libipcfuzz.so";
+const char kFuzzEntryName[] = "GetFilter";
+
+IPC::ChannelProxy::OutgoingMessageFilter* LoadExternalIPCFuzzer() {
+  IPC::ChannelProxy::OutgoingMessageFilter* result = NULL;
+
+#if defined(OS_LINUX)
+
+  // Fuzz is currently linux-only feature
+  void *fuzz_library =  dlopen(kFuzzLibraryName, RTLD_NOW);
+  if (fuzz_library) {
+    GetFuzzerFunction fuzz_entry_point =
+        reinterpret_cast<GetFuzzerFunction>(
+            dlsym(fuzz_library, kFuzzEntryName));
+
+    if (fuzz_entry_point)
+      result = fuzz_entry_point();
+  }
+
+  if (!result)
+    LOG(WARNING) << dlerror() << "\n";
+
+#endif // OS_LINUX
+
+  return result;
+}
+
+
+
diff --git a/chrome/common/external_ipc_fuzzer.h b/chrome/common/external_ipc_fuzzer.h
new file mode 100644
index 0000000..7f8fc63
--- /dev/null
+++ b/chrome/common/external_ipc_fuzzer.h
@@ -0,0 +1,13 @@
+// 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.
+
+#ifndef CHROME_COMMON_EXTERNAL_IPC_FUZZER_H_
+#define CHROME_COMMON_EXTERNAL_IPC_FUZZER_H_
+
+#include "ipc/ipc_channel_proxy.h"
+
+IPC::ChannelProxy::OutgoingMessageFilter* LoadExternalIPCFuzzer();
+
+#endif  // CHROME_COMMON_EXTERNAL_IPC_FUZZER_H_
+
diff --git a/chrome/common/extra_defines.vsprops b/chrome/common/extra_defines.vsprops
new file mode 100644
index 0000000..963972f
--- /dev/null
+++ b/chrome/common/extra_defines.vsprops
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="Windows-1252"?>

+<VisualStudioPropertySheet

+	ProjectType="Visual C++"

+	Version="8.00"

+	Name="extra"

+	>

+</VisualStudioPropertySheet>

diff --git a/chrome/common/favicon_url.cc b/chrome/common/favicon_url.cc
new file mode 100644
index 0000000..8b05d0e
--- /dev/null
+++ b/chrome/common/favicon_url.cc
@@ -0,0 +1,17 @@
+// 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.
+
+#include "chrome/common/favicon_url.h"
+
+FaviconURL::FaviconURL()
+  : icon_type(INVALID_ICON) {
+}
+
+FaviconURL::FaviconURL(const GURL& url, IconType type)
+    : icon_url(url),
+      icon_type(type) {
+}
+
+FaviconURL::~FaviconURL() {
+}
diff --git a/chrome/common/favicon_url.h b/chrome/common/favicon_url.h
new file mode 100644
index 0000000..4da256b
--- /dev/null
+++ b/chrome/common/favicon_url.h
@@ -0,0 +1,31 @@
+// 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.
+
+#ifndef CHROME_COMMON_FAVICON_URL_
+#define CHROME_COMMON_FAVICON_URL_
+
+#include "googleurl/src/gurl.h"
+
+// The favicon url from the render.
+struct FaviconURL {
+  // The icon type in a page. The definition must be same as history::IconType.
+  enum IconType {
+    INVALID_ICON = 0x0,
+    FAVICON = 1 << 0,
+    TOUCH_ICON = 1 << 1,
+    TOUCH_PRECOMPOSED_ICON = 1 << 2
+  };
+
+  FaviconURL();
+  FaviconURL(const GURL& url, IconType type);
+  ~FaviconURL();
+
+  // The url of the icon.
+  GURL icon_url;
+
+  // The type of the icon
+  IconType icon_type;
+};
+
+#endif  // CHROME_COMMON_FAVICON_URL_
diff --git a/chrome/common/form_data.cc b/chrome/common/form_data.cc
new file mode 100644
index 0000000..d12c77c
--- /dev/null
+++ b/chrome/common/form_data.cc
@@ -0,0 +1,32 @@
+// 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.
+
+#include "chrome/common/form_data.h"
+
+#include "base/string_util.h"
+
+FormData::FormData()
+    : user_submitted(false) {
+}
+
+FormData::FormData(const FormData& data)
+    : name(data.name),
+      method(data.method),
+      origin(data.origin),
+      action(data.action),
+      user_submitted(data.user_submitted),
+      fields(data.fields) {
+}
+
+FormData::~FormData() {
+}
+
+bool FormData::operator==(const FormData& form) const {
+  return (name == form.name &&
+          StringToLowerASCII(method) == StringToLowerASCII(form.method) &&
+          origin == form.origin &&
+          action == form.action &&
+          user_submitted == form.user_submitted &&
+          fields == form.fields);
+}
diff --git a/chrome/common/form_data.h b/chrome/common/form_data.h
new file mode 100644
index 0000000..fd987a8
--- /dev/null
+++ b/chrome/common/form_data.h
@@ -0,0 +1,37 @@
+// 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.
+
+#ifndef CHROME_COMMON_FORM_DATA_H__
+#define CHROME_COMMON_FORM_DATA_H__
+
+#include <vector>
+
+#include "base/string16.h"
+#include "chrome/common/form_field_data.h"
+#include "googleurl/src/gurl.h"
+
+// Holds information about a form to be filled and/or submitted.
+struct FormData {
+  // The name of the form.
+  string16 name;
+  // GET or POST.
+  string16 method;
+  // The URL (minus query parameters) containing the form.
+  GURL origin;
+  // The action target of the form.
+  GURL action;
+  // true if this form was submitted by a user gesture and not javascript.
+  bool user_submitted;
+  // A vector of all the input fields in the form.
+  std::vector<FormFieldData> fields;
+
+  FormData();
+  FormData(const FormData& data);
+  ~FormData();
+
+  // Used by FormStructureTest.
+  bool operator==(const FormData& form) const;
+};
+
+#endif  // CHROME_COMMON_FORM_DATA_H__
diff --git a/chrome/common/form_data_predictions.cc b/chrome/common/form_data_predictions.cc
new file mode 100644
index 0000000..6a03c5c
--- /dev/null
+++ b/chrome/common/form_data_predictions.cc
@@ -0,0 +1,18 @@
+// 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.
+
+#include "chrome/common/form_data_predictions.h"
+
+FormDataPredictions::FormDataPredictions() {
+}
+
+FormDataPredictions::FormDataPredictions(const FormDataPredictions& other)
+    : data(other.data),
+      signature(other.signature),
+      experiment_id(other.experiment_id),
+      fields(other.fields) {
+}
+
+FormDataPredictions::~FormDataPredictions() {
+}
diff --git a/chrome/common/form_data_predictions.h b/chrome/common/form_data_predictions.h
new file mode 100644
index 0000000..a4eb28f
--- /dev/null
+++ b/chrome/common/form_data_predictions.h
@@ -0,0 +1,30 @@
+// 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.
+
+#ifndef CHROME_COMMON_FORM_DATA_PREDICTIONS_H__
+#define CHROME_COMMON_FORM_DATA_PREDICTIONS_H__
+
+#include <string>
+#include <vector>
+
+#include "chrome/common/form_data.h"
+#include "chrome/common/form_field_data_predictions.h"
+
+// Holds information about a form to be filled and/or submitted.
+struct FormDataPredictions {
+  // Data for this form.
+  FormData data;
+  // The form signature for communication with the crowdsourcing server.
+  std::string signature;
+  // The experiment id for the server predictions.
+  std::string experiment_id;
+  // The form fields and their predicted field types.
+  std::vector<FormFieldDataPredictions> fields;
+
+  FormDataPredictions();
+  FormDataPredictions(const FormDataPredictions& other);
+  ~FormDataPredictions();
+};
+
+#endif  // CHROME_COMMON_FORM_DATA_PREDICTIONS_H__
diff --git a/chrome/common/form_field_data.cc b/chrome/common/form_field_data.cc
new file mode 100644
index 0000000..8ff3ce1
--- /dev/null
+++ b/chrome/common/form_field_data.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/form_field_data.h"
+
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+
+FormFieldData::FormFieldData()
+    : max_length(0),
+      is_autofilled(false),
+      is_focusable(false),
+      should_autocomplete(false) {
+}
+
+FormFieldData::~FormFieldData() {
+}
+
+bool FormFieldData::operator==(const FormFieldData& field) const {
+  // A FormFieldData stores a value, but the value is not part of the identity
+  // of the field, so we don't want to compare the values.
+  return (label == field.label &&
+          name == field.name &&
+          form_control_type == field.form_control_type &&
+          autocomplete_attribute == field.autocomplete_attribute &&
+          max_length == field.max_length);
+}
+
+bool FormFieldData::operator!=(const FormFieldData& field) const {
+  return !operator==(field);
+}
+
+bool FormFieldData::operator<(const FormFieldData& field) const {
+  if (label == field.label)
+    return name < field.name;
+
+  return label < field.label;
+}
+
+std::ostream& operator<<(std::ostream& os, const FormFieldData& field) {
+  return os
+      << UTF16ToUTF8(field.label)
+      << " "
+      << UTF16ToUTF8(field.name)
+      << " "
+      << UTF16ToUTF8(field.value)
+      << " "
+      << field.form_control_type
+      << " "
+      << field.autocomplete_attribute
+      << " "
+      << field.max_length
+      << " "
+      << (field.is_autofilled ? "true" : "false")
+      << " "
+      << (field.is_focusable ? "true" : "false")
+      << " "
+      << (field.should_autocomplete ? "true" : "false");
+}
diff --git a/chrome/common/form_field_data.h b/chrome/common/form_field_data.h
new file mode 100644
index 0000000..de9375f
--- /dev/null
+++ b/chrome/common/form_field_data.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_FORM_FIELD_DATA_H_
+#define CHROME_COMMON_FORM_FIELD_DATA_H_
+
+#include <vector>
+
+#include "base/string16.h"
+
+// Stores information about a field in a form.
+struct FormFieldData {
+  FormFieldData();
+  virtual ~FormFieldData();
+
+  // Equality tests for identity which does not include |value| or
+  // |is_autofilled|.
+  // TODO(dhollowa): These operators need to be revised when we implement field
+  // ids.
+  bool operator==(const FormFieldData& field) const;
+  bool operator!=(const FormFieldData& field) const;
+  // Comparsion operator exposed for STL map. Uses label, then name to sort.
+  bool operator<(const FormFieldData& field) const;
+
+  string16 label;
+  string16 name;
+  string16 value;
+  std::string form_control_type;
+  std::string autocomplete_attribute;
+  size_t max_length;
+  bool is_autofilled;
+  bool is_focusable;
+  bool should_autocomplete;
+
+  // For the HTML snippet |<option value="US">United States</option>|, the
+  // value is "US" and the contents are "United States".
+  std::vector<string16> option_values;
+  std::vector<string16> option_contents;
+};
+
+// So we can compare FormFieldDatas with EXPECT_EQ().
+std::ostream& operator<<(std::ostream& os, const FormFieldData& field);
+
+// Prefer to use this macro in place of |EXPECT_EQ()| for comparing
+// |FormFieldData|s in test code.
+#define EXPECT_FORM_FIELD_DATA_EQUALS(expected, actual) \
+  do { \
+    EXPECT_EQ(expected.label, actual.label); \
+    EXPECT_EQ(expected.name, actual.name); \
+    EXPECT_EQ(expected.value, actual.value); \
+    EXPECT_EQ(expected.form_control_type, actual.form_control_type); \
+    EXPECT_EQ(expected.autocomplete_attribute, actual.autocomplete_attribute); \
+    EXPECT_EQ(expected.max_length, actual.max_length); \
+    EXPECT_EQ(expected.is_autofilled, actual.is_autofilled); \
+  } while (0)
+
+#endif  // CHROME_COMMON_FORM_FIELD_DATA_H_
diff --git a/chrome/common/form_field_data_predictions.cc b/chrome/common/form_field_data_predictions.cc
new file mode 100644
index 0000000..9530a3b
--- /dev/null
+++ b/chrome/common/form_field_data_predictions.cc
@@ -0,0 +1,20 @@
+// 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.
+
+#include "chrome/common/form_field_data_predictions.h"
+
+FormFieldDataPredictions::FormFieldDataPredictions() {
+}
+
+FormFieldDataPredictions::FormFieldDataPredictions(
+    const FormFieldDataPredictions& other)
+    : field(other.field),
+      signature(other.signature),
+      heuristic_type(other.heuristic_type),
+      server_type(other.server_type),
+      overall_type(other.overall_type) {
+}
+
+FormFieldDataPredictions::~FormFieldDataPredictions() {
+}
diff --git a/chrome/common/form_field_data_predictions.h b/chrome/common/form_field_data_predictions.h
new file mode 100644
index 0000000..8dc5e34
--- /dev/null
+++ b/chrome/common/form_field_data_predictions.h
@@ -0,0 +1,26 @@
+// 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.
+
+#ifndef CHROME_COMMON_FORM_FIELD_DATA_PREDICTIONS_H_
+#define CHROME_COMMON_FORM_FIELD_DATA_PREDICTIONS_H_
+
+#include <string>
+#include <vector>
+
+#include "chrome/common/form_field_data.h"
+
+// Stores information about a field in a form.
+struct FormFieldDataPredictions {
+  FormFieldDataPredictions();
+  FormFieldDataPredictions(const FormFieldDataPredictions& other);
+  ~FormFieldDataPredictions();
+
+  FormFieldData field;
+  std::string signature;
+  std::string heuristic_type;
+  std::string server_type;
+  std::string overall_type;
+};
+
+#endif  // CHROME_COMMON_FORM_FIELD_DATA_PREDICTIONS_H_
diff --git a/chrome/common/icon_messages.h b/chrome/common/icon_messages.h
new file mode 100644
index 0000000..26e8f14
--- /dev/null
+++ b/chrome/common/icon_messages.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2012 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.
+
+// Multiply-included message file, no traditional include guard.
+#include <vector>
+
+#include "chrome/common/favicon_url.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_param_traits.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+#define IPC_MESSAGE_START IconMsgStart
+
+IPC_ENUM_TRAITS(FaviconURL::IconType)
+
+IPC_STRUCT_TRAITS_BEGIN(FaviconURL)
+  IPC_STRUCT_TRAITS_MEMBER(icon_url)
+  IPC_STRUCT_TRAITS_MEMBER(icon_type)
+IPC_STRUCT_TRAITS_END()
+
+// Messages sent from the browser to the renderer.
+
+// Requests the renderer to download the specified favicon image, decode it,
+// and send the image data back via IconHostMsg_DidDownloadFavicon.
+IPC_MESSAGE_ROUTED3(IconMsg_DownloadFavicon,
+                    int /* identifier for the request */,
+                    GURL /* URL of the image */,
+                    int /* Preferred favicon size. Passed on to
+                           IconHostMsg_DidDownloadFavicon, unused otherwise */)
+
+// Messages sent from the renderer to the browser.
+
+// Notification that the urls for the favicon of a site has been determined.
+IPC_MESSAGE_ROUTED2(IconHostMsg_UpdateFaviconURL,
+                    int32 /* page_id */,
+                    std::vector<FaviconURL> /* urls of the favicon */)
+
+IPC_MESSAGE_ROUTED5(IconHostMsg_DidDownloadFavicon,
+                    int /* Identifier of the request */,
+                    GURL /* URL of the image */,
+                    bool /* true if there was a network error */,
+                    int /* Preferred icon size passed to
+                           IconMsg_DownloadFavicon */,
+                    std::vector<SkBitmap> /* image_data */)
diff --git a/chrome/common/instant_types.cc b/chrome/common/instant_types.cc
new file mode 100644
index 0000000..cf50e1c
--- /dev/null
+++ b/chrome/common/instant_types.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/instant_types.h"
+
+InstantSuggestion::InstantSuggestion()
+    : behavior(INSTANT_COMPLETE_NOW),
+      type(INSTANT_SUGGESTION_SEARCH) {
+}
+
+InstantSuggestion::InstantSuggestion(const string16& in_text,
+                                     InstantCompleteBehavior in_behavior,
+                                     InstantSuggestionType in_type)
+    : text(in_text),
+      behavior(in_behavior),
+      type(in_type) {
+}
+
+InstantSuggestion::~InstantSuggestion() {
+}
+
+InstantAutocompleteResult::InstantAutocompleteResult()
+    : is_search(false),
+      relevance(0) {
+}
+
+InstantAutocompleteResult::~InstantAutocompleteResult() {
+}
diff --git a/chrome/common/instant_types.h b/chrome/common/instant_types.h
new file mode 100644
index 0000000..ed075b0
--- /dev/null
+++ b/chrome/common/instant_types.h
@@ -0,0 +1,98 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_INSTANT_TYPES_H_
+#define CHROME_COMMON_INSTANT_TYPES_H_
+
+#include "base/string16.h"
+#include "googleurl/src/gurl.h"
+
+// Ways that the Instant suggested text is autocompleted into the omnibox.
+enum InstantCompleteBehavior {
+  // Autocomplete the suggestion immediately.
+  INSTANT_COMPLETE_NOW,
+
+  // Do not autocomplete the suggestion. The suggestion may still be displayed
+  // in the omnibox, but not made a part of the omnibox text by default (e.g.,
+  // by displaying the suggestion as non-highlighted, non-selected gray text).
+  INSTANT_COMPLETE_NEVER,
+
+  // Treat the suggested text as the entire omnibox text, effectively replacing
+  // whatever the user has typed.
+  INSTANT_COMPLETE_REPLACE,
+};
+
+// The type of suggestion provided by Instant. For example, if Instant suggests
+// "yahoo.com", should that be considered a search string or a URL?
+enum InstantSuggestionType {
+  INSTANT_SUGGESTION_SEARCH,
+  INSTANT_SUGGESTION_URL,
+};
+
+// A wrapper to hold Instant suggested text and its metadata such as the type
+// of the suggestion and what completion behavior should be applied to it.
+struct InstantSuggestion {
+  InstantSuggestion();
+  InstantSuggestion(const string16& text,
+                    InstantCompleteBehavior behavior,
+                    InstantSuggestionType type);
+  ~InstantSuggestion();
+
+  string16 text;
+  InstantCompleteBehavior behavior;
+  InstantSuggestionType type;
+};
+
+// Omnibox dropdown matches provided by the native autocomplete providers.
+struct InstantAutocompleteResult {
+  InstantAutocompleteResult();
+  ~InstantAutocompleteResult();
+
+  // The provider name. May be empty.
+  string16 provider;
+
+  // True iff this is a search suggestion.
+  bool is_search;
+
+  // The title of the match.
+  string16 contents;
+
+  // The URL of the match.
+  // TODO(dhollowa): Remove this once the privacy story is sorted out.
+  GURL destination_url;
+
+  // The relevance score of this match. Same as the relevance score stored in
+  // AutocompleteMatch.
+  int relevance;
+};
+
+// How to interpret the size (height or width) of the Instant overlay (preview).
+enum InstantSizeUnits {
+  // As an absolute number of pixels.
+  INSTANT_SIZE_PIXELS,
+
+  // As a percentage of the height or width of the containing (parent) view.
+  INSTANT_SIZE_PERCENT,
+};
+
+// What the Instant page contains when it requests to be shown.
+enum InstantShownReason {
+  // Contents are not specified; the page wants to be shown unconditionally.
+  // This is a stopgap to display in unexpected situations, and should not
+  // normally be used.
+  INSTANT_SHOWN_NOT_SPECIFIED,
+
+  // Custom content on the NTP, e.g. a custom logo.
+  INSTANT_SHOWN_CUSTOM_NTP_CONTENT,
+
+  // Query suggestions and search results relevant when the user is typing in
+  // the omnibox.
+  INSTANT_SHOWN_QUERY_SUGGESTIONS,
+
+  // ZeroSuggest suggestions relevant when the user has focused in the omnibox,
+  // but not yet typed anything.
+  INSTANT_SHOWN_ZERO_SUGGESTIONS,
+};
+
+#endif  // CHROME_COMMON_INSTANT_TYPES_H_
diff --git a/chrome/common/json_schema_constants.cc b/chrome/common/json_schema_constants.cc
new file mode 100644
index 0000000..c364bdf
--- /dev/null
+++ b/chrome/common/json_schema_constants.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/json_schema_constants.h"
+
+namespace json_schema_constants {
+
+const char kAdditionalProperties[] = "additionalProperties";
+const char kAny[] = "any";
+const char kArray[] = "array";
+const char kBoolean[] = "boolean";
+const char kChoices[] = "choices";
+const char kEnum[] = "enum";
+const char kId[] = "id";
+const char kInteger[] = "integer";
+const char kItems[] = "items";
+const char kMaximum[] = "maximum";
+const char kMaxItems[] = "maxItems";
+const char kMaxLength[] = "maxLength";
+const char kMinimum[] = "minimum";
+const char kMinItems[] = "minItems";
+const char kMinLength[] = "minLength";
+const char kNull[] = "null";
+const char kNumber[] = "number";
+const char kObject[] = "object";
+const char kOptional[] = "optional";
+const char kPattern[] = "pattern";
+const char kProperties[] = "properties";
+const char kRef[] = "$ref";
+const char kString[] = "string";
+const char kType[] = "type";
+
+}  // namespace json_schema_constants
diff --git a/chrome/common/json_schema_constants.h b/chrome/common/json_schema_constants.h
new file mode 100644
index 0000000..8e3a7ee
--- /dev/null
+++ b/chrome/common/json_schema_constants.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_JSON_SCHEMA_CONSTANTS_H_
+#define CHROME_COMMON_JSON_SCHEMA_CONSTANTS_H_
+
+// These constants are shared by code that uses JSON schemas.
+namespace json_schema_constants {
+
+extern const char kAdditionalProperties[];
+extern const char kAny[];
+extern const char kArray[];
+extern const char kBoolean[];
+extern const char kChoices[];
+extern const char kEnum[];
+extern const char kId[];
+extern const char kInteger[];
+extern const char kItems[];
+extern const char kMaximum[];
+extern const char kMaxItems[];
+extern const char kMaxLength[];
+extern const char kMinimum[];
+extern const char kMinItems[];
+extern const char kMinLength[];
+extern const char kNull[];
+extern const char kNumber[];
+extern const char kObject[];
+extern const char kOptional[];
+extern const char kPattern[];
+extern const char kProperties[];
+extern const char kRef[];
+extern const char kString[];
+extern const char kType[];
+
+}  // namespace json_schema_constants
+
+#endif  // CHROME_COMMON_JSON_SCHEMA_CONSTANTS_H_
diff --git a/chrome/common/json_schema_validator.cc b/chrome/common/json_schema_validator.cc
new file mode 100644
index 0000000..0632a37
--- /dev/null
+++ b/chrome/common/json_schema_validator.cc
@@ -0,0 +1,487 @@
+// 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.
+
+#include "chrome/common/json_schema_validator.h"
+
+#include <cfloat>
+#include <cmath>
+
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/values.h"
+#include "chrome/common/json_schema_constants.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace schema = json_schema_constants;
+
+namespace {
+
+double GetNumberValue(Value* value) {
+  double result = 0;
+  CHECK(value->GetAsDouble(&result))
+      << "Unexpected value type: " << value->GetType();
+  return result;
+}
+
+}  // namespace
+
+
+JSONSchemaValidator::Error::Error() {
+}
+
+JSONSchemaValidator::Error::Error(const std::string& message)
+    : path(message) {
+}
+
+JSONSchemaValidator::Error::Error(const std::string& path,
+                                  const std::string& message)
+    : path(path), message(message) {
+}
+
+
+const char JSONSchemaValidator::kUnknownTypeReference[] =
+    "Unknown schema reference: *.";
+const char JSONSchemaValidator::kInvalidChoice[] =
+    "Value does not match any valid type choices.";
+const char JSONSchemaValidator::kInvalidEnum[] =
+    "Value does not match any valid enum choices.";
+const char JSONSchemaValidator::kObjectPropertyIsRequired[] =
+    "Property is required.";
+const char JSONSchemaValidator::kUnexpectedProperty[] =
+    "Unexpected property.";
+const char JSONSchemaValidator::kArrayMinItems[] =
+    "Array must have at least * items.";
+const char JSONSchemaValidator::kArrayMaxItems[] =
+    "Array must not have more than * items.";
+const char JSONSchemaValidator::kArrayItemRequired[] =
+    "Item is required.";
+const char JSONSchemaValidator::kStringMinLength[] =
+    "String must be at least * characters long.";
+const char JSONSchemaValidator::kStringMaxLength[] =
+    "String must not be more than * characters long.";
+const char JSONSchemaValidator::kStringPattern[] =
+    "String must match the pattern: *.";
+const char JSONSchemaValidator::kNumberMinimum[] =
+    "Value must not be less than *.";
+const char JSONSchemaValidator::kNumberMaximum[] =
+    "Value must not be greater than *.";
+const char JSONSchemaValidator::kInvalidType[] =
+    "Expected '*' but got '*'.";
+
+
+// static
+std::string JSONSchemaValidator::GetJSONSchemaType(Value* value) {
+  switch (value->GetType()) {
+    case Value::TYPE_NULL:
+      return schema::kNull;
+    case Value::TYPE_BOOLEAN:
+      return schema::kBoolean;
+    case Value::TYPE_INTEGER:
+      return schema::kInteger;
+    case Value::TYPE_DOUBLE: {
+      double double_value = 0;
+      value->GetAsDouble(&double_value);
+      if (std::abs(double_value) <= std::pow(2.0, DBL_MANT_DIG) &&
+          double_value == floor(double_value)) {
+        return schema::kInteger;
+      } else {
+        return schema::kNumber;
+      }
+    }
+    case Value::TYPE_STRING:
+      return schema::kString;
+    case Value::TYPE_DICTIONARY:
+      return schema::kObject;
+    case Value::TYPE_LIST:
+      return schema::kArray;
+    default:
+      NOTREACHED() << "Unexpected value type: " << value->GetType();
+      return "";
+  }
+}
+
+// static
+std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format,
+                                                    const std::string& s1) {
+  std::string ret_val = format;
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
+  return ret_val;
+}
+
+// static
+std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format,
+                                                    const std::string& s1,
+                                                    const std::string& s2) {
+  std::string ret_val = format;
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2);
+  return ret_val;
+}
+
+JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema)
+    : schema_root_(schema), default_allow_additional_properties_(false) {
+}
+
+JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema,
+                                         ListValue* types)
+    : schema_root_(schema), default_allow_additional_properties_(false) {
+  if (!types)
+    return;
+
+  for (size_t i = 0; i < types->GetSize(); ++i) {
+    DictionaryValue* type = NULL;
+    CHECK(types->GetDictionary(i, &type));
+
+    std::string id;
+    CHECK(type->GetString(schema::kId, &id));
+
+    CHECK(types_.find(id) == types_.end());
+    types_[id] = type;
+  }
+}
+
+JSONSchemaValidator::~JSONSchemaValidator() {}
+
+bool JSONSchemaValidator::Validate(Value* instance) {
+  errors_.clear();
+  Validate(instance, schema_root_, "");
+  return errors_.empty();
+}
+
+void JSONSchemaValidator::Validate(Value* instance,
+                                   DictionaryValue* schema,
+                                   const std::string& path) {
+  // If this schema defines itself as reference type, save it in this.types.
+  std::string id;
+  if (schema->GetString(schema::kId, &id)) {
+    TypeMap::iterator iter = types_.find(id);
+    if (iter == types_.end())
+      types_[id] = schema;
+    else
+      DCHECK(iter->second == schema);
+  }
+
+  // If the schema has a $ref property, the instance must validate against
+  // that schema. It must be present in types_ to be referenced.
+  std::string ref;
+  if (schema->GetString(schema::kRef, &ref)) {
+    TypeMap::iterator type = types_.find(ref);
+    if (type == types_.end()) {
+      errors_.push_back(
+          Error(path, FormatErrorMessage(kUnknownTypeReference, ref)));
+    } else {
+      Validate(instance, type->second, path);
+    }
+    return;
+  }
+
+  // If the schema has a choices property, the instance must validate against at
+  // least one of the items in that array.
+  ListValue* choices = NULL;
+  if (schema->GetList(schema::kChoices, &choices)) {
+    ValidateChoices(instance, choices, path);
+    return;
+  }
+
+  // If the schema has an enum property, the instance must be one of those
+  // values.
+  ListValue* enumeration = NULL;
+  if (schema->GetList(schema::kEnum, &enumeration)) {
+    ValidateEnum(instance, enumeration, path);
+    return;
+  }
+
+  std::string type;
+  schema->GetString(schema::kType, &type);
+  CHECK(!type.empty());
+  if (type != schema::kAny) {
+    if (!ValidateType(instance, type, path))
+      return;
+
+    // These casts are safe because of checks in ValidateType().
+    if (type == schema::kObject)
+      ValidateObject(static_cast<DictionaryValue*>(instance), schema, path);
+    else if (type == schema::kArray)
+      ValidateArray(static_cast<ListValue*>(instance), schema, path);
+    else if (type == schema::kString)
+      ValidateString(static_cast<StringValue*>(instance), schema, path);
+    else if (type == schema::kNumber || type == schema::kInteger)
+      ValidateNumber(instance, schema, path);
+    else if (type != schema::kBoolean && type != schema::kNull)
+      NOTREACHED() << "Unexpected type: " << type;
+  }
+}
+
+void JSONSchemaValidator::ValidateChoices(Value* instance,
+                                          ListValue* choices,
+                                          const std::string& path) {
+  size_t original_num_errors = errors_.size();
+
+  for (size_t i = 0; i < choices->GetSize(); ++i) {
+    DictionaryValue* choice = NULL;
+    CHECK(choices->GetDictionary(i, &choice));
+
+    Validate(instance, choice, path);
+    if (errors_.size() == original_num_errors)
+      return;
+
+    // We discard the error from each choice. We only want to know if any of the
+    // validations succeeded.
+    errors_.resize(original_num_errors);
+  }
+
+  // Now add a generic error that no choices matched.
+  errors_.push_back(Error(path, kInvalidChoice));
+  return;
+}
+
+void JSONSchemaValidator::ValidateEnum(Value* instance,
+                                       ListValue* choices,
+                                       const std::string& path) {
+  for (size_t i = 0; i < choices->GetSize(); ++i) {
+    Value* choice = NULL;
+    CHECK(choices->Get(i, &choice));
+    switch (choice->GetType()) {
+      case Value::TYPE_NULL:
+      case Value::TYPE_BOOLEAN:
+      case Value::TYPE_STRING:
+        if (instance->Equals(choice))
+          return;
+        break;
+
+      case Value::TYPE_INTEGER:
+      case Value::TYPE_DOUBLE:
+        if (instance->IsType(Value::TYPE_INTEGER) ||
+            instance->IsType(Value::TYPE_DOUBLE)) {
+          if (GetNumberValue(choice) == GetNumberValue(instance))
+            return;
+        }
+        break;
+
+      default:
+       NOTREACHED() << "Unexpected type in enum: " << choice->GetType();
+    }
+  }
+
+  errors_.push_back(Error(path, kInvalidEnum));
+}
+
+void JSONSchemaValidator::ValidateObject(DictionaryValue* instance,
+                                         DictionaryValue* schema,
+                                         const std::string& path) {
+  DictionaryValue* properties = NULL;
+  schema->GetDictionary(schema::kProperties, &properties);
+  if (properties) {
+    for (DictionaryValue::key_iterator key = properties->begin_keys();
+         key != properties->end_keys(); ++key) {
+      std::string prop_path = path.empty() ? *key : (path + "." + *key);
+      DictionaryValue* prop_schema = NULL;
+      CHECK(properties->GetDictionary(*key, &prop_schema));
+
+      Value* prop_value = NULL;
+      if (instance->Get(*key, &prop_value)) {
+        Validate(prop_value, prop_schema, prop_path);
+      } else {
+        // Properties are required unless there is an optional field set to
+        // 'true'.
+        bool is_optional = false;
+        prop_schema->GetBoolean(schema::kOptional, &is_optional);
+        if (!is_optional) {
+          errors_.push_back(Error(prop_path, kObjectPropertyIsRequired));
+        }
+      }
+    }
+  }
+
+  DictionaryValue* additional_properties_schema = NULL;
+  if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema))
+    return;
+
+  // Validate additional properties.
+  for (DictionaryValue::key_iterator key = instance->begin_keys();
+       key != instance->end_keys(); ++key) {
+    if (properties && properties->HasKey(*key))
+      continue;
+
+    std::string prop_path = path.empty() ? *key : path + "." + *key;
+    if (!additional_properties_schema) {
+      errors_.push_back(Error(prop_path, kUnexpectedProperty));
+    } else {
+      Value* prop_value = NULL;
+      CHECK(instance->Get(*key, &prop_value));
+      Validate(prop_value, additional_properties_schema, prop_path);
+    }
+  }
+}
+
+void JSONSchemaValidator::ValidateArray(ListValue* instance,
+                                        DictionaryValue* schema,
+                                        const std::string& path) {
+  DictionaryValue* single_type = NULL;
+  size_t instance_size = instance->GetSize();
+  if (schema->GetDictionary(schema::kItems, &single_type)) {
+    int min_items = 0;
+    if (schema->GetInteger(schema::kMinItems, &min_items)) {
+      CHECK(min_items >= 0);
+      if (instance_size < static_cast<size_t>(min_items)) {
+        errors_.push_back(Error(path, FormatErrorMessage(
+            kArrayMinItems, base::IntToString(min_items))));
+      }
+    }
+
+    int max_items = 0;
+    if (schema->GetInteger(schema::kMaxItems, &max_items)) {
+      CHECK(max_items >= 0);
+      if (instance_size > static_cast<size_t>(max_items)) {
+        errors_.push_back(Error(path, FormatErrorMessage(
+            kArrayMaxItems, base::IntToString(max_items))));
+      }
+    }
+
+    // If the items property is a single schema, each item in the array must
+    // validate against that schema.
+    for (size_t i = 0; i < instance_size; ++i) {
+      Value* item = NULL;
+      CHECK(instance->Get(i, &item));
+      std::string i_str = base::UintToString(i);
+      std::string item_path = path.empty() ? i_str : (path + "." + i_str);
+      Validate(item, single_type, item_path);
+    }
+
+    return;
+  }
+
+  // Otherwise, the list must be a tuple type, where each item in the list has a
+  // particular schema.
+  ValidateTuple(instance, schema, path);
+}
+
+void JSONSchemaValidator::ValidateTuple(ListValue* instance,
+                                        DictionaryValue* schema,
+                                        const std::string& path) {
+  ListValue* tuple_type = NULL;
+  schema->GetList(schema::kItems, &tuple_type);
+  size_t tuple_size = tuple_type ? tuple_type->GetSize() : 0;
+  if (tuple_type) {
+    for (size_t i = 0; i < tuple_size; ++i) {
+      std::string i_str = base::UintToString(i);
+      std::string item_path = path.empty() ? i_str : (path + "." + i_str);
+      DictionaryValue* item_schema = NULL;
+      CHECK(tuple_type->GetDictionary(i, &item_schema));
+      Value* item_value = NULL;
+      instance->Get(i, &item_value);
+      if (item_value && item_value->GetType() != Value::TYPE_NULL) {
+        Validate(item_value, item_schema, item_path);
+      } else {
+        bool is_optional = false;
+        item_schema->GetBoolean(schema::kOptional, &is_optional);
+        if (!is_optional) {
+          errors_.push_back(Error(item_path, kArrayItemRequired));
+          return;
+        }
+      }
+    }
+  }
+
+  DictionaryValue* additional_properties_schema = NULL;
+  if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema))
+    return;
+
+  size_t instance_size = instance->GetSize();
+  if (additional_properties_schema) {
+    // Any additional properties must validate against the additionalProperties
+    // schema.
+    for (size_t i = tuple_size; i < instance_size; ++i) {
+      std::string i_str = base::UintToString(i);
+      std::string item_path = path.empty() ? i_str : (path + "." + i_str);
+      Value* item_value = NULL;
+      CHECK(instance->Get(i, &item_value));
+      Validate(item_value, additional_properties_schema, item_path);
+    }
+  } else if (instance_size > tuple_size) {
+    errors_.push_back(Error(path, FormatErrorMessage(
+        kArrayMaxItems, base::UintToString(tuple_size))));
+  }
+}
+
+void JSONSchemaValidator::ValidateString(StringValue* instance,
+                                         DictionaryValue* schema,
+                                         const std::string& path) {
+  std::string value;
+  CHECK(instance->GetAsString(&value));
+
+  int min_length = 0;
+  if (schema->GetInteger(schema::kMinLength, &min_length)) {
+    CHECK(min_length >= 0);
+    if (value.size() < static_cast<size_t>(min_length)) {
+      errors_.push_back(Error(path, FormatErrorMessage(
+          kStringMinLength, base::IntToString(min_length))));
+    }
+  }
+
+  int max_length = 0;
+  if (schema->GetInteger(schema::kMaxLength, &max_length)) {
+    CHECK(max_length >= 0);
+    if (value.size() > static_cast<size_t>(max_length)) {
+      errors_.push_back(Error(path, FormatErrorMessage(
+          kStringMaxLength, base::IntToString(max_length))));
+    }
+  }
+
+  CHECK(!schema->HasKey(schema::kPattern)) << "Pattern is not supported.";
+}
+
+void JSONSchemaValidator::ValidateNumber(Value* instance,
+                                         DictionaryValue* schema,
+                                         const std::string& path) {
+  double value = GetNumberValue(instance);
+
+  // TODO(aa): It would be good to test that the double is not infinity or nan,
+  // but isnan and isinf aren't defined on Windows.
+
+  double minimum = 0;
+  if (schema->GetDouble(schema::kMinimum, &minimum)) {
+    if (value < minimum)
+      errors_.push_back(Error(path, FormatErrorMessage(
+          kNumberMinimum, base::DoubleToString(minimum))));
+  }
+
+  double maximum = 0;
+  if (schema->GetDouble(schema::kMaximum, &maximum)) {
+    if (value > maximum)
+      errors_.push_back(Error(path, FormatErrorMessage(
+          kNumberMaximum, base::DoubleToString(maximum))));
+  }
+}
+
+bool JSONSchemaValidator::ValidateType(Value* instance,
+                                       const std::string& expected_type,
+                                       const std::string& path) {
+  std::string actual_type = GetJSONSchemaType(instance);
+  if (expected_type == actual_type ||
+      (expected_type == schema::kNumber && actual_type == schema::kInteger)) {
+    return true;
+  } else {
+    errors_.push_back(Error(path, FormatErrorMessage(
+        kInvalidType, expected_type, actual_type)));
+    return false;
+  }
+}
+
+bool JSONSchemaValidator::SchemaAllowsAnyAdditionalItems(
+    DictionaryValue* schema, DictionaryValue** additional_properties_schema) {
+  // If the validator allows additional properties globally, and this schema
+  // doesn't override, then we can exit early.
+  schema->GetDictionary(schema::kAdditionalProperties,
+                        additional_properties_schema);
+
+  if (*additional_properties_schema) {
+    std::string additional_properties_type(schema::kAny);
+    CHECK((*additional_properties_schema)->GetString(
+        schema::kType, &additional_properties_type));
+    return additional_properties_type == schema::kAny;
+  } else {
+    return default_allow_additional_properties_;
+  }
+}
diff --git a/chrome/common/json_schema_validator.h b/chrome/common/json_schema_validator.h
new file mode 100644
index 0000000..1f3717e
--- /dev/null
+++ b/chrome/common/json_schema_validator.h
@@ -0,0 +1,218 @@
+// 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.
+
+#ifndef CHROME_COMMON_JSON_SCHEMA_VALIDATOR_H_
+#define CHROME_COMMON_JSON_SCHEMA_VALIDATOR_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class StringValue;
+class Value;
+}
+
+//==============================================================================
+// This class implements a subset of JSON Schema.
+// See: http://www.json.com/json-schema-proposal/ for more details.
+//
+// There is also an older JavaScript implementation of the same functionality in
+// chrome/renderer/resources/json_schema.js.
+//
+// The following features of JSON Schema are not implemented:
+// - requires
+// - unique
+// - disallow
+// - union types (but replaced with 'choices')
+// - number.maxDecimal
+// - string.pattern
+//
+// The following properties are not applicable to the interface exposed by
+// this class:
+// - options
+// - readonly
+// - title
+// - description
+// - format
+// - default
+// - transient
+// - hidden
+//
+// There are also these departures from the JSON Schema proposal:
+// - null counts as 'unspecified' for optional values
+// - added the 'choices' property, to allow specifying a list of possible types
+//   for a value
+// - by default an "object" typed schema does not allow additional properties.
+//   if present, "additionalProperties" is to be a schema against which all
+//   additional properties will be validated.
+//==============================================================================
+class JSONSchemaValidator {
+ public:
+  // Details about a validation error.
+  struct Error {
+    Error();
+
+    explicit Error(const std::string& message);
+
+    Error(const std::string& path, const std::string& message);
+
+    // The path to the location of the error in the JSON structure.
+    std::string path;
+
+    // An english message describing the error.
+    std::string message;
+  };
+
+  // Error messages.
+  static const char kUnknownTypeReference[];
+  static const char kInvalidChoice[];
+  static const char kInvalidEnum[];
+  static const char kObjectPropertyIsRequired[];
+  static const char kUnexpectedProperty[];
+  static const char kArrayMinItems[];
+  static const char kArrayMaxItems[];
+  static const char kArrayItemRequired[];
+  static const char kStringMinLength[];
+  static const char kStringMaxLength[];
+  static const char kStringPattern[];
+  static const char kNumberMinimum[];
+  static const char kNumberMaximum[];
+  static const char kInvalidType[];
+
+  // Classifies a Value as one of the JSON schema primitive types.
+  static std::string GetJSONSchemaType(base::Value* value);
+
+  // Utility methods to format error messages. The first method can have one
+  // wildcard represented by '*', which is replaced with s1. The second method
+  // can have two, which are replaced by s1 and s2.
+  static std::string FormatErrorMessage(const std::string& format,
+                                        const std::string& s1);
+  static std::string FormatErrorMessage(const std::string& format,
+                                        const std::string& s1,
+                                        const std::string& s2);
+
+  // Creates a validator for the specified schema.
+  //
+  // NOTE: This constructor assumes that |schema| is well formed and valid.
+  // Errors will result in CHECK at runtime; this constructor should not be used
+  // with untrusted schemas.
+  explicit JSONSchemaValidator(base::DictionaryValue* schema);
+
+  // Creates a validator for the specified schema and user-defined types. Each
+  // type must be a valid JSONSchema type description with an additional "id"
+  // field. Schema objects in |schema| can refer to these types with the "$ref"
+  // property.
+  //
+  // NOTE: This constructor assumes that |schema| and |types| are well-formed
+  // and valid. Errors will result in CHECK at runtime; this constructor should
+  // not be used with untrusted schemas.
+  JSONSchemaValidator(base::DictionaryValue* schema, base::ListValue* types);
+
+  ~JSONSchemaValidator();
+
+  // Whether the validator allows additional items for objects and lists, beyond
+  // those defined by their schema, by default.
+  //
+  // This setting defaults to false: all items in an instance list or object
+  // must be defined by the corresponding schema.
+  //
+  // This setting can be overridden on individual object and list schemas by
+  // setting the "additionalProperties" field.
+  bool default_allow_additional_properties() const {
+    return default_allow_additional_properties_;
+  }
+
+  void set_default_allow_additional_properties(bool val) {
+    default_allow_additional_properties_ = val;
+  }
+
+  // Returns any errors from the last call to to Validate().
+  const std::vector<Error>& errors() const {
+    return errors_;
+  }
+
+  // Validates a JSON value. Returns true if the instance is valid, false
+  // otherwise. If false is returned any errors are available from the errors()
+  // getter.
+  bool Validate(base::Value* instance);
+
+ private:
+  typedef std::map<std::string, base::DictionaryValue*> TypeMap;
+
+  // Each of the below methods handle a subset of the validation process. The
+  // path paramater is the path to |instance| from the root of the instance tree
+  // and is used in error messages.
+
+  // Validates any instance node against any schema node. This is called for
+  // every node in the instance tree, and it just decides which of the more
+  // detailed methods to call.
+  void Validate(base::Value* instance, base::DictionaryValue* schema,
+                const std::string& path);
+
+  // Validates a node against a list of possible schemas. If any one of the
+  // schemas match, the node is valid.
+  void ValidateChoices(base::Value* instance, base::ListValue* choices,
+                       const std::string& path);
+
+  // Validates a node against a list of exact primitive values, eg 42, "foobar".
+  void ValidateEnum(base::Value* instance, base::ListValue* choices,
+                    const std::string& path);
+
+  // Validates a JSON object against an object schema node.
+  void ValidateObject(base::DictionaryValue* instance,
+                      base::DictionaryValue* schema,
+                      const std::string& path);
+
+  // Validates a JSON array against an array schema node.
+  void ValidateArray(base::ListValue* instance, base::DictionaryValue* schema,
+                     const std::string& path);
+
+  // Validates a JSON array against an array schema node configured to be a
+  // tuple. In a tuple, there is one schema node for each item expected in the
+  // array.
+  void ValidateTuple(base::ListValue* instance, base::DictionaryValue* schema,
+                     const std::string& path);
+
+  // Validate a JSON string against a string schema node.
+  void ValidateString(base::StringValue* instance,
+                      base::DictionaryValue* schema,
+                      const std::string& path);
+
+  // Validate a JSON number against a number schema node.
+  void ValidateNumber(base::Value* instance,
+                      base::DictionaryValue* schema,
+                      const std::string& path);
+
+  // Validates that the JSON node |instance| has |expected_type|.
+  bool ValidateType(base::Value* instance, const std::string& expected_type,
+                    const std::string& path);
+
+  // Returns true if |schema| will allow additional items of any type.
+  bool SchemaAllowsAnyAdditionalItems(
+      base::DictionaryValue* schema,
+      base::DictionaryValue** addition_items_schema);
+
+  // The root schema node.
+  base::DictionaryValue* schema_root_;
+
+  // Map of user-defined name to type.
+  TypeMap types_;
+
+  // Whether we allow additional properties on objects by default. This can be
+  // overridden by the allow_additional_properties flag on an Object schema.
+  bool default_allow_additional_properties_;
+
+  // Errors accumulated since the last call to Validate().
+  std::vector<Error> errors_;
+
+
+  DISALLOW_COPY_AND_ASSIGN(JSONSchemaValidator);
+};
+
+#endif  // CHROME_COMMON_JSON_SCHEMA_VALIDATOR_H_
diff --git a/chrome/common/json_schema_validator_unittest.cc b/chrome/common/json_schema_validator_unittest.cc
new file mode 100644
index 0000000..ca4b388
--- /dev/null
+++ b/chrome/common/json_schema_validator_unittest.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2010 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.
+
+#include "base/values.h"
+#include "chrome/common/json_schema_validator.h"
+#include "chrome/common/json_schema_validator_unittest_base.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class JSONSchemaValidatorCPPTest : public JSONSchemaValidatorTestBase {
+ public:
+  JSONSchemaValidatorCPPTest()
+      : JSONSchemaValidatorTestBase(JSONSchemaValidatorTestBase::CPP) {
+  }
+
+ protected:
+  virtual void ExpectValid(const std::string& test_source,
+                           Value* instance, DictionaryValue* schema,
+                           ListValue* types) {
+    JSONSchemaValidator validator(schema, types);
+    if (validator.Validate(instance))
+      return;
+
+    for (size_t i = 0; i < validator.errors().size(); ++i) {
+      ADD_FAILURE() << test_source << ": "
+                    << validator.errors()[i].path << ": "
+                    << validator.errors()[i].message;
+    }
+  }
+
+  virtual void ExpectNotValid(const std::string& test_source,
+                              Value* instance, DictionaryValue* schema,
+                              ListValue* types,
+                              const std::string& expected_error_path,
+                              const std::string& expected_error_message) {
+    JSONSchemaValidator validator(schema, types);
+    if (validator.Validate(instance)) {
+      ADD_FAILURE() << test_source;
+      return;
+    }
+
+    ASSERT_EQ(1u, validator.errors().size()) << test_source;
+    EXPECT_EQ(expected_error_path, validator.errors()[0].path) << test_source;
+    EXPECT_EQ(expected_error_message, validator.errors()[0].message)
+        << test_source;
+  }
+};
+
+TEST_F(JSONSchemaValidatorCPPTest, Test) {
+  RunTests();
+}
diff --git a/chrome/common/json_schema_validator_unittest_base.cc b/chrome/common/json_schema_validator_unittest_base.cc
new file mode 100644
index 0000000..b3cc89d
--- /dev/null
+++ b/chrome/common/json_schema_validator_unittest_base.cc
@@ -0,0 +1,677 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/json_schema_validator_unittest_base.h"
+
+#include <cfloat>
+#include <cmath>
+#include <limits>
+
+#include "base/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/stringprintf.h"
+#include "base/values.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/json_schema_constants.h"
+#include "chrome/common/json_schema_validator.h"
+
+namespace schema = json_schema_constants;
+
+namespace {
+
+#define TEST_SOURCE base::StringPrintf("%s:%i", __FILE__, __LINE__)
+
+Value* LoadValue(const std::string& filename) {
+  FilePath path;
+  PathService::Get(chrome::DIR_TEST_DATA, &path);
+  path = path.AppendASCII("json_schema_validator").AppendASCII(filename);
+  EXPECT_TRUE(file_util::PathExists(path));
+
+  std::string error_message;
+  JSONFileValueSerializer serializer(path);
+  Value* result = serializer.Deserialize(NULL, &error_message);
+  if (!result)
+    ADD_FAILURE() << "Could not parse JSON: " << error_message;
+  return result;
+}
+
+Value* LoadValue(const std::string& filename, base::Value::Type type) {
+  scoped_ptr<Value> result(LoadValue(filename));
+  if (!result.get())
+    return NULL;
+  if (!result->IsType(type)) {
+    ADD_FAILURE() << "Expected type " << type << ", got: " << result->GetType();
+    return NULL;
+  }
+  return result.release();
+}
+
+ListValue* LoadList(const std::string& filename) {
+  return static_cast<ListValue*>(
+      LoadValue(filename, Value::TYPE_LIST));
+}
+
+DictionaryValue* LoadDictionary(const std::string& filename) {
+  return static_cast<DictionaryValue*>(
+      LoadValue(filename, Value::TYPE_DICTIONARY));
+}
+
+}  // namespace
+
+
+JSONSchemaValidatorTestBase::JSONSchemaValidatorTestBase(
+    JSONSchemaValidatorTestBase::ValidatorType type)
+    : type_(type) {
+}
+
+void JSONSchemaValidatorTestBase::RunTests() {
+  TestComplex();
+  TestStringPattern();
+  TestEnum();
+  TestChoices();
+  TestExtends();
+  TestObject();
+  TestTypeReference();
+  TestArrayTuple();
+  TestArrayNonTuple();
+  TestString();
+  TestNumber();
+  TestTypeClassifier();
+  TestTypes();
+}
+
+void JSONSchemaValidatorTestBase::TestComplex() {
+  scoped_ptr<DictionaryValue> schema(LoadDictionary("complex_schema.json"));
+  scoped_ptr<ListValue> instance(LoadList("complex_instance.json"));
+
+  ASSERT_TRUE(schema.get());
+  ASSERT_TRUE(instance.get());
+
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+  instance->Remove(instance->GetSize() - 1, NULL);
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+  instance->Append(new DictionaryValue());
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "1",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kNumber,
+                     schema::kObject));
+  instance->Remove(instance->GetSize() - 1, NULL);
+
+  DictionaryValue* item = NULL;
+  ASSERT_TRUE(instance->GetDictionary(0, &item));
+  item->SetString("url", "xxxxxxxxxxx");
+
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL,
+                 "0.url",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kStringMaxLength, "10"));
+}
+
+void JSONSchemaValidatorTestBase::TestStringPattern() {
+  // Regex patterns not supported in CPP validator.
+  if (type_ == CPP)
+    return;
+
+  scoped_ptr<DictionaryValue> schema(new DictionaryValue());
+  schema->SetString(schema::kType, schema::kString);
+  schema->SetString(schema::kPattern, "foo+");
+
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateStringValue("foo")).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateStringValue("foooooo")).get(),
+              schema.get(), NULL);
+  ExpectNotValid(TEST_SOURCE,
+                 scoped_ptr<Value>(Value::CreateStringValue("bar")).get(),
+                 schema.get(), NULL, "",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kStringPattern, "foo+"));
+}
+
+void JSONSchemaValidatorTestBase::TestEnum() {
+  scoped_ptr<DictionaryValue> schema(LoadDictionary("enum_schema.json"));
+
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateStringValue("foo")).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateIntegerValue(42)).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateBooleanValue(false)).get(),
+              schema.get(), NULL);
+
+  ExpectNotValid(TEST_SOURCE,
+                 scoped_ptr<Value>(Value::CreateStringValue("42")).get(),
+                 schema.get(), NULL, "", JSONSchemaValidator::kInvalidEnum);
+  ExpectNotValid(TEST_SOURCE,
+                 scoped_ptr<Value>(Value::CreateNullValue()).get(),
+                 schema.get(), NULL, "", JSONSchemaValidator::kInvalidEnum);
+}
+
+void JSONSchemaValidatorTestBase::TestChoices() {
+  scoped_ptr<DictionaryValue> schema(LoadDictionary("choices_schema.json"));
+
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateNullValue()).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateIntegerValue(42)).get(),
+              schema.get(), NULL);
+
+  scoped_ptr<DictionaryValue> instance(new DictionaryValue());
+  instance->SetString("foo", "bar");
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+
+  ExpectNotValid(TEST_SOURCE,
+                 scoped_ptr<Value>(Value::CreateStringValue("foo")).get(),
+                 schema.get(), NULL, "", JSONSchemaValidator::kInvalidChoice);
+  ExpectNotValid(TEST_SOURCE,
+                 scoped_ptr<Value>(new ListValue()).get(),
+                 schema.get(), NULL, "", JSONSchemaValidator::kInvalidChoice);
+
+  instance->SetInteger("foo", 42);
+  ExpectNotValid(TEST_SOURCE, instance.get(),
+                 schema.get(), NULL, "", JSONSchemaValidator::kInvalidChoice);
+}
+
+void JSONSchemaValidatorTestBase::TestExtends() {
+  // TODO(aa): JS only
+}
+
+void JSONSchemaValidatorTestBase::TestObject() {
+  scoped_ptr<DictionaryValue> schema(new DictionaryValue());
+  schema->SetString(schema::kType, schema::kObject);
+  schema->SetString("properties.foo.type", schema::kString);
+  schema->SetString("properties.bar.type", schema::kInteger);
+
+  scoped_ptr<DictionaryValue> instance(new DictionaryValue());
+  instance->SetString("foo", "foo");
+  instance->SetInteger("bar", 42);
+
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+
+  instance->SetBoolean("extra", true);
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL,
+                 "extra", JSONSchemaValidator::kUnexpectedProperty);
+
+  instance->Remove("extra", NULL);
+  instance->Remove("bar", NULL);
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "bar",
+                 JSONSchemaValidator::kObjectPropertyIsRequired);
+
+  instance->SetString("bar", "42");
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "bar",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kInteger,
+                     schema::kString));
+
+  DictionaryValue* additional_properties = new DictionaryValue();
+  additional_properties->SetString(schema::kType, schema::kAny);
+  schema->Set(schema::kAdditionalProperties, additional_properties);
+
+  instance->SetInteger("bar", 42);
+  instance->SetBoolean("extra", true);
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+
+  instance->SetString("extra", "foo");
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+
+  additional_properties->SetString(schema::kType, schema::kBoolean);
+  instance->SetBoolean("extra", true);
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+
+  instance->SetString("extra", "foo");
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL,
+                 "extra", JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kBoolean,
+                     schema::kString));
+
+  DictionaryValue* properties = NULL;
+  DictionaryValue* bar_property = NULL;
+  ASSERT_TRUE(schema->GetDictionary(schema::kProperties, &properties));
+  ASSERT_TRUE(properties->GetDictionary("bar", &bar_property));
+
+  bar_property->SetBoolean(schema::kOptional, true);
+  instance->Remove("extra", NULL);
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+  instance->Remove("bar", NULL);
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+  instance->Set("bar", Value::CreateNullValue());
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL,
+                 "bar", JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kInteger,
+                     schema::kNull));
+  instance->SetString("bar", "42");
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL,
+                 "bar", JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kInteger,
+                     schema::kString));
+}
+
+void JSONSchemaValidatorTestBase::TestTypeReference() {
+  scoped_ptr<ListValue> types(LoadList("reference_types.json"));
+  ASSERT_TRUE(types.get());
+
+  scoped_ptr<DictionaryValue> schema(new DictionaryValue());
+  schema->SetString(schema::kType, schema::kObject);
+  schema->SetString("properties.foo.type", schema::kString);
+  schema->SetString("properties.bar.$ref", "Max10Int");
+  schema->SetString("properties.baz.$ref", "MinLengthString");
+
+  scoped_ptr<DictionaryValue> schema_inline(new DictionaryValue());
+  schema_inline->SetString(schema::kType, schema::kObject);
+  schema_inline->SetString("properties.foo.type", schema::kString);
+  schema_inline->SetString("properties.bar.id", "NegativeInt");
+  schema_inline->SetString("properties.bar.type", schema::kInteger);
+  schema_inline->SetInteger("properties.bar.maximum", 0);
+  schema_inline->SetString("properties.baz.$ref", "NegativeInt");
+
+  scoped_ptr<DictionaryValue> instance(new DictionaryValue());
+  instance->SetString("foo", "foo");
+  instance->SetInteger("bar", 4);
+  instance->SetString("baz", "ab");
+
+  scoped_ptr<DictionaryValue> instance_inline(new DictionaryValue());
+  instance_inline->SetString("foo", "foo");
+  instance_inline->SetInteger("bar", -4);
+  instance_inline->SetInteger("baz", -2);
+
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), types.get());
+  ExpectValid(TEST_SOURCE, instance_inline.get(), schema_inline.get(), NULL);
+
+  // Validation failure, but successful schema reference.
+  instance->SetString("baz", "a");
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), types.get(),
+                 "baz", JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kStringMinLength, "2"));
+
+  instance_inline->SetInteger("bar", 20);
+  ExpectNotValid(TEST_SOURCE, instance_inline.get(), schema_inline.get(), NULL,
+                 "bar", JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kNumberMaximum, "0"));
+
+  // Remove MinLengthString type.
+  types->Remove(types->GetSize() - 1, NULL);
+  instance->SetString("baz", "ab");
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), types.get(),
+                 "bar", JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kUnknownTypeReference,
+                     "Max10Int"));
+
+  // Remove internal type "NegativeInt".
+  schema_inline->Remove("properties.bar", NULL);
+  instance_inline->Remove("bar", NULL);
+  ExpectNotValid(TEST_SOURCE, instance_inline.get(), schema_inline.get(), NULL,
+                 "baz", JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kUnknownTypeReference,
+                     "NegativeInt"));
+}
+
+void JSONSchemaValidatorTestBase::TestArrayTuple() {
+  scoped_ptr<DictionaryValue> schema(LoadDictionary("array_tuple_schema.json"));
+  ASSERT_TRUE(schema.get());
+
+  scoped_ptr<ListValue> instance(new ListValue());
+  instance->Append(Value::CreateStringValue("42"));
+  instance->Append(Value::CreateIntegerValue(42));
+
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+
+  instance->Append(Value::CreateStringValue("anything"));
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kArrayMaxItems, "2"));
+
+  instance->Remove(1, NULL);
+  instance->Remove(1, NULL);
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "1",
+                 JSONSchemaValidator::kArrayItemRequired);
+
+  instance->Set(0, Value::CreateIntegerValue(42));
+  instance->Append(Value::CreateIntegerValue(42));
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "0",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kString,
+                     schema::kInteger));
+
+  DictionaryValue* additional_properties = new DictionaryValue();
+  additional_properties->SetString(schema::kType, schema::kAny);
+  schema->Set(schema::kAdditionalProperties, additional_properties);
+  instance->Set(0, Value::CreateStringValue("42"));
+  instance->Append(Value::CreateStringValue("anything"));
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+  instance->Set(2, new ListValue());
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+
+  additional_properties->SetString(schema::kType, schema::kBoolean);
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "2",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kBoolean,
+                     schema::kArray));
+  instance->Set(2, Value::CreateBooleanValue(false));
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+
+  ListValue* items_schema = NULL;
+  DictionaryValue* item0_schema = NULL;
+  ASSERT_TRUE(schema->GetList(schema::kItems, &items_schema));
+  ASSERT_TRUE(items_schema->GetDictionary(0, &item0_schema));
+  item0_schema->SetBoolean(schema::kOptional, true);
+  instance->Remove(2, NULL);
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+  // TODO(aa): I think this is inconsistent with the handling of NULL+optional
+  // for objects.
+  instance->Set(0, Value::CreateNullValue());
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+  instance->Set(0, Value::CreateIntegerValue(42));
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "0",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kString,
+                     schema::kInteger));
+}
+
+void JSONSchemaValidatorTestBase::TestArrayNonTuple() {
+  scoped_ptr<DictionaryValue> schema(new DictionaryValue());
+  schema->SetString(schema::kType, schema::kArray);
+  schema->SetString("items.type", schema::kString);
+  schema->SetInteger(schema::kMinItems, 2);
+  schema->SetInteger(schema::kMaxItems, 3);
+
+  scoped_ptr<ListValue> instance(new ListValue());
+  instance->Append(Value::CreateStringValue("x"));
+  instance->Append(Value::CreateStringValue("x"));
+
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+  instance->Append(Value::CreateStringValue("x"));
+  ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL);
+
+  instance->Append(Value::CreateStringValue("x"));
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kArrayMaxItems, "3"));
+  instance->Remove(1, NULL);
+  instance->Remove(1, NULL);
+  instance->Remove(1, NULL);
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kArrayMinItems, "2"));
+
+  instance->Remove(1, NULL);
+  instance->Append(Value::CreateIntegerValue(42));
+  ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "1",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kString,
+                     schema::kInteger));
+}
+
+void JSONSchemaValidatorTestBase::TestString() {
+  scoped_ptr<DictionaryValue> schema(new DictionaryValue());
+  schema->SetString(schema::kType, schema::kString);
+  schema->SetInteger(schema::kMinLength, 1);
+  schema->SetInteger(schema::kMaxLength, 10);
+
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateStringValue("x")).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateStringValue("xxxxxxxxxx")).get(),
+              schema.get(), NULL);
+
+  ExpectNotValid(TEST_SOURCE,
+                 scoped_ptr<Value>(Value::CreateStringValue("")).get(),
+                 schema.get(), NULL, "",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kStringMinLength, "1"));
+  ExpectNotValid(
+      TEST_SOURCE,
+      scoped_ptr<Value>(Value::CreateStringValue("xxxxxxxxxxx")).get(),
+      schema.get(), NULL, "",
+      JSONSchemaValidator::FormatErrorMessage(
+          JSONSchemaValidator::kStringMaxLength, "10"));
+
+}
+
+void JSONSchemaValidatorTestBase::TestNumber() {
+  scoped_ptr<DictionaryValue> schema(new DictionaryValue());
+  schema->SetString(schema::kType, schema::kNumber);
+  schema->SetInteger(schema::kMinimum, 1);
+  schema->SetInteger(schema::kMaximum, 100);
+  schema->SetInteger("maxDecimal", 2);
+
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateIntegerValue(1)).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateIntegerValue(50)).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateIntegerValue(100)).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateDoubleValue(88.88)).get(),
+              schema.get(), NULL);
+
+  ExpectNotValid(
+      TEST_SOURCE,
+      scoped_ptr<Value>(Value::CreateDoubleValue(0.5)).get(),
+      schema.get(), NULL, "",
+      JSONSchemaValidator::FormatErrorMessage(
+          JSONSchemaValidator::kNumberMinimum, "1"));
+  ExpectNotValid(
+      TEST_SOURCE,
+      scoped_ptr<Value>(Value::CreateDoubleValue(100.1)).get(),
+      schema.get(), NULL, "",
+      JSONSchemaValidator::FormatErrorMessage(
+          JSONSchemaValidator::kNumberMaximum, "100"));
+}
+
+void JSONSchemaValidatorTestBase::TestTypeClassifier() {
+  EXPECT_EQ(std::string(schema::kBoolean),
+            JSONSchemaValidator::GetJSONSchemaType(
+                scoped_ptr<Value>(Value::CreateBooleanValue(true)).get()));
+  EXPECT_EQ(std::string(schema::kBoolean),
+            JSONSchemaValidator::GetJSONSchemaType(
+                scoped_ptr<Value>(Value::CreateBooleanValue(false)).get()));
+
+  // It doesn't matter whether the C++ type is 'integer' or 'real'. If the
+  // number is integral and within the representable range of integers in
+  // double, it's classified as 'integer'.
+  EXPECT_EQ(std::string(schema::kInteger),
+            JSONSchemaValidator::GetJSONSchemaType(
+                scoped_ptr<Value>(Value::CreateIntegerValue(42)).get()));
+  EXPECT_EQ(std::string(schema::kInteger),
+            JSONSchemaValidator::GetJSONSchemaType(
+                scoped_ptr<Value>(Value::CreateIntegerValue(0)).get()));
+  EXPECT_EQ(std::string(schema::kInteger),
+            JSONSchemaValidator::GetJSONSchemaType(
+                scoped_ptr<Value>(Value::CreateDoubleValue(42)).get()));
+  EXPECT_EQ(std::string(schema::kInteger),
+            JSONSchemaValidator::GetJSONSchemaType(scoped_ptr<Value>(
+                Value::CreateDoubleValue(pow(2.0, DBL_MANT_DIG))).get()));
+  EXPECT_EQ(std::string(schema::kInteger),
+            JSONSchemaValidator::GetJSONSchemaType(scoped_ptr<Value>(
+                Value::CreateDoubleValue(pow(-2.0, DBL_MANT_DIG))).get()));
+
+  // "number" is only used for non-integral numbers, or numbers beyond what
+  // double can accurately represent.
+  EXPECT_EQ(std::string(schema::kNumber),
+            JSONSchemaValidator::GetJSONSchemaType(
+                scoped_ptr<Value>(Value::CreateDoubleValue(88.8)).get()));
+  EXPECT_EQ(std::string(schema::kNumber),
+            JSONSchemaValidator::GetJSONSchemaType(scoped_ptr<Value>(
+                Value::CreateDoubleValue(pow(2.0, DBL_MANT_DIG) * 2)).get()));
+  EXPECT_EQ(std::string(schema::kNumber),
+            JSONSchemaValidator::GetJSONSchemaType(scoped_ptr<Value>(
+                Value::CreateDoubleValue(pow(-2.0, DBL_MANT_DIG) * 2)).get()));
+
+  EXPECT_EQ(std::string(schema::kString),
+            JSONSchemaValidator::GetJSONSchemaType(
+                scoped_ptr<Value>(Value::CreateStringValue("foo")).get()));
+  EXPECT_EQ(std::string(schema::kArray),
+            JSONSchemaValidator::GetJSONSchemaType(
+                scoped_ptr<Value>(new ListValue()).get()));
+  EXPECT_EQ(std::string(schema::kObject),
+            JSONSchemaValidator::GetJSONSchemaType(
+                scoped_ptr<Value>(new DictionaryValue()).get()));
+  EXPECT_EQ(std::string(schema::kNull),
+            JSONSchemaValidator::GetJSONSchemaType(
+                scoped_ptr<Value>(Value::CreateNullValue()).get()));
+}
+
+void JSONSchemaValidatorTestBase::TestTypes() {
+  scoped_ptr<DictionaryValue> schema(new DictionaryValue());
+
+  // valid
+  schema->SetString(schema::kType, schema::kObject);
+  ExpectValid(TEST_SOURCE, scoped_ptr<Value>(new DictionaryValue()).get(),
+              schema.get(), NULL);
+
+  schema->SetString(schema::kType, schema::kArray);
+  ExpectValid(TEST_SOURCE, scoped_ptr<Value>(new ListValue()).get(),
+              schema.get(), NULL);
+
+  schema->SetString(schema::kType, schema::kString);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateStringValue("foobar")).get(),
+              schema.get(), NULL);
+
+  schema->SetString(schema::kType, schema::kNumber);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateDoubleValue(88.8)).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateDoubleValue(42)).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateIntegerValue(42)).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateIntegerValue(0)).get(),
+              schema.get(), NULL);
+
+  schema->SetString(schema::kType, schema::kInteger);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateIntegerValue(42)).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateDoubleValue(42)).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateIntegerValue(0)).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(
+                  Value::CreateDoubleValue(pow(2.0, DBL_MANT_DIG))).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(
+                  Value::CreateDoubleValue(pow(-2.0, DBL_MANT_DIG))).get(),
+              schema.get(), NULL);
+
+  schema->SetString(schema::kType, schema::kBoolean);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateBooleanValue(false)).get(),
+              schema.get(), NULL);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateBooleanValue(true)).get(),
+              schema.get(), NULL);
+
+  schema->SetString(schema::kType, schema::kNull);
+  ExpectValid(TEST_SOURCE,
+              scoped_ptr<Value>(Value::CreateNullValue()).get(),
+              schema.get(), NULL);
+
+  // not valid
+  schema->SetString(schema::kType, schema::kObject);
+  ExpectNotValid(TEST_SOURCE, scoped_ptr<Value>(new ListValue()).get(),
+                 schema.get(), NULL, "",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kObject,
+                     schema::kArray));
+
+  schema->SetString(schema::kType, schema::kObject);
+  ExpectNotValid(TEST_SOURCE, scoped_ptr<Value>(Value::CreateNullValue()).get(),
+                 schema.get(), NULL, "",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kObject,
+                     schema::kNull));
+
+  schema->SetString(schema::kType, schema::kArray);
+  ExpectNotValid(TEST_SOURCE,
+                 scoped_ptr<Value>(Value::CreateIntegerValue(42)).get(),
+                 schema.get(), NULL, "",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kArray,
+                     schema::kInteger));
+
+  schema->SetString(schema::kType, schema::kString);
+  ExpectNotValid(TEST_SOURCE,
+                 scoped_ptr<Value>(Value::CreateIntegerValue(42)).get(),
+                 schema.get(), NULL, "",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kString,
+                     schema::kInteger));
+
+  schema->SetString(schema::kType, schema::kNumber);
+  ExpectNotValid(TEST_SOURCE,
+                 scoped_ptr<Value>(Value::CreateStringValue("42")).get(),
+                 schema.get(), NULL, "",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kNumber,
+                     schema::kString));
+
+  schema->SetString(schema::kType, schema::kInteger);
+  ExpectNotValid(TEST_SOURCE,
+                 scoped_ptr<Value>(Value::CreateDoubleValue(88.8)).get(),
+                 schema.get(), NULL, "",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kInteger,
+                     schema::kNumber));
+
+  schema->SetString(schema::kType, schema::kInteger);
+  ExpectNotValid(TEST_SOURCE,
+                 scoped_ptr<Value>(Value::CreateDoubleValue(88.8)).get(),
+                 schema.get(), NULL, "",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kInteger,
+                     schema::kNumber));
+
+  schema->SetString(schema::kType, schema::kBoolean);
+  ExpectNotValid(TEST_SOURCE,
+                 scoped_ptr<Value>(Value::CreateIntegerValue(1)).get(),
+                 schema.get(), NULL, "",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kBoolean,
+                     schema::kInteger));
+
+  schema->SetString(schema::kType, schema::kNull);
+  ExpectNotValid(TEST_SOURCE,
+                 scoped_ptr<Value>(Value::CreateBooleanValue(false)).get(),
+                 schema.get(), NULL, "",
+                 JSONSchemaValidator::FormatErrorMessage(
+                     JSONSchemaValidator::kInvalidType,
+                     schema::kNull,
+                     schema::kBoolean));
+}
diff --git a/chrome/common/json_schema_validator_unittest_base.h b/chrome/common/json_schema_validator_unittest_base.h
new file mode 100644
index 0000000..6208489
--- /dev/null
+++ b/chrome/common/json_schema_validator_unittest_base.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2010 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.
+
+#ifndef CHROME_COMMON_JSON_SCHEMA_VALIDATOR_UNITTEST_BASE_H_
+#define CHROME_COMMON_JSON_SCHEMA_VALIDATOR_UNITTEST_BASE_H_
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class Value;
+}
+
+// Base class for unit tests for JSONSchemaValidator. There is currently only
+// one implementation, JSONSchemaValidatorCPPTest.
+//
+// TODO(aa): Refactor chrome/test/data/json_schema_test.js into
+// JSONSchemaValidatorJSTest that inherits from this.
+class JSONSchemaValidatorTestBase : public testing::Test {
+ public:
+  enum ValidatorType {
+    CPP = 1,
+    JS = 2
+  };
+
+  explicit JSONSchemaValidatorTestBase(ValidatorType type);
+
+  void RunTests();
+
+ protected:
+  virtual void ExpectValid(const std::string& test_source,
+                           base::Value* instance,
+                           base::DictionaryValue* schema,
+                           base::ListValue* types) = 0;
+
+  virtual void ExpectNotValid(const std::string& test_source,
+                              base::Value* instance,
+                              base::DictionaryValue* schema,
+                              base::ListValue* types,
+                              const std::string& expected_error_path,
+                              const std::string& expected_error_message) = 0;
+
+ private:
+  void TestComplex();
+  void TestStringPattern();
+  void TestEnum();
+  void TestChoices();
+  void TestExtends();
+  void TestObject();
+  void TestTypeReference();
+  void TestArrayTuple();
+  void TestArrayNonTuple();
+  void TestString();
+  void TestNumber();
+  void TestTypeClassifier();
+  void TestTypes();
+
+  ValidatorType type_;
+};
+
+#endif  // CHROME_COMMON_JSON_SCHEMA_VALIDATOR_UNITTEST_BASE_H_
diff --git a/chrome/common/json_value_serializer_perftest.cc b/chrome/common/json_value_serializer_perftest.cc
new file mode 100644
index 0000000..791dc15
--- /dev/null
+++ b/chrome/common/json_value_serializer_perftest.cc
@@ -0,0 +1,90 @@
+// Copyright (c) 2012 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.
+
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/path_service.h"
+#include "base/perftimer.h"
+#include "base/string_util.h"
+#include "base/values.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/logging_chrome.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+class JSONValueSerializerTests : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    static const char* const kTestFilenames[] = {
+      "serializer_nested_test.js",
+      "serializer_test.js",
+      "serializer_test_nowhitespace.js",
+    };
+
+    // Load test cases
+    for (size_t i = 0; i < arraysize(kTestFilenames); ++i) {
+      FilePath filename;
+      EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &filename));
+      filename = filename.AppendASCII(kTestFilenames[i]);
+
+      std::string test_case;
+      EXPECT_TRUE(file_util::ReadFileToString(filename, &test_case));
+      test_cases_.push_back(test_case);
+    }
+  }
+
+  // Holds json strings to be tested.
+  std::vector<std::string> test_cases_;
+};
+
+}  // namespace
+
+// Test deserialization of a json string into a Value object.  We run the test
+// using 3 sample strings for both the current decoder and jsoncpp's decoder.
+TEST_F(JSONValueSerializerTests, Reading) {
+  printf("\n");
+  const int kIterations = 100000;
+
+  // Test chrome json implementation
+  PerfTimeLogger chrome_timer("chrome");
+  for (int i = 0; i < kIterations; ++i) {
+    for (size_t j = 0; j < test_cases_.size(); ++j) {
+      JSONStringValueSerializer reader(test_cases_[j]);
+      scoped_ptr<Value> root(reader.Deserialize(NULL, NULL));
+      ASSERT_TRUE(root.get());
+    }
+  }
+  chrome_timer.Done();
+}
+
+TEST_F(JSONValueSerializerTests, CompactWriting) {
+  printf("\n");
+  const int kIterations = 100000;
+  // Convert test cases to Value objects.
+  std::vector<Value*> test_cases;
+  for (size_t i = 0; i < test_cases_.size(); ++i) {
+    JSONStringValueSerializer reader(test_cases_[i]);
+    Value* root = reader.Deserialize(NULL, NULL);
+    ASSERT_TRUE(root);
+    test_cases.push_back(root);
+  }
+
+  PerfTimeLogger chrome_timer("chrome");
+  for (int i = 0; i < kIterations; ++i) {
+    for (size_t j = 0; j < test_cases.size(); ++j) {
+      std::string json;
+      JSONStringValueSerializer reader(&json);
+      ASSERT_TRUE(reader.Serialize(*test_cases[j]));
+    }
+  }
+  chrome_timer.Done();
+
+  // Clean up test cases.
+  for (size_t i = 0; i < test_cases.size(); ++i) {
+    delete test_cases[i];
+    test_cases[i] = NULL;
+  }
+}
diff --git a/chrome/common/json_value_serializer_unittest.cc b/chrome/common/json_value_serializer_unittest.cc
new file mode 100644
index 0000000..11982ec
--- /dev/null
+++ b/chrome/common/json_value_serializer_unittest.cc
@@ -0,0 +1,339 @@
+// Copyright (c) 2012 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.
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/json/json_writer.h"
+#include "base/path_service.h"
+#include "base/scoped_temp_dir.h"
+#include "base/string16.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/common/chrome_paths.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(JSONValueSerializerTest, Roundtrip) {
+  const std::string original_serialization =
+    "{\"bool\":true,\"double\":3.14,\"int\":42,\"list\":[1,2],\"null\":null}";
+  JSONStringValueSerializer serializer(original_serialization);
+  scoped_ptr<Value> root(serializer.Deserialize(NULL, NULL));
+  ASSERT_TRUE(root.get());
+  ASSERT_TRUE(root->IsType(Value::TYPE_DICTIONARY));
+
+  DictionaryValue* root_dict = static_cast<DictionaryValue*>(root.get());
+
+  Value* null_value = NULL;
+  ASSERT_TRUE(root_dict->Get("null", &null_value));
+  ASSERT_TRUE(null_value);
+  ASSERT_TRUE(null_value->IsType(Value::TYPE_NULL));
+
+  bool bool_value = false;
+  ASSERT_TRUE(root_dict->GetBoolean("bool", &bool_value));
+  ASSERT_TRUE(bool_value);
+
+  int int_value = 0;
+  ASSERT_TRUE(root_dict->GetInteger("int", &int_value));
+  ASSERT_EQ(42, int_value);
+
+  double double_value = 0.0;
+  ASSERT_TRUE(root_dict->GetDouble("double", &double_value));
+  ASSERT_DOUBLE_EQ(3.14, double_value);
+
+  // We shouldn't be able to write using this serializer, since it was
+  // initialized with a const string.
+  ASSERT_FALSE(serializer.Serialize(*root_dict));
+
+  std::string test_serialization = "";
+  JSONStringValueSerializer mutable_serializer(&test_serialization);
+  ASSERT_TRUE(mutable_serializer.Serialize(*root_dict));
+  ASSERT_EQ(original_serialization, test_serialization);
+
+  mutable_serializer.set_pretty_print(true);
+  ASSERT_TRUE(mutable_serializer.Serialize(*root_dict));
+  // JSON output uses a different newline style on Windows than on other
+  // platforms.
+#if defined(OS_WIN)
+#define JSON_NEWLINE "\r\n"
+#else
+#define JSON_NEWLINE "\n"
+#endif
+  const std::string pretty_serialization =
+    "{" JSON_NEWLINE
+    "   \"bool\": true," JSON_NEWLINE
+    "   \"double\": 3.14," JSON_NEWLINE
+    "   \"int\": 42," JSON_NEWLINE
+    "   \"list\": [ 1, 2 ]," JSON_NEWLINE
+    "   \"null\": null" JSON_NEWLINE
+    "}" JSON_NEWLINE;
+#undef JSON_NEWLINE
+  ASSERT_EQ(pretty_serialization, test_serialization);
+}
+
+TEST(JSONValueSerializerTest, StringEscape) {
+  string16 all_chars;
+  for (int i = 1; i < 256; ++i) {
+    all_chars += static_cast<char16>(i);
+  }
+  // Generated in in Firefox using the following js (with an extra backslash for
+  // double quote):
+  // var s = '';
+  // for (var i = 1; i < 256; ++i) { s += String.fromCharCode(i); }
+  // uneval(s).replace(/\\/g, "\\\\");
+  std::string all_chars_expected =
+      "\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000B\\f\\r"
+      "\\u000E\\u000F\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017"
+      "\\u0018\\u0019\\u001A\\u001B\\u001C\\u001D\\u001E\\u001F !\\\""
+      "#$%&'()*+,-./0123456789:;\\u003C=\\u003E?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\"
+      "\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\\u007F\\u0080\\u0081\\u0082\\u0083"
+      "\\u0084\\u0085\\u0086\\u0087\\u0088\\u0089\\u008A\\u008B\\u008C\\u008D"
+      "\\u008E\\u008F\\u0090\\u0091\\u0092\\u0093\\u0094\\u0095\\u0096\\u0097"
+      "\\u0098\\u0099\\u009A\\u009B\\u009C\\u009D\\u009E\\u009F\\u00A0\\u00A1"
+      "\\u00A2\\u00A3\\u00A4\\u00A5\\u00A6\\u00A7\\u00A8\\u00A9\\u00AA\\u00AB"
+      "\\u00AC\\u00AD\\u00AE\\u00AF\\u00B0\\u00B1\\u00B2\\u00B3\\u00B4\\u00B5"
+      "\\u00B6\\u00B7\\u00B8\\u00B9\\u00BA\\u00BB\\u00BC\\u00BD\\u00BE\\u00BF"
+      "\\u00C0\\u00C1\\u00C2\\u00C3\\u00C4\\u00C5\\u00C6\\u00C7\\u00C8\\u00C9"
+      "\\u00CA\\u00CB\\u00CC\\u00CD\\u00CE\\u00CF\\u00D0\\u00D1\\u00D2\\u00D3"
+      "\\u00D4\\u00D5\\u00D6\\u00D7\\u00D8\\u00D9\\u00DA\\u00DB\\u00DC\\u00DD"
+      "\\u00DE\\u00DF\\u00E0\\u00E1\\u00E2\\u00E3\\u00E4\\u00E5\\u00E6\\u00E7"
+      "\\u00E8\\u00E9\\u00EA\\u00EB\\u00EC\\u00ED\\u00EE\\u00EF\\u00F0\\u00F1"
+      "\\u00F2\\u00F3\\u00F4\\u00F5\\u00F6\\u00F7\\u00F8\\u00F9\\u00FA\\u00FB"
+      "\\u00FC\\u00FD\\u00FE\\u00FF";
+
+  std::string expected_output = "{\"all_chars\":\"" + all_chars_expected +
+                                 "\"}";
+  // Test JSONWriter interface
+  std::string output_js;
+  DictionaryValue valueRoot;
+  valueRoot.SetString("all_chars", all_chars);
+  base::JSONWriter::Write(&valueRoot, &output_js);
+  ASSERT_EQ(expected_output, output_js);
+
+  // Test JSONValueSerializer interface (uses JSONWriter).
+  JSONStringValueSerializer serializer(&output_js);
+  ASSERT_TRUE(serializer.Serialize(valueRoot));
+  ASSERT_EQ(expected_output, output_js);
+}
+
+TEST(JSONValueSerializerTest, UnicodeStrings) {
+  // unicode string json -> escaped ascii text
+  DictionaryValue root;
+  string16 test(WideToUTF16(L"\x7F51\x9875"));
+  root.SetString("web", test);
+
+  std::string expected = "{\"web\":\"\\u7F51\\u9875\"}";
+
+  std::string actual;
+  JSONStringValueSerializer serializer(&actual);
+  ASSERT_TRUE(serializer.Serialize(root));
+  ASSERT_EQ(expected, actual);
+
+  // escaped ascii text -> json
+  JSONStringValueSerializer deserializer(expected);
+  scoped_ptr<Value> deserial_root(deserializer.Deserialize(NULL, NULL));
+  ASSERT_TRUE(deserial_root.get());
+  DictionaryValue* dict_root =
+      static_cast<DictionaryValue*>(deserial_root.get());
+  string16 web_value;
+  ASSERT_TRUE(dict_root->GetString("web", &web_value));
+  ASSERT_EQ(test, web_value);
+}
+
+TEST(JSONValueSerializerTest, HexStrings) {
+  // hex string json -> escaped ascii text
+  DictionaryValue root;
+  string16 test(WideToUTF16(L"\x01\x02"));
+  root.SetString("test", test);
+
+  std::string expected = "{\"test\":\"\\u0001\\u0002\"}";
+
+  std::string actual;
+  JSONStringValueSerializer serializer(&actual);
+  ASSERT_TRUE(serializer.Serialize(root));
+  ASSERT_EQ(expected, actual);
+
+  // escaped ascii text -> json
+  JSONStringValueSerializer deserializer(expected);
+  scoped_ptr<Value> deserial_root(deserializer.Deserialize(NULL, NULL));
+  ASSERT_TRUE(deserial_root.get());
+  DictionaryValue* dict_root =
+      static_cast<DictionaryValue*>(deserial_root.get());
+  string16 test_value;
+  ASSERT_TRUE(dict_root->GetString("test", &test_value));
+  ASSERT_EQ(test, test_value);
+
+  // Test converting escaped regular chars
+  std::string escaped_chars = "{\"test\":\"\\u0067\\u006f\"}";
+  JSONStringValueSerializer deserializer2(escaped_chars);
+  deserial_root.reset(deserializer2.Deserialize(NULL, NULL));
+  ASSERT_TRUE(deserial_root.get());
+  dict_root = static_cast<DictionaryValue*>(deserial_root.get());
+  ASSERT_TRUE(dict_root->GetString("test", &test_value));
+  ASSERT_EQ(ASCIIToUTF16("go"), test_value);
+}
+
+TEST(JSONValueSerializerTest, AllowTrailingComma) {
+  scoped_ptr<Value> root;
+  scoped_ptr<Value> root_expected;
+  std::string test_with_commas("{\"key\": [true,],}");
+  std::string test_no_commas("{\"key\": [true]}");
+
+  JSONStringValueSerializer serializer(test_with_commas);
+  serializer.set_allow_trailing_comma(true);
+  JSONStringValueSerializer serializer_expected(test_no_commas);
+  root.reset(serializer.Deserialize(NULL, NULL));
+  ASSERT_TRUE(root.get());
+  root_expected.reset(serializer_expected.Deserialize(NULL, NULL));
+  ASSERT_TRUE(root_expected.get());
+  ASSERT_TRUE(root->Equals(root_expected.get()));
+}
+
+namespace {
+
+void ValidateJsonList(const std::string& json) {
+  scoped_ptr<Value> root(base::JSONReader::Read(json));
+  ASSERT_TRUE(root.get() && root->IsType(Value::TYPE_LIST));
+  ListValue* list = static_cast<ListValue*>(root.get());
+  ASSERT_EQ(1U, list->GetSize());
+  Value* elt = NULL;
+  ASSERT_TRUE(list->Get(0, &elt));
+  int value = 0;
+  ASSERT_TRUE(elt && elt->GetAsInteger(&value));
+  ASSERT_EQ(1, value);
+}
+
+}  // namespace
+
+TEST(JSONValueSerializerTest, JSONReaderComments) {
+  ValidateJsonList("[ // 2, 3, ignore me ] \n1 ]");
+  ValidateJsonList("[ /* 2, \n3, ignore me ]*/ \n1 ]");
+  ValidateJsonList("//header\n[ // 2, \n// 3, \n1 ]// footer");
+  ValidateJsonList("/*\n[ // 2, \n// 3, \n1 ]*/[1]");
+  ValidateJsonList("[ 1 /* one */ ] /* end */");
+  ValidateJsonList("[ 1 //// ,2\r\n ]");
+
+  scoped_ptr<Value> root;
+
+  // It's ok to have a comment in a string.
+  root.reset(base::JSONReader::Read("[\"// ok\\n /* foo */ \"]"));
+  ASSERT_TRUE(root.get() && root->IsType(Value::TYPE_LIST));
+  ListValue* list = static_cast<ListValue*>(root.get());
+  ASSERT_EQ(1U, list->GetSize());
+  Value* elt = NULL;
+  ASSERT_TRUE(list->Get(0, &elt));
+  std::string value;
+  ASSERT_TRUE(elt && elt->GetAsString(&value));
+  ASSERT_EQ("// ok\n /* foo */ ", value);
+
+  // You can't nest comments.
+  root.reset(base::JSONReader::Read("/* /* inner */ outer */ [ 1 ]"));
+  ASSERT_FALSE(root.get());
+
+  // Not a open comment token.
+  root.reset(base::JSONReader::Read("/ * * / [1]"));
+  ASSERT_FALSE(root.get());
+}
+
+class JSONFileValueSerializerTest : public testing::Test {
+protected:
+  virtual void SetUp() {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+  }
+
+  ScopedTempDir temp_dir_;
+};
+
+TEST_F(JSONFileValueSerializerTest, Roundtrip) {
+  FilePath original_file_path;
+  ASSERT_TRUE(
+    PathService::Get(chrome::DIR_TEST_DATA, &original_file_path));
+  original_file_path =
+      original_file_path.Append(FILE_PATH_LITERAL("serializer_test.js"));
+
+  ASSERT_TRUE(file_util::PathExists(original_file_path));
+
+  JSONFileValueSerializer deserializer(original_file_path);
+  scoped_ptr<Value> root;
+  root.reset(deserializer.Deserialize(NULL, NULL));
+
+  ASSERT_TRUE(root.get());
+  ASSERT_TRUE(root->IsType(Value::TYPE_DICTIONARY));
+
+  DictionaryValue* root_dict = static_cast<DictionaryValue*>(root.get());
+
+  Value* null_value = NULL;
+  ASSERT_TRUE(root_dict->Get("null", &null_value));
+  ASSERT_TRUE(null_value);
+  ASSERT_TRUE(null_value->IsType(Value::TYPE_NULL));
+
+  bool bool_value = false;
+  ASSERT_TRUE(root_dict->GetBoolean("bool", &bool_value));
+  ASSERT_TRUE(bool_value);
+
+  int int_value = 0;
+  ASSERT_TRUE(root_dict->GetInteger("int", &int_value));
+  ASSERT_EQ(42, int_value);
+
+  std::string string_value;
+  ASSERT_TRUE(root_dict->GetString("string", &string_value));
+  ASSERT_EQ("hello", string_value);
+
+  // Now try writing.
+  const FilePath written_file_path =
+      temp_dir_.path().Append(FILE_PATH_LITERAL("test_output.js"));
+
+  ASSERT_FALSE(file_util::PathExists(written_file_path));
+  JSONFileValueSerializer serializer(written_file_path);
+  ASSERT_TRUE(serializer.Serialize(*root));
+  ASSERT_TRUE(file_util::PathExists(written_file_path));
+
+  // Now compare file contents.
+  EXPECT_TRUE(file_util::TextContentsEqual(original_file_path,
+                                           written_file_path));
+  EXPECT_TRUE(file_util::Delete(written_file_path, false));
+}
+
+TEST_F(JSONFileValueSerializerTest, RoundtripNested) {
+  FilePath original_file_path;
+  ASSERT_TRUE(
+    PathService::Get(chrome::DIR_TEST_DATA, &original_file_path));
+  original_file_path =
+      original_file_path.Append(FILE_PATH_LITERAL("serializer_nested_test.js"));
+
+  ASSERT_TRUE(file_util::PathExists(original_file_path));
+
+  JSONFileValueSerializer deserializer(original_file_path);
+  scoped_ptr<Value> root;
+  root.reset(deserializer.Deserialize(NULL, NULL));
+  ASSERT_TRUE(root.get());
+
+  // Now try writing.
+  FilePath written_file_path =
+      temp_dir_.path().Append(FILE_PATH_LITERAL("test_output.js"));
+
+  ASSERT_FALSE(file_util::PathExists(written_file_path));
+  JSONFileValueSerializer serializer(written_file_path);
+  ASSERT_TRUE(serializer.Serialize(*root));
+  ASSERT_TRUE(file_util::PathExists(written_file_path));
+
+  // Now compare file contents.
+  EXPECT_TRUE(file_util::TextContentsEqual(original_file_path,
+                                           written_file_path));
+  EXPECT_TRUE(file_util::Delete(written_file_path, false));
+}
+
+TEST_F(JSONFileValueSerializerTest, NoWhitespace) {
+  FilePath source_file_path;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_file_path));
+  source_file_path = source_file_path.Append(
+      FILE_PATH_LITERAL("serializer_test_nowhitespace.js"));
+  ASSERT_TRUE(file_util::PathExists(source_file_path));
+  JSONFileValueSerializer serializer(source_file_path);
+  scoped_ptr<Value> root;
+  root.reset(serializer.Deserialize(NULL, NULL));
+  ASSERT_TRUE(root.get());
+}
diff --git a/chrome/common/jstemplate_builder.cc b/chrome/common/jstemplate_builder.cc
new file mode 100644
index 0000000..e8da8bd
--- /dev/null
+++ b/chrome/common/jstemplate_builder.cc
@@ -0,0 +1,156 @@
+// Copyright (c) 2012 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.
+
+// A helper function for using JsTemplate. See jstemplate_builder.h for more
+// info.
+
+#include "chrome/common/jstemplate_builder.h"
+
+#include "base/json/json_file_value_serializer.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "grit/common_resources.h"
+#include "ui/base/layout.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace {
+
+// Non-zero when building version 2 templates. See UseVersion2 class.
+int g_version2 = 0;
+
+}  // namespace
+
+namespace jstemplate_builder {
+
+UseVersion2::UseVersion2() {
+  g_version2++;
+}
+
+UseVersion2::~UseVersion2() {
+  g_version2--;
+}
+
+std::string GetTemplateHtml(const base::StringPiece& html_template,
+                            const DictionaryValue* json,
+                            const base::StringPiece& template_id) {
+  std::string output(html_template.data(), html_template.size());
+  AppendJsonHtml(json, &output);
+  AppendJsTemplateSourceHtml(&output);
+  AppendJsTemplateProcessHtml(template_id, &output);
+  return output;
+}
+
+std::string GetI18nTemplateHtml(const base::StringPiece& html_template,
+                                const DictionaryValue* json) {
+  std::string output(html_template.data(), html_template.size());
+  AppendJsonHtml(json, &output);
+  AppendI18nTemplateSourceHtml(&output);
+  AppendI18nTemplateProcessHtml(&output);
+  return output;
+}
+
+std::string GetTemplatesHtml(const base::StringPiece& html_template,
+                             const DictionaryValue* json,
+                             const base::StringPiece& template_id) {
+  std::string output(html_template.data(), html_template.size());
+  AppendI18nTemplateSourceHtml(&output);
+  AppendJsTemplateSourceHtml(&output);
+  AppendJsonHtml(json, &output);
+  AppendI18nTemplateProcessHtml(&output);
+  AppendJsTemplateProcessHtml(template_id, &output);
+  return output;
+}
+
+void AppendJsonHtml(const DictionaryValue* json, std::string* output) {
+  std::string javascript_string;
+  jstemplate_builder::AppendJsonJS(json, &javascript_string);
+
+  // </ confuses the HTML parser because it could be a </script> tag.  So we
+  // replace </ with <\/.  The extra \ will be ignored by the JS engine.
+  ReplaceSubstringsAfterOffset(&javascript_string, 0, "</", "<\\/");
+
+  output->append("<script>");
+  output->append(javascript_string);
+  output->append("</script>");
+}
+
+void AppendJsonJS(const DictionaryValue* json, std::string* output) {
+  // Convert the template data to a json string.
+  DCHECK(json) << "must include json data structure";
+
+  std::string jstext;
+  JSONStringValueSerializer serializer(&jstext);
+  serializer.Serialize(*json);
+  output->append(g_version2 ? "loadTimeData.data = " : "var templateData = ");
+  output->append(jstext);
+  output->append(";");
+}
+
+void AppendJsTemplateSourceHtml(std::string* output) {
+  // fetch and cache the pointer of the jstemplate resource source text.
+  static const base::StringPiece jstemplate_src(
+      ResourceBundle::GetSharedInstance().GetRawDataResource(
+          IDR_JSTEMPLATE_JS));
+
+  if (jstemplate_src.empty()) {
+    NOTREACHED() << "Unable to get jstemplate src";
+    return;
+  }
+
+  output->append("<script>");
+  output->append(jstemplate_src.data(), jstemplate_src.size());
+  output->append("</script>");
+}
+
+void AppendJsTemplateProcessHtml(const base::StringPiece& template_id,
+                                 std::string* output) {
+  output->append("<script>");
+  output->append("var tp = document.getElementById('");
+  output->append(template_id.data(), template_id.size());
+  output->append("');");
+  output->append("jstProcess(new JsEvalContext(templateData), tp);");
+  output->append("</script>");
+}
+
+void AppendI18nTemplateSourceHtml(std::string* output) {
+  // fetch and cache the pointer of the jstemplate resource source text.
+  static const base::StringPiece i18n_template_src(
+      ResourceBundle::GetSharedInstance().GetRawDataResource(
+          IDR_I18N_TEMPLATE_JS));
+  static const base::StringPiece i18n_template2_src(
+      ResourceBundle::GetSharedInstance().GetRawDataResource(
+          IDR_I18N_TEMPLATE2_JS));
+  const base::StringPiece* template_src = g_version2 ?
+      &i18n_template2_src : &i18n_template_src;
+
+  if (template_src->empty()) {
+    NOTREACHED() << "Unable to get i18n template src";
+    return;
+  }
+
+  output->append("<script>");
+  output->append(template_src->data(), template_src->size());
+  output->append("</script>");
+}
+
+void AppendI18nTemplateProcessHtml(std::string* output) {
+  if (g_version2)
+    return;
+
+  static const base::StringPiece i18n_process_src(
+      ResourceBundle::GetSharedInstance().GetRawDataResource(
+          IDR_I18N_PROCESS_JS));
+
+  if (i18n_process_src.empty()) {
+    NOTREACHED() << "Unable to get i18n process src";
+    return;
+  }
+
+  output->append("<script>");
+  output->append(i18n_process_src.data(), i18n_process_src.size());
+  output->append("</script>");
+}
+
+}  // namespace jstemplate_builder
diff --git a/chrome/common/jstemplate_builder.h b/chrome/common/jstemplate_builder.h
new file mode 100644
index 0000000..c867e73
--- /dev/null
+++ b/chrome/common/jstemplate_builder.h
@@ -0,0 +1,86 @@
+// Copyright (c) 2012 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.
+
+// This provides some helper methods for building and rendering an
+// internal html page.  The flow is as follows:
+// - instantiate a builder given a webframe that we're going to render content
+//   into
+// - load the template html and load the jstemplate javascript into the frame
+// - given a json data object, run the jstemplate javascript which fills in
+//   template values
+
+#ifndef CHROME_COMMON_JSTEMPLATE_BUILDER_H_
+#define CHROME_COMMON_JSTEMPLATE_BUILDER_H_
+
+#include <string>
+
+#include "base/string_piece.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace jstemplate_builder {
+
+// While an object of this class is in scope, the template builder will output
+// version 2 html. Version 2 uses load_time_data.js and i18n_template2.js, and
+// should soon become the default.
+class UseVersion2 {
+ public:
+  UseVersion2();
+  ~UseVersion2();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(UseVersion2);
+};
+
+// A helper function that generates a string of HTML to be loaded.  The
+// string includes the HTML and the javascript code necessary to generate the
+// full page with support for JsTemplates.
+std::string GetTemplateHtml(const base::StringPiece& html_template,
+                            const base::DictionaryValue* json,
+                            const base::StringPiece& template_id);
+
+// A helper function that generates a string of HTML to be loaded.  The
+// string includes the HTML and the javascript code necessary to generate the
+// full page with support for i18n Templates.
+std::string GetI18nTemplateHtml(const base::StringPiece& html_template,
+                                const base::DictionaryValue* json);
+
+// A helper function that generates a string of HTML to be loaded.  The
+// string includes the HTML and the javascript code necessary to generate the
+// full page with support for both i18n Templates and JsTemplates.
+std::string GetTemplatesHtml(const base::StringPiece& html_template,
+                             const base::DictionaryValue* json,
+                             const base::StringPiece& template_id);
+
+// The following functions build up the different parts that the above
+// templates use.
+
+// Appends a script tag with a variable name |templateData| that has the JSON
+// assigned to it.
+void AppendJsonHtml(const base::DictionaryValue* json, std::string* output);
+
+// Same as AppendJsonHtml(), except does not include the <script></script>
+// tag wrappers.
+void AppendJsonJS(const base::DictionaryValue* json, std::string* output);
+
+// Appends the source for JsTemplates in a script tag.
+void AppendJsTemplateSourceHtml(std::string* output);
+
+// Appends the code that processes the JsTemplate with the JSON. You should
+// call AppendJsTemplateSourceHtml and AppendJsonHtml before calling this.
+void AppendJsTemplateProcessHtml(const base::StringPiece& template_id,
+                                 std::string* output);
+
+// Appends the source for i18n Templates in a script tag.
+void AppendI18nTemplateSourceHtml(std::string* output);
+
+// Appends the code that processes the i18n Template with the JSON. You
+// should call AppendJsTemplateSourceHtml and AppendJsonHtml before calling
+// this.
+void AppendI18nTemplateProcessHtml(std::string* output);
+
+}  // namespace jstemplate_builder
+#endif  // CHROME_COMMON_JSTEMPLATE_BUILDER_H_
diff --git a/chrome/common/localized_error.cc b/chrome/common/localized_error.cc
new file mode 100644
index 0000000..832ad99
--- /dev/null
+++ b/chrome/common/localized_error.cc
@@ -0,0 +1,728 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/localized_error.h"
+
+#include "base/i18n/rtl.h"
+#include "base/logging.h"
+#include "base/string16.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/extensions/extension_icon_set.h"
+#include "chrome/common/extensions/extension_set.h"
+#include "googleurl/src/gurl.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "net/base/escape.h"
+#include "net/base/net_errors.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURLError.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "webkit/glue/webkit_glue.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+using WebKit::WebURLError;
+
+namespace {
+
+static const char kRedirectLoopLearnMoreUrl[] =
+    "https://www.google.com/support/chrome/bin/answer.py?answer=95626";
+static const char kWeakDHKeyLearnMoreUrl[] =
+    "http://sites.google.com/a/chromium.org/dev/"
+    "err_ssl_weak_server_ephemeral_dh_key";
+#if defined(OS_CHROMEOS)
+static const char kAppWarningLearnMoreUrl[] =
+    "chrome-extension://honijodknafkokifofgiaalefdiedpko/main.html"
+    "?answer=1721911";
+#endif  // defined(OS_CHROMEOS)
+
+enum NAV_SUGGESTIONS {
+  SUGGEST_NONE     = 0,
+  SUGGEST_RELOAD   = 1 << 0,
+  SUGGEST_HOSTNAME = 1 << 1,
+  SUGGEST_CHECK_CONNECTION = 1 << 2,
+  SUGGEST_DNS_CONFIG = 1 << 3,
+  SUGGEST_FIREWALL_CONFIG = 1 << 4,
+  SUGGEST_PROXY_CONFIG = 1 << 5,
+  SUGGEST_DISABLE_EXTENSION = 1 << 6,
+  SUGGEST_LEARNMORE = 1 << 7,
+};
+
+struct LocalizedErrorMap {
+  int error_code;
+  unsigned int title_resource_id;
+  unsigned int heading_resource_id;
+  unsigned int summary_resource_id;
+  unsigned int details_resource_id;
+  int suggestions;  // Bitmap of SUGGEST_* values.
+};
+
+const LocalizedErrorMap net_error_options[] = {
+  {net::ERR_TIMED_OUT,
+   IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+   IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+   IDS_ERRORPAGES_SUMMARY_TIMED_OUT,
+   IDS_ERRORPAGES_DETAILS_TIMED_OUT,
+   SUGGEST_RELOAD | SUGGEST_CHECK_CONNECTION | SUGGEST_FIREWALL_CONFIG |
+       SUGGEST_PROXY_CONFIG,
+  },
+  {net::ERR_CONNECTION_TIMED_OUT,
+   IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+   IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+   IDS_ERRORPAGES_SUMMARY_TIMED_OUT,
+   IDS_ERRORPAGES_DETAILS_TIMED_OUT,
+   SUGGEST_RELOAD | SUGGEST_CHECK_CONNECTION | SUGGEST_FIREWALL_CONFIG |
+       SUGGEST_PROXY_CONFIG,
+  },
+  {net::ERR_CONNECTION_CLOSED,
+   IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+   IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+   IDS_ERRORPAGES_SUMMARY_NOT_AVAILABLE,
+   IDS_ERRORPAGES_DETAILS_CONNECTION_CLOSED,
+   SUGGEST_RELOAD,
+  },
+  {net::ERR_CONNECTION_RESET,
+   IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+   IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+   IDS_ERRORPAGES_SUMMARY_CONNECTION_RESET,
+   IDS_ERRORPAGES_DETAILS_CONNECTION_RESET,
+   SUGGEST_RELOAD | SUGGEST_CHECK_CONNECTION | SUGGEST_FIREWALL_CONFIG |
+       SUGGEST_PROXY_CONFIG,
+  },
+  {net::ERR_CONNECTION_REFUSED,
+   IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+   IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+   IDS_ERRORPAGES_SUMMARY_CONNECTION_REFUSED,
+   IDS_ERRORPAGES_DETAILS_CONNECTION_REFUSED,
+   SUGGEST_RELOAD | SUGGEST_CHECK_CONNECTION | SUGGEST_FIREWALL_CONFIG |
+       SUGGEST_PROXY_CONFIG,
+  },
+  {net::ERR_CONNECTION_FAILED,
+   IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+   IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+   IDS_ERRORPAGES_SUMMARY_NOT_AVAILABLE,
+   IDS_ERRORPAGES_DETAILS_CONNECTION_FAILED,
+   SUGGEST_RELOAD,
+  },
+  {net::ERR_NAME_NOT_RESOLVED,
+   IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+   IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+   IDS_ERRORPAGES_SUMMARY_NAME_NOT_RESOLVED,
+   IDS_ERRORPAGES_DETAILS_NAME_NOT_RESOLVED,
+   SUGGEST_RELOAD | SUGGEST_CHECK_CONNECTION | SUGGEST_DNS_CONFIG |
+       SUGGEST_FIREWALL_CONFIG | SUGGEST_PROXY_CONFIG,
+  },
+  {net::ERR_ADDRESS_UNREACHABLE,
+   IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+   IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+   IDS_ERRORPAGES_SUMMARY_ADDRESS_UNREACHABLE,
+   IDS_ERRORPAGES_DETAILS_ADDRESS_UNREACHABLE,
+   SUGGEST_RELOAD | SUGGEST_FIREWALL_CONFIG | SUGGEST_PROXY_CONFIG,
+  },
+  {net::ERR_NETWORK_ACCESS_DENIED,
+   IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+   IDS_ERRORPAGES_HEADING_NETWORK_ACCESS_DENIED,
+   IDS_ERRORPAGES_SUMMARY_NETWORK_ACCESS_DENIED,
+   IDS_ERRORPAGES_DETAILS_NETWORK_ACCESS_DENIED,
+   SUGGEST_FIREWALL_CONFIG,
+  },
+  {net::ERR_PROXY_CONNECTION_FAILED,
+   IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+   IDS_ERRORPAGES_HEADING_PROXY_CONNECTION_FAILED,
+   IDS_ERRORPAGES_SUMMARY_PROXY_CONNECTION_FAILED,
+   IDS_ERRORPAGES_DETAILS_PROXY_CONNECTION_FAILED,
+   SUGGEST_PROXY_CONFIG,
+  },
+  {net::ERR_INTERNET_DISCONNECTED,
+   IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+   IDS_ERRORPAGES_HEADING_INTERNET_DISCONNECTED,
+   IDS_ERRORPAGES_SUMMARY_INTERNET_DISCONNECTED,
+   IDS_ERRORPAGES_DETAILS_INTERNET_DISCONNECTED,
+   SUGGEST_NONE,
+  },
+  {net::ERR_FILE_NOT_FOUND,
+   IDS_ERRORPAGES_TITLE_NOT_FOUND,
+   IDS_ERRORPAGES_HEADING_NOT_FOUND,
+   IDS_ERRORPAGES_SUMMARY_NOT_FOUND,
+   IDS_ERRORPAGES_DETAILS_FILE_NOT_FOUND,
+   SUGGEST_NONE,
+  },
+  {net::ERR_CACHE_MISS,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_CACHE_MISS,
+   IDS_ERRORPAGES_SUMMARY_CACHE_MISS,
+   IDS_ERRORPAGES_DETAILS_CACHE_MISS,
+   SUGGEST_RELOAD,
+  },
+  {net::ERR_CACHE_READ_FAILURE,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_CACHE_READ_FAILURE,
+   IDS_ERRORPAGES_SUMMARY_CACHE_READ_FAILURE,
+   IDS_ERRORPAGES_DETAILS_CACHE_READ_FAILURE,
+   SUGGEST_RELOAD,
+  },
+  {net::ERR_NETWORK_IO_SUSPENDED,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_NETWORK_IO_SUSPENDED,
+   IDS_ERRORPAGES_SUMMARY_NETWORK_IO_SUSPENDED,
+   IDS_ERRORPAGES_DETAILS_NETWORK_IO_SUSPENDED,
+   SUGGEST_RELOAD,
+  },
+  {net::ERR_TOO_MANY_REDIRECTS,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_TOO_MANY_REDIRECTS,
+   IDS_ERRORPAGES_SUMMARY_TOO_MANY_REDIRECTS,
+   IDS_ERRORPAGES_DETAILS_TOO_MANY_REDIRECTS,
+   SUGGEST_RELOAD | SUGGEST_LEARNMORE,
+  },
+  {net::ERR_EMPTY_RESPONSE,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_EMPTY_RESPONSE,
+   IDS_ERRORPAGES_SUMMARY_EMPTY_RESPONSE,
+   IDS_ERRORPAGES_DETAILS_EMPTY_RESPONSE,
+   SUGGEST_RELOAD,
+  },
+  {net::ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_DUPLICATE_HEADERS,
+   IDS_ERRORPAGES_SUMMARY_DUPLICATE_HEADERS,
+   IDS_ERRORPAGES_DETAILS_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH,
+   SUGGEST_NONE,
+  },
+  {net::ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_DUPLICATE_HEADERS,
+   IDS_ERRORPAGES_SUMMARY_DUPLICATE_HEADERS,
+   IDS_ERRORPAGES_DETAILS_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION,
+   SUGGEST_NONE,
+  },
+  {net::ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_DUPLICATE_HEADERS,
+   IDS_ERRORPAGES_SUMMARY_DUPLICATE_HEADERS,
+   IDS_ERRORPAGES_DETAILS_RESPONSE_HEADERS_MULTIPLE_LOCATION,
+   SUGGEST_NONE,
+  },
+  {net::ERR_CONTENT_LENGTH_MISMATCH,
+   IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+   IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+   IDS_ERRORPAGES_SUMMARY_NOT_AVAILABLE,
+   IDS_ERRORPAGES_DETAILS_CONNECTION_CLOSED,
+   SUGGEST_RELOAD,
+  },
+  {net::ERR_INCOMPLETE_CHUNKED_ENCODING,
+   IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+   IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+   IDS_ERRORPAGES_SUMMARY_NOT_AVAILABLE,
+   IDS_ERRORPAGES_DETAILS_CONNECTION_CLOSED,
+   SUGGEST_RELOAD,
+  },
+  {net::ERR_SSL_PROTOCOL_ERROR,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_SSL_PROTOCOL_ERROR,
+   IDS_ERRORPAGES_SUMMARY_SSL_PROTOCOL_ERROR,
+   IDS_ERRORPAGES_DETAILS_SSL_PROTOCOL_ERROR,
+   SUGGEST_NONE,
+  },
+  {net::ERR_SSL_UNSAFE_NEGOTIATION,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_SSL_PROTOCOL_ERROR,
+   IDS_ERRORPAGES_SUMMARY_SSL_PROTOCOL_ERROR,
+   IDS_ERRORPAGES_DETAILS_SSL_UNSAFE_NEGOTIATION,
+   SUGGEST_NONE,
+  },
+  {net::ERR_BAD_SSL_CLIENT_AUTH_CERT,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_BAD_SSL_CLIENT_AUTH_CERT,
+   IDS_ERRORPAGES_SUMMARY_BAD_SSL_CLIENT_AUTH_CERT,
+   IDS_ERRORPAGES_DETAILS_BAD_SSL_CLIENT_AUTH_CERT,
+   SUGGEST_NONE,
+  },
+  {net::ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_WEAK_SERVER_EPHEMERAL_DH_KEY,
+   IDS_ERRORPAGES_SUMMARY_WEAK_SERVER_EPHEMERAL_DH_KEY,
+   IDS_ERRORPAGES_DETAILS_SSL_PROTOCOL_ERROR,
+   SUGGEST_LEARNMORE,
+  },
+  {net::ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_PINNING_FAILURE,
+   IDS_ERRORPAGES_SUMMARY_PINNING_FAILURE,
+   IDS_ERRORPAGES_DETAILS_PINNING_FAILURE,
+   SUGGEST_NONE,
+  },
+  {net::ERR_TEMPORARILY_THROTTLED,
+   IDS_ERRORPAGES_TITLE_ACCESS_DENIED,
+   IDS_ERRORPAGES_HEADING_ACCESS_DENIED,
+   IDS_ERRORPAGES_SUMMARY_TEMPORARILY_THROTTLED,
+   IDS_ERRORPAGES_DETAILS_TEMPORARILY_THROTTLED,
+   SUGGEST_NONE,
+  },
+  {net::ERR_BLOCKED_BY_CLIENT,
+   IDS_ERRORPAGES_TITLE_BLOCKED,
+   IDS_ERRORPAGES_HEADING_BLOCKED,
+   IDS_ERRORPAGES_SUMMARY_BLOCKED,
+   IDS_ERRORPAGES_DETAILS_BLOCKED,
+   SUGGEST_DISABLE_EXTENSION,
+  },
+};
+
+const LocalizedErrorMap http_error_options[] = {
+  {403,
+   IDS_ERRORPAGES_TITLE_ACCESS_DENIED,
+   IDS_ERRORPAGES_HEADING_ACCESS_DENIED,
+   IDS_ERRORPAGES_SUMMARY_FORBIDDEN,
+   IDS_ERRORPAGES_DETAILS_FORBIDDEN,
+   SUGGEST_NONE,
+  },
+  {410,
+   IDS_ERRORPAGES_TITLE_NOT_FOUND,
+   IDS_ERRORPAGES_HEADING_NOT_FOUND,
+   IDS_ERRORPAGES_SUMMARY_GONE,
+   IDS_ERRORPAGES_DETAILS_GONE,
+   SUGGEST_NONE,
+  },
+
+  {500,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_HTTP_SERVER_ERROR,
+   IDS_ERRORPAGES_SUMMARY_INTERNAL_SERVER_ERROR,
+   IDS_ERRORPAGES_DETAILS_INTERNAL_SERVER_ERROR,
+   SUGGEST_RELOAD,
+  },
+  {501,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_HTTP_SERVER_ERROR,
+   IDS_ERRORPAGES_SUMMARY_WEBSITE_CANNOT_HANDLE,
+   IDS_ERRORPAGES_DETAILS_NOT_IMPLEMENTED,
+   SUGGEST_NONE,
+  },
+  {502,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_HTTP_SERVER_ERROR,
+   IDS_ERRORPAGES_SUMMARY_BAD_GATEWAY,
+   IDS_ERRORPAGES_DETAILS_BAD_GATEWAY,
+   SUGGEST_RELOAD,
+  },
+  {503,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_HTTP_SERVER_ERROR,
+   IDS_ERRORPAGES_SUMMARY_SERVICE_UNAVAILABLE,
+   IDS_ERRORPAGES_DETAILS_SERVICE_UNAVAILABLE,
+   SUGGEST_RELOAD,
+  },
+  {504,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_HTTP_SERVER_ERROR,
+   IDS_ERRORPAGES_SUMMARY_GATEWAY_TIMEOUT,
+   IDS_ERRORPAGES_DETAILS_GATEWAY_TIMEOUT,
+   SUGGEST_RELOAD,
+  },
+  {505,
+   IDS_ERRORPAGES_TITLE_LOAD_FAILED,
+   IDS_ERRORPAGES_HEADING_HTTP_SERVER_ERROR,
+   IDS_ERRORPAGES_SUMMARY_WEBSITE_CANNOT_HANDLE,
+   IDS_ERRORPAGES_DETAILS_HTTP_VERSION_NOT_SUPPORTED,
+   SUGGEST_NONE,
+  },
+};
+
+const char* HttpErrorToString(int status_code) {
+  switch (status_code) {
+  case 403:
+    return "Forbidden";
+  case 410:
+    return "Gone";
+  case 500:
+    return "Internal Server Error";
+  case 501:
+    return "Not Implemented";
+  case 502:
+    return "Bad Gateway";
+  case 503:
+    return "Service Unavailable";
+  case 504:
+    return "Gateway Timeout";
+  case 505:
+    return "HTTP Version Not Supported";
+  default:
+    return "";
+  }
+}
+
+string16 GetErrorDetailsString(const std::string& error_domain,
+                               int error_code,
+                               const string16& details) {
+  int error_page_template;
+  const char* error_string;
+  if (error_domain == net::kErrorDomain) {
+    error_page_template = IDS_ERRORPAGES_DETAILS_TEMPLATE;
+    error_string = net::ErrorToString(error_code);
+    DCHECK(error_code < 0);  // Net error codes are negative.
+    error_code = -error_code;
+  } else if (error_domain == LocalizedError::kHttpErrorDomain) {
+    error_page_template = IDS_ERRORPAGES_HTTP_DETAILS_TEMPLATE;
+    error_string = HttpErrorToString(error_code);
+  } else {
+    NOTREACHED();
+    return string16();
+  }
+  return l10n_util::GetStringFUTF16(
+      error_page_template,
+      base::IntToString16(error_code),
+      ASCIIToUTF16(error_string),
+      details);
+}
+
+const LocalizedErrorMap* FindErrorMapInArray(const LocalizedErrorMap* maps,
+                                                   size_t num_maps,
+                                                   int error_code) {
+  for (size_t i = 0; i < num_maps; ++i) {
+    if (maps[i].error_code == error_code)
+      return &maps[i];
+  }
+  return NULL;
+}
+
+const LocalizedErrorMap* LookupErrorMap(const std::string& error_domain,
+                                        int error_code) {
+  if (error_domain == net::kErrorDomain) {
+    return FindErrorMapInArray(net_error_options,
+                               arraysize(net_error_options),
+                               error_code);
+  } else if (error_domain == LocalizedError::kHttpErrorDomain) {
+    return FindErrorMapInArray(http_error_options,
+                               arraysize(http_error_options),
+                               error_code);
+  } else {
+    NOTREACHED();
+    return NULL;
+  }
+}
+
+bool LocaleIsRTL() {
+#if defined(TOOLKIT_GTK)
+  // base::i18n::IsRTL() uses the GTK text direction, which doesn't work within
+  // the renderer sandbox.
+  return base::i18n::ICUIsRTL();
+#else
+  return base::i18n::IsRTL();
+#endif
+}
+
+// Returns a dictionary containing the strings for the settings menu under the
+// wrench, and the advanced settings button.
+DictionaryValue* GetStandardMenuItemsText() {
+  DictionaryValue* standard_menu_items_text = new DictionaryValue();
+  standard_menu_items_text->SetString("settingsTitle",
+      l10n_util::GetStringUTF16(IDS_SETTINGS_TITLE));
+  standard_menu_items_text->SetString("advancedTitle",
+      l10n_util::GetStringUTF16(IDS_SETTINGS_SHOW_ADVANCED_SETTINGS));
+  return standard_menu_items_text;
+}
+
+}  // namespace
+
+const char LocalizedError::kHttpErrorDomain[] = "http";
+
+void LocalizedError::GetStrings(const WebKit::WebURLError& error,
+                                DictionaryValue* error_strings,
+                                const std::string& locale) {
+  bool rtl = LocaleIsRTL();
+  error_strings->SetString("textdirection", rtl ? "rtl" : "ltr");
+
+  // Grab the strings and settings that depend on the error type.  Init
+  // options with default values.
+  LocalizedErrorMap options = {
+    0,
+    IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+    IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+    IDS_ERRORPAGES_SUMMARY_NOT_AVAILABLE,
+    IDS_ERRORPAGES_DETAILS_UNKNOWN,
+    SUGGEST_NONE,
+  };
+
+  const std::string error_domain = error.domain.utf8();
+  int error_code = error.reason;
+  const LocalizedErrorMap* error_map =
+      LookupErrorMap(error_domain, error_code);
+  if (error_map)
+    options = *error_map;
+
+  if (options.suggestions != SUGGEST_NONE) {
+    error_strings->SetString(
+        "suggestionsHeading",
+        l10n_util::GetStringUTF16(IDS_ERRORPAGES_SUGGESTION_HEADING));
+  }
+
+  const GURL failed_url = error.unreachableURL;
+
+  // If we got "access denied" but the url was a file URL, then we say it was a
+  // file instead of just using the "not available" default message. Just adding
+  // ERR_ACCESS_DENIED to the map isn't sufficient, since that message may be
+  // generated by some OSs when the operation doesn't involve a file URL.
+  if (error_domain == net::kErrorDomain &&
+      error_code == net::ERR_ACCESS_DENIED &&
+      failed_url.scheme() == "file") {
+    options.title_resource_id = IDS_ERRORPAGES_TITLE_ACCESS_DENIED;
+    options.heading_resource_id = IDS_ERRORPAGES_HEADING_FILE_ACCESS_DENIED;
+    options.summary_resource_id = IDS_ERRORPAGES_SUMMARY_FILE_ACCESS_DENIED;
+    options.details_resource_id = IDS_ERRORPAGES_DETAILS_FILE_ACCESS_DENIED;
+    options.suggestions = SUGGEST_NONE;
+  }
+
+  string16 failed_url_string(ASCIIToUTF16(failed_url.spec()));
+  // URLs are always LTR.
+  if (rtl)
+    base::i18n::WrapStringWithLTRFormatting(&failed_url_string);
+  error_strings->SetString("title",
+      l10n_util::GetStringFUTF16(options.title_resource_id, failed_url_string));
+  error_strings->SetString("heading",
+      l10n_util::GetStringUTF16(options.heading_resource_id));
+
+  DictionaryValue* summary = new DictionaryValue;
+  summary->SetString("msg",
+      l10n_util::GetStringUTF16(options.summary_resource_id));
+  // TODO(tc): We want the unicode url and host here since they're being
+  //           displayed.
+  summary->SetString("failedUrl", failed_url_string);
+  summary->SetString("hostName", failed_url.host());
+  summary->SetString("productName",
+                     l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
+  error_strings->Set("summary", summary);
+
+  string16 details = l10n_util::GetStringUTF16(options.details_resource_id);
+  error_strings->SetString("details",
+      GetErrorDetailsString(error_domain, error_code, details));
+
+  // Platform specific instructions for diagnosing network issues on OSX and
+  // Windows.
+#if defined(OS_MACOSX) || defined(OS_WIN)
+  if (error_domain == net::kErrorDomain &&
+      error_code == net::ERR_INTERNET_DISCONNECTED) {
+    int platform_string_id =
+        IDS_ERRORPAGES_SUMMARY_INTERNET_DISCONNECTED_PLATFORM;
+#if defined(OS_WIN)
+    // Different versions of Windows have different instructions.
+    base::win::Version windows_version = base::win::GetVersion();
+    if (windows_version < base::win::VERSION_VISTA) {
+      // XP, XP64, and Server 2003.
+      platform_string_id =
+          IDS_ERRORPAGES_SUMMARY_INTERNET_DISCONNECTED_PLATFORM_XP;
+    } else if (windows_version == base::win::VERSION_VISTA) {
+      // Vista
+      platform_string_id =
+          IDS_ERRORPAGES_SUMMARY_INTERNET_DISCONNECTED_PLATFORM_VISTA;
+    }
+#endif  // defined(OS_WIN)
+    // Lead with the general error description, and suffix with the platform
+    // dependent portion of the summary section.
+    summary->SetString("msg",
+        l10n_util::GetStringFUTF16(
+            IDS_ERRORPAGES_SUMMARY_INTERNET_DISCONNECTED_INSTRUCTIONS_TEMPLATE,
+            l10n_util::GetStringUTF16(options.summary_resource_id),
+            l10n_util::GetStringUTF16(platform_string_id)));
+  }
+#endif  // defined(OS_MACOSX) || defined(OS_WIN)
+
+  if (options.suggestions & SUGGEST_RELOAD) {
+    DictionaryValue* suggest_reload = new DictionaryValue;
+    suggest_reload->SetString("msg",
+        l10n_util::GetStringUTF16(IDS_ERRORPAGES_SUGGESTION_RELOAD));
+    suggest_reload->SetString("reloadUrl", failed_url_string);
+    error_strings->Set("suggestionsReload", suggest_reload);
+  }
+
+  if (options.suggestions & SUGGEST_HOSTNAME) {
+    // Only show the "Go to hostname" suggestion if the failed_url has a path.
+    if (std::string() == failed_url.path()) {
+      DictionaryValue* suggest_home_page = new DictionaryValue;
+      suggest_home_page->SetString("suggestionsHomepageMsg",
+          l10n_util::GetStringUTF16(IDS_ERRORPAGES_SUGGESTION_HOMEPAGE));
+      string16 homepage(ASCIIToUTF16(failed_url.GetWithEmptyPath().spec()));
+      // URLs are always LTR.
+      if (rtl)
+        base::i18n::WrapStringWithLTRFormatting(&homepage);
+      suggest_home_page->SetString("homePage", homepage);
+      // TODO(tc): we actually want the unicode hostname
+      suggest_home_page->SetString("hostName", failed_url.host());
+      error_strings->Set("suggestionsHomepage", suggest_home_page);
+    }
+  }
+
+  if (options.suggestions & SUGGEST_CHECK_CONNECTION) {
+    DictionaryValue* suggest_check_connection = new DictionaryValue;
+    suggest_check_connection->SetString("msg",
+        l10n_util::GetStringUTF16(IDS_ERRORPAGES_SUGGESTION_CHECK_CONNECTION));
+    error_strings->Set("suggestionsCheckConnection", suggest_check_connection);
+  }
+
+  if (options.suggestions & SUGGEST_DNS_CONFIG) {
+    DictionaryValue* suggest_dns_config = new DictionaryValue;
+    suggest_dns_config->SetString("msg",
+        l10n_util::GetStringUTF16(IDS_ERRORPAGES_SUGGESTION_DNS_CONFIG));
+    error_strings->Set("suggestionsDNSConfig", suggest_dns_config);
+
+    DictionaryValue* suggest_network_prediction = GetStandardMenuItemsText();
+    suggest_network_prediction->SetString("msg",
+        l10n_util::GetStringUTF16(
+            IDS_ERRORPAGES_SUGGESTION_NETWORK_PREDICTION));
+    suggest_network_prediction->SetString(
+        "noNetworkPredictionTitle",
+        l10n_util::GetStringUTF16(
+            IDS_NETWORK_PREDICTION_ENABLED_DESCRIPTION));
+    error_strings->Set("suggestionsDisableNetworkPrediction",
+                       suggest_network_prediction);
+  }
+
+  if (options.suggestions & SUGGEST_FIREWALL_CONFIG) {
+    DictionaryValue* suggest_firewall_config = new DictionaryValue;
+    suggest_firewall_config->SetString("msg",
+        l10n_util::GetStringUTF16(IDS_ERRORPAGES_SUGGESTION_FIREWALL_CONFIG));
+    suggest_firewall_config->SetString("productName",
+        l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
+    error_strings->Set("suggestionsFirewallConfig", suggest_firewall_config);
+  }
+
+  if (options.suggestions & SUGGEST_PROXY_CONFIG) {
+#if defined(OS_CHROMEOS)
+    DictionaryValue* suggest_proxy_config = new DictionaryValue();
+#else
+    DictionaryValue* suggest_proxy_config = GetStandardMenuItemsText();
+#endif  // defined(OS_CHROMEOS)
+    suggest_proxy_config->SetString("msg",
+        l10n_util::GetStringFUTF16(IDS_ERRORPAGES_SUGGESTION_PROXY_CONFIG,
+            l10n_util::GetStringUTF16(
+                IDS_ERRORPAGES_SUGGESTION_PROXY_DISABLE_PLATFORM)));
+#if defined(OS_CHROMEOS)
+    suggest_proxy_config->SetString("settingsTitle",
+        l10n_util::GetStringUTF16(IDS_SETTINGS_TITLE));
+    suggest_proxy_config->SetString("internetTitle",
+        l10n_util::GetStringUTF16(IDS_OPTIONS_INTERNET_TAB_LABEL));
+    suggest_proxy_config->SetString("optionsButton",
+        l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_OPTIONS));
+    suggest_proxy_config->SetString("networkTab",
+        l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_INTERNET_TAB_NETWORK));
+    suggest_proxy_config->SetString("proxyButton",
+        l10n_util::GetStringUTF16(
+            IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_CHANGE_PROXY_BUTTON));
+#else
+    suggest_proxy_config->SetString("proxyTitle",
+        l10n_util::GetStringUTF16(IDS_OPTIONS_PROXIES_CONFIGURE_BUTTON));
+#endif  // defined(OS_CHROMEOS)
+    error_strings->Set("suggestionsProxyConfig", suggest_proxy_config);
+  }
+
+  if (options.suggestions & SUGGEST_DISABLE_EXTENSION) {
+    DictionaryValue* suggestion = new DictionaryValue;
+    suggestion->SetString("msg",
+        l10n_util::GetStringUTF16(IDS_ERRORPAGES_SUGGESTION_DISABLE_EXTENSION));
+    suggestion->SetString("reloadUrl", failed_url_string);
+    error_strings->Set("suggestionsDisableExtension", suggestion);
+  }
+
+  if (options.suggestions & SUGGEST_LEARNMORE) {
+    GURL learn_more_url;
+    switch (options.error_code) {
+      case net::ERR_TOO_MANY_REDIRECTS:
+        learn_more_url = GURL(kRedirectLoopLearnMoreUrl);
+        break;
+      case net::ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY:
+        learn_more_url = GURL(kWeakDHKeyLearnMoreUrl);
+        break;
+      default:
+        break;
+    }
+
+    if (learn_more_url.is_valid()) {
+      // Add the language parameter to the URL.
+      std::string query = learn_more_url.query() + "&hl=" + locale;
+      GURL::Replacements repl;
+      repl.SetQueryStr(query);
+      learn_more_url = learn_more_url.ReplaceComponents(repl);
+
+      DictionaryValue* suggest_learn_more = new DictionaryValue;
+      suggest_learn_more->SetString("msg",
+          l10n_util::GetStringUTF16(IDS_ERRORPAGES_SUGGESTION_LEARNMORE));
+      suggest_learn_more->SetString("learnMoreUrl", learn_more_url.spec());
+      error_strings->Set("suggestionsLearnMore", suggest_learn_more);
+    }
+  }
+}
+
+string16 LocalizedError::GetErrorDetails(const WebKit::WebURLError& error) {
+  const LocalizedErrorMap* error_map =
+      LookupErrorMap(error.domain.utf8(), error.reason);
+  if (error_map)
+    return l10n_util::GetStringUTF16(error_map->details_resource_id);
+  else
+    return l10n_util::GetStringUTF16(IDS_ERRORPAGES_DETAILS_UNKNOWN);
+}
+
+bool LocalizedError::HasStrings(const std::string& error_domain,
+                                int error_code) {
+  return LookupErrorMap(error_domain, error_code) != NULL;
+}
+
+void LocalizedError::GetFormRepostStrings(const GURL& display_url,
+                                          DictionaryValue* error_strings) {
+  bool rtl = LocaleIsRTL();
+  error_strings->SetString("textdirection", rtl ? "rtl" : "ltr");
+
+  string16 failed_url(ASCIIToUTF16(display_url.spec()));
+  // URLs are always LTR.
+  if (rtl)
+    base::i18n::WrapStringWithLTRFormatting(&failed_url);
+  error_strings->SetString(
+      "title", l10n_util::GetStringFUTF16(IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+                                          failed_url));
+  error_strings->SetString(
+      "heading", l10n_util::GetStringUTF16(IDS_HTTP_POST_WARNING_TITLE));
+  DictionaryValue* summary = new DictionaryValue;
+  summary->SetString(
+      "msg", l10n_util::GetStringUTF16(IDS_ERRORPAGES_HTTP_POST_WARNING));
+  error_strings->Set("summary", summary);
+}
+
+void LocalizedError::GetAppErrorStrings(
+    const WebURLError& error,
+    const GURL& display_url,
+    const extensions::Extension* app,
+    DictionaryValue* error_strings) {
+  DCHECK(app);
+
+  bool rtl = LocaleIsRTL();
+  error_strings->SetString("textdirection", rtl ? "rtl" : "ltr");
+
+  string16 failed_url(ASCIIToUTF16(display_url.spec()));
+  // URLs are always LTR.
+  if (rtl)
+    base::i18n::WrapStringWithLTRFormatting(&failed_url);
+  error_strings->SetString(
+     "url", l10n_util::GetStringFUTF16(IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+                                       failed_url.c_str()));
+
+  error_strings->SetString("title", app->name());
+  error_strings->SetString("icon",
+      app->GetIconURL(extension_misc::EXTENSION_ICON_GIGANTOR,
+                      ExtensionIconSet::MATCH_SMALLER).spec());
+  error_strings->SetString("name", app->name());
+  error_strings->SetString("msg",
+      l10n_util::GetStringUTF16(IDS_ERRORPAGES_APP_WARNING));
+
+#if defined(OS_CHROMEOS)
+  GURL learn_more_url(kAppWarningLearnMoreUrl);
+  DictionaryValue* suggest_learn_more = new DictionaryValue();
+  suggest_learn_more->SetString("msg",
+                                l10n_util::GetStringUTF16(
+                                    IDS_ERRORPAGES_SUGGESTION_LEARNMORE));
+  suggest_learn_more->SetString("learnMoreUrl", learn_more_url.spec());
+  error_strings->Set("suggestionsLearnMore", suggest_learn_more);
+#endif  // defined(OS_CHROMEOS)
+}
diff --git a/chrome/common/localized_error.h b/chrome/common/localized_error.h
new file mode 100644
index 0000000..18b0e5c
--- /dev/null
+++ b/chrome/common/localized_error.h
@@ -0,0 +1,63 @@
+// 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.
+
+#ifndef CHROME_COMMON_LOCALIZED_ERROR_H_
+#define CHROME_COMMON_LOCALIZED_ERROR_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/string16.h"
+
+class GURL;
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace extensions {
+class Extension;
+}
+
+namespace WebKit {
+struct WebURLError;
+}
+
+class LocalizedError {
+ public:
+  // Fills |error_strings| with values to be used to build an error page used
+  // on HTTP errors, like 404 or connection reset.
+  static void GetStrings(const WebKit::WebURLError& error,
+                         base::DictionaryValue* strings,
+                         const std::string& locale);
+
+  // Returns a description of the encountered error.
+  static string16 GetErrorDetails(const WebKit::WebURLError& error);
+
+  // Returns true if an error page exists for the specified parameters.
+  static bool HasStrings(const std::string& error_domain, int error_code);
+
+  // Fills |error_strings| with values to be used to build an error page which
+  // warns against reposting form data. This is special cased because the form
+  // repost "error page" has no real error associated with it, and doesn't have
+  // enough strings localized to meaningfully fill the net error template.
+  static void GetFormRepostStrings(const GURL& display_url,
+                                   base::DictionaryValue* error_strings);
+
+  // Fills |error_strings| with values to be used to build an error page used
+  // on HTTP errors, like 404 or connection reset, but using information from
+  // the associated |app| in order to make the error page look like it's more
+  // part of the app.
+  static void GetAppErrorStrings(const WebKit::WebURLError& error,
+                                 const GURL& display_url,
+                                 const extensions::Extension* app,
+                                 base::DictionaryValue* error_strings);
+
+  static const char kHttpErrorDomain[];
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(LocalizedError);
+};
+
+#endif  // CHROME_COMMON_LOCALIZED_ERROR_H_
diff --git a/chrome/common/logging_chrome.cc b/chrome/common/logging_chrome.cc
new file mode 100644
index 0000000..f738c3f
--- /dev/null
+++ b/chrome/common/logging_chrome.cc
@@ -0,0 +1,478 @@
+// Copyright (c) 2012 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.
+
+#include "build/build_config.h"
+
+// Need to include this before most other files because it defines
+// IPC_MESSAGE_LOG_ENABLED. We need to use it to define
+// IPC_MESSAGE_MACROS_LOG_ENABLED so render_messages.h will generate the
+// ViewMsgLog et al. functions.
+#include "ipc/ipc_message.h"
+
+// On Windows, the about:ipc dialog shows IPCs; on POSIX, we hook up a
+// logger in this file.  (We implement about:ipc on Mac but implement
+// the loggers here anyway).  We need to do this real early to be sure
+// IPC_MESSAGE_MACROS_LOG_ENABLED doesn't get undefined.
+#if defined(OS_POSIX) && defined(IPC_MESSAGE_LOG_ENABLED)
+#define IPC_MESSAGE_MACROS_LOG_ENABLED
+#include "content/public/common/content_ipc_logging.h"
+#define IPC_LOG_TABLE_ADD_ENTRY(msg_id, logger) \
+    content::RegisterIPCLogger(msg_id, logger)
+#include "chrome/common/all_messages.h"
+#endif
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+#include <fstream>
+
+#include "chrome/common/logging_chrome.h"
+
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/debug/debugger.h"
+#include "base/environment.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/env_vars.h"
+#include "ipc/ipc_logging.h"
+
+#if defined(OS_WIN)
+#include "base/logging_win.h"
+#include <initguid.h>
+#endif
+
+namespace {
+
+// When true, this means that error dialogs should not be shown.
+bool dialogs_are_suppressed_ = false;
+
+// This should be true for exactly the period between the end of
+// InitChromeLogging() and the beginning of CleanupChromeLogging().
+bool chrome_logging_initialized_ = false;
+
+// Set if we called InitChromeLogging() but failed to initialize.
+bool chrome_logging_failed_ = false;
+
+// This should be true for exactly the period between the end of
+// InitChromeLogging() and the beginning of CleanupChromeLogging().
+bool chrome_logging_redirected_ = false;
+
+#if defined(OS_WIN)
+// {7FE69228-633E-4f06-80C1-527FEA23E3A7}
+const GUID kChromeTraceProviderName = {
+    0x7fe69228, 0x633e, 0x4f06,
+        { 0x80, 0xc1, 0x52, 0x7f, 0xea, 0x23, 0xe3, 0xa7 } };
+#endif
+
+#if defined(USE_LINUX_BREAKPAD) || defined(OS_MACOSX)
+// Pointer to the function that's called by DumpWithoutCrashing() to dump the
+// process's memory.
+void (*dump_without_crashing_function_)() = NULL;
+#endif
+
+// Assertion handler for logging errors that occur when dialogs are
+// silenced.  To record a new error, pass the log string associated
+// with that error in the str parameter.
+MSVC_DISABLE_OPTIMIZE();
+void SilentRuntimeAssertHandler(const std::string& str) {
+  base::debug::BreakDebugger();
+}
+void SilentRuntimeReportHandler(const std::string& str) {
+}
+#if defined(OS_WIN)
+// Handler to silently dump the current process when there is an assert in
+// chrome.
+void DumpProcessAssertHandler(const std::string& str) {
+  // Get the breakpad pointer from chrome.exe
+  typedef void (__cdecl *DumpProcessFunction)();
+  DumpProcessFunction DumpProcess = reinterpret_cast<DumpProcessFunction>(
+      ::GetProcAddress(::GetModuleHandle(chrome::kBrowserProcessExecutableName),
+                       "DumpProcess"));
+  if (DumpProcess)
+    DumpProcess();
+}
+#endif  // OS_WIN
+MSVC_ENABLE_OPTIMIZE();
+
+// Suppresses error/assertion dialogs and enables the logging of
+// those errors into silenced_errors_.
+void SuppressDialogs() {
+  if (dialogs_are_suppressed_)
+    return;
+
+  logging::SetLogAssertHandler(SilentRuntimeAssertHandler);
+  logging::SetLogReportHandler(SilentRuntimeReportHandler);
+
+#if defined(OS_WIN)
+  UINT new_flags = SEM_FAILCRITICALERRORS |
+                   SEM_NOGPFAULTERRORBOX |
+                   SEM_NOOPENFILEERRORBOX;
+
+  // Preserve existing error mode, as discussed at http://t/dmea
+  UINT existing_flags = SetErrorMode(new_flags);
+  SetErrorMode(existing_flags | new_flags);
+#endif
+
+  dialogs_are_suppressed_ = true;
+}
+
+}  // anonymous namespace
+
+namespace logging {
+
+LoggingDestination DetermineLogMode(const CommandLine& command_line) {
+  // only use OutputDebugString in debug mode
+#ifdef NDEBUG
+  bool enable_logging = false;
+  const char *kInvertLoggingSwitch = switches::kEnableLogging;
+  const logging::LoggingDestination kDefaultLoggingMode =
+      logging::LOG_ONLY_TO_FILE;
+#else
+  bool enable_logging = true;
+  const char *kInvertLoggingSwitch = switches::kDisableLogging;
+  const logging::LoggingDestination kDefaultLoggingMode =
+      logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG;
+#endif
+
+  if (command_line.HasSwitch(kInvertLoggingSwitch))
+    enable_logging = !enable_logging;
+
+  logging::LoggingDestination log_mode;
+  if (enable_logging) {
+    // Let --enable-logging=stderr force only stderr, particularly useful for
+    // non-debug builds where otherwise you can't get logs to stderr at all.
+    if (command_line.GetSwitchValueASCII(switches::kEnableLogging) == "stderr")
+      log_mode = logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG;
+    else
+      log_mode = kDefaultLoggingMode;
+  } else {
+    log_mode = logging::LOG_NONE;
+  }
+  return log_mode;
+}
+
+#if defined(OS_CHROMEOS)
+namespace {
+FilePath SetUpSymlinkIfNeeded(const FilePath& symlink_path, bool new_log) {
+  DCHECK(!symlink_path.empty());
+
+  // If not starting a new log, then just log through the existing
+  // symlink, but if the symlink doesn't exist, create it.  If
+  // starting a new log, then delete the old symlink and make a new
+  // one to a fresh log file.
+  FilePath target_path;
+  bool symlink_exists = file_util::PathExists(symlink_path);
+  if (new_log || !symlink_exists) {
+    target_path = GenerateTimestampedName(symlink_path, base::Time::Now());
+
+    // We don't care if the unlink fails; we're going to continue anyway.
+    if (::unlink(symlink_path.value().c_str()) == -1) {
+      if (symlink_exists) // only warn if we might expect it to succeed.
+        DPLOG(WARNING) << "Unable to unlink " << symlink_path.value();
+    }
+    if (!file_util::CreateSymbolicLink(target_path, symlink_path)) {
+      DPLOG(ERROR) << "Unable to create symlink " << symlink_path.value()
+                   << " pointing at " << target_path.value();
+    }
+  } else {
+    if (!file_util::ReadSymbolicLink(symlink_path, &target_path))
+      DPLOG(ERROR) << "Unable to read symlink " << symlink_path.value();
+  }
+  return target_path;
+}
+
+void RemoveSymlinkAndLog(const FilePath& link_path,
+                         const FilePath& target_path) {
+  if (::unlink(link_path.value().c_str()) == -1)
+    DPLOG(WARNING) << "Unable to unlink symlink " << link_path.value();
+  if (::unlink(target_path.value().c_str()) == -1)
+    DPLOG(WARNING) << "Unable to unlink log file " << target_path.value();
+}
+
+}  // anonymous namespace
+
+FilePath GetSessionLogFile(const CommandLine& command_line) {
+  FilePath log_dir;
+  std::string log_dir_str;
+  scoped_ptr<base::Environment> env(base::Environment::Create());
+  if (env->GetVar(env_vars::kSessionLogDir, &log_dir_str) &&
+      !log_dir_str.empty()) {
+    log_dir = FilePath(log_dir_str);
+  } else {
+    PathService::Get(chrome::DIR_USER_DATA, &log_dir);
+    FilePath login_profile =
+        command_line.GetSwitchValuePath(switches::kLoginProfile);
+    log_dir = log_dir.Append(login_profile);
+  }
+  return log_dir.Append(GetLogFileName().BaseName());
+}
+
+void RedirectChromeLogging(const CommandLine& command_line) {
+  DCHECK(!chrome_logging_redirected_) <<
+    "Attempted to redirect logging when it was already initialized.";
+
+  // Redirect logs to the session log directory, if set.  Otherwise
+  // defaults to the profile dir.
+  FilePath log_path = GetSessionLogFile(command_line);
+
+  // Creating symlink causes us to do blocking IO on UI thread.
+  // Temporarily allow it until we fix http://crbug.com/61143
+  base::ThreadRestrictions::ScopedAllowIO allow_io;
+  // Always force a new symlink when redirecting.
+  FilePath target_path = SetUpSymlinkIfNeeded(log_path, true);
+
+  logging::DcheckState dcheck_state =
+      command_line.HasSwitch(switches::kEnableDCHECK) ?
+      logging::ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS :
+      logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS;
+
+  // ChromeOS always logs through the symlink, so it shouldn't be
+  // deleted if it already exists.
+  if (!InitLogging(log_path.value().c_str(),
+                   DetermineLogMode(command_line),
+                   logging::LOCK_LOG_FILE,
+                   logging::APPEND_TO_OLD_LOG_FILE,
+                   dcheck_state)) {
+    DLOG(ERROR) << "Unable to initialize logging to " << log_path.value();
+    RemoveSymlinkAndLog(log_path, target_path);
+  } else {
+    chrome_logging_redirected_ = true;
+  }
+}
+
+#endif  // OS_CHROMEOS
+
+void InitChromeLogging(const CommandLine& command_line,
+                       OldFileDeletionState delete_old_log_file) {
+  DCHECK(!chrome_logging_initialized_) <<
+    "Attempted to initialize logging when it was already initialized.";
+
+  LoggingDestination logging_dest = DetermineLogMode(command_line);
+  LogLockingState log_locking_state = LOCK_LOG_FILE;
+  FilePath log_path;
+#if defined(OS_CHROMEOS)
+  FilePath target_path;
+#endif
+
+  // Don't resolve the log path unless we need to. Otherwise we leave an open
+  // ALPC handle after sandbox lockdown on Windows.
+  if (logging_dest == LOG_ONLY_TO_FILE ||
+      logging_dest == LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG) {
+    log_path = GetLogFileName();
+
+#if defined(OS_CHROMEOS)
+    // For BWSI (Incognito) logins, we want to put the logs in the user
+    // profile directory that is created for the temporary session instead
+    // of in the system log directory, for privacy reasons.
+    if (command_line.HasSwitch(switches::kGuestSession))
+      log_path = GetSessionLogFile(command_line);
+
+    // On ChromeOS we log to the symlink.  We force creation of a new
+    // symlink if we've been asked to delete the old log, since that
+    // indicates the start of a new session.
+    target_path = SetUpSymlinkIfNeeded(
+        log_path, delete_old_log_file == logging::DELETE_OLD_LOG_FILE);
+
+    // Because ChromeOS manages the move to a new session by redirecting
+    // the link, it shouldn't remove the old file in the logging code,
+    // since that will remove the newly created link instead.
+    delete_old_log_file = logging::APPEND_TO_OLD_LOG_FILE;
+#endif
+  } else {
+    log_locking_state = DONT_LOCK_LOG_FILE;
+  }
+
+  logging::DcheckState dcheck_state =
+      command_line.HasSwitch(switches::kEnableDCHECK) ?
+      logging::ENABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS :
+      logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS;
+
+  bool success = InitLogging(log_path.value().c_str(),
+                             logging_dest,
+                             log_locking_state,
+                             delete_old_log_file,
+                             dcheck_state);
+
+#if defined(OS_CHROMEOS)
+  if (!success) {
+    DPLOG(ERROR) << "Unable to initialize logging to " << log_path.value()
+                << " (which should be a link to " << target_path.value() << ")";
+    RemoveSymlinkAndLog(log_path, target_path);
+    chrome_logging_failed_ = true;
+    return;
+  }
+#else
+  if (!success) {
+    DPLOG(ERROR) << "Unable to initialize logging to " << log_path.value();
+    chrome_logging_failed_ = true;
+    return;
+  }
+#endif
+
+  // Default to showing error dialogs.
+  if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoErrorDialogs))
+    logging::SetShowErrorDialogs(true);
+
+  // we want process and thread IDs because we have a lot of things running
+  logging::SetLogItems(true,  // enable_process_id
+                       true,  // enable_thread_id
+                       true,  // enable_timestamp
+                       false);  // enable_tickcount
+
+  // We call running in unattended mode "headless", and allow
+  // headless mode to be configured either by the Environment
+  // Variable or by the Command Line Switch.  This is for
+  // automated test purposes.
+  scoped_ptr<base::Environment> env(base::Environment::Create());
+  if (env->HasVar(env_vars::kHeadless) ||
+      command_line.HasSwitch(switches::kNoErrorDialogs))
+    SuppressDialogs();
+
+  // Use a minimum log level if the command line asks for one,
+  // otherwise leave it at the default level (INFO).
+  if (command_line.HasSwitch(switches::kLoggingLevel)) {
+    std::string log_level = command_line.GetSwitchValueASCII(
+        switches::kLoggingLevel);
+    int level = 0;
+    if (base::StringToInt(log_level, &level) &&
+        level >= 0 && level < LOG_NUM_SEVERITIES) {
+      logging::SetMinLogLevel(level);
+    } else {
+      DLOG(WARNING) << "Bad log level: " << log_level;
+    }
+  }
+
+#if defined(OS_WIN)
+  // Enable trace control and transport through event tracing for Windows.
+  logging::LogEventProvider::Initialize(kChromeTraceProviderName);
+#endif
+
+#ifdef NDEBUG
+  if (command_line.HasSwitch(switches::kSilentDumpOnDCHECK) &&
+      command_line.HasSwitch(switches::kEnableDCHECK)) {
+#if defined(OS_WIN)
+    logging::SetLogReportHandler(DumpProcessAssertHandler);
+#endif
+  }
+#endif  // NDEBUG
+
+  chrome_logging_initialized_ = true;
+}
+
+// This is a no-op, but we'll keep it around in case
+// we need to do more cleanup in the future.
+void CleanupChromeLogging() {
+  if (chrome_logging_failed_)
+    return;  // We failed to initiailize logging, no cleanup.
+
+  DCHECK(chrome_logging_initialized_) <<
+    "Attempted to clean up logging when it wasn't initialized.";
+
+  CloseLogFile();
+
+  chrome_logging_initialized_ = false;
+  chrome_logging_redirected_ = false;
+}
+
+FilePath GetLogFileName() {
+  std::string filename;
+  scoped_ptr<base::Environment> env(base::Environment::Create());
+  if (env->GetVar(env_vars::kLogFileName, &filename) && !filename.empty()) {
+#if defined(OS_WIN)
+    return FilePath(UTF8ToWide(filename));
+#elif defined(OS_POSIX)
+    return FilePath(filename);
+#endif
+  }
+
+  const FilePath log_filename(FILE_PATH_LITERAL("chrome_debug.log"));
+  FilePath log_path;
+
+  if (PathService::Get(chrome::DIR_LOGS, &log_path)) {
+    log_path = log_path.Append(log_filename);
+    return log_path;
+  } else {
+    // error with path service, just use some default file somewhere
+    return log_filename;
+  }
+}
+
+bool DialogsAreSuppressed() {
+  return dialogs_are_suppressed_;
+}
+
+size_t GetFatalAssertions(AssertionList* assertions) {
+  // In this function, we don't assume that assertions is non-null, so
+  // that if you just want an assertion count, you can pass in NULL.
+  if (assertions)
+    assertions->clear();
+  size_t assertion_count = 0;
+
+  std::ifstream log_file;
+  log_file.open(GetLogFileName().value().c_str());
+  if (!log_file.is_open())
+    return 0;
+
+  std::string utf8_line;
+  std::wstring wide_line;
+  while (!log_file.eof()) {
+    getline(log_file, utf8_line);
+    if (utf8_line.find(":FATAL:") != std::string::npos) {
+      wide_line = UTF8ToWide(utf8_line);
+      if (assertions)
+        assertions->push_back(wide_line);
+      ++assertion_count;
+    }
+  }
+  log_file.close();
+
+  return assertion_count;
+}
+
+void DumpWithoutCrashing() {
+#if defined(OS_WIN)
+  std::string str;
+  DumpProcessAssertHandler(str);
+#elif defined(USE_LINUX_BREAKPAD) || defined(OS_MACOSX)
+  if (dump_without_crashing_function_)
+    (*dump_without_crashing_function_)();
+#else
+  NOTIMPLEMENTED();
+#endif
+}
+
+#if defined(USE_LINUX_BREAKPAD) || defined(OS_MACOSX)
+void SetDumpWithoutCrashingFunction(void (*function)()) {
+  dump_without_crashing_function_ = function;
+}
+#endif
+
+FilePath GenerateTimestampedName(const FilePath& base_path,
+                                 base::Time timestamp) {
+  base::Time::Exploded time_deets;
+  timestamp.LocalExplode(&time_deets);
+  std::string suffix = base::StringPrintf("_%02d%02d%02d-%02d%02d%02d",
+                                          time_deets.year,
+                                          time_deets.month,
+                                          time_deets.day_of_month,
+                                          time_deets.hour,
+                                          time_deets.minute,
+                                          time_deets.second);
+  return base_path.InsertBeforeExtensionASCII(suffix);
+}
+
+}  // namespace logging
diff --git a/chrome/common/logging_chrome.h b/chrome/common/logging_chrome.h
new file mode 100644
index 0000000..a0d2d0d
--- /dev/null
+++ b/chrome/common/logging_chrome.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_LOGGING_CHROME_H__
+#define CHROME_COMMON_LOGGING_CHROME_H__
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/time.h"
+
+class CommandLine;
+class FilePath;
+
+namespace logging {
+
+// Call to initialize logging for Chrome. This sets up the chrome-specific
+// logfile naming scheme and might do other things like log modules and
+// setting levels in the future.
+//
+// The main process might want to delete any old log files on startup by
+// setting delete_old_log_file, but the renderer processes should not, or
+// they will delete each others' logs.
+//
+// XXX
+// Setting suppress_error_dialogs to true disables any dialogs that would
+// normally appear for assertions and crashes, and makes any catchable
+// errors (namely assertions) available via GetSilencedErrorCount()
+// and GetSilencedError().
+void InitChromeLogging(const CommandLine& command_line,
+                       OldFileDeletionState delete_old_log_file);
+
+#if defined(OS_CHROMEOS)
+// Get the log file location.
+FilePath GetSessionLogFile(const CommandLine& command_line);
+
+// Redirects chrome logging to the appropriate session log dir.
+void RedirectChromeLogging(const CommandLine& command_line);
+#endif
+
+// Call when done using logging for Chrome.
+void CleanupChromeLogging();
+
+// Returns the fully-qualified name of the log file.
+FilePath GetLogFileName();
+
+// Returns true when error/assertion dialogs are to be shown,
+// false otherwise.
+bool DialogsAreSuppressed();
+
+typedef std::vector<std::wstring> AssertionList;
+
+// Gets the list of fatal assertions in the current log file, and
+// returns the number of fatal assertions.  (If you don't care
+// about the actual list of assertions, you can pass in NULL.)
+// NOTE: Since this reads the log file to determine the assertions,
+// this operation is O(n) over the length of the log.
+// NOTE: This can fail if the file is locked for writing.  However,
+// this is unlikely as this function is most useful after
+// the program writing the log has terminated.
+size_t GetFatalAssertions(AssertionList* assertions);
+
+// Handler to silently dump the current process without crashing.
+void DumpWithoutCrashing();
+
+#if defined(USE_LINUX_BREAKPAD) || defined(OS_MACOSX)
+// Sets a function that'll be invoked to dump the current process when
+// DumpWithoutCrashing() is called.
+void SetDumpWithoutCrashingFunction(void (*function)());
+#endif
+
+// Inserts timestamp before file extension in the format
+// "_yymmdd-hhmmss".
+FilePath GenerateTimestampedName(const FilePath& base_path,
+                                 base::Time timestamp);
+}  // namespace logging
+
+#endif  // CHROME_COMMON_LOGGING_CHROME_H_
diff --git a/chrome/common/mac/DEPS b/chrome/common/mac/DEPS
new file mode 100644
index 0000000..77a551b
--- /dev/null
+++ b/chrome/common/mac/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+third_party/mach_override",
+]
diff --git a/chrome/common/mac/OWNERS b/chrome/common/mac/OWNERS
new file mode 100644
index 0000000..dc83bad
--- /dev/null
+++ b/chrome/common/mac/OWNERS
@@ -0,0 +1,4 @@
+mark@chromium.org
+thomasvl@chromium.org
+thakis@chromium.org
+rsesek@chromium.org
diff --git a/chrome/common/mac/app_mode_chrome_locator.h b/chrome/common/mac/app_mode_chrome_locator.h
new file mode 100644
index 0000000..860298c
--- /dev/null
+++ b/chrome/common/mac/app_mode_chrome_locator.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_MAC_APP_MODE_CHROME_LOCATOR_H_
+#define CHROME_COMMON_MAC_APP_MODE_CHROME_LOCATOR_H_
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "base/string16.h"
+
+@class NSString;
+
+class FilePath;
+
+namespace app_mode {
+
+// Given a bundle id, return the path of the corresponding bundle.
+// Returns true if the bundle was found, false otherwise.
+bool FindBundleById(NSString* bundle_id, FilePath* out_bundle);
+
+// Given the path to the Chrome bundle, read the following information:
+// |raw_version_str| - Chrome version.
+// |version_path| - |chrome_bundle|/Contents/Versions/|raw_version_str|/
+// |framework_shlib_path| - Path to the chrome framework's shared library (not
+//                          the framework directory).
+// Returns true if all information read succesfuly, false otherwise.
+bool GetChromeBundleInfo(const FilePath& chrome_bundle,
+                         string16* raw_version_str,
+                         FilePath* version_path,
+                         FilePath* framework_shlib_path);
+
+}  // namespace app_mode
+
+#endif  // CHROME_COMMON_MAC_APP_MODE_CHROME_LOCATOR_H_
diff --git a/chrome/common/mac/app_mode_chrome_locator.mm b/chrome/common/mac/app_mode_chrome_locator.mm
new file mode 100644
index 0000000..b64778f
--- /dev/null
+++ b/chrome/common/mac/app_mode_chrome_locator.mm
@@ -0,0 +1,84 @@
+// Copyright (c) 2012 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.
+
+#import "chrome/common/mac/app_mode_chrome_locator.h"
+
+#import <AppKit/AppKit.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "base/file_path.h"
+#include "base/mac/foundation_util.h"
+#include "base/sys_string_conversions.h"
+
+namespace app_mode {
+
+bool FindBundleById(NSString* bundle_id, FilePath* out_bundle) {
+  NSWorkspace* ws = [NSWorkspace sharedWorkspace];
+  NSString *bundlePath = [ws absolutePathForAppBundleWithIdentifier:bundle_id];
+  if (!bundlePath)
+    return false;
+
+  *out_bundle = base::mac::NSStringToFilePath(bundlePath);
+  return true;
+}
+
+bool GetChromeBundleInfo(const FilePath& chrome_bundle,
+                         string16* raw_version_str,
+                         FilePath* version_path,
+                         FilePath* framework_shlib_path) {
+  using base::mac::ObjCCast;
+
+  NSString* cr_bundle_path = base::mac::FilePathToNSString(chrome_bundle);
+  NSBundle* cr_bundle = [NSBundle bundleWithPath:cr_bundle_path];
+
+  if (!cr_bundle)
+    return false;
+
+  // Read raw version string.
+  NSString* cr_version =
+       ObjCCast<NSString>(
+          [cr_bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]);
+  if (!cr_version)
+    return false;
+
+  // Get versioned directory.
+  NSArray* cr_versioned_path_components =
+      [NSArray arrayWithObjects:cr_bundle_path,
+                                @"Contents",
+                                @"Versions",
+                                cr_version,
+                                nil];
+  NSString* cr_versioned_path =
+    [NSString pathWithComponents:cr_versioned_path_components];
+
+  // Get the framework path.
+  NSString* cr_bundle_exe =
+      ObjCCast<NSString>(
+          [cr_bundle objectForInfoDictionaryKey:@"CFBundleExecutable"]);
+  NSString* cr_framework_shlib_path =
+      [cr_versioned_path stringByAppendingPathComponent:
+          [cr_bundle_exe stringByAppendingString:@" Framework.framework"]];
+  cr_framework_shlib_path =
+      [cr_framework_shlib_path stringByAppendingPathComponent:
+          [cr_bundle_exe stringByAppendingString:@" Framework"]];
+  if (!cr_bundle_exe || !cr_framework_shlib_path)
+    return false;
+
+  // A few more sanity checks.
+  BOOL is_directory;
+  BOOL exists = [[NSFileManager defaultManager]
+                    fileExistsAtPath:cr_framework_shlib_path
+                         isDirectory:&is_directory];
+  if (!exists || is_directory)
+    return false;
+
+  // Everything OK, copy output parameters.
+  *raw_version_str = base::SysNSStringToUTF16(cr_version);
+  *version_path = base::mac::NSStringToFilePath(cr_versioned_path);
+  *framework_shlib_path =
+      base::mac::NSStringToFilePath(cr_framework_shlib_path);
+  return true;
+}
+
+}  // namespace app_mode
diff --git a/chrome/common/mac/app_mode_chrome_locator_unittest.mm b/chrome/common/mac/app_mode_chrome_locator_unittest.mm
new file mode 100644
index 0000000..b54fee7
--- /dev/null
+++ b/chrome/common/mac/app_mode_chrome_locator_unittest.mm
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 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.
+
+#import "chrome/common/mac/app_mode_chrome_locator.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/scoped_temp_dir.h"
+#include "chrome/common/chrome_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Return the path to the Chrome/Chromium app bundle compiled along with the
+// test executable.
+void GetChromeBundlePath(FilePath* chrome_bundle) {
+  FilePath path;
+  PathService::Get(base::DIR_EXE, &path);
+  path = path.Append(chrome::kBrowserProcessExecutableNameChromium);
+  path = path.ReplaceExtension(FilePath::StringType("app"));
+  *chrome_bundle = path;
+}
+
+}  // namespace
+
+TEST(ChromeLocatorTest, FindBundle) {
+  FilePath finder_bundle_path;
+  EXPECT_TRUE(
+      app_mode::FindBundleById(@"com.apple.finder", &finder_bundle_path));
+  EXPECT_TRUE(file_util::DirectoryExists(finder_bundle_path));
+}
+
+TEST(ChromeLocatorTest, FindNonExistentBundle) {
+  FilePath dummy;
+  EXPECT_FALSE(app_mode::FindBundleById(@"this.doesnt.exist", &dummy));
+}
+
+TEST(ChromeLocatorTest, GetNonExistentBundleInfo) {
+  ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  string16 raw_version;
+  FilePath version_path;
+  FilePath framework_path;
+  EXPECT_FALSE(app_mode::GetChromeBundleInfo(temp_dir.path(),
+      &raw_version, &version_path, &framework_path));
+}
+
+TEST(ChromeLocatorTest, GetChromeBundleInfo) {
+  using app_mode::GetChromeBundleInfo;
+
+  FilePath chrome_bundle_path;
+  GetChromeBundlePath(&chrome_bundle_path);
+  ASSERT_TRUE(file_util::DirectoryExists(chrome_bundle_path));
+
+  string16 raw_version;
+  FilePath version_path;
+  FilePath framework_path;
+  EXPECT_TRUE(GetChromeBundleInfo(chrome_bundle_path,
+      &raw_version, &version_path, &framework_path));
+  EXPECT_GT(raw_version.size(), 0U);
+  EXPECT_TRUE(file_util::DirectoryExists(version_path));
+  EXPECT_TRUE(file_util::PathExists(framework_path));
+}
diff --git a/chrome/common/mac/app_mode_common.h b/chrome/common/mac/app_mode_common.h
new file mode 100644
index 0000000..265e047
--- /dev/null
+++ b/chrome/common/mac/app_mode_common.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_MAC_APP_MODE_COMMON_H_
+#define CHROME_COMMON_MAC_APP_MODE_COMMON_H_
+
+#import <Foundation/Foundation.h>
+
+#include "base/file_path.h"
+#include "base/string16.h"
+
+// This file contains constants, interfaces, etc. which are common to the
+// browser application and the app mode loader (a.k.a. shim).
+
+namespace app_mode {
+
+// The key under which the browser's bundle ID will be stored in the
+// app mode launcher bundle's Info.plist.
+extern NSString* const kBrowserBundleIDKey;
+
+// Key for the shortcut ID.
+extern NSString* const kCrAppModeShortcutIDKey;
+
+// Key for the app's name.
+extern NSString* const kCrAppModeShortcutNameKey;
+
+// Key for the app's URL.
+extern NSString* const kCrAppModeShortcutURLKey;
+
+// Key for the app user data directory.
+extern NSString* const kCrAppModeUserDataDirKey;
+
+// Key for the app's extension path.
+extern NSString* const kCrAppModeExtensionPathKey;
+
+// When the Chrome browser is run, it stores its location in the defaults
+// system using this key.
+extern NSString* const kLastRunAppBundlePathPrefsKey;
+
+// Placeholders used in the app mode loader bundle' Info.plist:
+extern NSString* const kShortcutIdPlaceholder; // Extension shortcut ID.
+extern NSString* const kShortcutNamePlaceholder; // Extension name.
+extern NSString* const kShortcutURLPlaceholder;
+// Bundle ID of the Chrome browser bundle.
+extern NSString* const kShortcutBrowserBundleIDPlaceholder;
+
+// Current major/minor version numbers of |ChromeAppModeInfo| (defined below).
+const unsigned kCurrentChromeAppModeInfoMajorVersion = 1;
+const unsigned kCurrentChromeAppModeInfoMinorVersion = 0;
+
+// The structure used to pass information from the app mode loader to the
+// (browser) framework. This is versioned using major and minor version numbers,
+// written below as v<major>.<minor>. Version-number checking is done by the
+// framework, and the framework must accept all structures with the same major
+// version number. It may refuse to load if the major version of the structure
+// is different from the one it accepts.
+struct ChromeAppModeInfo {
+ public:
+  ChromeAppModeInfo();
+  ~ChromeAppModeInfo();
+
+  // Major and minor version number of this structure.
+  unsigned major_version;  // Required: all versions
+  unsigned minor_version;  // Required: all versions
+
+  // Original |argc| and |argv|.
+  int argc;  // Required: v1.0
+  char** argv;  // Required: v1.0
+
+  // Versioned path to the browser which is being loaded.
+  FilePath chrome_versioned_path;  // Required: v1.0
+
+  // Path to Chrome app bundle.
+  FilePath chrome_outer_bundle_path;  // Required: v1.0
+
+  // Information about the App Mode shortcut:
+
+  // Path to the App Mode Loader application bundle that launched the process.
+  FilePath app_mode_bundle_path;  // Optional: v1.0
+
+  // Short ID string, preferably derived from |app_mode_short_name|. Should be
+  // safe for the file system.
+  std::string app_mode_id;  // Required: v1.0
+
+  // Unrestricted (e.g., several-word) UTF8-encoded name for the shortcut.
+  string16 app_mode_name;  // Optional: v1.0
+
+  // URL for the shortcut. Must be a valid URL.
+  std::string app_mode_url;  // Required: v1.0
+
+  // Path to the app's user data directory.
+  FilePath user_data_dir;
+
+  // Path to the app's extension.
+  FilePath extension_path;
+};
+
+}  // namespace app_mode
+
+#endif  // CHROME_COMMON_MAC_APP_MODE_COMMON_H_
diff --git a/chrome/common/mac/app_mode_common.mm b/chrome/common/mac/app_mode_common.mm
new file mode 100644
index 0000000..c6d5573
--- /dev/null
+++ b/chrome/common/mac/app_mode_common.mm
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/mac/app_mode_common.h"
+
+namespace app_mode {
+
+NSString* const kBrowserBundleIDKey = @"CrBundleIdentifier";
+NSString* const kCrAppModeShortcutIDKey = @"CrAppModeShortcutID";
+NSString* const kCrAppModeShortcutNameKey = @"CrAppModeShortcutName";
+NSString* const kCrAppModeShortcutURLKey = @"CrAppModeShortcutURL";
+NSString* const kCrAppModeUserDataDirKey = @"CrAppModeUserDataDir";
+NSString* const kCrAppModeExtensionPathKey = @"CrAppModeExtensionPath";
+
+NSString* const kLastRunAppBundlePathPrefsKey = @"LastRunAppBundlePath";
+
+NSString* const kShortcutIdPlaceholder = @"APP_MODE_SHORTCUT_ID";
+NSString* const kShortcutNamePlaceholder = @"APP_MODE_SHORTCUT_NAME";
+NSString* const kShortcutURLPlaceholder = @"APP_MODE_SHORTCUT_URL";
+NSString* const kShortcutBrowserBundleIDPlaceholder =
+                    @"APP_MODE_BROWSER_BUNDLE_ID";
+
+ChromeAppModeInfo::ChromeAppModeInfo()
+    : major_version(0),
+      minor_version(0),
+      argc(0),
+      argv(0) {
+}
+
+ChromeAppModeInfo::~ChromeAppModeInfo() {
+}
+
+}  // namespace app_mode
diff --git a/chrome/common/mac/cfbundle_blocker.h b/chrome/common/mac/cfbundle_blocker.h
new file mode 100644
index 0000000..4222035
--- /dev/null
+++ b/chrome/common/mac/cfbundle_blocker.h
@@ -0,0 +1,43 @@
+// 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.
+
+#ifndef CHROME_COMMON_MAC_CFBUNDLE_BLOCKER_H_
+#define CHROME_COMMON_MAC_CFBUNDLE_BLOCKER_H_
+
+#if defined(__OBJC__)
+@class NSString;
+#else
+class NSString;
+#endif
+
+namespace chrome {
+namespace common {
+namespace mac {
+
+// Arranges to block loading of some third-party plug-in code that might try
+// to inject itself into the process. Modules loaded by CFBundle are blocked
+// if located within specific directories. Because NSBundle uses CFBundle
+// behind the scenes, this also blocks modules loaded by NSBundle when located
+// in those same specific directories.
+//
+// Blocked modules include input managers, contextual menu items, and
+// scripting additions installed in per-user (~/Library), per-machine
+// (/Library), or network (/Network/Library) locations. Modules installed in
+// the operating system location (/System/Library) are never blocked.
+//
+// This mechanism does not prevent CFBundle (or NSBundle) objects from being
+// created, but it does block them from loading modules into the process.
+void EnableCFBundleBlocker();
+
+// Returns true if |bundle_id| and |version| identify a bundle that is allowed
+// to be loaded even when found in a blocked directory.
+//
+// Exposed only for testing. Do not call from outside the implementation.
+bool IsBundleAllowed(NSString* bundle_id, NSString* version);
+
+}  // namespace mac
+}  // namespace common
+}  // namespace chrome
+
+#endif  // CHROME_COMMON_MAC_CFBUNDLE_BLOCKER_H_
diff --git a/chrome/common/mac/cfbundle_blocker.mm b/chrome/common/mac/cfbundle_blocker.mm
new file mode 100644
index 0000000..3dbc34f
--- /dev/null
+++ b/chrome/common/mac/cfbundle_blocker.mm
@@ -0,0 +1,332 @@
+// 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.
+
+#include "chrome/common/mac/cfbundle_blocker.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#import <Foundation/Foundation.h>
+
+#include "base/logging.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/mac/scoped_nsautorelease_pool.h"
+#import "base/memory/scoped_nsobject.h"
+#include "base/sys_string_conversions.h"
+#include "third_party/mach_override/mach_override.h"
+
+extern "C" {
+
+// _CFBundleLoadExecutableAndReturnError is the internal implementation that
+// results in a dylib being loaded via dlopen. Both CFBundleLoadExecutable and
+// CFBundleLoadExecutableAndReturnError are funneled into this routine. Other
+// CFBundle functions may also call directly into here, perhaps due to
+// inlining their calls to CFBundleLoadExecutable.
+//
+// See CF-476.19/CFBundle.c (10.5.8), CF-550.43/CFBundle.c (10.6.8), and
+// CF-635/Bundle.c (10.7.0) and the disassembly of the shipping object code.
+//
+// Because this is a private function not declared by
+// <CoreFoundation/CoreFoundation.h>, provide a declaration here.
+Boolean _CFBundleLoadExecutableAndReturnError(CFBundleRef bundle,
+                                              Boolean force_global,
+                                              CFErrorRef* error);
+
+}  // extern "C"
+
+namespace chrome {
+namespace common {
+namespace mac {
+
+namespace {
+
+// Returns an autoreleased array of paths that contain plug-ins that should be
+// forbidden to load. Each element of the array will be a string containing
+// an absolute pathname ending in '/'.
+NSArray* BlockedPaths() {
+  NSMutableArray* blocked_paths;
+
+  {
+    base::mac::ScopedNSAutoreleasePool autorelease_pool;
+
+    // ~/Library, /Library, and /Network/Library. Things in /System/Library
+    // aren't blacklisted.
+    NSArray* blocked_prefixes =
+       NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
+                                           NSUserDomainMask |
+                                               NSLocalDomainMask |
+                                               NSNetworkDomainMask,
+                                           YES);
+
+    // Everything in the suffix list has a trailing slash so as to only block
+    // loading things contained in these directories.
+    NSString* const blocked_suffixes[] = {
+#if !defined(__LP64__)
+      // Contextual menu manager plug-ins are unavailable to 64-bit processes.
+      // http://developer.apple.com/library/mac/releasenotes/Cocoa/AppKitOlderNotes.html#NSMenu
+      // Contextual menu plug-ins are loaded when a contextual menu is opened,
+      // for example, from within
+      // +[NSMenu popUpContextMenu:withEvent:forView:].
+      @"Contextual Menu Items/",
+
+      // Input managers are deprecated, would only be loaded under specific
+      // circumstances, and are entirely unavailable to 64-bit processes.
+      // http://developer.apple.com/library/mac/releasenotes/Cocoa/AppKitOlderNotes.html#NSInputManager
+      // Input managers are loaded when the NSInputManager class is
+      // initialized.
+      @"InputManagers/",
+#endif  // __LP64__
+
+      // Don't load third-party scripting additions either. Scripting
+      // additions are loaded by AppleScript from within AEProcessAppleEvent
+      // in response to an Apple Event.
+      @"ScriptingAdditions/"
+
+      // This list is intentionally incomplete. For example, it doesn't block
+      // printer drivers or Internet plug-ins.
+    };
+
+    NSUInteger blocked_paths_count = [blocked_prefixes count] *
+                                     arraysize(blocked_suffixes);
+
+    // Not autoreleased here, because the enclosing pool is scoped too
+    // narrowly.
+    blocked_paths =
+        [[NSMutableArray alloc] initWithCapacity:blocked_paths_count];
+
+    // Build a flat list by adding each suffix to each prefix.
+    for (NSString* blocked_prefix in blocked_prefixes) {
+      for (size_t blocked_suffix_index = 0;
+           blocked_suffix_index < arraysize(blocked_suffixes);
+           ++blocked_suffix_index) {
+        NSString* blocked_suffix = blocked_suffixes[blocked_suffix_index];
+        NSString* blocked_path =
+            [blocked_prefix stringByAppendingPathComponent:blocked_suffix];
+
+        [blocked_paths addObject:blocked_path];
+      }
+    }
+
+    DCHECK_EQ([blocked_paths count], blocked_paths_count);
+  }
+
+  return [blocked_paths autorelease];
+}
+
+// Returns true if bundle_path identifies a path within a blocked directory.
+// Blocked directories are those returned by BlockedPaths().
+bool IsBundlePathBlocked(NSString* bundle_path) {
+  static NSArray* blocked_paths = [BlockedPaths() retain];
+
+  for (NSString* blocked_path in blocked_paths) {
+    NSUInteger blocked_path_length = [blocked_path length];
+
+    // Do a case-insensitive comparison because most users will be on
+    // case-insensitive HFS+ filesystems and it's cheaper than asking the
+    // disk. This is like [bundle_path hasPrefix:blocked_path] but is
+    // case-insensitive.
+    if ([bundle_path length] >= blocked_path_length &&
+        [bundle_path compare:blocked_path
+                     options:NSCaseInsensitiveSearch
+                       range:NSMakeRange(0, blocked_path_length)] ==
+        NSOrderedSame) {
+      // If bundle_path is inside blocked_path (it has blocked_path as a
+      // prefix), refuse to load it.
+      return true;
+    }
+  }
+
+  // bundle_path is not inside any blocked_path from blocked_paths.
+  return false;
+}
+
+typedef Boolean (*_CFBundleLoadExecutableAndReturnError_Type)(CFBundleRef,
+                                                              Boolean,
+                                                              CFErrorRef*);
+
+// Call this to execute the original implementation of
+// _CFBundleLoadExecutableAndReturnError.
+_CFBundleLoadExecutableAndReturnError_Type
+    g_original_underscore_cfbundle_load_executable_and_return_error;
+
+Boolean ChromeCFBundleLoadExecutableAndReturnError(CFBundleRef bundle,
+                                                   Boolean force_global,
+                                                   CFErrorRef* error) {
+  base::mac::ScopedNSAutoreleasePool autorelease_pool;
+
+  DCHECK(g_original_underscore_cfbundle_load_executable_and_return_error);
+
+  base::mac::ScopedCFTypeRef<CFURLRef> url_cf(CFBundleCopyBundleURL(bundle));
+  scoped_nsobject<NSString> path(base::mac::CFToNSCast(
+      CFURLCopyFileSystemPath(url_cf, kCFURLPOSIXPathStyle)));
+
+  NSString* bundle_id = base::mac::CFToNSCast(CFBundleGetIdentifier(bundle));
+
+  NSDictionary* bundle_dictionary =
+      base::mac::CFToNSCast(CFBundleGetInfoDictionary(bundle));
+  NSString* version = [bundle_dictionary objectForKey:
+      base::mac::CFToNSCast(kCFBundleVersionKey)];
+  if (![version isKindOfClass:[NSString class]]) {
+    // Deal with pranksters.
+    version = nil;
+  }
+
+  if (IsBundlePathBlocked(path) && !IsBundleAllowed(bundle_id, version)) {
+    NSString* bundle_id_print = bundle_id ? bundle_id : @"(nil)";
+    NSString* version_print = version ? version : @"(nil)";
+
+    // Provide a hint for the user (or module developer) to figure out
+    // that the bundle was blocked.
+    LOG(INFO) << "Blocking attempt to load bundle "
+              << [bundle_id_print UTF8String]
+              << " version "
+              << [version_print UTF8String]
+              << " at "
+              << [path fileSystemRepresentation];
+
+    if (error) {
+      base::mac::ScopedCFTypeRef<CFStringRef> app_bundle_id(
+          base::SysUTF8ToCFStringRef(base::mac::BaseBundleID()));
+
+      // 0xb10c10ad = "block load"
+      const CFIndex kBundleLoadBlocked = 0xb10c10ad;
+
+      NSMutableDictionary* error_dict =
+          [NSMutableDictionary dictionaryWithCapacity:4];
+      if (bundle_id) {
+        [error_dict setObject:bundle_id forKey:@"bundle_id"];
+      }
+      if (version) {
+        [error_dict setObject:version forKey:@"version"];
+      }
+      if (path) {
+        [error_dict setObject:path forKey:@"path"];
+      }
+      NSURL* url_ns = base::mac::CFToNSCast(url_cf);
+      NSString* url_absolute_string = [url_ns absoluteString];
+      if (url_absolute_string) {
+        [error_dict setObject:url_absolute_string forKey:@"url"];
+      }
+
+      *error = CFErrorCreate(NULL,
+                             app_bundle_id,
+                             kBundleLoadBlocked,
+                             base::mac::NSToCFCast(error_dict));
+    }
+
+    return FALSE;
+  }
+
+  // Not blocked. Call through to the original implementation.
+  return g_original_underscore_cfbundle_load_executable_and_return_error(
+      bundle, force_global, error);
+}
+
+}  // namespace
+
+void EnableCFBundleBlocker() {
+  mach_error_t err = mach_override_ptr(
+      reinterpret_cast<void*>(_CFBundleLoadExecutableAndReturnError),
+      reinterpret_cast<void*>(ChromeCFBundleLoadExecutableAndReturnError),
+      reinterpret_cast<void**>(
+          &g_original_underscore_cfbundle_load_executable_and_return_error));
+  if (err != err_none) {
+    DLOG(WARNING) << "mach_override _CFBundleLoadExecutableAndReturnError: "
+                  << err;
+  }
+}
+
+namespace {
+
+struct AllowedBundle {
+  // The bundle identifier to permit. These are matched with a case-sensitive
+  // literal comparison. "Children" of the declared bundle ID are permitted:
+  // if bundle_id here is @"org.chromium", it would match both @"org.chromium"
+  // and @"org.chromium.Chromium".
+  NSString* bundle_id;
+
+  // If bundle_id should only be permitted as of a certain minimum version,
+  // this string defines that version, which will be compared to the bundle's
+  // version with a numeric comparison. If bundle_id may be permitted at any
+  // version, set minimum_version to nil.
+  NSString* minimum_version;
+};
+
+}  // namespace
+
+bool IsBundleAllowed(NSString* bundle_id, NSString* version) {
+  // The list of bundles that are allowed to load. Before adding an entry to
+  // this list, be sure that it's well-behaved. Specifically, anything that
+  // uses mach_override
+  // (https://github.com/rentzsch/mach_star/tree/master/mach_override) must
+  // use version 51ae3d199463fa84548f466d649f0821d579fdaf (July 22, 2011) or
+  // newer. Products added to the list must not cause crashes. Entries should
+  // include the name of the product, URL, and the name and e-mail address of
+  // someone responsible for the product's engineering. To add items to this
+  // list, file a bug at http://new.crbug.com/ using the "Defect on Mac OS"
+  // template, and provide the bundle ID (or IDs) and minimum CFBundleVersion
+  // that's safe for Chrome to load, along with the necessary product and
+  // contact information. Whitelisted bundles in this list may be removed if
+  // they are found to cause instability or otherwise behave badly. With
+  // proper contact information, Chrome developers may try to contact
+  // maintainers to resolve any problems.
+  const AllowedBundle kAllowedBundles[] = {
+    // Google Authenticator BT
+    // Dave MacLachlan <dmaclach@google.com>
+    { @"com.google.osax.Google_Authenticator_BT", nil },
+
+    // Default Folder X, http://www.stclairsoft.com/DefaultFolderX/
+    // Jon Gotow <gotow@stclairsoft.com>
+    { @"com.stclairsoft.DefaultFolderX", @"4.4.3" },
+
+    // MySpeed, http://www.enounce.com/myspeed
+    // Edward Bianchi <ejbianchi@enounce.com>
+    { @"com.enounce.MySpeed.osax", @"1201" },
+
+    // SIMBL (fork), https://github.com/albertz/simbl
+    // Albert Zeyer <albzey@googlemail.com>
+    { @"net.culater.SIMBL", nil },
+
+    // Smart Scroll, http://marcmoini.com/sx_en.html
+    // Marc Moini <marc@a9ff.com>
+    { @"com.marcmoini.SmartScroll", @"3.9" },
+  };
+
+  for (size_t index = 0; index < arraysize(kAllowedBundles); ++index) {
+    const AllowedBundle& allowed_bundle = kAllowedBundles[index];
+    NSString* allowed_bundle_id = allowed_bundle.bundle_id;
+    NSUInteger allowed_bundle_id_length = [allowed_bundle_id length];
+
+    // Permit bundle identifiers that are exactly equal to the allowed
+    // identifier, as well as "children" of the allowed identifier.
+    if ([bundle_id isEqualToString:allowed_bundle_id] ||
+        ([bundle_id length] > allowed_bundle_id_length &&
+         [bundle_id characterAtIndex:allowed_bundle_id_length] == '.' &&
+         [bundle_id hasPrefix:allowed_bundle_id])) {
+      NSString* minimum_version = allowed_bundle.minimum_version;
+      if (!minimum_version) {
+        // If the rule didn't declare any version requirement, the bundle is
+        // allowed to load.
+        return true;
+      }
+
+      if (!version) {
+        // If there wasn't any version but one was required, the bundle isn't
+        // allowed to load.
+        return false;
+      }
+
+      // A numeric search is appropriate for comparing version numbers.
+      NSComparisonResult result = [version compare:minimum_version
+                                           options:NSNumericSearch];
+      return result != NSOrderedAscending;
+    }
+  }
+
+  // Nothing matched.
+  return false;
+}
+
+}  // namespace mac
+}  // namespace common
+}  // namespace chrome
diff --git a/chrome/common/mac/cfbundle_blocker_unittest.mm b/chrome/common/mac/cfbundle_blocker_unittest.mm
new file mode 100644
index 0000000..2e23fe2
--- /dev/null
+++ b/chrome/common/mac/cfbundle_blocker_unittest.mm
@@ -0,0 +1,93 @@
+// 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.
+
+#include "chrome/common/mac/cfbundle_blocker.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome {
+namespace common {
+namespace mac {
+namespace {
+
+struct IsBundleAllowedTestcase {
+  NSString* bundle_id;
+  NSString* version;
+  bool allowed;
+};
+
+TEST(CFBundleBlockerTest, IsBundleAllowed) {
+  const IsBundleAllowedTestcase kTestcases[] = {
+    // Block things without a bundle ID.
+    { nil, nil, false },
+
+    // Block bundle IDs that aren't in the whitelist.
+    { @"org.chromium.Chromium.evil", nil, false },
+
+    // The AllowedBundle structure for Google Authetnicator BT doesn't
+    // require a version, so this should work equally well with any version
+    // including no version at all.
+    { @"com.google.osax.Google_Authenticator_BT", nil, true },
+    { @"com.google.osax.Google_Authenticator_BT", @"0.5.0.0", true },
+
+    // Typos should be blocked.
+    { @"com.google.osax.Google_Authenticator_B", nil, false },
+    { @"com.google.osax.Google_Authenticator_BQ", nil, false },
+    { @"com.google.osax.Google_Authenticator_BTQ", nil, false },
+    { @"com.google.osax", nil, false },
+    { @"com.google", nil, false },
+    { @"com", nil, false },
+    { @"", nil, false },
+
+    // MySpeed requires a version, so make sure that versions below don't work
+    // and versions above do.
+    { @"com.enounce.MySpeed.osax", nil, false },
+    { @"com.enounce.MySpeed.osax", @"", false },
+    { @"com.enounce.MySpeed.osax", @"1200", false },
+    { @"com.enounce.MySpeed.osax", @"1201", true },
+    { @"com.enounce.MySpeed.osax", @"1202", true },
+
+    // DefaultFolderX is whitelisted as com.stclairsoft.DefaultFolderX. Make
+    // sure that "child" IDs such as com.stclairsoft.DefaultFolderX.osax work.
+    // It uses a dotted versioning scheme, so test the version comparator out.
+    { @"com.stclairsoft.DefaultFolderX.osax", nil, false },
+    { @"com.stclairsoft.DefaultFolderX.osax", @"", false },
+    { @"com.stclairsoft.DefaultFolderX.osax", @"3.5.4", false },
+    { @"com.stclairsoft.DefaultFolderX.osax", @"4.3.4", false },
+    { @"com.stclairsoft.DefaultFolderX.osax", @"4.4.2", false },
+    { @"com.stclairsoft.DefaultFolderX.osax", @"4.4.3", true },
+    { @"com.stclairsoft.DefaultFolderX.osax", @"4.4.4", true },
+    { @"com.stclairsoft.DefaultFolderX.osax", @"4.4.10", true },
+    { @"com.stclairsoft.DefaultFolderX.osax", @"4.5", true },
+    { @"com.stclairsoft.DefaultFolderX.osax", @"4.5.2", true },
+    { @"com.stclairsoft.DefaultFolderX.osax", @"4.10", true },
+    { @"com.stclairsoft.DefaultFolderX.osax", @"4.10.2", true },
+    { @"com.stclairsoft.DefaultFolderX.osax", @"5", true },
+    { @"com.stclairsoft.DefaultFolderX.osax", @"5.3", true },
+    { @"com.stclairsoft.DefaultFolderX.osax", @"5.3.2", true },
+
+    // Other "child" IDs that might want to load.
+    { @"com.stclairsoft.DefaultFolderX.CarbonPatcher", @"4.4.3", true },
+    { @"com.stclairsoft.DefaultFolderX.CocoaPatcher", @"4.4.3", true },
+  };
+
+  for (size_t index = 0; index < arraysize(kTestcases); ++index) {
+    const IsBundleAllowedTestcase& testcase = kTestcases[index];
+    NSString* bundle_id = testcase.bundle_id;
+    NSString* version = testcase.version;
+    NSString* version_print = version ? version : @"(nil)";
+    EXPECT_EQ(testcase.allowed, IsBundleAllowed(bundle_id, version))
+        << "index " << index
+        << ", bundle_id " << [bundle_id UTF8String]
+        << ", version " << [version_print UTF8String];
+  }
+}
+
+}  // namespace
+}  // namespace mac
+}  // namespace common
+}  // namespace chrome
diff --git a/chrome/common/mac/launchd.h b/chrome/common/mac/launchd.h
new file mode 100644
index 0000000..46d4080
--- /dev/null
+++ b/chrome/common/mac/launchd.h
@@ -0,0 +1,100 @@
+// 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.
+
+#ifndef CHROME_COMMON_MAC_LAUNCHD_H_
+#define CHROME_COMMON_MAC_LAUNCHD_H_
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "base/basictypes.h"
+#include "base/memory/singleton.h"
+
+class Launchd {
+ public:
+  enum Type {
+    Agent,  // LaunchAgent
+    Daemon  // LaunchDaemon
+  };
+
+  // Domains map to NSSearchPathDomainMask so Foundation does not need to be
+  // included.
+  enum Domain {
+    User = 1,  // ~/Library/Launch*
+    Local = 2,  // /Library/Launch*
+    Network = 4,  // /Network/Library/Launch*
+    System = 8  // /System/Library/Launch*
+  };
+
+  // TODO(dmaclach): Get rid of this pseudo singleton, and inject it
+  // appropriately wherever it is used.
+  // http://crbug.com/76925
+  static Launchd* GetInstance();
+
+  virtual ~Launchd();
+
+  // Return a dictionary with the launchd export settings.
+  virtual CFDictionaryRef CopyExports();
+
+  // Return a dictionary with the launchd entries for job labeled |name|.
+  virtual CFDictionaryRef CopyJobDictionary(CFStringRef label);
+
+  // Return a dictionary for launchd process.
+  virtual CFDictionaryRef CopyDictionaryByCheckingIn(CFErrorRef* error);
+
+  // Remove a launchd process from launchd.
+  virtual bool RemoveJob(CFStringRef label, CFErrorRef* error);
+
+  // Used by a process controlled by launchd to restart itself.
+  // |session_type| can be "Aqua", "LoginWindow", "Background", "StandardIO" or
+  // "System".
+  // RestartLaunchdJob starts up a separate process to tell launchd to
+  // send this process a SIGTERM. This call will return, but a SIGTERM will be
+  // received shortly.
+  virtual bool RestartJob(Domain domain,
+                          Type type,
+                          CFStringRef name,
+                          CFStringRef session_type);
+
+  // Read a launchd plist from disk.
+  // |name| should not have an extension.
+  virtual CFMutableDictionaryRef CreatePlistFromFile(Domain domain,
+                                                     Type type,
+                                                     CFStringRef name);
+  // Write a launchd plist to disk.
+  // |name| should not have an extension.
+  virtual bool WritePlistToFile(Domain domain,
+                                Type type,
+                                CFStringRef name,
+                                CFDictionaryRef dict);
+
+  // Delete a launchd plist.
+  // |name| should not have an extension.
+  virtual bool DeletePlist(Domain domain, Type type, CFStringRef name);
+
+  // TODO(dmaclach): remove this once http://crbug.com/76925 is fixed.
+  // Scaffolding for doing unittests with our singleton.
+  static void SetInstance(Launchd* instance);
+  class ScopedInstance {
+   public:
+    explicit ScopedInstance(Launchd* instance) {
+      Launchd::SetInstance(instance);
+    }
+    ~ScopedInstance() {
+      Launchd::SetInstance(NULL);
+    }
+  };
+
+ protected:
+  Launchd() { }
+
+ private:
+  // TODO(dmaclach): remove this once http://crbug.com/76925 is fixed.
+  // Scaffolding for doing unittests with our singleton.
+  friend struct DefaultSingletonTraits<Launchd>;
+  static Launchd* g_instance_;
+
+  DISALLOW_COPY_AND_ASSIGN(Launchd);
+};
+
+#endif  // CHROME_COMMON_MAC_LAUNCHD_H_
diff --git a/chrome/common/mac/launchd.mm b/chrome/common/mac/launchd.mm
new file mode 100644
index 0000000..9bbdf6d
--- /dev/null
+++ b/chrome/common/mac/launchd.mm
@@ -0,0 +1,169 @@
+// 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.
+
+#include "chrome/common/mac/launchd.h"
+
+#import <Foundation/Foundation.h>
+#include <launch.h>
+
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/process_util.h"
+#include "base/stringprintf.h"
+#include "base/sys_string_conversions.h"
+#include "third_party/GTM/Foundation/GTMServiceManagement.h"
+
+namespace {
+
+NSString* SanitizeShellArgument(NSString* arg) {
+  if (!arg) {
+    return nil;
+  }
+  NSString *sanitize = [arg stringByReplacingOccurrencesOfString:@"'"
+                                                      withString:@"'\''"];
+  return [NSString stringWithFormat:@"'%@'", sanitize];
+}
+
+NSURL* GetPlistURL(Launchd::Domain domain,
+                   Launchd::Type type,
+                   CFStringRef name) {
+  NSArray* library_paths =
+      NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, domain, YES);
+  DCHECK_EQ([library_paths count], 1U);
+  NSString* library_path = [library_paths objectAtIndex:0];
+
+  NSString *launch_dir_name = (type == Launchd::Daemon) ? @"LaunchDaemons"
+                                                        : @"LaunchAgents";
+  NSString* launch_dir =
+      [library_path stringByAppendingPathComponent:launch_dir_name];
+
+  NSError* err;
+  if (![[NSFileManager defaultManager] createDirectoryAtPath:launch_dir
+                                 withIntermediateDirectories:YES
+                                                  attributes:nil
+                                                       error:&err]) {
+    DLOG(ERROR) << "GetPlistURL " << base::mac::NSToCFCast(err);
+    return nil;
+  }
+
+  NSString* plist_file_path =
+      [launch_dir stringByAppendingPathComponent:base::mac::CFToNSCast(name)];
+  plist_file_path = [plist_file_path stringByAppendingPathExtension:@"plist"];
+  return [NSURL fileURLWithPath:plist_file_path isDirectory:NO];
+}
+
+}  // namespace
+
+COMPILE_ASSERT(static_cast<int>(Launchd::User) ==
+               static_cast<int>(NSUserDomainMask),
+               NSUserDomainMask_value_changed);
+COMPILE_ASSERT(static_cast<int>(Launchd::Local) ==
+               static_cast<int>(NSLocalDomainMask),
+               NSLocalDomainMask_value_changed);
+COMPILE_ASSERT(static_cast<int>(Launchd::Network) ==
+               static_cast<int>(NSNetworkDomainMask),
+               NSNetworkDomainMask_value_changed);
+COMPILE_ASSERT(static_cast<int>(Launchd::System) ==
+               static_cast<int>(NSSystemDomainMask),
+               NSSystemDomainMask_value_changed);
+
+Launchd* Launchd::g_instance_ = NULL;
+
+Launchd* Launchd::GetInstance() {
+  if (!g_instance_) {
+    g_instance_ = Singleton<Launchd>::get();
+  }
+  return g_instance_;
+}
+
+void Launchd::SetInstance(Launchd* instance) {
+  if (instance) {
+    CHECK(!g_instance_);
+  }
+  g_instance_ = instance;
+}
+
+Launchd::~Launchd() { }
+
+CFDictionaryRef Launchd::CopyExports() {
+  return GTMCopyLaunchdExports();
+}
+
+CFDictionaryRef Launchd::CopyJobDictionary(CFStringRef label) {
+  return GTMSMJobCopyDictionary(label);
+}
+
+CFDictionaryRef Launchd::CopyDictionaryByCheckingIn(CFErrorRef* error) {
+  return GTMSMCopyJobCheckInDictionary(error);
+}
+
+bool Launchd::RemoveJob(CFStringRef label, CFErrorRef* error) {
+  return GTMSMJobRemove(label, error);
+}
+
+bool Launchd::RestartJob(Domain domain,
+                         Type type,
+                         CFStringRef name,
+                         CFStringRef cf_session_type) {
+  base::mac::ScopedNSAutoreleasePool pool;
+  NSURL* url = GetPlistURL(domain, type, name);
+  NSString* ns_path = [url path];
+  ns_path = SanitizeShellArgument(ns_path);
+  const char* file_path = [ns_path fileSystemRepresentation];
+
+  NSString* ns_session_type =
+      SanitizeShellArgument(base::mac::CFToNSCast(cf_session_type));
+  if (!file_path || !ns_session_type) {
+    return false;
+  }
+
+  std::vector<std::string> argv;
+  argv.push_back("/bin/bash");
+  argv.push_back("--noprofile");
+  argv.push_back("-c");
+  std::string command = base::StringPrintf(
+      "/bin/launchctl unload -S %s %s;"
+      "/bin/launchctl load -S %s %s;",
+      [ns_session_type UTF8String], file_path,
+      [ns_session_type UTF8String], file_path);
+  argv.push_back(command);
+
+  base::LaunchOptions options;
+  options.new_process_group = true;
+  return base::LaunchProcess(argv, options, NULL);
+}
+
+CFMutableDictionaryRef Launchd::CreatePlistFromFile(Domain domain,
+                                                    Type type,
+                                                    CFStringRef name) {
+  base::mac::ScopedNSAutoreleasePool pool;
+  NSURL* ns_url = GetPlistURL(domain, type, name);
+  NSMutableDictionary* plist =
+      [[NSMutableDictionary alloc] initWithContentsOfURL:ns_url];
+  return base::mac::NSToCFCast(plist);
+}
+
+bool Launchd::WritePlistToFile(Domain domain,
+                               Type type,
+                               CFStringRef name,
+                               CFDictionaryRef dict) {
+  base::mac::ScopedNSAutoreleasePool pool;
+  NSURL* ns_url = GetPlistURL(domain, type, name);
+  return [base::mac::CFToNSCast(dict) writeToURL:ns_url atomically:YES];
+}
+
+bool Launchd::DeletePlist(Domain domain, Type type, CFStringRef name) {
+  base::mac::ScopedNSAutoreleasePool pool;
+  NSURL* ns_url = GetPlistURL(domain, type, name);
+  NSError* err = nil;
+  if (![[NSFileManager defaultManager] removeItemAtPath:[ns_url path]
+                                                  error:&err]) {
+    if ([err code] != NSFileNoSuchFileError) {
+      DLOG(ERROR) << "DeletePlist: " << base::mac::NSToCFCast(err);
+    }
+    return false;
+  }
+  return true;
+}
diff --git a/chrome/common/mac/mock_launchd.cc b/chrome/common/mac/mock_launchd.cc
new file mode 100644
index 0000000..b6689d7
--- /dev/null
+++ b/chrome/common/mac/mock_launchd.cc
@@ -0,0 +1,301 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/mac/mock_launchd.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/message_loop.h"
+#include "base/process_util.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/sys_string_conversions.h"
+#include "chrome/common/chrome_version_info.h"
+#include "chrome/common/mac/launchd.h"
+#include "chrome/common/service_process_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+static sockaddr_un* throwaway_sockaddr_un;
+static const size_t kMaxPipeNameLength =
+    sizeof(throwaway_sockaddr_un->sun_path);
+
+// static
+bool MockLaunchd::MakeABundle(const FilePath& dst,
+                              const std::string& name,
+                              FilePath* bundle_root,
+                              FilePath* executable) {
+  *bundle_root = dst.Append(name + std::string(".app"));
+  FilePath contents = bundle_root->AppendASCII("Contents");
+  FilePath mac_os = contents.AppendASCII("MacOS");
+  *executable = mac_os.Append(name);
+  FilePath info_plist = contents.Append("Info.plist");
+
+  if (!file_util::CreateDirectory(mac_os)) {
+    return false;
+  }
+  const char *data = "#! testbundle\n";
+  int len = strlen(data);
+  if (file_util::WriteFile(*executable, data, len) != len) {
+    return false;
+  }
+  if (chmod(executable->value().c_str(), 0555) != 0) {
+    return false;
+  }
+
+  chrome::VersionInfo version_info;
+
+  const char* info_plist_format =
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+      "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
+          "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+      "<plist version=\"1.0\">\n"
+      "<dict>\n"
+      "  <key>CFBundleDevelopmentRegion</key>\n"
+      "  <string>English</string>\n"
+      "  <key>CFBundleExecutable</key>\n"
+      "  <string>%s</string>\n"
+      "  <key>CFBundleIdentifier</key>\n"
+      "  <string>com.test.%s</string>\n"
+      "  <key>CFBundleInfoDictionaryVersion</key>\n"
+      "  <string>6.0</string>\n"
+      "  <key>CFBundleShortVersionString</key>\n"
+      "  <string>%s</string>\n"
+      "  <key>CFBundleVersion</key>\n"
+      "  <string>1</string>\n"
+      "</dict>\n"
+      "</plist>\n";
+  std::string info_plist_data =
+      base::StringPrintf(info_plist_format,
+                         name.c_str(),
+                         name.c_str(),
+                         version_info.Version().c_str());
+  len = info_plist_data.length();
+  if (file_util::WriteFile(info_plist, info_plist_data.c_str(), len) != len) {
+    return false;
+  }
+  const UInt8* bundle_root_path =
+      reinterpret_cast<const UInt8*>(bundle_root->value().c_str());
+  base::mac::ScopedCFTypeRef<CFURLRef> url(
+      CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
+                                              bundle_root_path,
+                                              bundle_root->value().length(),
+                                              true));
+  base::mac::ScopedCFTypeRef<CFBundleRef> bundle(
+      CFBundleCreate(kCFAllocatorDefault, url));
+  return bundle.get();
+}
+
+MockLaunchd::MockLaunchd(const FilePath& file, MessageLoop* loop,
+                         bool create_socket, bool as_service)
+    : file_(file),
+      message_loop_(loop),
+      create_socket_(create_socket),
+      as_service_(as_service),
+      restart_called_(false),
+      remove_called_(false),
+      checkin_called_(false),
+      write_called_(false),
+      delete_called_(false) {
+  std::string pipe_suffix("_SOCKET");
+  FilePath socket_path = file_;
+  while (socket_path.value().length() + pipe_suffix.length() >
+         kMaxPipeNameLength - 2) {
+    socket_path = socket_path.DirName();
+  }
+  pipe_name_ = socket_path.value() + pipe_suffix;
+}
+
+MockLaunchd::~MockLaunchd() {
+}
+
+CFDictionaryRef MockLaunchd::CopyExports() {
+  if (!create_socket_) {
+    ADD_FAILURE();
+    return NULL;
+  }
+
+  CFStringRef env_var =
+      base::mac::NSToCFCast(GetServiceProcessLaunchDSocketEnvVar());
+  base::mac::ScopedCFTypeRef<CFStringRef> socket_path(
+      CFStringCreateWithCString(kCFAllocatorDefault, pipe_name_.c_str(),
+                                kCFStringEncodingUTF8));
+  const void *keys[] = { env_var };
+  const void *values[] = { socket_path };
+  COMPILE_ASSERT(arraysize(keys) == arraysize(values), array_sizes_must_match);
+  return CFDictionaryCreate(kCFAllocatorDefault,
+                            keys,
+                            values,
+                            arraysize(keys),
+                            &kCFTypeDictionaryKeyCallBacks,
+                            &kCFTypeDictionaryValueCallBacks);
+}
+
+CFDictionaryRef MockLaunchd::CopyJobDictionary(CFStringRef label) {
+  if (!as_service_) {
+    scoped_ptr<MultiProcessLock> running_lock(
+        TakeNamedLock(pipe_name_, false));
+    if (running_lock.get())
+      return NULL;
+  }
+
+  CFStringRef program = CFSTR(LAUNCH_JOBKEY_PROGRAM);
+  CFStringRef program_pid = CFSTR(LAUNCH_JOBKEY_PID);
+  const void *keys[] = { program, program_pid };
+  base::mac::ScopedCFTypeRef<CFStringRef> path(
+      base::SysUTF8ToCFStringRef(file_.value()));
+  int process_id = base::GetCurrentProcId();
+  base::mac::ScopedCFTypeRef<CFNumberRef> pid(
+      CFNumberCreate(NULL, kCFNumberIntType, &process_id));
+  const void *values[] = { path, pid };
+  COMPILE_ASSERT(arraysize(keys) == arraysize(values), array_sizes_must_match);
+  return CFDictionaryCreate(kCFAllocatorDefault,
+                            keys,
+                            values,
+                            arraysize(keys),
+                            &kCFTypeDictionaryKeyCallBacks,
+                            &kCFTypeDictionaryValueCallBacks);
+}
+
+CFDictionaryRef MockLaunchd::CopyDictionaryByCheckingIn(CFErrorRef* error) {
+  checkin_called_ = true;
+  CFStringRef program = CFSTR(LAUNCH_JOBKEY_PROGRAM);
+  CFStringRef program_args = CFSTR(LAUNCH_JOBKEY_PROGRAMARGUMENTS);
+  base::mac::ScopedCFTypeRef<CFStringRef> path(
+      base::SysUTF8ToCFStringRef(file_.value()));
+  const void *array_values[] = { path.get() };
+  base::mac::ScopedCFTypeRef<CFArrayRef> args(
+      CFArrayCreate(kCFAllocatorDefault,
+                    array_values,
+                    1,
+                    &kCFTypeArrayCallBacks));
+
+  if (!create_socket_) {
+    const void *keys[] = { program, program_args };
+    const void *values[] = { path, args };
+    COMPILE_ASSERT(arraysize(keys) == arraysize(values),
+                   array_sizes_must_match);
+    return CFDictionaryCreate(kCFAllocatorDefault,
+                              keys,
+                              values,
+                              arraysize(keys),
+                              &kCFTypeDictionaryKeyCallBacks,
+                              &kCFTypeDictionaryValueCallBacks);
+  }
+
+  CFStringRef socket_key = CFSTR(LAUNCH_JOBKEY_SOCKETS);
+  int local_pipe = -1;
+  EXPECT_TRUE(as_service_);
+
+  // Create unix_addr structure.
+  struct sockaddr_un unix_addr = {0};
+  unix_addr.sun_family = AF_UNIX;
+  size_t path_len =
+      base::strlcpy(unix_addr.sun_path, pipe_name_.c_str(), kMaxPipeNameLength);
+  DCHECK_EQ(pipe_name_.length(), path_len);
+  unix_addr.sun_len = SUN_LEN(&unix_addr);
+
+  CFSocketSignature signature;
+  signature.protocolFamily = PF_UNIX;
+  signature.socketType = SOCK_STREAM;
+  signature.protocol = 0;
+  size_t unix_addr_len = offsetof(struct sockaddr_un,
+                                  sun_path) + path_len + 1;
+  base::mac::ScopedCFTypeRef<CFDataRef> address(
+      CFDataCreate(NULL, reinterpret_cast<UInt8*>(&unix_addr), unix_addr_len));
+  signature.address = address;
+
+  CFSocketRef socket =
+      CFSocketCreateWithSocketSignature(NULL, &signature, 0, NULL, NULL);
+
+  local_pipe = CFSocketGetNative(socket);
+  EXPECT_NE(-1, local_pipe);
+  if (local_pipe == -1) {
+    if (error) {
+      *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX,
+                             errno, NULL);
+    }
+    return NULL;
+  }
+
+  base::mac::ScopedCFTypeRef<CFNumberRef> socket_fd(
+      CFNumberCreate(NULL, kCFNumberIntType, &local_pipe));
+  const void *socket_array_values[] = { socket_fd };
+  base::mac::ScopedCFTypeRef<CFArrayRef> sockets(
+      CFArrayCreate(kCFAllocatorDefault,
+                    socket_array_values,
+                    1,
+                    &kCFTypeArrayCallBacks));
+  CFStringRef socket_dict_key = CFSTR("ServiceProcessSocket");
+  const void *socket_keys[] = { socket_dict_key };
+  const void *socket_values[] = { sockets };
+  COMPILE_ASSERT(arraysize(socket_keys) == arraysize(socket_values),
+                 socket_array_sizes_must_match);
+  base::mac::ScopedCFTypeRef<CFDictionaryRef> socket_dict(
+      CFDictionaryCreate(kCFAllocatorDefault,
+                         socket_keys,
+                         socket_values,
+                         arraysize(socket_keys),
+                         &kCFTypeDictionaryKeyCallBacks,
+                         &kCFTypeDictionaryValueCallBacks));
+  const void *keys[] = { program, program_args, socket_key };
+  const void *values[] = { path, args, socket_dict };
+  COMPILE_ASSERT(arraysize(keys) == arraysize(values), array_sizes_must_match);
+  return CFDictionaryCreate(kCFAllocatorDefault,
+                            keys,
+                            values,
+                            arraysize(keys),
+                            &kCFTypeDictionaryKeyCallBacks,
+                            &kCFTypeDictionaryValueCallBacks);
+}
+
+bool MockLaunchd::RemoveJob(CFStringRef label, CFErrorRef* error) {
+  remove_called_ = true;
+  message_loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+  return true;
+}
+
+bool MockLaunchd::RestartJob(Domain domain,
+                             Type type,
+                             CFStringRef name,
+                             CFStringRef session_type) {
+  restart_called_ = true;
+  message_loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+  return true;
+}
+
+CFMutableDictionaryRef MockLaunchd::CreatePlistFromFile(
+    Domain domain,
+    Type type,
+    CFStringRef name)  {
+  base::mac::ScopedCFTypeRef<CFDictionaryRef> dict(
+      CopyDictionaryByCheckingIn(NULL));
+  return CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, dict);
+}
+
+bool MockLaunchd::WritePlistToFile(Domain domain,
+                                   Type type,
+                                   CFStringRef name,
+                                   CFDictionaryRef dict) {
+  write_called_ = true;
+  return true;
+}
+
+bool MockLaunchd::DeletePlist(Domain domain,
+                              Type type,
+                              CFStringRef name) {
+  delete_called_ = true;
+  return true;
+}
+
+void MockLaunchd::SignalReady() {
+  ASSERT_TRUE(as_service_);
+  running_lock_.reset(TakeNamedLock(pipe_name_, true));
+}
diff --git a/chrome/common/mac/mock_launchd.h b/chrome/common/mac/mock_launchd.h
new file mode 100644
index 0000000..147925c
--- /dev/null
+++ b/chrome/common/mac/mock_launchd.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_MAC_MOCK_LAUNCHD_H_
+#define CHROME_COMMON_MAC_MOCK_LAUNCHD_H_
+
+#include <launch.h>
+
+#include <string>
+
+#include "base/file_path.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/common/mac/launchd.h"
+#include "chrome/common/multi_process_lock.h"
+
+class MessageLoop;
+
+// TODO(dmaclach): Write this in terms of a real mock.
+// http://crbug.com/76923
+class MockLaunchd : public Launchd {
+ public:
+  static bool MakeABundle(const FilePath& dst,
+                          const std::string& name,
+                          FilePath* bundle_root,
+                          FilePath* executable);
+
+  MockLaunchd(const FilePath& file, MessageLoop* loop,
+              bool create_socket, bool as_service);
+  virtual ~MockLaunchd();
+
+  virtual CFDictionaryRef CopyExports() OVERRIDE;
+  virtual CFDictionaryRef CopyJobDictionary(CFStringRef label) OVERRIDE;
+  virtual CFDictionaryRef CopyDictionaryByCheckingIn(CFErrorRef* error)
+      OVERRIDE;
+  virtual bool RemoveJob(CFStringRef label, CFErrorRef* error) OVERRIDE;
+  virtual bool RestartJob(Domain domain,
+                          Type type,
+                          CFStringRef name,
+                          CFStringRef session_type) OVERRIDE;
+  virtual CFMutableDictionaryRef CreatePlistFromFile(
+      Domain domain,
+      Type type,
+      CFStringRef name) OVERRIDE;
+  virtual bool WritePlistToFile(Domain domain,
+                                Type type,
+                                CFStringRef name,
+                                CFDictionaryRef dict) OVERRIDE;
+  virtual bool DeletePlist(Domain domain,
+                           Type type,
+                           CFStringRef name) OVERRIDE;
+
+  void SignalReady();
+
+  bool restart_called() const { return restart_called_; }
+  bool remove_called() const { return remove_called_; }
+  bool checkin_called() const { return checkin_called_; }
+  bool write_called() const { return write_called_; }
+  bool delete_called() const { return delete_called_; }
+
+ private:
+  FilePath file_;
+  std::string pipe_name_;
+  MessageLoop* message_loop_;
+  scoped_ptr<MultiProcessLock> running_lock_;
+  bool create_socket_;
+  bool as_service_;
+  bool restart_called_;
+  bool remove_called_;
+  bool checkin_called_;
+  bool write_called_;
+  bool delete_called_;
+};
+
+#endif  // CHROME_COMMON_MAC_MOCK_LAUNCHD_H_
diff --git a/chrome/common/mac/nscoder_util.h b/chrome/common/mac/nscoder_util.h
new file mode 100644
index 0000000..7e73f24
--- /dev/null
+++ b/chrome/common/mac/nscoder_util.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_MAC_NSCODER_UTIL_H_
+#define CHROME_COMMON_MAC_NSCODER_UTIL_H_
+
+#import <Foundation/Foundation.h>
+
+#include <string>
+
+namespace nscoder_util {
+
+// Archives a std::string in an Objective-C key archiver.
+void EncodeString(NSCoder* coder, NSString* key, const std::string& string);
+
+// Decode a std::string from an Objective-C key unarchiver.
+std::string DecodeString(NSCoder* decoder, NSString* key);
+
+}  // namespace nscoder_util
+
+#endif  // CHROME_COMMON_MAC_NSCODER_UTIL_H_
diff --git a/chrome/common/mac/nscoder_util.mm b/chrome/common/mac/nscoder_util.mm
new file mode 100644
index 0000000..6fb1b1c
--- /dev/null
+++ b/chrome/common/mac/nscoder_util.mm
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 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.
+
+#include <string>
+
+#include "chrome/common/mac/nscoder_util.h"
+
+namespace nscoder_util {
+
+void EncodeString(NSCoder* coder, NSString* key, const std::string& string) {
+  [coder encodeBytes:reinterpret_cast<const uint8_t*>(string.data())
+              length:string.size()
+              forKey:key];
+}
+
+std::string DecodeString(NSCoder* decoder, NSString* key) {
+  NSUInteger length;
+  const uint8_t* bytes = [decoder decodeBytesForKey:key
+                                     returnedLength:&length];
+  return std::string(reinterpret_cast<const char*>(bytes), length);
+}
+
+}  // namespace nscoder_util
diff --git a/chrome/common/mac/nscoder_util_unittest.mm b/chrome/common/mac/nscoder_util_unittest.mm
new file mode 100644
index 0000000..204ebd7
--- /dev/null
+++ b/chrome/common/mac/nscoder_util_unittest.mm
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 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.
+
+#import <Foundation/Foundation.h>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_nsobject.h"
+#include "chrome/common/mac/nscoder_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace {
+
+typedef PlatformTest NSCoderStdStringTest;
+
+const char* testStrings[] = {
+  "Arf",
+  "",
+  "This is working™",
+  "古池や蛙飛込む水の音\nふるいけやかわずとびこむみずのおと",
+  "ἀγεωμέτρητος μηδεὶς εἰσίτω",
+  "Bang!\t\n"
+};
+
+TEST_F(NSCoderStdStringTest, encodeDecode) {
+  for (size_t i = 0; i < arraysize(testStrings); ++i) {
+    NSMutableData *data = [NSMutableData data];
+
+    scoped_nsobject<NSKeyedArchiver> archiver(
+        [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]);
+    nscoder_util::EncodeString(archiver, @"test", testStrings[i]);
+    [archiver finishEncoding];
+
+    scoped_nsobject<NSKeyedUnarchiver> unarchiver(
+        [[NSKeyedUnarchiver alloc] initForReadingWithData:data]);
+    const std::string decoded = nscoder_util::DecodeString(unarchiver, @"test");
+
+    EXPECT_EQ(decoded, testStrings[i]);
+  }
+}
+
+TEST_F(NSCoderStdStringTest, decodeEmpty) {
+  NSMutableData *data = [NSMutableData data];
+
+  scoped_nsobject<NSKeyedArchiver> archiver(
+      [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]);
+  [archiver finishEncoding];
+
+  scoped_nsobject<NSKeyedUnarchiver> unarchiver(
+      [[NSKeyedUnarchiver alloc] initForReadingWithData:data]);
+  const std::string decoded = nscoder_util::DecodeString(unarchiver, @"test");
+
+  EXPECT_EQ(decoded, "");
+}
+
+}  // namespace
diff --git a/chrome/common/mac/objc_method_swizzle.h b/chrome/common/mac/objc_method_swizzle.h
new file mode 100644
index 0000000..f756fa3
--- /dev/null
+++ b/chrome/common/mac/objc_method_swizzle.h
@@ -0,0 +1,27 @@
+// 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.
+
+#ifndef CHROME_COMMON_MAC_OBJC_METHOD_SWIZZLE_H_
+#define CHROME_COMMON_MAC_OBJC_METHOD_SWIZZLE_H_
+
+#import <objc/runtime.h>
+
+// You should think twice every single time you use anything from this
+// namespace.
+namespace ObjcEvilDoers {
+
+// This is similar to class_getInstanceMethod(), except that it
+// returns NULL if |aClass| does not directly implement |aSelector|.
+Method GetImplementedInstanceMethod(Class aClass, SEL aSelector);
+
+// Exchanges the implementation of |originalSelector| and
+// |alternateSelector| within |aClass|.  Both selectors must be
+// implemented directly by |aClass|, not inherited.  The IMP returned
+// is for |originalSelector| (for purposes of forwarding).
+IMP SwizzleImplementedInstanceMethods(
+    Class aClass, const SEL originalSelector, const SEL alternateSelector);
+
+}  // namespace ObjcEvilDoers
+
+#endif  // CHROME_COMMON_MAC_OBJC_METHOD_SWIZZLE_H_
diff --git a/chrome/common/mac/objc_method_swizzle.mm b/chrome/common/mac/objc_method_swizzle.mm
new file mode 100644
index 0000000..4ed4df8
--- /dev/null
+++ b/chrome/common/mac/objc_method_swizzle.mm
@@ -0,0 +1,58 @@
+// 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.
+
+#import "chrome/common/mac/objc_method_swizzle.h"
+
+#import "base/logging.h"
+#import "base/memory/scoped_nsobject.h"
+
+namespace ObjcEvilDoers {
+
+Method GetImplementedInstanceMethod(Class aClass, SEL aSelector) {
+  Method method = NULL;
+  unsigned int methodCount = 0;
+  Method* methodList = class_copyMethodList(aClass, &methodCount);
+  if (methodList) {
+    for (unsigned int i = 0; i < methodCount; ++i) {
+      if (method_getName(methodList[i]) == aSelector) {
+        method = methodList[i];
+        break;
+      }
+    }
+    free(methodList);
+  }
+  return method;
+}
+
+IMP SwizzleImplementedInstanceMethods(
+    Class aClass, const SEL originalSelector, const SEL alternateSelector) {
+  // The methods must both be implemented by the target class, not
+  // inherited from a superclass.
+  Method original = GetImplementedInstanceMethod(aClass, originalSelector);
+  Method alternate = GetImplementedInstanceMethod(aClass, alternateSelector);
+  DCHECK(original);
+  DCHECK(alternate);
+  if (!original || !alternate) {
+    return NULL;
+  }
+
+  // The argument and return types must match exactly.
+  const char* originalTypes = method_getTypeEncoding(original);
+  const char* alternateTypes = method_getTypeEncoding(alternate);
+  DCHECK(originalTypes);
+  DCHECK(alternateTypes);
+  DCHECK(0 == strcmp(originalTypes, alternateTypes));
+  if (!originalTypes || !alternateTypes ||
+      strcmp(originalTypes, alternateTypes)) {
+    return NULL;
+  }
+
+  IMP ret = method_getImplementation(original);
+  if (ret) {
+    method_exchangeImplementations(original, alternate);
+  }
+  return ret;
+}
+
+}  // namespace ObjcEvilDoers
diff --git a/chrome/common/mac/objc_method_swizzle_unittest.mm b/chrome/common/mac/objc_method_swizzle_unittest.mm
new file mode 100644
index 0000000..261e24d
--- /dev/null
+++ b/chrome/common/mac/objc_method_swizzle_unittest.mm
@@ -0,0 +1,76 @@
+// 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.
+
+#import "chrome/common/mac/objc_method_swizzle.h"
+
+#include "base/memory/scoped_nsobject.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+@interface ObjcMethodSwizzleTest : NSObject
+- (id)self;
+
+- (NSInteger)one;
+- (NSInteger)two;
+@end
+
+@implementation ObjcMethodSwizzleTest : NSObject
+- (id)self {
+  return [super self];
+}
+
+- (NSInteger)one {
+  return 1;
+}
+- (NSInteger)two {
+  return 2;
+}
+@end
+
+@interface ObjcMethodSwizzleTest (ObjcMethodSwizzleTestCategory)
+- (NSUInteger)hash;
+@end
+
+@implementation ObjcMethodSwizzleTest (ObjcMethodSwizzleTestCategory)
+- (NSUInteger)hash {
+  return [super hash];
+}
+@end
+
+namespace ObjcEvilDoers {
+
+TEST(ObjcMethodSwizzleTest, GetImplementedInstanceMethod) {
+  EXPECT_EQ(class_getInstanceMethod([NSObject class], @selector(dealloc)),
+            GetImplementedInstanceMethod([NSObject class], @selector(dealloc)));
+  EXPECT_EQ(class_getInstanceMethod([NSObject class], @selector(self)),
+            GetImplementedInstanceMethod([NSObject class], @selector(self)));
+  EXPECT_EQ(class_getInstanceMethod([NSObject class], @selector(hash)),
+            GetImplementedInstanceMethod([NSObject class], @selector(hash)));
+
+  Class testClass = [ObjcMethodSwizzleTest class];
+  EXPECT_EQ(class_getInstanceMethod(testClass, @selector(self)),
+            GetImplementedInstanceMethod(testClass, @selector(self)));
+  EXPECT_NE(class_getInstanceMethod([NSObject class], @selector(self)),
+            class_getInstanceMethod(testClass, @selector(self)));
+
+  EXPECT_TRUE(class_getInstanceMethod(testClass, @selector(dealloc)));
+  EXPECT_FALSE(GetImplementedInstanceMethod(testClass, @selector(dealloc)));
+}
+
+TEST(ObjcMethodSwizzleTest, SwizzleImplementedInstanceMethods) {
+  scoped_nsobject<ObjcMethodSwizzleTest> object(
+      [[ObjcMethodSwizzleTest alloc] init]);
+  EXPECT_EQ([object one], 1);
+  EXPECT_EQ([object two], 2);
+
+  Class testClass = [object class];
+  SwizzleImplementedInstanceMethods(testClass, @selector(one), @selector(two));
+  EXPECT_EQ([object one], 2);
+  EXPECT_EQ([object two], 1);
+
+  SwizzleImplementedInstanceMethods(testClass, @selector(one), @selector(two));
+  EXPECT_EQ([object one], 1);
+  EXPECT_EQ([object two], 2);
+}
+
+}  // namespace ObjcEvilDoers
diff --git a/chrome/common/mac/objc_zombie.h b/chrome/common/mac/objc_zombie.h
new file mode 100644
index 0000000..1cfae56
--- /dev/null
+++ b/chrome/common/mac/objc_zombie.h
@@ -0,0 +1,46 @@
+// 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.
+
+#ifndef CHROME_COMMON_MAC_OBJC_ZOMBIE_H_
+#define CHROME_COMMON_MAC_OBJC_ZOMBIE_H_
+
+#include "base/basictypes.h"
+
+// You should think twice every single time you use anything from this
+// namespace.
+namespace ObjcEvilDoers {
+
+// Enable zombie object debugging. This implements a variant of Apple's
+// NSZombieEnabled which can help expose use-after-free errors where messages
+// are sent to freed Objective-C objects in production builds.
+//
+// Returns NO if it fails to enable.
+//
+// When |zombieAllObjects| is YES, all objects inheriting from
+// NSObject become zombies on -dealloc.  If NO, -shouldBecomeCrZombie
+// is queried to determine whether to make the object a zombie.
+//
+// |zombieCount| controls how many zombies to store before freeing the
+// oldest.  Set to 0 to free objects immediately after making them
+// zombies.
+bool ZombieEnable(bool zombieAllObjects, size_t zombieCount);
+
+// Disable zombies.
+void ZombieDisable();
+
+}  // namespace ObjcEvilDoers
+
+#if defined(OS_MACOSX)
+#if defined(__OBJC__)
+
+#import <Foundation/Foundation.h>
+
+@interface NSObject (CrZombie)
+- (BOOL)shouldBecomeCrZombie;
+@end
+
+#endif  // __OBJC__
+#endif  // OS_MACOSX
+
+#endif  // CHROME_COMMON_MAC_OBJC_ZOMBIE_H_
diff --git a/chrome/common/mac/objc_zombie.mm b/chrome/common/mac/objc_zombie.mm
new file mode 100644
index 0000000..cd6a31a
--- /dev/null
+++ b/chrome/common/mac/objc_zombie.mm
@@ -0,0 +1,441 @@
+// Copyright (c) 2012 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.
+
+#import "chrome/common/mac/objc_zombie.h"
+
+#include <AvailabilityMacros.h>
+
+#include <execinfo.h>
+#import <objc/runtime.h>
+
+#include <algorithm>
+
+#include "base/debug/stack_trace.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/mac/crash_logging.h"
+#include "base/synchronization/lock.h"
+#import "chrome/common/mac/objc_method_swizzle.h"
+
+
+#if !defined(OS_IOS) && (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_6)
+// Apparently objc/runtime.h doesn't define this with the 10.6 SDK yet.
+// The docs say it exists since 10.6 however.
+OBJC_EXPORT void *objc_destructInstance(id obj);
+#endif
+
+// Deallocated objects are re-classed as |CrZombie|.  No superclass
+// because then the class would have to override many/most of the
+// inherited methods (|NSObject| is like a category magnet!).
+// Without the __attribute__, clang's -Wobjc-root-class warns on the missing
+// superclass.
+//
+//  The version of clang that ships with Xcode 4.5 does not include this
+//  warning, so it is disabled on iOS.  This may change in future Xcode
+//  releases.
+#if !defined(OS_IOS)
+__attribute__((objc_root_class))
+#endif
+@interface CrZombie  {
+  Class isa;
+}
+@end
+
+// Objects with enough space are made into "fat" zombies, which
+// directly remember which class they were until reallocated.
+@interface CrFatZombie : CrZombie {
+ @public
+  Class wasa;
+}
+@end
+
+namespace {
+
+// The depth of backtrace to store with zombies.  This directly influences
+// the amount of memory required to track zombies, so should be kept as
+// small as is useful.  Unfortunately, too small and it won't poke through
+// deep autorelease and event loop stacks.
+// NOTE(shess): Breakpad currently restricts values to 255 bytes.  The
+// trace is hex-encoded with "0x" prefix and " " separators, meaning
+// the maximum number of 32-bit items which can be encoded is 23.
+const size_t kBacktraceDepth = 20;
+
+// The original implementation for |-[NSObject dealloc]|.
+IMP g_originalDeallocIMP = NULL;
+
+// Classes which freed objects become.  |g_fatZombieSize| is the
+// minimum object size which can be made into a fat zombie (which can
+// remember which class it was before free, even after falling off the
+// treadmill).
+Class g_zombieClass = Nil;  // cached [CrZombie class]
+Class g_fatZombieClass = Nil;  // cached [CrFatZombie class]
+size_t g_fatZombieSize = 0;
+
+// Whether to zombie all freed objects, or only those which return YES
+// from |-shouldBecomeCrZombie|.
+BOOL g_zombieAllObjects = NO;
+
+// Protects |g_zombieCount|, |g_zombieIndex|, and |g_zombies|.
+base::LazyInstance<base::Lock>::Leaky g_lock = LAZY_INSTANCE_INITIALIZER;
+
+// How many zombies to keep before freeing, and the current head of
+// the circular buffer.
+size_t g_zombieCount = 0;
+size_t g_zombieIndex = 0;
+
+typedef struct {
+  id object;   // The zombied object.
+  Class wasa;  // Value of |object->isa| before we replaced it.
+  void* trace[kBacktraceDepth];  // Backtrace at point of deallocation.
+  size_t traceDepth;             // Actual depth of trace[].
+} ZombieRecord;
+
+ZombieRecord* g_zombies = NULL;
+
+// Replacement |-dealloc| which turns objects into zombies and places
+// them into |g_zombies| to be freed later.
+void ZombieDealloc(id self, SEL _cmd) {
+  // This code should only be called when it is implementing |-dealloc|.
+  DCHECK_EQ(_cmd, @selector(dealloc));
+
+  // Use the original |-dealloc| if the object doesn't wish to be
+  // zombied.
+  if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) {
+    g_originalDeallocIMP(self, _cmd);
+    return;
+  }
+
+  Class wasa = object_getClass(self);
+  const size_t size = class_getInstanceSize(wasa);
+
+  // Destroy the instance by calling C++ destructors and clearing it
+  // to something unlikely to work well if someone references it.
+  // NOTE(shess): |object_dispose()| will call this again when the
+  // zombie falls off the treadmill!  But by then |isa| will be a
+  // class without C++ destructors or associative references, so it
+  // won't hurt anything.
+  objc_destructInstance(self);
+  // TODO(shess): Temporarily disable clearing the object to debug
+  // http://crbug.com/154483
+#if 0
+  memset(self, '!', size);
+#endif
+
+  // If the instance is big enough, make it into a fat zombie and have
+  // it remember the old |isa|.  Otherwise make it a regular zombie.
+  // Setting |isa| rather than using |object_setClass()| because that
+  // function is implemented with a memory barrier.  The runtime's
+  // |_internal_object_dispose()| (in objc-class.m) does this, so it
+  // should be safe (messaging free'd objects shouldn't be expected to
+  // be thread-safe in the first place).
+#pragma clang diagnostic push  // clang warns about direct access to isa.
+#pragma clang diagnostic ignored "-Wdeprecated-objc-isa-usage"
+  if (size >= g_fatZombieSize) {
+    self->isa = g_fatZombieClass;
+    static_cast<CrFatZombie*>(self)->wasa = wasa;
+  } else {
+    self->isa = g_zombieClass;
+  }
+#pragma clang diagnostic pop
+
+  // The new record to swap into |g_zombies|.  If |g_zombieCount| is
+  // zero, then |self| will be freed immediately.
+  ZombieRecord zombieToFree = {self, wasa};
+  zombieToFree.traceDepth =
+      std::max(backtrace(zombieToFree.trace, kBacktraceDepth), 0);
+
+  // Don't involve the lock when creating zombies without a treadmill.
+  if (g_zombieCount > 0) {
+    base::AutoLock pin(g_lock.Get());
+
+    // Check the count again in a thread-safe manner.
+    if (g_zombieCount > 0) {
+      // Put the current object on the treadmill and keep the previous
+      // occupant.
+      std::swap(zombieToFree, g_zombies[g_zombieIndex]);
+
+      // Bump the index forward.
+      g_zombieIndex = (g_zombieIndex + 1) % g_zombieCount;
+    }
+  }
+
+  // Do the free out here to prevent any chance of deadlock.
+  if (zombieToFree.object)
+    object_dispose(zombieToFree.object);
+}
+
+// Search the treadmill for |object| and fill in |*record| if found.
+// Returns YES if found.
+BOOL GetZombieRecord(id object, ZombieRecord* record) {
+  // Holding the lock is reasonable because this should be fast, and
+  // the process is going to crash presently anyhow.
+  base::AutoLock pin(g_lock.Get());
+  for (size_t i = 0; i < g_zombieCount; ++i) {
+    if (g_zombies[i].object == object) {
+      *record = g_zombies[i];
+      return YES;
+    }
+  }
+  return NO;
+}
+
+// Dump the symbols.  This is pulled out into a function to make it
+// easy to use DCHECK to dump only in debug builds.
+BOOL DumpDeallocTrace(const void* const* array, int size) {
+  fprintf(stderr, "Backtrace from -dealloc:\n");
+  base::debug::StackTrace(array, size).PrintBacktrace();
+
+  return YES;
+}
+
+// Log a message to a freed object.  |wasa| is the object's original
+// class.  |aSelector| is the selector which the calling code was
+// attempting to send.  |viaSelector| is the selector of the
+// dispatch-related method which is being invoked to send |aSelector|
+// (for instance, -respondsToSelector:).
+void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) {
+  ZombieRecord record;
+  BOOL found = GetZombieRecord(object, &record);
+
+  // The object's class can be in the zombie record, but if that is
+  // not available it can also be in the object itself (in most cases).
+  Class wasa = Nil;
+  if (found) {
+    wasa = record.wasa;
+  } else if (object_getClass(object) == g_fatZombieClass) {
+    wasa = static_cast<CrFatZombie*>(object)->wasa;
+  }
+  const char* wasaName = (wasa ? class_getName(wasa) : "<unknown>");
+
+  NSString* aString =
+      [NSString stringWithFormat:@"Zombie <%s: %p> received -%s",
+                                 wasaName, object, sel_getName(aSelector)];
+  if (viaSelector != NULL) {
+    const char* viaName = sel_getName(viaSelector);
+    aString = [aString stringByAppendingFormat:@" (via -%s)", viaName];
+  }
+
+  // Set a value for breakpad to report.
+  base::mac::SetCrashKeyValue(@"zombie", aString);
+
+  // Encode trace into a breakpad key.
+  if (found) {
+    base::mac::SetCrashKeyFromAddresses(
+        @"zombie_dealloc_bt", record.trace, record.traceDepth);
+  }
+
+  // Log -dealloc backtrace in debug builds then crash with a useful
+  // stack trace.
+  if (found && record.traceDepth) {
+    DCHECK(DumpDeallocTrace(record.trace, record.traceDepth));
+  } else {
+    DLOG(INFO) << "Unable to generate backtrace from -dealloc.";
+  }
+  DLOG(FATAL) << [aString UTF8String];
+
+  // This is how about:crash is implemented.  Using instead of
+  // |base::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of
+  // stack more immediately obvious in crash dumps.
+  int* zero = NULL;
+  *zero = 0;
+}
+
+// Initialize our globals, returning YES on success.
+BOOL ZombieInit() {
+  static BOOL initialized = NO;
+  if (initialized)
+    return YES;
+
+  Class rootClass = [NSObject class];
+  g_originalDeallocIMP =
+      class_getMethodImplementation(rootClass, @selector(dealloc));
+  // objc_getClass() so CrZombie doesn't need +class.
+  g_zombieClass = objc_getClass("CrZombie");
+  g_fatZombieClass = objc_getClass("CrFatZombie");
+  g_fatZombieSize = class_getInstanceSize(g_fatZombieClass);
+
+  if (!g_originalDeallocIMP || !g_zombieClass || !g_fatZombieClass)
+    return NO;
+
+  initialized = YES;
+  return YES;
+}
+
+}  // namespace
+
+@implementation CrZombie
+
+// The Objective-C runtime needs to be able to call this successfully.
++ (void)initialize {
+}
+
+// Any method not explicitly defined will end up here, forcing a
+// crash.
+- (id)forwardingTargetForSelector:(SEL)aSelector {
+  ZombieObjectCrash(self, aSelector, NULL);
+  return nil;
+}
+
+// Override a few methods often used for dynamic dispatch to log the
+// message the caller is attempting to send, rather than the utility
+// method being used to send it.
+- (BOOL)respondsToSelector:(SEL)aSelector {
+  ZombieObjectCrash(self, aSelector, _cmd);
+  return NO;
+}
+
+- (id)performSelector:(SEL)aSelector {
+  ZombieObjectCrash(self, aSelector, _cmd);
+  return nil;
+}
+
+- (id)performSelector:(SEL)aSelector withObject:(id)anObject {
+  ZombieObjectCrash(self, aSelector, _cmd);
+  return nil;
+}
+
+- (id)performSelector:(SEL)aSelector
+           withObject:(id)anObject
+           withObject:(id)anotherObject {
+  ZombieObjectCrash(self, aSelector, _cmd);
+  return nil;
+}
+
+- (void)performSelector:(SEL)aSelector
+             withObject:(id)anArgument
+             afterDelay:(NSTimeInterval)delay {
+  ZombieObjectCrash(self, aSelector, _cmd);
+}
+
+@end
+
+@implementation CrFatZombie
+
+// This implementation intentionally left empty.
+
+@end
+
+@implementation NSObject (CrZombie)
+
+- (BOOL)shouldBecomeCrZombie {
+  return NO;
+}
+
+@end
+
+namespace ObjcEvilDoers {
+
+bool ZombieEnable(bool zombieAllObjects,
+                  size_t zombieCount) {
+  // Only allow enable/disable on the main thread, just to keep things
+  // simple.
+  DCHECK([NSThread isMainThread]);
+
+  if (!ZombieInit())
+    return false;
+
+  g_zombieAllObjects = zombieAllObjects;
+
+  // Replace the implementation of -[NSObject dealloc].
+  Method m = class_getInstanceMethod([NSObject class], @selector(dealloc));
+  if (!m)
+    return false;
+
+  const IMP prevDeallocIMP = method_setImplementation(m, (IMP)ZombieDealloc);
+  DCHECK(prevDeallocIMP == g_originalDeallocIMP ||
+         prevDeallocIMP == (IMP)ZombieDealloc);
+
+  // Grab the current set of zombies.  This is thread-safe because
+  // only the main thread can change these.
+  const size_t oldCount = g_zombieCount;
+  ZombieRecord* oldZombies = g_zombies;
+
+  {
+    base::AutoLock pin(g_lock.Get());
+
+    // Save the old index in case zombies need to be transferred.
+    size_t oldIndex = g_zombieIndex;
+
+    // Create the new zombie treadmill, disabling zombies in case of
+    // failure.
+    g_zombieIndex = 0;
+    g_zombieCount = zombieCount;
+    g_zombies = NULL;
+    if (g_zombieCount) {
+      g_zombies =
+          static_cast<ZombieRecord*>(calloc(g_zombieCount, sizeof(*g_zombies)));
+      if (!g_zombies) {
+        NOTREACHED();
+        g_zombies = oldZombies;
+        g_zombieCount = oldCount;
+        g_zombieIndex = oldIndex;
+        ZombieDisable();
+        return false;
+      }
+    }
+
+    // If the count is changing, allow some of the zombies to continue
+    // shambling forward.
+    const size_t sharedCount = std::min(oldCount, zombieCount);
+    if (sharedCount) {
+      // Get index of the first shared zombie.
+      oldIndex = (oldIndex + oldCount - sharedCount) % oldCount;
+
+      for (; g_zombieIndex < sharedCount; ++ g_zombieIndex) {
+        DCHECK_LT(g_zombieIndex, g_zombieCount);
+        DCHECK_LT(oldIndex, oldCount);
+        std::swap(g_zombies[g_zombieIndex], oldZombies[oldIndex]);
+        oldIndex = (oldIndex + 1) % oldCount;
+      }
+      g_zombieIndex %= g_zombieCount;
+    }
+  }
+
+  // Free the old treadmill and any remaining zombies.
+  if (oldZombies) {
+    for (size_t i = 0; i < oldCount; ++i) {
+      if (oldZombies[i].object)
+        object_dispose(oldZombies[i].object);
+    }
+    free(oldZombies);
+  }
+
+  return true;
+}
+
+void ZombieDisable() {
+  // Only allow enable/disable on the main thread, just to keep things
+  // simple.
+  DCHECK([NSThread isMainThread]);
+
+  // |ZombieInit()| was never called.
+  if (!g_originalDeallocIMP)
+    return;
+
+  // Put back the original implementation of -[NSObject dealloc].
+  Method m = class_getInstanceMethod([NSObject class], @selector(dealloc));
+  DCHECK(m);
+  method_setImplementation(m, g_originalDeallocIMP);
+
+  // Can safely grab this because it only happens on the main thread.
+  const size_t oldCount = g_zombieCount;
+  ZombieRecord* oldZombies = g_zombies;
+
+  {
+    base::AutoLock pin(g_lock.Get());  // In case any -dealloc are in progress.
+    g_zombieCount = 0;
+    g_zombies = NULL;
+  }
+
+  // Free any remaining zombies.
+  if (oldZombies) {
+    for (size_t i = 0; i < oldCount; ++i) {
+      if (oldZombies[i].object)
+        object_dispose(oldZombies[i].object);
+    }
+    free(oldZombies);
+  }
+}
+
+}  // namespace ObjcEvilDoers
diff --git a/chrome/common/mac/objc_zombie_unittest.mm b/chrome/common/mac/objc_zombie_unittest.mm
new file mode 100644
index 0000000..bb11cf0
--- /dev/null
+++ b/chrome/common/mac/objc_zombie_unittest.mm
@@ -0,0 +1,98 @@
+// 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.
+
+#import <Foundation/Foundation.h>
+#include <objc/runtime.h>
+
+#include "base/logging.h"
+#import "base/memory/scoped_nsobject.h"
+#import "chrome/common/mac/objc_zombie.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+@interface ZombieCxxDestructTest : NSObject
+{
+  scoped_nsobject<id> aRef_;
+}
+- (id)initWith:(id)anObject;
+@end
+
+@implementation ZombieCxxDestructTest
+- (id)initWith:(id)anObject {
+  self = [super init];
+  if (self) {
+    aRef_.reset([anObject retain]);
+  }
+  return self;
+}
+@end
+
+@interface ZombieAssociatedObjectTest : NSObject
+- (id)initWithAssociatedObject:(id)anObject;
+@end
+
+@implementation ZombieAssociatedObjectTest
+
+- (id)initWithAssociatedObject:(id)anObject {
+  if ((self = [super init])) {
+    // The address of the variable itself is the unique key, the
+    // contents don't matter.
+    static char kAssociatedObjectKey = 'x';
+    objc_setAssociatedObject(
+        self, &kAssociatedObjectKey, anObject, OBJC_ASSOCIATION_RETAIN);
+  }
+  return self;
+}
+
+@end
+
+namespace {
+
+// Verify that the C++ destructors run when the last reference to the
+// object is released.
+// NOTE(shess): To test the negative, comment out the |g_objectDestruct()|
+// call in |ZombieDealloc()|.
+TEST(ObjcZombieTest, CxxDestructors) {
+  scoped_nsobject<id> anObject([[NSObject alloc] init]);
+  EXPECT_EQ(1u, [anObject retainCount]);
+
+  ASSERT_TRUE(ObjcEvilDoers::ZombieEnable(YES, 100));
+
+  scoped_nsobject<ZombieCxxDestructTest> soonInfected(
+      [[ZombieCxxDestructTest alloc] initWith:anObject]);
+  EXPECT_EQ(2u, [anObject retainCount]);
+
+  // When |soonInfected| becomes a zombie, the C++ destructors should
+  // run and release a reference to |anObject|.
+  soonInfected.reset();
+  EXPECT_EQ(1u, [anObject retainCount]);
+
+  // The local reference should remain (C++ destructors aren't re-run).
+  ObjcEvilDoers::ZombieDisable();
+  EXPECT_EQ(1u, [anObject retainCount]);
+}
+
+// Verify that the associated objects are released when the object is
+// released.
+TEST(ObjcZombieTest, AssociatedObjectsReleased) {
+  scoped_nsobject<id> anObject([[NSObject alloc] init]);
+  EXPECT_EQ(1u, [anObject retainCount]);
+
+  ASSERT_TRUE(ObjcEvilDoers::ZombieEnable(YES, 100));
+
+  scoped_nsobject<ZombieAssociatedObjectTest> soonInfected(
+      [[ZombieAssociatedObjectTest alloc] initWithAssociatedObject:anObject]);
+  EXPECT_EQ(2u, [anObject retainCount]);
+
+  // When |soonInfected| becomes a zombie, the associated object
+  // should be released.
+  soonInfected.reset();
+  EXPECT_EQ(1u, [anObject retainCount]);
+
+  // The local reference should remain (associated objects not re-released).
+  ObjcEvilDoers::ZombieDisable();
+  EXPECT_EQ(1u, [anObject retainCount]);
+}
+
+}  // namespace
diff --git a/chrome/common/metrics/OWNERS b/chrome/common/metrics/OWNERS
new file mode 100644
index 0000000..8563800
--- /dev/null
+++ b/chrome/common/metrics/OWNERS
@@ -0,0 +1,4 @@
+isherman@chromium.org
+jar@chromium.org
+asvitkine@chromium.org
+stevet@chromium.org
diff --git a/chrome/common/metrics/entropy_provider.cc b/chrome/common/metrics/entropy_provider.cc
new file mode 100644
index 0000000..13081d8
--- /dev/null
+++ b/chrome/common/metrics/entropy_provider.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/metrics/entropy_provider.h"
+
+#include <algorithm>
+#include <limits>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/sha1.h"
+#include "base/sys_byteorder.h"
+#include "chrome/common/metrics/metrics_util.h"
+
+namespace metrics {
+
+namespace internal {
+
+SeededRandGenerator::SeededRandGenerator(uint32 seed) {
+  mersenne_twister_.init_genrand(seed);
+}
+
+SeededRandGenerator::~SeededRandGenerator() {
+}
+
+uint32 SeededRandGenerator::operator()(uint32 range) {
+  // Based on base::RandGenerator().
+  DCHECK_GT(range, 0u);
+
+  // We must discard random results above this number, as they would
+  // make the random generator non-uniform (consider e.g. if
+  // MAX_UINT64 was 7 and |range| was 5, then a result of 1 would be twice
+  // as likely as a result of 3 or 4).
+  uint32 max_acceptable_value =
+      (std::numeric_limits<uint32>::max() / range) * range - 1;
+
+  uint32 value;
+  do {
+    value = mersenne_twister_.genrand_int32();
+  } while (value > max_acceptable_value);
+
+  return value % range;
+}
+
+void PermuteMappingUsingTrialName(const std::string& trial_name,
+                                  std::vector<uint16>* mapping) {
+  for (size_t i = 0; i < mapping->size(); ++i)
+    (*mapping)[i] = static_cast<uint16>(i);
+
+  SeededRandGenerator generator(HashName(trial_name));
+  std::random_shuffle(mapping->begin(), mapping->end(), generator);
+}
+
+}  // namespace internal
+
+SHA1EntropyProvider::SHA1EntropyProvider(const std::string& entropy_source)
+    : entropy_source_(entropy_source) {
+}
+
+SHA1EntropyProvider::~SHA1EntropyProvider() {
+}
+
+double SHA1EntropyProvider::GetEntropyForTrial(
+    const std::string& trial_name) const {
+  // Given enough input entropy, SHA-1 will produce a uniformly random spread
+  // in its output space. In this case, the input entropy that is used is the
+  // combination of the original |entropy_source_| and the |trial_name|.
+  //
+  // Note: If |entropy_source_| has very low entropy, such as 13 bits or less,
+  // it has been observed that this method does not result in a uniform
+  // distribution given the same |trial_name|. When using such a low entropy
+  // source, PermutedEntropyProvider should be used instead.
+  std::string input(entropy_source_ + trial_name);
+  unsigned char sha1_hash[base::kSHA1Length];
+  base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(input.c_str()),
+                      input.size(),
+                      sha1_hash);
+
+  uint64 bits;
+  COMPILE_ASSERT(sizeof(bits) < sizeof(sha1_hash), need_more_data);
+  memcpy(&bits, sha1_hash, sizeof(bits));
+  bits = base::ByteSwapToLE64(bits);
+
+  return base::BitsToOpenEndedUnitInterval(bits);
+}
+
+PermutedEntropyProvider::PermutedEntropyProvider(
+    uint16 low_entropy_source,
+    size_t low_entropy_source_max)
+    : low_entropy_source_(low_entropy_source),
+      low_entropy_source_max_(low_entropy_source_max) {
+  DCHECK_LT(low_entropy_source, low_entropy_source_max);
+  DCHECK_LE(low_entropy_source_max, std::numeric_limits<uint16>::max());
+}
+
+PermutedEntropyProvider::~PermutedEntropyProvider() {
+}
+
+double PermutedEntropyProvider::GetEntropyForTrial(
+    const std::string& trial_name) const {
+  std::vector<uint16> mapping(low_entropy_source_max_);
+  internal::PermuteMappingUsingTrialName(trial_name, &mapping);
+
+  return mapping[low_entropy_source_] /
+         static_cast<double>(low_entropy_source_max_);
+}
+
+}  // namespace metrics
diff --git a/chrome/common/metrics/entropy_provider.h b/chrome/common/metrics/entropy_provider.h
new file mode 100644
index 0000000..7938a5e
--- /dev/null
+++ b/chrome/common/metrics/entropy_provider.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_METRICS_ENTROPY_PROVIDER_H_
+#define CHROME_COMMON_METRICS_ENTROPY_PROVIDER_H_
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/metrics/field_trial.h"
+#include "third_party/mt19937ar/mt19937ar.h"
+
+namespace metrics {
+
+// Internals of entropy_provider.cc exposed for testing.
+namespace internal {
+
+// A functor that generates random numbers based on a seed, using the Mersenne
+// Twister algorithm. Suitable for use with std::random_shuffle().
+struct SeededRandGenerator : std::unary_function<uint32, uint32> {
+  explicit SeededRandGenerator(uint32 seed);
+  ~SeededRandGenerator();
+
+  // Returns a random number in range [0, range).
+  uint32 operator()(uint32 range);
+
+  MersenneTwister mersenne_twister_;
+};
+
+// Fills |mapping| to create a bijection of values in the range of
+// [0, |mapping.size()|), permuted based on |trial_name|.
+void PermuteMappingUsingTrialName(const std::string& trial_name,
+                                  std::vector<uint16>* mapping);
+
+}  // namespace internal
+
+// SHA1EntropyProvider is an entropy provider suitable for high entropy
+// sources. It works by taking the first 64 bits of the SHA1 hash of the
+// entropy source concatenated with the trial name and using that for the
+// final entropy value.
+class SHA1EntropyProvider : public base::FieldTrial::EntropyProvider {
+ public:
+  // Creates a SHA1EntropyProvider with the given |entropy_source|, which
+  // should contain a large amount of entropy - for example, a textual
+  // representation of a persistent randomly-generated 128-bit value.
+  explicit SHA1EntropyProvider(const std::string& entropy_source);
+  virtual ~SHA1EntropyProvider();
+
+  // base::FieldTrial::EntropyProvider implementation:
+  virtual double GetEntropyForTrial(const std::string& trial_name) const
+      OVERRIDE;
+
+ private:
+  std::string entropy_source_;
+
+  DISALLOW_COPY_AND_ASSIGN(SHA1EntropyProvider);
+};
+
+// PermutedEntropyProvider is an entropy provider suitable for low entropy
+// sources (below 16 bits). It uses the field trial name to generate a
+// permutation of a mapping array from an initial entropy value to a new value.
+// Note: This provider's performance is O(2^n), where n is the number of bits
+// in the entropy source.
+class PermutedEntropyProvider : public base::FieldTrial::EntropyProvider {
+ public:
+  // Creates a PermutedEntropyProvider with the given |low_entropy_source|,
+  // which should have a value in the range of [0, low_entropy_source_max).
+  PermutedEntropyProvider(uint16 low_entropy_source,
+                          size_t low_entropy_source_max);
+  virtual ~PermutedEntropyProvider();
+
+  // base::FieldTrial::EntropyProvider implementation:
+  virtual double GetEntropyForTrial(const std::string& trial_name) const
+      OVERRIDE;
+
+ private:
+  uint16 low_entropy_source_;
+  size_t low_entropy_source_max_;
+
+  DISALLOW_COPY_AND_ASSIGN(PermutedEntropyProvider);
+};
+
+}  // namespace metrics
+
+#endif  // CHROME_COMMON_METRICS_ENTROPY_PROVIDER_H_
diff --git a/chrome/common/metrics/entropy_provider_unittest.cc b/chrome/common/metrics/entropy_provider_unittest.cc
new file mode 100644
index 0000000..5b4d56b
--- /dev/null
+++ b/chrome/common/metrics/entropy_provider_unittest.cc
@@ -0,0 +1,338 @@
+// Copyright (c) 2012 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.
+
+#include <cmath>
+#include <limits>
+#include <numeric>
+
+#include "base/basictypes.h"
+#include "base/guid.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/rand_util.h"
+#include "base/string_number_conversions.h"
+#include "chrome/common/metrics/entropy_provider.h"
+#include "chrome/common/metrics/metrics_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+// Size of the low entropy source to use for the permuted entropy provider
+// in tests.
+const size_t kMaxLowEntropySize = (1 << 13);
+
+// Field trial names used in unit tests.
+const std::string kTestTrialNames[] = { "TestTrial", "AnotherTestTrial",
+                                        "NewTabButton" };
+
+// Computes the Chi-Square statistic for |values| assuming they follow a uniform
+// distribution, where each entry has expected value |expected_value|.
+//
+// The Chi-Square statistic is defined as Sum((O-E)^2/E) where O is the observed
+// value and E is the expected value.
+double ComputeChiSquare(const std::vector<int>& values,
+                        double expected_value) {
+  double sum = 0;
+  for (size_t i = 0; i < values.size(); ++i) {
+    const double delta = values[i] - expected_value;
+    sum += (delta * delta) / expected_value;
+  }
+  return sum;
+}
+
+// Computes SHA1-based entropy for the given |trial_name| based on
+// |entropy_source|
+double GenerateSHA1Entropy(const std::string& entropy_source,
+                           const std::string& trial_name) {
+  SHA1EntropyProvider sha1_provider(entropy_source);
+  return sha1_provider.GetEntropyForTrial(trial_name);
+}
+
+// Generates permutation-based entropy for the given |trial_name| based on
+// |entropy_source| which must be in the range [0, entropy_max).
+double GeneratePermutedEntropy(uint16 entropy_source,
+                               size_t entropy_max,
+                               const std::string& trial_name) {
+  PermutedEntropyProvider permuted_provider(entropy_source, entropy_max);
+  return permuted_provider.GetEntropyForTrial(trial_name);
+}
+
+// Helper interface for testing used to generate entropy values for a given
+// field trial. Unlike EntropyProvider, which keeps the low/high entropy source
+// value constant and generates entropy for different trial names, instances
+// of TrialEntropyGenerator keep the trial name constant and generate low/high
+// entropy source values internally to produce each output entropy value.
+class TrialEntropyGenerator {
+ public:
+  virtual ~TrialEntropyGenerator() {}
+  virtual double GenerateEntropyValue() const = 0;
+};
+
+// An TrialEntropyGenerator that uses the SHA1EntropyProvider with the high
+// entropy source (random GUID with 128 bits of entropy + 13 additional bits of
+// entropy corresponding to a low entropy source).
+class SHA1EntropyGenerator : public TrialEntropyGenerator {
+ public:
+  explicit SHA1EntropyGenerator(const std::string& trial_name)
+      : trial_name_(trial_name) {
+  }
+
+  ~SHA1EntropyGenerator() {
+  }
+
+  virtual double GenerateEntropyValue() const OVERRIDE {
+    // Use a random GUID + 13 additional bits of entropy to match how the
+    // SHA1EntropyProvider is used in metrics_service.cc.
+    const int low_entropy_source =
+        static_cast<uint16>(base::RandInt(0, kMaxLowEntropySize - 1));
+    const std::string high_entropy_source =
+        base::GenerateGUID() + base::IntToString(low_entropy_source);
+    return GenerateSHA1Entropy(high_entropy_source, trial_name_);
+  }
+
+ private:
+  const std::string& trial_name_;
+
+  DISALLOW_COPY_AND_ASSIGN(SHA1EntropyGenerator);
+};
+
+// An TrialEntropyGenerator that uses the permuted entropy provider algorithm,
+// using 13-bit low entropy source values.
+class PermutedEntropyGenerator : public TrialEntropyGenerator {
+ public:
+  explicit PermutedEntropyGenerator(const std::string& trial_name)
+      : mapping_(kMaxLowEntropySize) {
+    // Note: Given a trial name, the computed mapping will be the same.
+    // As a performance optimization, pre-compute the mapping once per trial
+    // name and index into it for each entropy value.
+    internal::PermuteMappingUsingTrialName(trial_name, &mapping_);
+  }
+
+  ~PermutedEntropyGenerator() {
+  }
+
+  virtual double GenerateEntropyValue() const OVERRIDE {
+    const int low_entropy_source =
+        static_cast<uint16>(base::RandInt(0, kMaxLowEntropySize - 1));
+    return mapping_[low_entropy_source] /
+           static_cast<double>(kMaxLowEntropySize);
+  }
+
+ private:
+  std::vector<uint16> mapping_;
+
+  DISALLOW_COPY_AND_ASSIGN(PermutedEntropyGenerator);
+};
+
+// Tests uniformity of a given |entropy_generator| using the Chi-Square Goodness
+// of Fit Test.
+void PerformEntropyUniformityTest(
+    const std::string& trial_name,
+    const TrialEntropyGenerator& entropy_generator) {
+  // Number of buckets in the simulated field trials.
+  const size_t kBucketCount = 20;
+  // Max number of iterations to perform before giving up and failing.
+  const size_t kMaxIterationCount = 100000;
+  // The number of iterations to perform before each time the statistical
+  // significance of the results is checked.
+  const size_t kCheckIterationCount = 10000;
+  // This is the Chi-Square threshold from the Chi-Square statistic table for
+  // 19 degrees of freedom (based on |kBucketCount|) with a 99.9% confidence
+  // level. See: http://www.medcalc.org/manual/chi-square-table.php
+  const double kChiSquareThreshold = 43.82;
+
+  std::vector<int> distribution(kBucketCount);
+
+  for (size_t i = 1; i <= kMaxIterationCount; ++i) {
+    const double entropy_value = entropy_generator.GenerateEntropyValue();
+    const size_t bucket = static_cast<size_t>(kBucketCount * entropy_value);
+    ASSERT_LT(bucket, kBucketCount);
+    distribution[bucket] += 1;
+
+    // After |kCheckIterationCount| iterations, compute the Chi-Square
+    // statistic of the distribution. If the resulting statistic is greater
+    // than |kChiSquareThreshold|, we can conclude with 99.9% confidence
+    // that the observed samples do not follow a uniform distribution.
+    //
+    // However, since 99.9% would still result in a false negative every
+    // 1000 runs of the test, do not treat it as a failure (else the test
+    // will be flaky). Instead, perform additional iterations to determine
+    // if the distribution will converge, up to |kMaxIterationCount|.
+    if ((i % kCheckIterationCount) == 0) {
+      const double expected_value_per_bucket =
+          static_cast<double>(i) / kBucketCount;
+      const double chi_square =
+          ComputeChiSquare(distribution, expected_value_per_bucket);
+      if (chi_square < kChiSquareThreshold)
+        break;
+
+      // If |i == kMaxIterationCount|, the Chi-Square statistic did not
+      // converge after |kMaxIterationCount|.
+      EXPECT_NE(i, kMaxIterationCount) << "Failed for trial " <<
+          trial_name << " with chi_square = " << chi_square <<
+          " after " << kMaxIterationCount << " iterations.";
+    }
+  }
+}
+
+}  // namespace
+
+class EntropyProviderTest : public testing::Test {
+};
+
+TEST_F(EntropyProviderTest, UseOneTimeRandomizationSHA1) {
+  // Simply asserts that two trials using one-time randomization
+  // that have different names, normally generate different results.
+  //
+  // Note that depending on the one-time random initialization, they
+  // _might_ actually give the same result, but we know that given
+  // the particular client_id we use for unit tests they won't.
+  base::FieldTrialList field_trial_list(new SHA1EntropyProvider("client_id"));
+  scoped_refptr<base::FieldTrial> trials[] = {
+      base::FieldTrialList::FactoryGetFieldTrial("one", 100, "default",
+          base::FieldTrialList::kExpirationYearInFuture, 1, 1, NULL),
+      base::FieldTrialList::FactoryGetFieldTrial("two", 100, "default",
+          base::FieldTrialList::kExpirationYearInFuture, 1, 1, NULL) };
+
+  for (size_t i = 0; i < arraysize(trials); ++i) {
+    trials[i]->UseOneTimeRandomization();
+
+    for (int j = 0; j < 100; ++j)
+      trials[i]->AppendGroup("", 1);
+  }
+
+  // The trials are most likely to give different results since they have
+  // different names.
+  EXPECT_NE(trials[0]->group(), trials[1]->group());
+  EXPECT_NE(trials[0]->group_name(), trials[1]->group_name());
+}
+
+TEST_F(EntropyProviderTest, UseOneTimeRandomizationPermuted) {
+  // Simply asserts that two trials using one-time randomization
+  // that have different names, normally generate different results.
+  //
+  // Note that depending on the one-time random initialization, they
+  // _might_ actually give the same result, but we know that given
+  // the particular client_id we use for unit tests they won't.
+  base::FieldTrialList field_trial_list(
+      new PermutedEntropyProvider(1234, kMaxLowEntropySize));
+  scoped_refptr<base::FieldTrial> trials[] = {
+      base::FieldTrialList::FactoryGetFieldTrial("one", 100, "default",
+          base::FieldTrialList::kExpirationYearInFuture, 1, 1, NULL),
+      base::FieldTrialList::FactoryGetFieldTrial("two", 100, "default",
+          base::FieldTrialList::kExpirationYearInFuture, 1, 1, NULL) };
+
+  for (size_t i = 0; i < arraysize(trials); ++i) {
+    trials[i]->UseOneTimeRandomization();
+
+    for (int j = 0; j < 100; ++j)
+      trials[i]->AppendGroup("", 1);
+  }
+
+  // The trials are most likely to give different results since they have
+  // different names.
+  EXPECT_NE(trials[0]->group(), trials[1]->group());
+  EXPECT_NE(trials[0]->group_name(), trials[1]->group_name());
+}
+
+TEST_F(EntropyProviderTest, SHA1Entropy) {
+  const double results[] = { GenerateSHA1Entropy("hi", "1"),
+                             GenerateSHA1Entropy("there", "1") };
+
+  EXPECT_NE(results[0], results[1]);
+  for (size_t i = 0; i < arraysize(results); ++i) {
+    EXPECT_LE(0.0, results[i]);
+    EXPECT_GT(1.0, results[i]);
+  }
+
+  EXPECT_EQ(GenerateSHA1Entropy("yo", "1"),
+            GenerateSHA1Entropy("yo", "1"));
+  EXPECT_NE(GenerateSHA1Entropy("yo", "something"),
+            GenerateSHA1Entropy("yo", "else"));
+}
+
+TEST_F(EntropyProviderTest, PermutedEntropy) {
+  const double results[] = {
+      GeneratePermutedEntropy(1234, kMaxLowEntropySize, "1"),
+      GeneratePermutedEntropy(4321, kMaxLowEntropySize, "1") };
+
+  EXPECT_NE(results[0], results[1]);
+  for (size_t i = 0; i < arraysize(results); ++i) {
+    EXPECT_LE(0.0, results[i]);
+    EXPECT_GT(1.0, results[i]);
+  }
+
+  EXPECT_EQ(GeneratePermutedEntropy(1234, kMaxLowEntropySize, "1"),
+            GeneratePermutedEntropy(1234, kMaxLowEntropySize, "1"));
+  EXPECT_NE(GeneratePermutedEntropy(1234, kMaxLowEntropySize, "something"),
+            GeneratePermutedEntropy(1234, kMaxLowEntropySize, "else"));
+}
+
+TEST_F(EntropyProviderTest, PermutedEntropyProviderResults) {
+  // Verifies that PermutedEntropyProvider produces expected results. This
+  // ensures that the results are the same between platforms and ensures that
+  // changes to the implementation do not regress this accidentally.
+
+  EXPECT_DOUBLE_EQ(2194 / static_cast<double>(kMaxLowEntropySize),
+                   GeneratePermutedEntropy(1234, kMaxLowEntropySize, "XYZ"));
+  EXPECT_DOUBLE_EQ(5676 / static_cast<double>(kMaxLowEntropySize),
+                   GeneratePermutedEntropy(1, kMaxLowEntropySize, "Test"));
+  EXPECT_DOUBLE_EQ(1151 / static_cast<double>(kMaxLowEntropySize),
+                   GeneratePermutedEntropy(5000, kMaxLowEntropySize, "Foo"));
+}
+
+TEST_F(EntropyProviderTest, SHA1EntropyIsUniform) {
+  for (size_t i = 0; i < arraysize(kTestTrialNames); ++i) {
+    SHA1EntropyGenerator entropy_generator(kTestTrialNames[i]);
+    PerformEntropyUniformityTest(kTestTrialNames[i], entropy_generator);
+  }
+}
+
+TEST_F(EntropyProviderTest, PermutedEntropyIsUniform) {
+  for (size_t i = 0; i < arraysize(kTestTrialNames); ++i) {
+    PermutedEntropyGenerator entropy_generator(kTestTrialNames[i]);
+    PerformEntropyUniformityTest(kTestTrialNames[i], entropy_generator);
+  }
+}
+
+TEST_F(EntropyProviderTest, SeededRandGeneratorIsUniform) {
+  // Verifies that SeededRandGenerator has a uniform distribution.
+  //
+  // Mirrors RandUtilTest.RandGeneratorIsUniform in base/rand_util_unittest.cc.
+
+  const uint32 kTopOfRange = (std::numeric_limits<uint32>::max() / 4ULL) * 3ULL;
+  const uint32 kExpectedAverage = kTopOfRange / 2ULL;
+  const uint32 kAllowedVariance = kExpectedAverage / 50ULL;  // +/- 2%
+  const int kMinAttempts = 1000;
+  const int kMaxAttempts = 1000000;
+
+  for (size_t i = 0; i < arraysize(kTestTrialNames); ++i) {
+    const uint32 seed = HashName(kTestTrialNames[i]);
+    internal::SeededRandGenerator rand_generator(seed);
+
+    double cumulative_average = 0.0;
+    int count = 0;
+    while (count < kMaxAttempts) {
+      uint32 value = rand_generator(kTopOfRange);
+      cumulative_average = (count * cumulative_average + value) / (count + 1);
+
+      // Don't quit too quickly for things to start converging, or we may have
+      // a false positive.
+      if (count > kMinAttempts &&
+          kExpectedAverage - kAllowedVariance < cumulative_average &&
+          cumulative_average < kExpectedAverage + kAllowedVariance) {
+        break;
+      }
+
+      ++count;
+    }
+
+    ASSERT_LT(count, kMaxAttempts) << "Expected average was " <<
+        kExpectedAverage << ", average ended at " << cumulative_average <<
+        ", for trial " << kTestTrialNames[i];
+  }
+}
+
+}  // namespace metrics
diff --git a/chrome/common/metrics/metrics_log_base.cc b/chrome/common/metrics/metrics_log_base.cc
new file mode 100644
index 0000000..938828f
--- /dev/null
+++ b/chrome/common/metrics/metrics_log_base.cc
@@ -0,0 +1,512 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/metrics/metrics_log_base.h"
+
+#include "base/base64.h"
+#include "base/basictypes.h"
+#include "base/md5.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/perftimer.h"
+#include "base/string_number_conversions.h"
+#include "base/sys_byteorder.h"
+#include "base/sys_info.h"
+#include "base/third_party/nspr/prtime.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/chrome_version_info.h"
+#include "chrome/common/logging_chrome.h"
+#include "chrome/common/metrics/proto/histogram_event.pb.h"
+#include "chrome/common/metrics/proto/system_profile.pb.h"
+#include "chrome/common/metrics/proto/user_action_event.pb.h"
+#include "libxml/xmlwriter.h"
+
+#define OPEN_ELEMENT_FOR_SCOPE(name) ScopedElement scoped_element(this, name)
+
+using base::Histogram;
+using base::HistogramBase;
+using base::HistogramSamples;
+using base::SampleCountIterator;
+using base::Time;
+using base::TimeDelta;
+using metrics::HistogramEventProto;
+using metrics::SystemProfileProto;
+using metrics::UserActionEventProto;
+
+namespace {
+
+// libxml take xmlChar*, which is unsigned char*
+inline const unsigned char* UnsignedChar(const char* input) {
+  return reinterpret_cast<const unsigned char*>(input);
+}
+
+// Any id less than 16 bytes is considered to be a testing id.
+bool IsTestingID(const std::string& id) {
+  return id.size() < 16;
+}
+
+// Converts the 8-byte prefix of an MD5 hash into a uint64 value.
+inline uint64 HashToUInt64(const std::string& hash) {
+  uint64 value;
+  DCHECK_GE(hash.size(), sizeof(value));
+  memcpy(&value, hash.data(), sizeof(value));
+  return base::HostToNet64(value);
+}
+
+// Creates an MD5 hash of the given value, and returns hash as a byte buffer
+// encoded as an std::string.
+std::string CreateHash(const std::string& value) {
+  base::MD5Context context;
+  base::MD5Init(&context);
+  base::MD5Update(&context, value);
+
+  base::MD5Digest digest;
+  base::MD5Final(&digest, &context);
+
+  std::string hash(reinterpret_cast<char*>(digest.a), arraysize(digest.a));
+
+  // The following log is VERY helpful when folks add some named histogram into
+  // the code, but forgot to update the descriptive list of histograms.  When
+  // that happens, all we get to see (server side) is a hash of the histogram
+  // name.  We can then use this logging to find out what histogram name was
+  // being hashed to a given MD5 value by just running the version of Chromium
+  // in question with --enable-logging.
+  DVLOG(1) << "Metrics: Hash numeric [" << value << "]=[" << HashToUInt64(hash)
+           << "]";
+
+  return hash;
+}
+
+SystemProfileProto::Channel AsProtobufChannel(
+    chrome::VersionInfo::Channel channel) {
+  switch (channel) {
+    case chrome::VersionInfo::CHANNEL_UNKNOWN:
+      return SystemProfileProto::CHANNEL_UNKNOWN;
+    case chrome::VersionInfo::CHANNEL_CANARY:
+      return SystemProfileProto::CHANNEL_CANARY;
+    case chrome::VersionInfo::CHANNEL_DEV:
+      return SystemProfileProto::CHANNEL_DEV;
+    case chrome::VersionInfo::CHANNEL_BETA:
+      return SystemProfileProto::CHANNEL_BETA;
+    case chrome::VersionInfo::CHANNEL_STABLE:
+      return SystemProfileProto::CHANNEL_STABLE;
+    default:
+      NOTREACHED();
+      return SystemProfileProto::CHANNEL_UNKNOWN;
+  }
+}
+
+}  // namespace
+
+class MetricsLogBase::XmlWrapper {
+ public:
+  XmlWrapper()
+      : doc_(NULL),
+        buffer_(NULL),
+        writer_(NULL) {
+    buffer_ = xmlBufferCreate();
+    DCHECK(buffer_);
+
+    #if defined(OS_CHROMEOS)
+      writer_ = xmlNewTextWriterDoc(&doc_, /* compression */ 0);
+    #else
+      writer_ = xmlNewTextWriterMemory(buffer_, /* compression */ 0);
+    #endif  // OS_CHROMEOS
+    DCHECK(writer_);
+
+    int result = xmlTextWriterSetIndent(writer_, 2);
+    DCHECK_EQ(0, result);
+  }
+
+  ~XmlWrapper() {
+    FreeDocWriter();
+    if (buffer_) {
+      xmlBufferFree(buffer_);
+      buffer_ = NULL;
+    }
+  }
+
+  void FreeDocWriter() {
+    if (writer_) {
+      xmlFreeTextWriter(writer_);
+      writer_ = NULL;
+    }
+    if (doc_) {
+      xmlFreeDoc(doc_);
+      doc_ = NULL;
+    }
+  }
+
+  xmlDocPtr doc() const { return doc_; }
+  xmlTextWriterPtr writer() const { return writer_; }
+  xmlBufferPtr buffer() const { return buffer_; }
+
+ private:
+  xmlDocPtr doc_;
+  xmlBufferPtr buffer_;
+  xmlTextWriterPtr writer_;
+};
+
+MetricsLogBase::MetricsLogBase(const std::string& client_id, int session_id,
+                               const std::string& version_string)
+    : num_events_(0),
+      start_time_(Time::Now()),
+      client_id_(client_id),
+      session_id_(base::IntToString(session_id)),
+      locked_(false),
+      xml_wrapper_(new XmlWrapper) {
+  int64_t build_time = GetBuildTime();
+
+  // Write the XML version.
+  StartElement("log");
+  WriteAttribute("clientid", client_id_);
+  WriteInt64Attribute("buildtime", build_time);
+  WriteAttribute("appversion", version_string);
+
+  // Write the protobuf version.
+  if (IsTestingID(client_id_)) {
+    uma_proto_.set_client_id(0);
+  } else {
+    uma_proto_.set_client_id(HashToUInt64(CreateHash(client_id)));
+  }
+  uma_proto_.set_session_id(session_id);
+  uma_proto_.mutable_system_profile()->set_build_timestamp(build_time);
+  uma_proto_.mutable_system_profile()->set_app_version(version_string);
+  uma_proto_.mutable_system_profile()->set_channel(
+      AsProtobufChannel(chrome::VersionInfo::GetChannel()));
+}
+
+MetricsLogBase::~MetricsLogBase() {
+  if (!locked_) {
+    locked_ = true;
+    int result = xmlTextWriterEndDocument(xml_wrapper_->writer());
+    DCHECK_GE(result, 0);
+  }
+  delete xml_wrapper_;
+  xml_wrapper_ = NULL;
+}
+
+// static
+void MetricsLogBase::CreateHashes(const std::string& string,
+                                  std::string* base64_encoded_hash,
+                                  uint64* numeric_hash) {
+  std::string hash = CreateHash(string);
+
+  std::string encoded_digest;
+  if (base::Base64Encode(hash, &encoded_digest))
+    DVLOG(1) << "Metrics: Hash [" << encoded_digest << "]=[" << string << "]";
+
+  *base64_encoded_hash = encoded_digest;
+  *numeric_hash = HashToUInt64(hash);
+}
+
+// static
+int64 MetricsLogBase::GetBuildTime() {
+  static int64 integral_build_time = 0;
+  if (!integral_build_time) {
+    Time time;
+    const char* kDateTime = __DATE__ " " __TIME__ " GMT";
+    bool result = Time::FromString(kDateTime, &time);
+    DCHECK(result);
+    integral_build_time = static_cast<int64>(time.ToTimeT());
+  }
+  return integral_build_time;
+}
+
+// static
+int64 MetricsLogBase::GetCurrentTime() {
+  return (base::TimeTicks::Now() - base::TimeTicks()).InSeconds();
+}
+
+void MetricsLogBase::CloseLog() {
+  DCHECK(!locked_);
+  locked_ = true;
+
+  int result = xmlTextWriterEndDocument(xml_wrapper_->writer());
+  DCHECK_GE(result, 0);
+
+  result = xmlTextWriterFlush(xml_wrapper_->writer());
+  DCHECK_GE(result, 0);
+
+#if defined(OS_CHROMEOS)
+  // TODO(isherman): Once the XML pipeline is deprecated, there will be no need
+  // to track the hardware class in a separate member variable and only write it
+  // out when the log is closed.
+  xmlNodePtr root = xmlDocGetRootElement(xml_wrapper_->doc());
+  if (!hardware_class_.empty()) {
+    // The hardware class is determined after the first ongoing log is
+    // constructed, so this adds the root element's "hardwareclass"
+    // attribute when the log is closed instead.
+    xmlNewProp(root, UnsignedChar("hardwareclass"),
+               UnsignedChar(hardware_class_.c_str()));
+
+    // Write to the protobuf too.
+    uma_proto_.mutable_system_profile()->mutable_hardware()->set_hardware_class(
+        hardware_class_);
+  }
+
+  // Flattens the XML tree into a character buffer.
+  PerfTimer dump_timer;
+  result = xmlNodeDump(xml_wrapper_->buffer(), xml_wrapper_->doc(),
+                       root, /* level */ 0, /* format */ 1);
+  DCHECK_GE(result, 0);
+  UMA_HISTOGRAM_TIMES("UMA.XMLNodeDumpTime", dump_timer.Elapsed());
+
+  PerfTimer free_timer;
+  xml_wrapper_->FreeDocWriter();
+  UMA_HISTOGRAM_TIMES("UMA.XMLWriterDestructionTime", free_timer.Elapsed());
+#endif  // OS_CHROMEOS
+}
+
+int MetricsLogBase::GetEncodedLogSizeXml() {
+  DCHECK(locked_);
+  CHECK(xml_wrapper_);
+  CHECK(xml_wrapper_->buffer());
+  return xml_wrapper_->buffer()->use;
+}
+
+bool MetricsLogBase::GetEncodedLogXml(char* buffer, int buffer_size) {
+  DCHECK(locked_);
+  if (buffer_size < GetEncodedLogSizeXml())
+    return false;
+
+  memcpy(buffer, xml_wrapper_->buffer()->content, GetEncodedLogSizeXml());
+  return true;
+}
+
+void MetricsLogBase::GetEncodedLogProto(std::string* encoded_log) {
+  DCHECK(locked_);
+  uma_proto_.SerializeToString(encoded_log);
+}
+
+int MetricsLogBase::GetElapsedSeconds() {
+  return static_cast<int>((Time::Now() - start_time_).InSeconds());
+}
+
+void MetricsLogBase::RecordUserAction(const char* key) {
+  DCHECK(!locked_);
+
+  std::string base64_hash;
+  uint64 numeric_hash;
+  CreateHashes(key, &base64_hash, &numeric_hash);
+  if (base64_hash.empty()) {
+    NOTREACHED() << "Unable generate encoded hash of command: " << key;
+    return;
+  }
+
+  // Write the XML version.
+  OPEN_ELEMENT_FOR_SCOPE("uielement");
+  WriteAttribute("action", "command");
+  WriteAttribute("targetidhash", base64_hash);
+
+  // Write the protobuf version.
+  UserActionEventProto* user_action = uma_proto_.add_user_action_event();
+  user_action->set_name_hash(numeric_hash);
+  user_action->set_time(MetricsLogBase::GetCurrentTime());
+
+  // TODO(jhughes): Properly track windows.
+  WriteIntAttribute("window", 0);
+  WriteCommonEventAttributes();
+
+  ++num_events_;
+}
+
+void MetricsLogBase::RecordLoadEvent(int window_id,
+                                     const GURL& url,
+                                     content::PageTransition origin,
+                                     int session_index,
+                                     TimeDelta load_time) {
+  DCHECK(!locked_);
+
+  OPEN_ELEMENT_FOR_SCOPE("document");
+  WriteAttribute("action", "load");
+  WriteIntAttribute("docid", session_index);
+  WriteIntAttribute("window", window_id);
+  WriteAttribute("loadtime", base::Int64ToString(load_time.InMilliseconds()));
+
+  std::string origin_string;
+
+  switch (content::PageTransitionStripQualifier(origin)) {
+    // TODO(jhughes): Some of these mappings aren't right... we need to add
+    // some values to the server's enum.
+    case content::PAGE_TRANSITION_LINK:
+    case content::PAGE_TRANSITION_MANUAL_SUBFRAME:
+      origin_string = "link";
+      break;
+
+    case content::PAGE_TRANSITION_TYPED:
+      origin_string = "typed";
+      break;
+
+    case content::PAGE_TRANSITION_AUTO_BOOKMARK:
+      origin_string = "bookmark";
+      break;
+
+    case content::PAGE_TRANSITION_AUTO_SUBFRAME:
+    case content::PAGE_TRANSITION_RELOAD:
+      origin_string = "refresh";
+      break;
+
+    case content::PAGE_TRANSITION_GENERATED:
+    case content::PAGE_TRANSITION_KEYWORD:
+      origin_string = "global-history";
+      break;
+
+    case content::PAGE_TRANSITION_AUTO_TOPLEVEL:
+      origin_string = "auto-toplevel";
+      break;
+
+    case content::PAGE_TRANSITION_FORM_SUBMIT:
+      origin_string = "form-submit";
+      break;
+
+    default:
+      NOTREACHED() << "Received an unknown page transition type: " <<
+                      content::PageTransitionStripQualifier(origin);
+  }
+  if (!origin_string.empty())
+    WriteAttribute("origin", origin_string);
+
+  WriteCommonEventAttributes();
+
+  ++num_events_;
+}
+
+void MetricsLogBase::RecordWindowEvent(WindowEventType type,
+                                   int window_id,
+                                   int parent_id) {
+  DCHECK(!locked_);
+
+  OPEN_ELEMENT_FOR_SCOPE("window");
+  WriteAttribute("action", WindowEventTypeToString(type));
+  WriteAttribute("windowid", base::IntToString(window_id));
+  if (parent_id >= 0)
+    WriteAttribute("parent", base::IntToString(parent_id));
+  WriteCommonEventAttributes();
+
+  ++num_events_;
+}
+
+std::string MetricsLogBase::GetCurrentTimeString() {
+  return base::Uint64ToString(Time::Now().ToTimeT());
+}
+
+// These are the attributes that are common to every event.
+void MetricsLogBase::WriteCommonEventAttributes() {
+  WriteAttribute("session", session_id_);
+  WriteAttribute("time", GetCurrentTimeString());
+}
+
+void MetricsLogBase::WriteAttribute(const std::string& name,
+                                    const std::string& value) {
+  DCHECK(!locked_);
+  DCHECK(!name.empty());
+
+  int result = xmlTextWriterWriteAttribute(xml_wrapper_->writer(),
+                                           UnsignedChar(name.c_str()),
+                                           UnsignedChar(value.c_str()));
+  DCHECK_GE(result, 0);
+}
+
+void MetricsLogBase::WriteIntAttribute(const std::string& name, int value) {
+  WriteAttribute(name, base::IntToString(value));
+}
+
+void MetricsLogBase::WriteInt64Attribute(const std::string& name, int64 value) {
+  WriteAttribute(name, base::Int64ToString(value));
+}
+
+// static
+const char* MetricsLogBase::WindowEventTypeToString(WindowEventType type) {
+  switch (type) {
+    case WINDOW_CREATE:  return "create";
+    case WINDOW_OPEN:    return "open";
+    case WINDOW_CLOSE:   return "close";
+    case WINDOW_DESTROY: return "destroy";
+
+    default:
+      NOTREACHED();
+      return "unknown";  // Can't return NULL as this is used in a required
+                         // attribute.
+  }
+}
+
+void MetricsLogBase::StartElement(const char* name) {
+  DCHECK(!locked_);
+  DCHECK(name);
+
+  int result = xmlTextWriterStartElement(xml_wrapper_->writer(),
+                                         UnsignedChar(name));
+  DCHECK_GE(result, 0);
+}
+
+void MetricsLogBase::EndElement() {
+  DCHECK(!locked_);
+
+  int result = xmlTextWriterEndElement(xml_wrapper_->writer());
+  DCHECK_GE(result, 0);
+}
+
+// TODO(JAR): A The following should really be part of the histogram class.
+// Internal state is being needlessly exposed, and it would be hard to reuse
+// this code. If we moved this into the Histogram class, then we could use
+// the same infrastructure for logging StatsCounters, RatesCounters, etc.
+void MetricsLogBase::RecordHistogramDelta(
+    const Histogram& histogram,
+    const HistogramSamples& snapshot) {
+  DCHECK(!locked_);
+  DCHECK_NE(0, snapshot.TotalCount());
+
+  // We will ignore the MAX_INT/infinite value in the last element of range[].
+
+  OPEN_ELEMENT_FOR_SCOPE("histogram");
+
+  std::string base64_name_hash;
+  uint64 numeric_name_hash;
+  CreateHashes(histogram.histogram_name(),
+               &base64_name_hash,
+               &numeric_name_hash);
+
+  // Write the XML version.
+  WriteAttribute("name", base64_name_hash);
+
+  WriteInt64Attribute("sum", snapshot.sum());
+  // TODO(jar): Remove sumsquares when protobuffer accepts this as optional.
+  WriteInt64Attribute("sumsquares", 0);
+
+  for (scoped_ptr<SampleCountIterator> it = snapshot.Iterator();
+       !it->Done();
+       it->Next()) {
+    OPEN_ELEMENT_FOR_SCOPE("histogrambucket");
+    HistogramBase::Sample min;
+    HistogramBase::Sample max;
+    HistogramBase::Count count;
+    it->Get(&min, &max, &count);
+    WriteIntAttribute("min", min);
+    WriteIntAttribute("max", max);
+    WriteIntAttribute("count", count);
+  }
+
+  // Write the protobuf version.
+  HistogramEventProto* histogram_proto = uma_proto_.add_histogram_event();
+  histogram_proto->set_name_hash(numeric_name_hash);
+  histogram_proto->set_sum(snapshot.sum());
+
+  for (scoped_ptr<SampleCountIterator> it = snapshot.Iterator();
+       !it->Done();
+       it->Next()) {
+    HistogramBase::Sample min;
+    HistogramBase::Sample max;
+    HistogramBase::Count count;
+    it->Get(&min, &max, &count);
+    HistogramEventProto::Bucket* bucket = histogram_proto->add_bucket();
+    bucket->set_min(min);
+    bucket->set_max(max);
+    bucket->set_count(count);
+
+    size_t index;
+    if (it->GetBucketIndex(&index))
+      bucket->set_bucket_index(index);
+  }
+}
diff --git a/chrome/common/metrics/metrics_log_base.h b/chrome/common/metrics/metrics_log_base.h
new file mode 100644
index 0000000..b11f032
--- /dev/null
+++ b/chrome/common/metrics/metrics_log_base.h
@@ -0,0 +1,186 @@
+// Copyright (c) 2012 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.
+
+// This file defines a set of user experience metrics data recorded by
+// the MetricsService.  This is the unit of data that is sent to the server.
+
+#ifndef CHROME_COMMON_METRICS_METRICS_LOG_BASE_H_
+#define CHROME_COMMON_METRICS_METRICS_LOG_BASE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/metrics/histogram.h"
+#include "base/time.h"
+#include "chrome/common/metrics/proto/chrome_user_metrics_extension.pb.h"
+#include "content/public/common/page_transition_types.h"
+
+class GURL;
+
+namespace base {
+class HistogramSamples;
+}  // namespace base
+
+// This class provides base functionality for logging metrics data.
+class MetricsLogBase {
+ public:
+  // Creates a new metrics log
+  // client_id is the identifier for this profile on this installation
+  // session_id is an integer that's incremented on each application launch
+  MetricsLogBase(const std::string& client_id,
+                 int session_id,
+                 const std::string& version_string);
+  virtual ~MetricsLogBase();
+
+  // Computes the MD5 hash of the given string.
+  // Fills |base64_encoded_hash| with the hash, encoded in base64.
+  // Fills |numeric_hash| with the first 8 bytes of the hash.
+  static void CreateHashes(const std::string& string,
+                           std::string* base64_encoded_hash,
+                           uint64* numeric_hash);
+
+  // Get the GMT buildtime for the current binary, expressed in seconds since
+  // Januray 1, 1970 GMT.
+  // The value is used to identify when a new build is run, so that previous
+  // reliability stats, from other builds, can be abandoned.
+  static int64 GetBuildTime();
+
+  // Convenience function to return the current time at a resolution in seconds.
+  // This wraps base::TimeTicks, and hence provides an abstract time that is
+  // always incrementing for use in measuring time durations.
+  static int64 GetCurrentTime();
+
+  // Records a user-initiated action.
+  void RecordUserAction(const char* key);
+
+  enum WindowEventType {
+    WINDOW_CREATE = 0,
+    WINDOW_OPEN,
+    WINDOW_CLOSE,
+    WINDOW_DESTROY
+  };
+
+  void RecordWindowEvent(WindowEventType type, int window_id, int parent_id);
+
+  // Records a page load.
+  // window_id - the index of the tab in which the load took place
+  // url - which URL was loaded
+  // origin - what kind of action initiated the load
+  // load_time - how long it took to load the page
+  void RecordLoadEvent(int window_id,
+                       const GURL& url,
+                       content::PageTransition origin,
+                       int session_index,
+                       base::TimeDelta load_time);
+
+  // Record any changes in a given histogram for transmission.
+  void RecordHistogramDelta(const base::Histogram& histogram,
+                            const base::HistogramSamples& snapshot);
+
+  // Stop writing to this record and generate the encoded representation.
+  // None of the Record* methods can be called after this is called.
+  void CloseLog();
+
+  // These methods allow retrieval of the encoded representation of the
+  // record.  They can only be called after CloseLog() has been called.
+  // GetEncodedLog returns false if buffer_size is less than
+  // GetEncodedLogSize();
+  int GetEncodedLogSizeXml();
+  bool GetEncodedLogXml(char* buffer, int buffer_size);
+
+  // Fills |encoded_log| with the protobuf representation of the record.  Can
+  // only be called after CloseLog() has been called.
+  void GetEncodedLogProto(std::string* encoded_log);
+
+  // Returns the amount of time in seconds that this log has been in use.
+  int GetElapsedSeconds();
+
+  int num_events() { return num_events_; }
+
+  void set_hardware_class(const std::string& hardware_class) {
+    hardware_class_ = hardware_class;
+  }
+
+ protected:
+  class XmlWrapper;
+
+  // Returns a string containing the current time.
+  // Virtual so that it can be overridden for testing.
+  // TODO(isherman): Remove this method once the XML pipeline is old news.
+  virtual std::string GetCurrentTimeString();
+  // Helper class that invokes StartElement from constructor, and EndElement
+  // from destructor.
+  //
+  // Use the macro OPEN_ELEMENT_FOR_SCOPE to help avoid usage problems.
+  class ScopedElement {
+   public:
+    ScopedElement(MetricsLogBase* log, const std::string& name) : log_(log) {
+      DCHECK(log);
+      log->StartElement(name.c_str());
+    }
+
+    ScopedElement(MetricsLogBase* log, const char* name) : log_(log) {
+      DCHECK(log);
+      log->StartElement(name);
+    }
+
+    ~ScopedElement() {
+      log_->EndElement();
+    }
+
+   private:
+     MetricsLogBase* log_;
+  };
+  friend class ScopedElement;
+
+  static const char* WindowEventTypeToString(WindowEventType type);
+
+  // Frees the resources allocated by the XML document writer: the
+  // main writer object as well as the XML tree structure, if
+  // applicable.
+  void FreeDocWriter();
+
+  // Convenience versions of xmlWriter functions
+  void StartElement(const char* name);
+  void EndElement();
+  void WriteAttribute(const std::string& name, const std::string& value);
+  void WriteIntAttribute(const std::string& name, int value);
+  void WriteInt64Attribute(const std::string& name, int64 value);
+
+  // Write the attributes that are common to every metrics event type.
+  void WriteCommonEventAttributes();
+
+  bool locked() const { return locked_; }
+
+  metrics::ChromeUserMetricsExtension* uma_proto() { return &uma_proto_; }
+  const metrics::ChromeUserMetricsExtension* uma_proto() const {
+    return &uma_proto_;
+  }
+
+  // TODO(isherman): Remove this once the XML pipeline is outta here.
+  int num_events_;  // the number of events recorded in this log
+
+ private:
+  base::Time start_time_;
+  base::Time end_time_;
+
+  std::string client_id_;
+  std::string session_id_;
+  std::string hardware_class_;
+
+  // locked_ is true when record has been packed up for sending, and should
+  // no longer be written to.  It is only used for sanity checking and is
+  // not a real lock.
+  bool locked_;
+
+  // Isolated to limit the dependency on the XML library for our consumers.
+  XmlWrapper* xml_wrapper_;
+
+  // Stores the protocol buffer representation for this log.
+  metrics::ChromeUserMetricsExtension uma_proto_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsLogBase);
+};
+
+#endif  // CHROME_COMMON_METRICS_METRICS_LOG_BASE_H_
diff --git a/chrome/common/metrics/metrics_log_base_unittest.cc b/chrome/common/metrics/metrics_log_base_unittest.cc
new file mode 100644
index 0000000..610c11c
--- /dev/null
+++ b/chrome/common/metrics/metrics_log_base_unittest.cc
@@ -0,0 +1,195 @@
+// Copyright (c) 2012 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.
+
+#include <string>
+
+#include "base/base64.h"
+#include "base/format_macros.h"
+#include "base/stringprintf.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "chrome/common/metrics/metrics_log_base.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+
+namespace {
+
+// Since buildtime is highly variable, this function will scan an output log and
+// replace it with a consistent number.
+void NormalizeBuildtime(std::string* xml_encoded) {
+  std::string prefix = "buildtime=\"";
+  const char postfix = '\"';
+  size_t offset = xml_encoded->find(prefix);
+  ASSERT_NE(std::string::npos, offset);
+  offset += prefix.size();
+  size_t postfix_position = xml_encoded->find(postfix, offset);
+  ASSERT_NE(std::string::npos, postfix_position);
+  for (size_t i = offset; i < postfix_position; ++i) {
+    char digit = xml_encoded->at(i);
+    ASSERT_GE(digit, '0');
+    ASSERT_LE(digit, '9');
+  }
+
+  // Insert a single fake buildtime.
+  xml_encoded->replace(offset, postfix_position - offset, "123246");
+}
+
+class NoTimeMetricsLogBase : public MetricsLogBase {
+ public:
+  NoTimeMetricsLogBase(std::string client_id,
+                       int session_id,
+                       std::string version_string):
+      MetricsLogBase(client_id, session_id, version_string) {}
+  virtual ~NoTimeMetricsLogBase() {}
+
+  // Override this so that output is testable.
+  virtual std::string GetCurrentTimeString() OVERRIDE {
+    return std::string();
+  }
+};
+
+}  // namespace
+
+TEST(MetricsLogBaseTest, EmptyRecord) {
+  std::string expected_output =
+#if defined(OS_CHROMEOS)
+      "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
+      "appversion=\"bogus version\" hardwareclass=\"sample-class\"/>";
+#else
+      "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
+      "appversion=\"bogus version\"/>";
+#endif  // OS_CHROMEOS
+
+  MetricsLogBase log("bogus client ID", 0, "bogus version");
+  log.set_hardware_class("sample-class");
+  log.CloseLog();
+
+  int size = log.GetEncodedLogSizeXml();
+  ASSERT_GT(size, 0);
+
+  std::string encoded;
+  // Leave room for the NUL terminator.
+  ASSERT_TRUE(log.GetEncodedLogXml(WriteInto(&encoded, size + 1), size));
+  TrimWhitespaceASCII(encoded, TRIM_ALL, &encoded);
+  NormalizeBuildtime(&encoded);
+  NormalizeBuildtime(&expected_output);
+
+  ASSERT_EQ(expected_output, encoded);
+}
+
+TEST(MetricsLogBaseTest, WindowEvent) {
+  std::string expected_output =
+      "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
+          "appversion=\"bogus version\">\n"
+      " <window action=\"create\" windowid=\"0\" session=\"0\" time=\"\"/>\n"
+      " <window action=\"open\" windowid=\"1\" parent=\"0\" "
+          "session=\"0\" time=\"\"/>\n"
+      " <window action=\"close\" windowid=\"1\" parent=\"0\" "
+          "session=\"0\" time=\"\"/>\n"
+      " <window action=\"destroy\" windowid=\"0\" session=\"0\" time=\"\"/>\n"
+      "</log>";
+
+  NoTimeMetricsLogBase log("bogus client ID", 0, "bogus version");
+  log.RecordWindowEvent(MetricsLogBase::WINDOW_CREATE, 0, -1);
+  log.RecordWindowEvent(MetricsLogBase::WINDOW_OPEN, 1, 0);
+  log.RecordWindowEvent(MetricsLogBase::WINDOW_CLOSE, 1, 0);
+  log.RecordWindowEvent(MetricsLogBase::WINDOW_DESTROY, 0, -1);
+  log.CloseLog();
+
+  ASSERT_EQ(4, log.num_events());
+
+  int size = log.GetEncodedLogSizeXml();
+  ASSERT_GT(size, 0);
+
+  std::string encoded;
+  // Leave room for the NUL terminator.
+  ASSERT_TRUE(log.GetEncodedLogXml(WriteInto(&encoded, size + 1), size));
+  TrimWhitespaceASCII(encoded, TRIM_ALL, &encoded);
+  NormalizeBuildtime(&encoded);
+  NormalizeBuildtime(&expected_output);
+
+  ASSERT_EQ(expected_output, encoded);
+}
+
+TEST(MetricsLogBaseTest, LoadEvent) {
+  std::string expected_output =
+#if defined(OS_CHROMEOS)
+      "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
+          "appversion=\"bogus version\" hardwareclass=\"sample-class\">\n"
+      " <document action=\"load\" docid=\"1\" window=\"3\" loadtime=\"7219\" "
+          "origin=\"link\" session=\"0\" time=\"\"/>\n"
+      "</log>";
+#else
+      "<log clientid=\"bogus client ID\" buildtime=\"123456789\" "
+          "appversion=\"bogus version\">\n"
+      " <document action=\"load\" docid=\"1\" window=\"3\" loadtime=\"7219\" "
+          "origin=\"link\" session=\"0\" time=\"\"/>\n"
+      "</log>";
+#endif  // OS_CHROMEOS
+
+  NoTimeMetricsLogBase log("bogus client ID", 0, "bogus version");
+  log.RecordLoadEvent(3, GURL("http://google.com"),
+                      content::PAGE_TRANSITION_LINK, 1,
+                      TimeDelta::FromMilliseconds(7219));
+  log.set_hardware_class("sample-class");
+  log.CloseLog();
+
+  ASSERT_EQ(1, log.num_events());
+
+  int size = log.GetEncodedLogSizeXml();
+  ASSERT_GT(size, 0);
+
+  std::string encoded;
+  // Leave room for the NUL terminator.
+  ASSERT_TRUE(log.GetEncodedLogXml(WriteInto(&encoded, size + 1), size));
+  TrimWhitespaceASCII(encoded, TRIM_ALL, &encoded);
+  NormalizeBuildtime(&encoded);
+  NormalizeBuildtime(&expected_output);
+
+  ASSERT_EQ(expected_output, encoded);
+}
+
+// Make sure our ID hashes are the same as what we see on the server side.
+TEST(MetricsLogBaseTest, CreateHashes) {
+  static const struct {
+    std::string input;
+    std::string output;
+  } cases[] = {
+    {"Back", "0x0557fa923dcee4d0"},
+    {"Forward", "0x67d2f6740a8eaebf"},
+    {"NewTab", "0x290eb683f96572f1"},
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) {
+    std::string hash_string;
+    uint64 hash_numeric;
+    MetricsLogBase::CreateHashes(cases[i].input, &hash_string, &hash_numeric);
+
+    // Verify the numeric representation.
+    {
+      std::string hash_numeric_hex =
+          base::StringPrintf("0x%016" PRIx64, hash_numeric);
+      EXPECT_EQ(cases[i].output, hash_numeric_hex);
+    }
+
+    // Verify the string representation.
+    {
+      std::string hash_string_decoded;
+      ASSERT_TRUE(base::Base64Decode(hash_string, &hash_string_decoded));
+
+      // Convert to hex string
+      // We're only checking the first 8 bytes, because that's what
+      // the metrics server uses.
+      std::string hash_string_hex = "0x";
+      ASSERT_GE(hash_string_decoded.size(), 8U);
+      for (size_t j = 0; j < 8; j++) {
+        base::StringAppendF(&hash_string_hex, "%02x",
+                            static_cast<uint8>(hash_string_decoded.data()[j]));
+      }
+      EXPECT_EQ(cases[i].output, hash_string_hex);
+    }
+  }
+};
diff --git a/chrome/common/metrics/metrics_log_manager.cc b/chrome/common/metrics/metrics_log_manager.cc
new file mode 100644
index 0000000..e958074
--- /dev/null
+++ b/chrome/common/metrics/metrics_log_manager.cc
@@ -0,0 +1,290 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/metrics/metrics_log_manager.h"
+
+#if defined(USE_SYSTEM_LIBBZ2)
+#include <bzlib.h>
+#else
+#include "third_party/bzip2/bzlib.h"
+#endif
+
+#include <algorithm>
+
+#include "base/metrics/histogram.h"
+#include "base/string_util.h"
+#include "chrome/common/metrics/metrics_log_base.h"
+
+namespace {
+
+// Used to keep track of discarded protobuf logs without having to track xml and
+// protobuf logs in separate lists.
+const char kDiscardedLog[] = "Log discarded";
+
+}  // anonymous namespace
+
+MetricsLogManager::MetricsLogManager()
+    : current_log_type_(NO_LOG),
+      paused_log_type_(NO_LOG),
+      staged_log_type_(NO_LOG),
+      max_ongoing_log_store_size_(0),
+      last_provisional_store_index_(-1),
+      last_provisional_store_type_(INITIAL_LOG) {}
+
+MetricsLogManager::~MetricsLogManager() {}
+
+bool MetricsLogManager::SerializedLog::empty() const {
+  DCHECK_EQ(xml.empty(), proto.empty());
+  return xml.empty();
+}
+
+size_t MetricsLogManager::SerializedLog::length() const {
+  return std::max(xml.length(), proto.length());
+}
+
+void MetricsLogManager::SerializedLog::swap(SerializedLog& log) {
+  xml.swap(log.xml);
+  proto.swap(log.proto);
+}
+
+void MetricsLogManager::BeginLoggingWithLog(MetricsLogBase* log,
+                                            LogType log_type) {
+  DCHECK(log_type != NO_LOG);
+  DCHECK(!current_log_.get());
+  current_log_.reset(log);
+  current_log_type_ = log_type;
+}
+
+void MetricsLogManager::FinishCurrentLog() {
+  DCHECK(current_log_.get());
+  DCHECK(current_log_type_ != NO_LOG);
+  current_log_->CloseLog();
+  SerializedLog compressed_log;
+  CompressCurrentLog(&compressed_log);
+  if (!compressed_log.empty())
+    StoreLog(&compressed_log, current_log_type_, NORMAL_STORE);
+  current_log_.reset();
+  current_log_type_ = NO_LOG;
+}
+
+void MetricsLogManager::StageNextLogForUpload() {
+  // Prioritize initial logs for uploading.
+  std::vector<SerializedLog>* source_list =
+      unsent_initial_logs_.empty() ? &unsent_ongoing_logs_
+                                   : &unsent_initial_logs_;
+  LogType source_type = (source_list == &unsent_ongoing_logs_) ? ONGOING_LOG
+                                                               : INITIAL_LOG;
+  // CHECK, rather than DCHECK, because swap()ing with an empty list causes
+  // hard-to-identify crashes much later.
+  CHECK(!source_list->empty());
+  DCHECK(staged_log_text_.empty());
+  DCHECK(staged_log_type_ == NO_LOG);
+  staged_log_text_.swap(source_list->back());
+  staged_log_type_ = source_type;
+  source_list->pop_back();
+
+  // If the staged log was the last provisional store, clear that.
+  if (last_provisional_store_index_ != -1) {
+    if (source_type == last_provisional_store_type_ &&
+        static_cast<unsigned int>(last_provisional_store_index_) ==
+            source_list->size()) {
+      last_provisional_store_index_ = -1;
+    }
+  }
+}
+
+bool MetricsLogManager::has_staged_log() const {
+  return has_staged_log_proto() || has_staged_log_xml();
+}
+
+bool MetricsLogManager::has_staged_log_proto() const {
+  return !staged_log_text().proto.empty() &&
+      staged_log_text().proto != kDiscardedLog;
+}
+
+bool MetricsLogManager::has_staged_log_xml() const {
+  return !staged_log_text().xml.empty() &&
+      staged_log_text().xml != kDiscardedLog;
+}
+
+void MetricsLogManager::DiscardStagedLog() {
+  staged_log_text_.xml.clear();
+  staged_log_text_.proto.clear();
+  staged_log_type_ = NO_LOG;
+}
+
+void MetricsLogManager::DiscardStagedLogProto() {
+  staged_log_text_.proto = kDiscardedLog;
+
+  // If we're discarding the last piece of the log, reset the staged log state.
+  if (!has_staged_log())
+    DiscardStagedLog();
+}
+
+void MetricsLogManager::DiscardStagedLogXml() {
+  staged_log_text_.xml = kDiscardedLog;
+
+  // If we're discarding the last piece of the log, reset the staged log state.
+  if (!has_staged_log())
+    DiscardStagedLog();
+}
+
+void MetricsLogManager::DiscardCurrentLog() {
+  current_log_->CloseLog();
+  current_log_.reset();
+  current_log_type_ = NO_LOG;
+}
+
+void MetricsLogManager::PauseCurrentLog() {
+  DCHECK(!paused_log_.get());
+  DCHECK(paused_log_type_ == NO_LOG);
+  paused_log_.reset(current_log_.release());
+  paused_log_type_ = current_log_type_;
+  current_log_type_ = NO_LOG;
+}
+
+void MetricsLogManager::ResumePausedLog() {
+  DCHECK(!current_log_.get());
+  DCHECK(current_log_type_ == NO_LOG);
+  current_log_.reset(paused_log_.release());
+  current_log_type_ = paused_log_type_;
+  paused_log_type_ = NO_LOG;
+}
+
+void MetricsLogManager::StoreStagedLogAsUnsent(StoreType store_type) {
+  DCHECK(has_staged_log());
+
+  // If compressing the log failed, there's nothing to store.
+  if (staged_log_text_.empty())
+    return;
+
+  StoreLog(&staged_log_text_, staged_log_type_, store_type);
+  DiscardStagedLog();
+}
+
+void MetricsLogManager::StoreLog(SerializedLog* log_text,
+                                 LogType log_type,
+                                 StoreType store_type) {
+  DCHECK(log_type != NO_LOG);
+  std::vector<SerializedLog>* destination_list =
+      (log_type == INITIAL_LOG) ? &unsent_initial_logs_
+                                : &unsent_ongoing_logs_;
+  destination_list->push_back(SerializedLog());
+  destination_list->back().swap(*log_text);
+
+  if (store_type == PROVISIONAL_STORE) {
+    last_provisional_store_index_ = destination_list->size() - 1;
+    last_provisional_store_type_ = log_type;
+  }
+}
+
+void MetricsLogManager::DiscardLastProvisionalStore() {
+  if (last_provisional_store_index_ == -1)
+    return;
+  std::vector<SerializedLog>* source_list =
+      (last_provisional_store_type_ == ONGOING_LOG) ? &unsent_ongoing_logs_
+                                                    : &unsent_initial_logs_;
+  DCHECK_LT(static_cast<unsigned int>(last_provisional_store_index_),
+            source_list->size());
+  source_list->erase(source_list->begin() + last_provisional_store_index_);
+  last_provisional_store_index_ = -1;
+}
+
+void MetricsLogManager::PersistUnsentLogs() {
+  DCHECK(log_serializer_.get());
+  if (!log_serializer_.get())
+    return;
+  // Remove any ongoing logs that are over the serialization size limit.
+  if (max_ongoing_log_store_size_) {
+    for (std::vector<SerializedLog>::iterator it = unsent_ongoing_logs_.begin();
+         it != unsent_ongoing_logs_.end();) {
+      size_t log_size = it->xml.length();
+      if (log_size > max_ongoing_log_store_size_) {
+        // TODO(isherman): We probably want a similar check for protobufs, but
+        // we don't want to prevent XML upload just because the protobuf version
+        // is too long.  In practice, I'm pretty sure the XML version should
+        // always be longer, or at least on the same order of magnitude in
+        // length.
+        UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted",
+                             static_cast<int>(log_size));
+        it = unsent_ongoing_logs_.erase(it);
+      } else {
+        ++it;
+      }
+    }
+  }
+  log_serializer_->SerializeLogs(unsent_initial_logs_, INITIAL_LOG);
+  log_serializer_->SerializeLogs(unsent_ongoing_logs_, ONGOING_LOG);
+}
+
+void MetricsLogManager::LoadPersistedUnsentLogs() {
+  DCHECK(log_serializer_.get());
+  if (!log_serializer_.get())
+    return;
+  log_serializer_->DeserializeLogs(INITIAL_LOG, &unsent_initial_logs_);
+  log_serializer_->DeserializeLogs(ONGOING_LOG, &unsent_ongoing_logs_);
+}
+
+void MetricsLogManager::CompressCurrentLog(SerializedLog* compressed_log) {
+  int text_size = current_log_->GetEncodedLogSizeXml();
+  DCHECK_GT(text_size, 0);
+  std::string log_text;
+  current_log_->GetEncodedLogXml(WriteInto(&log_text, text_size + 1),
+                                 text_size);
+
+  bool success = Bzip2Compress(log_text, &(compressed_log->xml));
+  if (success) {
+    // Allow security-conscious users to see all metrics logs that we send.
+    DVLOG(1) << "METRICS LOG: " << log_text;
+
+    // Note that we only save the protobuf version if we succeeded in
+    // compressing the XML, so that the two data streams are the same.
+    current_log_->GetEncodedLogProto(&(compressed_log->proto));
+  } else {
+    NOTREACHED() << "Failed to compress log for transmission.";
+  }
+}
+
+// static
+// This implementation is based on the Firefox MetricsService implementation.
+bool MetricsLogManager::Bzip2Compress(const std::string& input,
+                                      std::string* output) {
+  bz_stream stream = {0};
+  // As long as our input is smaller than the bzip2 block size, we should get
+  // the best compression.  For example, if your input was 250k, using a block
+  // size of 300k or 500k should result in the same compression ratio.  Since
+  // our data should be under 100k, using the minimum block size of 100k should
+  // allocate less temporary memory, but result in the same compression ratio.
+  int result = BZ2_bzCompressInit(&stream,
+                                  1,   // 100k (min) block size
+                                  0,   // quiet
+                                  0);  // default "work factor"
+  if (result != BZ_OK) {  // out of memory?
+    return false;
+  }
+
+  output->clear();
+
+  stream.next_in = const_cast<char*>(input.data());
+  stream.avail_in = static_cast<int>(input.size());
+  // NOTE: we don't need a BZ_RUN phase since our input buffer contains
+  //       the entire input
+  do {
+    output->resize(output->size() + 1024);
+    stream.next_out = &((*output)[stream.total_out_lo32]);
+    stream.avail_out = static_cast<int>(output->size()) - stream.total_out_lo32;
+    result = BZ2_bzCompress(&stream, BZ_FINISH);
+  } while (result == BZ_FINISH_OK);
+  if (result != BZ_STREAM_END) {  // unknown failure?
+    output->clear();
+    // TODO(jar): See if it would be better to do a CHECK() here.
+    return false;
+  }
+  result = BZ2_bzCompressEnd(&stream);
+  DCHECK(result == BZ_OK);
+
+  output->resize(stream.total_out_lo32);
+
+  return true;
+}
diff --git a/chrome/common/metrics/metrics_log_manager.h b/chrome/common/metrics/metrics_log_manager.h
new file mode 100644
index 0000000..291648b
--- /dev/null
+++ b/chrome/common/metrics/metrics_log_manager.h
@@ -0,0 +1,221 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_METRICS_METRICS_LOG_MANAGER_H_
+#define CHROME_COMMON_METRICS_METRICS_LOG_MANAGER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+
+#include <string>
+#include <vector>
+
+class MetricsLogBase;
+
+// Manages all the log objects used by a MetricsService implementation. Keeps
+// track of both an in progress log and a log that is staged for uploading as
+// text, as well as saving logs to, and loading logs from, persistent storage.
+class MetricsLogManager {
+ public:
+  MetricsLogManager();
+  ~MetricsLogManager();
+
+  // Stores both XML and protocol buffer serializations for a log.
+  struct SerializedLog {
+   public:
+    // Exposed to reduce code churn as we transition from the XML pipeline to
+    // the protocol buffer pipeline.
+    bool empty() const;
+    size_t length() const;
+    void swap(SerializedLog& log);
+
+    std::string xml;
+    std::string proto;
+  };
+
+  enum LogType {
+    INITIAL_LOG,  // The first log of a session.
+    ONGOING_LOG,  // Subsequent logs in a session.
+    NO_LOG,       // Placeholder value for when there is no log.
+  };
+
+  enum StoreType {
+    NORMAL_STORE,       // A standard store operation.
+    PROVISIONAL_STORE,  // A store operation that can be easily reverted later.
+  };
+
+  // Takes ownership of |log|, which has type |log_type|, and makes it the
+  // current_log. This should only be called if there is not a current log.
+  void BeginLoggingWithLog(MetricsLogBase* log, LogType log_type);
+
+  // Returns the in-progress log.
+  MetricsLogBase* current_log() { return current_log_.get(); }
+
+  // Closes current_log(), compresses it, and stores the compressed log for
+  // later, leaving current_log() NULL.
+  void FinishCurrentLog();
+
+  // Returns true if there are any logs waiting to be uploaded.
+  bool has_unsent_logs() const {
+    return !unsent_initial_logs_.empty() || !unsent_ongoing_logs_.empty();
+  }
+
+  // Populates staged_log_text() with the next stored log to send.
+  // Should only be called if has_unsent_logs() is true.
+  void StageNextLogForUpload();
+
+  // Returns true if there is a log that needs to be, or is being, uploaded.
+  bool has_staged_log() const;
+
+  // Returns true if there is a protobuf log that needs to be uploaded.
+  // In the case that an XML upload needs to be re-issued due to a previous
+  // failure, has_staged_log() can return true while this returns false.
+  bool has_staged_log_proto() const;
+
+  // Returns true if there is an xml log that needs to be uploaded.
+  // In the case that a protobuf upload needs to be re-issued due to a previous
+  // failure, has_staged_log() can return true while this returns false.
+  bool has_staged_log_xml() const;
+
+  // The text of the staged log, in compressed XML or protobuf format. Empty if
+  // there is no staged log, or if compression of the staged log failed.
+  const SerializedLog& staged_log_text() const {
+    return staged_log_text_;
+  }
+
+  // Discards the staged log (both the XML and the protobuf data).
+  void DiscardStagedLog();
+
+  // Discards the protobuf data in the staged log.
+  // This is useful to prevent needlessly re-issuing successful protobuf uploads
+  // due to XML upload failures.
+  void DiscardStagedLogProto();
+
+  // Discards the XML data in the staged log.
+  // This is useful to prevent needlessly re-issuing successful XML uploads
+  // due to protobuf upload failures.
+  void DiscardStagedLogXml();
+
+  // Closes and discards |current_log|.
+  void DiscardCurrentLog();
+
+  // Sets current_log to NULL, but saves the current log for future use with
+  // ResumePausedLog(). Only one log may be paused at a time.
+  // TODO(stuartmorgan): Pause/resume support is really a workaround for a
+  // design issue in initial log writing; that should be fixed, and pause/resume
+  // removed.
+  void PauseCurrentLog();
+
+  // Restores the previously paused log (if any) to current_log().
+  // This should only be called if there is not a current log.
+  void ResumePausedLog();
+
+  // Saves the staged log, then clears staged_log().
+  // If |store_type| is PROVISIONAL_STORE, it can be dropped from storage with
+  // a later call to DiscardLastProvisionalStore (if it hasn't already been
+  // staged again).
+  // This is intended to be used when logs are being saved while an upload is in
+  // progress, in case the upload later succeeds.
+  // This can only be called if has_staged_log() is true.
+  void StoreStagedLogAsUnsent(StoreType store_type);
+
+  // Discards the last log stored with StoreStagedLogAsUnsent with |store_type|
+  // set to PROVISIONAL_STORE, as long as it hasn't already been re-staged. If
+  // the log is no longer present, this is a no-op.
+  void DiscardLastProvisionalStore();
+
+  // Sets the threshold for how large an onging log can be and still be written
+  // to persistant storage. Ongoing logs larger than this will be discarded
+  // before persisting. 0 is interpreted as no limit.
+  void set_max_ongoing_log_store_size(size_t max_size) {
+    max_ongoing_log_store_size_ = max_size;
+  }
+
+  // Interface for a utility class to serialize and deserialize logs for
+  // persistent storage.
+  class LogSerializer {
+   public:
+    virtual ~LogSerializer() {}
+
+    // Serializes |logs| to persistent storage, replacing any previously
+    // serialized logs of the same type.
+    virtual void SerializeLogs(const std::vector<SerializedLog>& logs,
+                               LogType log_type) = 0;
+
+    // Populates |logs| with logs of type |log_type| deserialized from
+    // persistent storage.
+    virtual void DeserializeLogs(LogType log_type,
+                                 std::vector<SerializedLog>* logs) = 0;
+  };
+
+  // Sets the serializer to use for persisting and loading logs; takes ownership
+  // of |serializer|.
+  void set_log_serializer(LogSerializer* serializer) {
+    log_serializer_.reset(serializer);
+  }
+
+  // Saves any unsent logs to persistent storage using the current log
+  // serializer. Can only be called after set_log_serializer.
+  void PersistUnsentLogs();
+
+  // Loads any unsent logs from persistent storage using the current log
+  // serializer. Can only be called after set_log_serializer.
+  void LoadPersistedUnsentLogs();
+
+ private:
+  // Saves |log_text| as the given type (or discards it in accordance with
+  // |max_ongoing_log_store_size_|).
+  // NOTE: This clears the contents of |log_text| (to avoid an expensive
+  // string copy), so the log should be discarded after this call.
+  void StoreLog(SerializedLog* log_text,
+                LogType log_type,
+                StoreType store_type);
+
+  // Compresses current_log_ into compressed_log.
+  void CompressCurrentLog(SerializedLog* compressed_log);
+
+  // Compresses the text in |input| using bzip2, store the result in |output|.
+  static bool Bzip2Compress(const std::string& input, std::string* output);
+
+  // The log that we are still appending to.
+  scoped_ptr<MetricsLogBase> current_log_;
+  LogType current_log_type_;
+
+  // A paused, previously-current log.
+  scoped_ptr<MetricsLogBase> paused_log_;
+  LogType paused_log_type_;
+
+  // Helper class to handle serialization/deserialization of logs for persistent
+  // storage. May be NULL.
+  scoped_ptr<LogSerializer> log_serializer_;
+
+  // The text representations of the staged log, ready for upload to the server.
+  // The first item in the pair is the compressed XML representation; the second
+  // is the protobuf representation.
+  SerializedLog staged_log_text_;
+  LogType staged_log_type_;
+
+  // Logs from a previous session that have not yet been sent.
+  // The first item in each pair is the XML representation; the second item is
+  // the protobuf representation.
+  // Note that the vector has the oldest logs listed first (early in the
+  // vector), and we'll discard old logs if we have gathered too many logs.
+  std::vector<SerializedLog> unsent_initial_logs_;
+  std::vector<SerializedLog> unsent_ongoing_logs_;
+
+  size_t max_ongoing_log_store_size_;
+
+  // The index and type of the last provisional store. If nothing has been
+  // provisionally stored, or the last provisional store has already been
+  // re-staged, the index will be -1;
+  // This is necessary because during an upload there are two logs (staged
+  // and current) and a client might store them in either order, so it's
+  // not necessarily the case that the provisional store is the last store.
+  int last_provisional_store_index_;
+  LogType last_provisional_store_type_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsLogManager);
+};
+
+#endif  // CHROME_COMMON_METRICS_METRICS_LOG_MANAGER_H_
diff --git a/chrome/common/metrics/metrics_log_manager_unittest.cc b/chrome/common/metrics/metrics_log_manager_unittest.cc
new file mode 100644
index 0000000..dc58454
--- /dev/null
+++ b/chrome/common/metrics/metrics_log_manager_unittest.cc
@@ -0,0 +1,420 @@
+// Copyright (c) 2012 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.
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "chrome/common/metrics/metrics_log_base.h"
+#include "chrome/common/metrics/metrics_log_manager.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+typedef MetricsLogManager::SerializedLog SerializedLog;
+
+namespace {
+
+class MetricsLogManagerTest : public testing::Test {
+};
+
+// Dummy serializer that just stores logs in memory.
+class DummyLogSerializer : public MetricsLogManager::LogSerializer {
+ public:
+  virtual void SerializeLogs(const std::vector<SerializedLog>& logs,
+                             MetricsLogManager::LogType log_type) {
+    persisted_logs_[log_type] = logs;
+  }
+
+  virtual void DeserializeLogs(MetricsLogManager::LogType log_type,
+                               std::vector<SerializedLog>* logs) {
+    ASSERT_NE(static_cast<void*>(NULL), logs);
+    *logs = persisted_logs_[log_type];
+  }
+
+  // Returns the number of logs of the given type.
+  size_t TypeCount(MetricsLogManager::LogType log_type) {
+    return persisted_logs_[log_type].size();
+  }
+
+  // In-memory "persitent storage".
+  std::vector<SerializedLog> persisted_logs_[2];
+};
+
+}  // namespace
+
+TEST(MetricsLogManagerTest, StandardFlow) {
+  MetricsLogManager log_manager;
+
+  // Make sure a new manager has a clean slate.
+  EXPECT_EQ(NULL, log_manager.current_log());
+  EXPECT_FALSE(log_manager.has_staged_log());
+  EXPECT_FALSE(log_manager.has_unsent_logs());
+
+  // Check that the normal flow works.
+  MetricsLogBase* initial_log = new MetricsLogBase("id", 0, "version");
+  log_manager.BeginLoggingWithLog(initial_log, MetricsLogManager::INITIAL_LOG);
+  EXPECT_EQ(initial_log, log_manager.current_log());
+  EXPECT_FALSE(log_manager.has_staged_log());
+
+  log_manager.FinishCurrentLog();
+  EXPECT_EQ(NULL, log_manager.current_log());
+  EXPECT_TRUE(log_manager.has_unsent_logs());
+  EXPECT_FALSE(log_manager.has_staged_log());
+
+  MetricsLogBase* second_log = new MetricsLogBase("id", 0, "version");
+  log_manager.BeginLoggingWithLog(second_log, MetricsLogManager::ONGOING_LOG);
+  EXPECT_EQ(second_log, log_manager.current_log());
+
+  log_manager.StageNextLogForUpload();
+  EXPECT_TRUE(log_manager.has_staged_log());
+  EXPECT_FALSE(log_manager.staged_log_text().empty());
+
+  log_manager.DiscardStagedLogXml();
+  log_manager.DiscardStagedLogProto();
+  EXPECT_EQ(second_log, log_manager.current_log());
+  EXPECT_FALSE(log_manager.has_staged_log());
+  EXPECT_FALSE(log_manager.has_unsent_logs());
+  EXPECT_TRUE(log_manager.staged_log_text().empty());
+
+  EXPECT_FALSE(log_manager.has_unsent_logs());
+}
+
+TEST(MetricsLogManagerTest, AbandonedLog) {
+  MetricsLogManager log_manager;
+
+  MetricsLogBase* dummy_log = new MetricsLogBase("id", 0, "version");
+  log_manager.BeginLoggingWithLog(dummy_log, MetricsLogManager::INITIAL_LOG);
+  EXPECT_EQ(dummy_log, log_manager.current_log());
+
+  log_manager.DiscardCurrentLog();
+  EXPECT_EQ(NULL, log_manager.current_log());
+  EXPECT_FALSE(log_manager.has_staged_log());
+}
+
+TEST(MetricsLogManagerTest, InterjectedLog) {
+  MetricsLogManager log_manager;
+
+  MetricsLogBase* ongoing_log = new MetricsLogBase("id", 0, "version");
+  MetricsLogBase* temp_log = new MetricsLogBase("id", 0, "version");
+
+  log_manager.BeginLoggingWithLog(ongoing_log, MetricsLogManager::ONGOING_LOG);
+  EXPECT_EQ(ongoing_log, log_manager.current_log());
+
+  log_manager.PauseCurrentLog();
+  EXPECT_EQ(NULL, log_manager.current_log());
+
+  log_manager.BeginLoggingWithLog(temp_log, MetricsLogManager::INITIAL_LOG);
+  EXPECT_EQ(temp_log, log_manager.current_log());
+  log_manager.FinishCurrentLog();
+  EXPECT_EQ(NULL, log_manager.current_log());
+
+  log_manager.ResumePausedLog();
+  EXPECT_EQ(ongoing_log, log_manager.current_log());
+
+  EXPECT_FALSE(log_manager.has_staged_log());
+  log_manager.StageNextLogForUpload();
+  log_manager.DiscardStagedLogXml();
+  log_manager.DiscardStagedLogProto();
+  EXPECT_FALSE(log_manager.has_unsent_logs());
+}
+
+TEST(MetricsLogManagerTest, InterjectedLogPreservesType) {
+  MetricsLogManager log_manager;
+
+  MetricsLogBase* ongoing_log = new MetricsLogBase("id", 0, "version");
+  MetricsLogBase* temp_log = new MetricsLogBase("id", 0, "version");
+
+  log_manager.BeginLoggingWithLog(ongoing_log, MetricsLogManager::ONGOING_LOG);
+  log_manager.PauseCurrentLog();
+  log_manager.BeginLoggingWithLog(temp_log, MetricsLogManager::INITIAL_LOG);
+  log_manager.FinishCurrentLog();
+  log_manager.ResumePausedLog();
+  log_manager.StageNextLogForUpload();
+  log_manager.DiscardStagedLogXml();
+  log_manager.DiscardStagedLogProto();
+
+  // Verify that the remaining log (which is the original ongoing log) still
+  // has the right type.
+  DummyLogSerializer* serializer = new DummyLogSerializer;
+  log_manager.set_log_serializer(serializer);
+  log_manager.FinishCurrentLog();
+  log_manager.PersistUnsentLogs();
+  EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
+  EXPECT_EQ(1U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
+}
+
+TEST(MetricsLogManagerTest, StoreAndLoad) {
+  std::vector<SerializedLog> initial_logs;
+  std::vector<SerializedLog> ongoing_logs;
+
+  // Set up some in-progress logging in a scoped log manager simulating the
+  // leadup to quitting, then persist as would be done on quit.
+  {
+    MetricsLogManager log_manager;
+    DummyLogSerializer* serializer = new DummyLogSerializer;
+    log_manager.set_log_serializer(serializer);
+    // Simulate a log having already been unsent from a previous session.
+    SerializedLog log = {"xml", "proto"};
+    serializer->persisted_logs_[MetricsLogManager::ONGOING_LOG].push_back(log);
+    EXPECT_FALSE(log_manager.has_unsent_logs());
+    log_manager.LoadPersistedUnsentLogs();
+    EXPECT_TRUE(log_manager.has_unsent_logs());
+
+    MetricsLogBase* log1 = new MetricsLogBase("id", 0, "version");
+    MetricsLogBase* log2 = new MetricsLogBase("id", 0, "version");
+    log_manager.BeginLoggingWithLog(log1, MetricsLogManager::INITIAL_LOG);
+    log_manager.FinishCurrentLog();
+    log_manager.BeginLoggingWithLog(log2, MetricsLogManager::ONGOING_LOG);
+    log_manager.StageNextLogForUpload();
+    log_manager.StoreStagedLogAsUnsent(MetricsLogManager::NORMAL_STORE);
+    log_manager.FinishCurrentLog();
+
+    // Nothing should be written out until PersistUnsentLogs is called.
+    EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
+    EXPECT_EQ(1U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
+    log_manager.PersistUnsentLogs();
+    EXPECT_EQ(1U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
+    EXPECT_EQ(2U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
+
+    // Save the logs to transfer over to a new serializer (since log_manager
+    // owns |serializer|, so it's about to go away.
+    initial_logs = serializer->persisted_logs_[MetricsLogManager::INITIAL_LOG];
+    ongoing_logs = serializer->persisted_logs_[MetricsLogManager::ONGOING_LOG];
+  }
+
+  // Now simulate the relaunch, ensure that the log manager restores
+  // everything correctly, and verify that once the are handled they are not
+  // re-persisted.
+  {
+    MetricsLogManager log_manager;
+
+    DummyLogSerializer* serializer = new DummyLogSerializer;
+    serializer->persisted_logs_[MetricsLogManager::INITIAL_LOG] = initial_logs;
+    serializer->persisted_logs_[MetricsLogManager::ONGOING_LOG] = ongoing_logs;
+
+    log_manager.set_log_serializer(serializer);
+    log_manager.LoadPersistedUnsentLogs();
+    EXPECT_TRUE(log_manager.has_unsent_logs());
+
+    log_manager.StageNextLogForUpload();
+    log_manager.DiscardStagedLogXml();
+    log_manager.DiscardStagedLogProto();
+    // The initial log should be sent first; update the persisted storage to
+    // verify.
+    log_manager.PersistUnsentLogs();
+    EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
+    EXPECT_EQ(2U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
+
+    // Handle the first ongoing log.
+    log_manager.StageNextLogForUpload();
+    log_manager.DiscardStagedLogXml();
+    log_manager.DiscardStagedLogProto();
+    EXPECT_TRUE(log_manager.has_unsent_logs());
+
+    // Handle the last log.
+    log_manager.StageNextLogForUpload();
+    log_manager.DiscardStagedLogXml();
+    log_manager.DiscardStagedLogProto();
+    EXPECT_FALSE(log_manager.has_unsent_logs());
+
+    // Nothing should have changed "on disk" since PersistUnsentLogs hasn't been
+    // called again.
+    EXPECT_EQ(2U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
+    // Persist, and make sure nothing is left.
+    log_manager.PersistUnsentLogs();
+    EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
+    EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
+  }
+}
+
+TEST(MetricsLogManagerTest, StoreStagedLogTypes) {
+  // Ensure that types are preserved when storing staged logs.
+  {
+    MetricsLogManager log_manager;
+    DummyLogSerializer* serializer = new DummyLogSerializer;
+    log_manager.set_log_serializer(serializer);
+
+    MetricsLogBase* log = new MetricsLogBase("id", 0, "version");
+    log_manager.BeginLoggingWithLog(log, MetricsLogManager::ONGOING_LOG);
+    log_manager.FinishCurrentLog();
+    log_manager.StageNextLogForUpload();
+    log_manager.StoreStagedLogAsUnsent(MetricsLogManager::NORMAL_STORE);
+    log_manager.PersistUnsentLogs();
+
+    EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
+    EXPECT_EQ(1U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
+  }
+
+  {
+    MetricsLogManager log_manager;
+    DummyLogSerializer* serializer = new DummyLogSerializer;
+    log_manager.set_log_serializer(serializer);
+
+    MetricsLogBase* log = new MetricsLogBase("id", 0, "version");
+    log_manager.BeginLoggingWithLog(log, MetricsLogManager::INITIAL_LOG);
+    log_manager.FinishCurrentLog();
+    log_manager.StageNextLogForUpload();
+    log_manager.StoreStagedLogAsUnsent(MetricsLogManager::NORMAL_STORE);
+    log_manager.PersistUnsentLogs();
+
+    EXPECT_EQ(1U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
+    EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
+  }
+}
+
+TEST(MetricsLogManagerTest, LargeLogDiscarding) {
+  MetricsLogManager log_manager;
+  DummyLogSerializer* serializer = new DummyLogSerializer;
+  log_manager.set_log_serializer(serializer);
+  // Set the size threshold very low, to verify that it's honored.
+  log_manager.set_max_ongoing_log_store_size(1);
+
+  MetricsLogBase* log1 = new MetricsLogBase("id", 0, "version");
+  MetricsLogBase* log2 = new MetricsLogBase("id", 0, "version");
+  log_manager.BeginLoggingWithLog(log1, MetricsLogManager::INITIAL_LOG);
+  log_manager.FinishCurrentLog();
+  log_manager.BeginLoggingWithLog(log2, MetricsLogManager::ONGOING_LOG);
+  log_manager.FinishCurrentLog();
+
+  // Only the ongoing log should be written out, due to the threshold.
+  log_manager.PersistUnsentLogs();
+  EXPECT_EQ(1U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
+  EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
+}
+
+TEST(MetricsLogManagerTest, ProvisionalStoreStandardFlow) {
+  // Ensure that provisional store works, and discards the correct log.
+  {
+    MetricsLogManager log_manager;
+    MetricsLogBase* log1 = new MetricsLogBase("id", 0, "version");
+    MetricsLogBase* log2 = new MetricsLogBase("id", 0, "version");
+    log_manager.BeginLoggingWithLog(log1, MetricsLogManager::INITIAL_LOG);
+    log_manager.FinishCurrentLog();
+    log_manager.BeginLoggingWithLog(log2, MetricsLogManager::ONGOING_LOG);
+    log_manager.StageNextLogForUpload();
+    log_manager.StoreStagedLogAsUnsent(MetricsLogManager::PROVISIONAL_STORE);
+    log_manager.FinishCurrentLog();
+    log_manager.DiscardLastProvisionalStore();
+
+    DummyLogSerializer* serializer = new DummyLogSerializer;
+    log_manager.set_log_serializer(serializer);
+    log_manager.PersistUnsentLogs();
+    EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
+    EXPECT_EQ(1U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
+  }
+}
+
+TEST(MetricsLogManagerTest, ProvisionalStoreNoop) {
+  // Ensure that trying to drop a sent log is a no-op, even if another log has
+  // since been staged.
+  {
+    MetricsLogManager log_manager;
+    MetricsLogBase* log1 = new MetricsLogBase("id", 0, "version");
+    MetricsLogBase* log2 = new MetricsLogBase("id", 0, "version");
+    log_manager.BeginLoggingWithLog(log1, MetricsLogManager::ONGOING_LOG);
+    log_manager.FinishCurrentLog();
+    log_manager.StageNextLogForUpload();
+    log_manager.StoreStagedLogAsUnsent(MetricsLogManager::PROVISIONAL_STORE);
+    log_manager.StageNextLogForUpload();
+    log_manager.DiscardStagedLogXml();
+    log_manager.DiscardStagedLogProto();
+    log_manager.BeginLoggingWithLog(log2, MetricsLogManager::ONGOING_LOG);
+    log_manager.FinishCurrentLog();
+    log_manager.StageNextLogForUpload();
+    log_manager.StoreStagedLogAsUnsent(MetricsLogManager::NORMAL_STORE);
+    log_manager.DiscardLastProvisionalStore();
+
+    DummyLogSerializer* serializer = new DummyLogSerializer;
+    log_manager.set_log_serializer(serializer);
+    log_manager.PersistUnsentLogs();
+    EXPECT_EQ(1U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
+  }
+
+  // Ensure that trying to drop more than once is a no-op
+  {
+    MetricsLogManager log_manager;
+    MetricsLogBase* log1 = new MetricsLogBase("id", 0, "version");
+    MetricsLogBase* log2 = new MetricsLogBase("id", 0, "version");
+    log_manager.BeginLoggingWithLog(log1, MetricsLogManager::ONGOING_LOG);
+    log_manager.FinishCurrentLog();
+    log_manager.StageNextLogForUpload();
+    log_manager.StoreStagedLogAsUnsent(MetricsLogManager::NORMAL_STORE);
+    log_manager.BeginLoggingWithLog(log2, MetricsLogManager::ONGOING_LOG);
+    log_manager.FinishCurrentLog();
+    log_manager.StageNextLogForUpload();
+    log_manager.StoreStagedLogAsUnsent(MetricsLogManager::PROVISIONAL_STORE);
+    log_manager.DiscardLastProvisionalStore();
+    log_manager.DiscardLastProvisionalStore();
+
+    DummyLogSerializer* serializer = new DummyLogSerializer;
+    log_manager.set_log_serializer(serializer);
+    log_manager.PersistUnsentLogs();
+    EXPECT_EQ(1U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
+  }
+}
+
+// Test that discarding just the XML log, then the protobuf log, works.
+TEST(MetricsLogManagerTest, DiscardXmlLogFirst) {
+  MetricsLogManager log_manager;
+  EXPECT_FALSE(log_manager.has_staged_log());
+  EXPECT_FALSE(log_manager.has_staged_log_xml());
+  EXPECT_FALSE(log_manager.has_staged_log_proto());
+
+  MetricsLogBase* initial_log = new MetricsLogBase("id", 0, "version");
+  log_manager.BeginLoggingWithLog(initial_log, MetricsLogManager::INITIAL_LOG);
+  log_manager.FinishCurrentLog();
+  EXPECT_FALSE(log_manager.has_staged_log());
+  EXPECT_FALSE(log_manager.has_staged_log_xml());
+  EXPECT_FALSE(log_manager.has_staged_log_proto());
+
+  log_manager.StageNextLogForUpload();
+  EXPECT_TRUE(log_manager.has_staged_log());
+  EXPECT_TRUE(log_manager.has_staged_log_xml());
+  EXPECT_TRUE(log_manager.has_staged_log_proto());
+  EXPECT_FALSE(log_manager.staged_log_text().empty());
+
+  log_manager.DiscardStagedLogXml();
+  EXPECT_TRUE(log_manager.has_staged_log());
+  EXPECT_FALSE(log_manager.has_staged_log_xml());
+  EXPECT_TRUE(log_manager.has_staged_log_proto());
+  EXPECT_FALSE(log_manager.staged_log_text().empty());
+
+  log_manager.DiscardStagedLogProto();
+  EXPECT_FALSE(log_manager.has_staged_log());
+  EXPECT_FALSE(log_manager.has_staged_log_xml());
+  EXPECT_FALSE(log_manager.has_staged_log_proto());
+  EXPECT_TRUE(log_manager.staged_log_text().empty());
+}
+
+// Test that discarding just the protobuf log, then the XML log, works.
+TEST(MetricsLogManagerTest, DiscardProtoLogFirst) {
+  MetricsLogManager log_manager;
+  EXPECT_FALSE(log_manager.has_staged_log());
+  EXPECT_FALSE(log_manager.has_staged_log_xml());
+  EXPECT_FALSE(log_manager.has_staged_log_proto());
+
+  MetricsLogBase* initial_log = new MetricsLogBase("id", 0, "version");
+  log_manager.BeginLoggingWithLog(initial_log, MetricsLogManager::INITIAL_LOG);
+  log_manager.FinishCurrentLog();
+  EXPECT_FALSE(log_manager.has_staged_log());
+  EXPECT_FALSE(log_manager.has_staged_log_xml());
+  EXPECT_FALSE(log_manager.has_staged_log_proto());
+
+  log_manager.StageNextLogForUpload();
+  EXPECT_TRUE(log_manager.has_staged_log());
+  EXPECT_TRUE(log_manager.has_staged_log_xml());
+  EXPECT_TRUE(log_manager.has_staged_log_proto());
+  EXPECT_FALSE(log_manager.staged_log_text().empty());
+
+  log_manager.DiscardStagedLogProto();
+  EXPECT_TRUE(log_manager.has_staged_log());
+  EXPECT_TRUE(log_manager.has_staged_log_xml());
+  EXPECT_FALSE(log_manager.has_staged_log_proto());
+  EXPECT_FALSE(log_manager.staged_log_text().empty());
+
+  log_manager.DiscardStagedLogXml();
+  EXPECT_FALSE(log_manager.has_staged_log());
+  EXPECT_FALSE(log_manager.has_staged_log_xml());
+  EXPECT_FALSE(log_manager.has_staged_log_proto());
+  EXPECT_TRUE(log_manager.staged_log_text().empty());
+}
diff --git a/chrome/common/metrics/metrics_service_base.cc b/chrome/common/metrics/metrics_service_base.cc
new file mode 100644
index 0000000..84410c4
--- /dev/null
+++ b/chrome/common/metrics/metrics_service_base.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/metrics/metrics_service_base.h"
+
+#include <cstdlib>
+
+#include "chrome/common/metrics/metrics_log_base.h"
+
+using base::Histogram;
+
+MetricsServiceBase::MetricsServiceBase()
+    : ALLOW_THIS_IN_INITIALIZER_LIST(histogram_snapshot_manager_(this)) {
+}
+
+MetricsServiceBase::~MetricsServiceBase() {
+}
+
+// static
+const char MetricsServiceBase::kServerUrlXml[] =
+    "https://clients4.google.com/firefox/metrics/collect";
+const char MetricsServiceBase::kServerUrlProto[] =
+    "https://clients4.google.com/uma/v2";
+
+// static
+const char MetricsServiceBase::kMimeTypeXml[] =
+    "application/vnd.mozilla.metrics.bz2";
+// static
+const char MetricsServiceBase::kMimeTypeProto[] =
+    "application/vnd.chrome.uma";
+
+void MetricsServiceBase::RecordCurrentHistograms() {
+  DCHECK(log_manager_.current_log());
+  histogram_snapshot_manager_.PrepareDeltas(base::Histogram::kNoFlags, true);
+}
+
+void MetricsServiceBase::RecordDelta(
+    const base::Histogram& histogram,
+    const base::HistogramSamples& snapshot) {
+  log_manager_.current_log()->RecordHistogramDelta(histogram, snapshot);
+}
+
+void MetricsServiceBase::InconsistencyDetected(
+    Histogram::Inconsistencies problem) {
+  UMA_HISTOGRAM_ENUMERATION("Histogram.InconsistenciesBrowser",
+                            problem, Histogram::NEVER_EXCEEDED_VALUE);
+}
+
+void MetricsServiceBase::UniqueInconsistencyDetected(
+    Histogram::Inconsistencies problem) {
+  UMA_HISTOGRAM_ENUMERATION("Histogram.InconsistenciesBrowserUnique",
+                            problem, Histogram::NEVER_EXCEEDED_VALUE);
+}
+
+void MetricsServiceBase::InconsistencyDetectedInLoggedCount(int amount) {
+  UMA_HISTOGRAM_COUNTS("Histogram.InconsistentSnapshotBrowser",
+                       std::abs(amount));
+}
diff --git a/chrome/common/metrics/metrics_service_base.h b/chrome/common/metrics/metrics_service_base.h
new file mode 100644
index 0000000..fb72377
--- /dev/null
+++ b/chrome/common/metrics/metrics_service_base.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_METRICS_METRICS_SERVICE_BASE_H_
+#define CHROME_COMMON_METRICS_METRICS_SERVICE_BASE_H_
+
+#include "base/basictypes.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_flattener.h"
+#include "base/metrics/histogram_snapshot_manager.h"
+#include "chrome/common/metrics/metrics_log_manager.h"
+
+namespace base {
+class HistogramSamples;
+}  // namespace base
+
+// This class provides base functionality for logging metrics data.
+// TODO(ananta): Factor out more common code from chrome and chrome frame
+// metrics service into this class.
+class MetricsServiceBase : public base::HistogramFlattener {
+ public:
+  // HistogramFlattener interface (override) methods.
+  virtual void RecordDelta(const base::Histogram& histogram,
+                           const base::HistogramSamples& snapshot) OVERRIDE;
+  virtual void InconsistencyDetected(
+      base::Histogram::Inconsistencies problem) OVERRIDE;
+  virtual void UniqueInconsistencyDetected(
+      base::Histogram::Inconsistencies problem) OVERRIDE;
+  virtual void InconsistencyDetectedInLoggedCount(int amount) OVERRIDE;
+
+ protected:
+  MetricsServiceBase();
+  virtual ~MetricsServiceBase();
+
+  // The metrics servers' URLs, for XML and protobuf uploads.
+  static const char kServerUrlXml[];
+  static const char kServerUrlProto[];
+
+  // The MIME types for the uploaded metrics data, for XML and protobuf uploads.
+  static const char kMimeTypeXml[];
+  static const char kMimeTypeProto[];
+
+  // Record complete list of histograms into the current log.
+  // Called when we close a log.
+  void RecordCurrentHistograms();
+
+  // Manager for the various in-flight logs.
+  MetricsLogManager log_manager_;
+
+ private:
+  // |histogram_snapshot_manager_| prepares histogram deltas for transmission.
+  base::HistogramSnapshotManager histogram_snapshot_manager_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsServiceBase);
+};
+
+#endif  // CHROME_COMMON_METRICS_METRICS_SERVICE_BASE_H_
diff --git a/chrome/common/metrics/metrics_util.cc b/chrome/common/metrics/metrics_util.cc
new file mode 100644
index 0000000..775501d
--- /dev/null
+++ b/chrome/common/metrics/metrics_util.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/metrics/metrics_util.h"
+
+#include "base/sha1.h"
+#include "base/sys_byteorder.h"
+
+namespace metrics {
+
+uint32 HashName(const std::string& name) {
+  // SHA-1 is designed to produce a uniformly random spread in its output space,
+  // even for nearly-identical inputs.
+  unsigned char sha1_hash[base::kSHA1Length];
+  base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(name.c_str()),
+                      name.size(),
+                      sha1_hash);
+
+  uint32 bits;
+  COMPILE_ASSERT(sizeof(bits) < sizeof(sha1_hash), need_more_data);
+  memcpy(&bits, sha1_hash, sizeof(bits));
+
+  return base::ByteSwapToLE32(bits);
+}
+
+}  // namespace metrics
diff --git a/chrome/common/metrics/metrics_util.h b/chrome/common/metrics/metrics_util.h
new file mode 100644
index 0000000..306ed09
--- /dev/null
+++ b/chrome/common/metrics/metrics_util.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_METRICS_METRICS_UTIL_H_
+#define CHROME_COMMON_METRICS_METRICS_UTIL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+
+namespace metrics {
+
+// Computes a uint32 hash of a given string based on its SHA1 hash. Suitable for
+// uniquely identifying field trial names and group names.
+uint32 HashName(const std::string& name);
+
+}  // namespace metrics
+
+#endif  // CHROME_COMMON_METRICS_METRICS_UTIL_H_
diff --git a/chrome/common/metrics/metrics_util_unittest.cc b/chrome/common/metrics/metrics_util_unittest.cc
new file mode 100644
index 0000000..7c21dfd
--- /dev/null
+++ b/chrome/common/metrics/metrics_util_unittest.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/metrics/metrics_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+TEST(MetricsUtilTest, HashName) {
+  // Checks that hashing is stable on all platforms.
+  struct {
+    const char* name;
+    uint32 hash_value;
+  } known_hashes[] = {
+    {"a", 937752454u},
+    {"1", 723085877u},
+    {"Trial Name", 2713117220u},
+    {"Group Name", 3201815843u},
+    {"My Favorite Experiment", 3722155194u},
+    {"My Awesome Group Name", 4109503236u},
+    {"abcdefghijklmonpqrstuvwxyz", 787728696u},
+    {"0123456789ABCDEF", 348858318U}
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(known_hashes); ++i)
+    EXPECT_EQ(known_hashes[i].hash_value, HashName(known_hashes[i].name));
+}
+
+}  // namespace metrics
diff --git a/chrome/common/metrics/proto/chrome_experiments.proto b/chrome/common/metrics/proto/chrome_experiments.proto
new file mode 100644
index 0000000..818df39
--- /dev/null
+++ b/chrome/common/metrics/proto/chrome_experiments.proto
@@ -0,0 +1,14 @@
+// Copyright (c) 2012 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.
+//
+// Summary of Chrome variations from experiments.
+
+syntax = "proto2";
+
+package metrics;
+
+message ChromeVariations {
+  // A list of Chrome experiment variation IDs that are active.
+  repeated int32 variation_id = 1;
+}
diff --git a/chrome/common/metrics/proto/chrome_user_metrics_extension.proto b/chrome/common/metrics/proto/chrome_user_metrics_extension.proto
new file mode 100644
index 0000000..b24bb2d
--- /dev/null
+++ b/chrome/common/metrics/proto/chrome_user_metrics_extension.proto
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 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.
+//
+// Protocol buffer for Chrome UMA (User Metrics Analysis).
+
+syntax = "proto2";
+
+package metrics;
+
+import "histogram_event.proto";
+import "omnibox_event.proto";
+import "profiler_event.proto";
+import "system_profile.proto";
+import "user_action_event.proto";
+
+// Next tag: 8
+message ChromeUserMetricsExtension {
+  // The id of the browser installation that generated these events.
+  // Technically, this id is unique to a top-level (one level above the
+  // "Default" directory) Chrome user data directory [1], and so is shared among
+  // all Chrome user profiles contained in this user data directory.
+  // An id of 0 is reserved for test data (monitoring and internal testing) and
+  // should normally be ignored in analysis of the data.
+  // [1] http://www.chromium.org/user-experience/user-data-directory
+  optional fixed64 client_id = 1;
+
+  // The session id for this user.
+  // Values such as tab ids are only meaningful within a particular session.
+  // The client keeps track of the session id and sends it with each event.
+  // The session id is simply an integer that is incremented each time the user
+  // relaunches Chrome.
+  optional int32 session_id = 2;
+
+  // Information about the user's browser and system configuration.
+  optional SystemProfileProto system_profile = 3;
+
+  // This message will log one or more of the following event types:
+  repeated UserActionEventProto user_action_event = 4;
+  repeated OmniboxEventProto omnibox_event = 5;
+  repeated HistogramEventProto histogram_event = 6;
+  repeated ProfilerEventProto profiler_event = 7;
+}
diff --git a/chrome/common/metrics/proto/histogram_event.proto b/chrome/common/metrics/proto/histogram_event.proto
new file mode 100644
index 0000000..0970e24
--- /dev/null
+++ b/chrome/common/metrics/proto/histogram_event.proto
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 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.
+//
+// Histogram-collected metrics.
+
+syntax = "proto2";
+
+package metrics;
+
+// Next tag: 4
+message HistogramEventProto {
+  // The name of the histogram, hashed.
+  optional fixed64 name_hash = 1;
+
+  // The sum of all the sample values.
+  // Together with the total count of the sample values, this allows us to
+  // compute the average value.  The count of all sample values is just the sum
+  // of the counts of all the buckets.
+  optional int64 sum = 2;
+
+  // The per-bucket data.
+  message Bucket {
+    // Each bucket's range is bounded by min <= x < max.
+    // We expect min and max (as well all other fields in this file) to always
+    // be set.  They're marked as optional because that is considered to be good
+    // practice in protocol buffer design, for the sake of
+    // forward-compatibility.
+    optional int64 min = 1;
+    optional int64 max = 2;
+
+    // The bucket's index in the list of buckets, sorted in ascending order.
+    // Historically, we've had trouble with corruption of the min or the max,
+    // most commonly in the client.  The bucket index gives us some redundancy,
+    // which allows the processing code to "vote" on what the correct range
+    // should be for each bucket.  This in turn allows us to better identify and
+    // discard corrupted reports.
+    optional int32 bucket_index = 3;
+
+    // The number of entries in this bucket.
+    optional int64 count = 4;
+  }
+  repeated Bucket bucket = 3;
+}
diff --git a/chrome/common/metrics/proto/omnibox_event.proto b/chrome/common/metrics/proto/omnibox_event.proto
new file mode 100644
index 0000000..bb12aa1
--- /dev/null
+++ b/chrome/common/metrics/proto/omnibox_event.proto
@@ -0,0 +1,150 @@
+// Copyright (c) 2012 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.
+//
+// Stores information about an omnibox interaction.
+
+syntax = "proto2";
+
+package metrics;
+
+// Next tag: 13
+message OmniboxEventProto {
+  // The timestamp for the event, in seconds since the epoch.
+  optional int64 time = 1;
+
+  // The id of the originating tab for this omnibox interaction.
+  // This is the current tab *unless* the user opened the target in a new tab.
+  // In those cases, this is unset.  Tab ids are unique for a given session_id
+  // (in the containing protocol buffer ChromeUserMetricsExtensionsProto).
+  optional int32 tab_id = 2;
+
+  // The number of characters the user had typed before autocompleting.
+  optional int32 typed_length = 3;
+
+  // Whether the user deleted text immediately before selecting an omnibox
+  // suggestion.  This is usually the result of pressing backspace or delete.
+  optional bool just_deleted_text = 11;
+
+  // The number of terms that the user typed in the omnibox.
+  optional int32 num_typed_terms = 4;
+
+  // The index of the item that the user selected in the omnibox popup list.
+  // This corresponds the index of the |suggestion| below.
+  optional int32 selected_index = 5;
+
+  // The length of the inline autocomplete text in the omnibox.
+  // The sum |typed_length| + |completed_length| gives the full length of the
+  // user-visible text in the omnibox.
+  optional int32 completed_length = 6;
+
+  // The amount of time, in milliseconds, since the user first began modifying
+  // the text in the omnibox.  If at some point after modifying the text, the
+  // user reverts the modifications (thus seeing the current web page's URL
+  // again), then writes in the omnibox again, this elapsed time should start
+  // from the time of the second series of modification.
+  optional int64 typing_duration_ms = 7;
+
+  // The type of page currently displayed when the user used the omnibox.
+  enum PageClassification {
+    INVALID_SPEC = 0;   // invalid URI; shouldn't happen
+    NEW_TAB_PAGE = 1;   // chrome://newtab/
+    // Note that chrome://newtab/ doesn't have to be the built-in
+    // version; it could be replaced by an extension.
+    BLANK = 2;          // about:blank
+    HOMEPAGE = 3;       // user switched settings to "open this page" mode.
+    // Note that if the homepage is set to the new tab page or about blank,
+    // then we'll classify the web page into those categories, not HOMEPAGE.
+    OTHER = 4;          // everything else
+  }
+  optional PageClassification current_page_classification = 10;
+
+  // What kind of input the user provided.
+  enum InputType {
+    INVALID = 0;        // Empty input (should not reach here)
+    UNKNOWN = 1;        // Valid input whose type cannot be determined
+    REQUESTED_URL = 2;  // Input autodetected as UNKNOWN, which the user wants
+                        // to treat as an URL by specifying a desired_tld
+    URL = 3;            // Input autodetected as a URL
+    QUERY = 4;          // Input autodetected as a query
+    FORCED_QUERY = 5;   // Input forced to be a query by an initial '?'
+  }
+  optional InputType input_type = 8;
+
+  // An enum used in multiple places below.
+  enum ProviderType {
+    UNKNOWN_PROVIDER = 0;  // Unknown provider (should not reach here)
+    HISTORY_URL = 1;       // URLs in history, or user-typed URLs
+    HISTORY_CONTENTS = 2;  // Matches for page contents of pages in history
+    HISTORY_QUICK = 3;     // Matches for recently or frequently visited pages
+                           // in history
+    SEARCH = 4;            // Search suggestions for the default search engine
+    KEYWORD = 5;           // Keyword-triggered searches
+    BUILTIN = 6;           // Built-in URLs, such as chrome://version
+    SHORTCUTS = 7;         // Recently selected omnibox suggestions
+    EXTENSION_APPS = 8;    // Custom suggestions from extensions and/or apps
+    CONTACT = 9;           // The user's contacts
+    BOOKMARK = 10;         // The user's bookmarks
+  }
+
+  // The result set displayed on the completion popup
+  // Next tag: 6
+  message Suggestion {
+    // Where does this result come from?
+    optional ProviderType provider = 1;
+
+    // What kind of result this is.
+    // This corresponds to the AutocompleteMatch::Type enumeration in
+    // chrome/browser/autocomplete/autocomplete_match.h
+    enum ResultType {
+      UNKNOWN_RESULT_TYPE = 0;    // Unknown type (should not reach here)
+      URL_WHAT_YOU_TYPED = 1;     // The input as a URL
+      HISTORY_URL = 2;            // A past page whose URL contains the input
+      HISTORY_TITLE = 3;          // A past page whose title contains the input
+      HISTORY_BODY = 4;           // A past page whose body contains the input
+      HISTORY_KEYWORD = 5;        // A past page whose keyword contains the
+                                  // input
+      NAVSUGGEST = 6;             // A suggested URL
+      SEARCH_WHAT_YOU_TYPED = 7;  // The input as a search query (with the
+                                  // default engine)
+      SEARCH_HISTORY = 8;         // A past search (with the default engine)
+                                  // containing the input
+      SEARCH_SUGGEST = 9;         // A suggested search (with the default
+                                  // engine)
+      SEARCH_OTHER_ENGINE = 10;   // A search with a non-default engine
+      EXTENSION_APP = 11;         // An Extension App with a title/url that
+                                  // contains the input
+      CONTACT = 12;               // One of the user's contacts
+      BOOKMARK_TITLE = 13;        // A bookmark whose title contains the input.
+    };
+    optional ResultType result_type = 2;
+
+    // The relevance score for this suggestion.
+    optional int32 relevance = 3;
+
+    // How many times this result was typed in / selected from the omnibox.
+    // Only set for some providers and result_types.  At the time of
+    // writing this comment, it is only set for HistoryURL and
+    // HistoryQuickProvider matches.
+    optional int32 typed_count = 5;
+
+    // Whether this item is starred (bookmarked) or not.
+    optional bool is_starred = 4;
+  }
+  repeated Suggestion suggestion = 9;
+
+  // A data structure that holds per-provider information, general information
+  // not associated with a particular result.
+  message ProviderInfo {
+    // Which provider generated this ProviderInfo entry.
+    optional ProviderType provider = 1;
+
+    // The provider's done() value, i.e., whether it's completed processing
+    // the query.  Providers which don't do any asynchronous processing
+    // will always be done.
+    optional bool provider_done = 2;
+  }
+  // A list of diagnostic information about each provider.  Providers
+  // will appear at most once in this list.
+  repeated ProviderInfo provider_info = 12;
+}
diff --git a/chrome/common/metrics/proto/profiler_event.proto b/chrome/common/metrics/proto/profiler_event.proto
new file mode 100644
index 0000000..ea18a61
--- /dev/null
+++ b/chrome/common/metrics/proto/profiler_event.proto
@@ -0,0 +1,90 @@
+// Copyright (c) 2012 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.
+//
+// Performance metrics collected via Chrome's built-in profiler.
+
+syntax = "proto2";
+
+package metrics;
+
+
+// Next tag: 4
+message ProfilerEventProto {
+  // The type of this profile.
+  enum ProfileType {
+    UNKNOWN_PROFILE = 0;  // Unknown type (should not reach here).
+    STARTUP_PROFILE = 1;  // Startup profile, logged approximately 60 seconds
+                          // after launch.
+  }
+  optional ProfileType profile_type = 1;
+
+  // The source based upon which "time" measurements are made.
+  // We currently only measure wall clock time; but we are exploring other
+  // measurement sources as well, such as CPU time or TCMalloc statistics.
+  enum TimeSource {
+    UNKNOWN_TIME_SOURCE = 0;  // Unknown source (should not reach here).
+    WALL_CLOCK_TIME = 1;      // Total time elapsed between the start and end of
+                              // the task's execution.
+  }
+  optional TimeSource time_source = 2;
+
+  // Data for a single tracked object (typically, a Task).
+  message TrackedObject {
+    // The name of the thread from which this task was posted, hashed.
+    optional fixed64 birth_thread_name_hash = 1;
+
+    // The name of the thread on which this task was executed, hashed.
+    optional fixed64 exec_thread_name_hash = 2;
+
+    // The source file name from which this task was posted, hashed.
+    optional fixed64 source_file_name_hash = 3;
+
+    // Function name from which this task was posted, hashed.
+    optional fixed64 source_function_name_hash = 4;
+
+    // The line number within the source file from which this task was posted.
+    optional int32 source_line_number = 5;
+
+    // The number of times this task was executed.
+    optional int32 exec_count = 6;
+
+    // The total execution time for instances this task.
+    optional int32 exec_time_total = 7;
+
+    // The execution time for a uniformly randomly sampled instance of this
+    // task.
+    optional int32 exec_time_sampled = 8;
+
+    // The total time instances this task spent waiting (e.g. in a message loop)
+    // before they were run.
+    optional int32 queue_time_total = 9;
+
+    // The time that a uniformly randomly sampled instance of this task spent
+    // waiting (e.g.  in a message loop) before it was run.
+    optional int32 queue_time_sampled = 10;
+
+    // The type of process within which this task was executed.
+    enum ProcessType {
+      UNKNOWN = 0;  // Should not reach here
+      BROWSER = 1;
+      RENDERER = 2;
+      PLUGIN = 3;
+      WORKER = 4;
+      NACL_LOADER = 5;
+      UTILITY = 6;
+      PROFILE_IMPORT = 7;
+      ZYGOTE = 8;
+      SANDBOX_HELPER = 9;
+      NACL_BROKER = 10;
+      GPU = 11;
+      PPAPI_PLUGIN = 12;
+      PPAPI_BROKER = 13;
+    }
+    optional ProcessType process_type = 11;
+
+    // The local PID for the process within which this task was executed.
+    optional uint32 process_id = 12;
+  }
+  repeated TrackedObject tracked_object = 3;
+}
diff --git a/chrome/common/metrics/proto/system_profile.proto b/chrome/common/metrics/proto/system_profile.proto
new file mode 100644
index 0000000..ff28edb
--- /dev/null
+++ b/chrome/common/metrics/proto/system_profile.proto
@@ -0,0 +1,312 @@
+// Copyright (c) 2012 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.
+//
+// Stores information about the user's brower and system configuration.
+// The system configuration fields are recorded once per client session.
+
+syntax = "proto2";
+
+package metrics;
+
+// Next tag: 13
+message SystemProfileProto {
+  // The time when the client was compiled/linked, in seconds since the epoch.
+  optional int64 build_timestamp = 1;
+
+  // A version number string for the application.
+  // Most commonly this is the browser version number found in a user agent
+  // string, and is typically a 4-tuple of numbers separated by periods.  In
+  // cases where the user agent version might be ambiguous (example: Linux 64-
+  // bit build, rather than 32-bit build, or a Windows version used in some
+  // special context, such as ChromeFrame running in IE), then this may include
+  // some additional postfix to provide clarification not available in the UA
+  // string.
+  //
+  // An example of a browser version 4-tuple is "5.0.322.0".  Currently used
+  // postfixes are:
+  //
+  //   "-64": a 64-bit build
+  //   "-F": Chrome is running under control of ChromeFrame
+  //   "-devel": this is not an official build of Chrome
+  //
+  // A full version number string could look similar to:
+  // "5.0.322.0-F-devel".
+  //
+  // This value, when available, is more trustworthy than the UA string
+  // associated with the request; and including the postfix, may be more
+  // specific.
+  optional string app_version = 2;
+
+  // The possible channels for an installation, from least to most stable.
+  enum Channel {
+    CHANNEL_UNKNOWN = 0;  // Unknown channel -- perhaps an unofficial build?
+    CHANNEL_CANARY = 1;
+    CHANNEL_DEV = 2;
+    CHANNEL_BETA = 3;
+    CHANNEL_STABLE = 4;
+  }
+  optional Channel channel = 10;
+
+  // The date the user enabled UMA, in seconds since the epoch.
+  // If the user has toggled the UMA enabled state multiple times, this will
+  // be the most recent date on which UMA was enabled.
+  optional int64 uma_enabled_date = 3;
+
+  // The user's selected application locale, i.e. the user interface language.
+  // The locale includes a language code and, possibly, also a country code,
+  // e.g. "en-US".
+  optional string application_locale = 4;
+
+  // Information on the user's operating system.
+  message OS {
+    // The user's operating system.
+    optional string name = 1;
+
+    // The version of the OS.  The meaning of this field is OS-dependent.
+    optional string version = 2;
+
+    // The fingerprint of the build.  This field is used only on Android.
+    optional string fingerprint = 3;
+  }
+  optional OS os = 5;
+
+  // Information on the user's hardware.
+  message Hardware {
+    // The CPU architecture (x86, PowerPC, x86_64, ...)
+    optional string cpu_architecture = 1;
+
+    // The amount of RAM present on the system, in megabytes.
+    optional int64 system_ram_mb = 2;
+
+    // The base memory address that chrome.dll was loaded at.
+    // (Logged only on Windows.)
+    optional int64 dll_base = 3;
+
+    // The Chrome OS device hardware class ID is a unique string associated with
+    // each Chrome OS device product revision generally assigned at hardware
+    // qualification time.  The hardware class effectively identifies the
+    // configured system components such as CPU, WiFi adapter, etc.
+    //
+    // An example of such a hardware class is "IEC MARIO PONY 6101".  An
+    // internal database associates this hardware class with the qualified
+    // device specifications including OEM information, schematics, hardware
+    // qualification reports, test device tags, etc.
+    optional string hardware_class = 4;
+
+    // The number of physical screens.
+    optional int32 screen_count = 5;
+
+    // The screen dimensions of the primary screen, in pixels.
+    optional int32 primary_screen_width = 6;
+    optional int32 primary_screen_height = 7;
+
+    // The device scale factor of the primary screen.
+    optional float primary_screen_scale_factor = 12;
+
+    // Information on the GPU
+    message Graphics {
+      // The GPU manufacturer's vendor id.
+      optional uint32 vendor_id = 1;
+
+      // The GPU manufacturer's device id for the chip set.
+      optional uint32 device_id = 2;
+
+      // The driver version on the GPU.
+      optional string driver_version = 3;
+
+      // The driver date on the GPU.
+      optional string driver_date = 4;
+
+      // The GPU performance statistics.
+      // See http://src.chromium.org/viewvc/chrome/trunk/src/content/public/common/gpu_performance_stats.h?view=markup
+      // for details.  Currently logged only on Windows.
+      message PerformanceStatistics {
+        optional float graphics_score = 1;
+        optional float gaming_score = 2;
+        optional float overall_score = 3;
+      }
+      optional PerformanceStatistics performance_statistics = 5;
+    }
+    optional Graphics gpu = 8;
+  }
+  optional Hardware hardware = 6;
+
+  // Information on the Google Update install that is managing this client.
+  message GoogleUpdate {
+    // Whether the Google Update install is system-level or user-level.
+    optional bool is_system_install = 1;
+
+    // The date at which Google Update last started performing an automatic
+    // update check, in seconds since the Unix epoch.
+    optional int64 last_automatic_start_timestamp = 2;
+
+    // The date at which Google Update last successfully sent an update check
+    // and recieved an intact response from the server, in seconds since the
+    // Unix epoch. (The updates don't need to be successfully installed.)
+    optional int64 last_update_check_timestamp = 3;
+
+    // Describes a product being managed by Google Update. (This can also
+    // describe Google Update itself.)
+    message ProductInfo {
+      // The current version of the product that is installed.
+      optional string version = 1;
+
+      // The date at which Google Update successfully updated this product,
+      // stored in seconds since the Unix epoch.  This is updated when an update
+      // is successfully applied, or if the server reports that no update
+      // is available.
+      optional int64 last_update_success_timestamp = 2;
+
+      // The result reported by the product updater on its last run.
+      enum InstallResult {
+        INSTALL_RESULT_SUCCESS = 0;
+        INSTALL_RESULT_FAILED_CUSTOM_ERROR = 1;
+        INSTALL_RESULT_FAILED_MSI_ERROR = 2;
+        INSTALL_RESULT_FAILED_SYSTEM_ERROR = 3;
+        INSTALL_RESULT_EXIT_CODE = 4;
+      }
+      optional InstallResult last_result = 3;
+
+      // The error code reported by the product updater on its last run.  This
+      // will typically be a error code specific to the product installer.
+      optional int32 last_error = 4;
+
+      // The extra error code reported by the product updater on its last run.
+      // This will typically be a Win32 error code.
+      optional int32 last_extra_error = 5;
+    }
+    optional ProductInfo google_update_status = 4;
+    optional ProductInfo client_status = 5;
+  }
+  optional GoogleUpdate google_update = 11;
+
+  // Information on all installed plugins.
+  message Plugin {
+    // The plugin's self-reported name and filename (without path).
+    optional string name = 1;
+    optional string filename = 2;
+
+    // The plugin's version.
+    optional string version = 3;
+
+    // True if the plugin is disabled.
+    // If a client has multiple local Chrome user accounts, this is logged based
+    // on the first user account launched during the current session.
+    optional bool is_disabled = 4;
+  }
+  repeated Plugin plugin = 7;
+
+  // Figures that can be used to generate application stability metrics.
+  // All values are counts of events since the last time that these
+  // values were reported.
+  message Stability {
+    // Total amount of time that the program was running, in seconds.
+    optional int64 uptime_sec = 1;
+
+    // Page loads along with renderer crashes and hangs, since page load count
+    // roughly corresponds to usage.
+    optional int32 page_load_count = 2;
+    optional int32 renderer_crash_count = 3;
+    optional int32 renderer_hang_count = 4;
+
+    // Number of renderer crashes that were for extensions.
+    // TODO(isherman): Figure out whether this is also counted in
+    // |renderer_crash_count|.
+    optional int32 extension_renderer_crash_count = 5;
+
+    // Number of non-renderer child process crashes.
+    optional int32 child_process_crash_count = 6;
+
+    // Number of times the browser has crashed while logged in as the "other
+    // user" (guest) account.
+    // Logged on ChromeOS only.
+    optional int32 other_user_crash_count = 7;
+
+    // Number of times the kernel has crashed.
+    // Logged on ChromeOS only.
+    optional int32 kernel_crash_count = 8;
+
+    // Number of times the system has shut down uncleanly.
+    // Logged on ChromeOS only.
+    optional int32 unclean_system_shutdown_count = 9;
+
+    //
+    // All the remaining fields in the Stability are recorded at most once per
+    // client session.
+    //
+
+    // The number of times the program was launched.
+    // This will typically be equal to 1.  However, it is possible that Chrome
+    // was unable to upload stability metrics for previous launches (e.g. due to
+    // crashing early during startup), and hence this value might be greater
+    // than 1.
+    optional int32 launch_count = 15;
+    // The number of times that it didn't exit cleanly (which we assume to be
+    // mostly crashes).
+    optional int32 crash_count = 16;
+
+    // The number of times the program began, but did not complete, the shutdown
+    // process.  (For example, this may occur when Windows is shutting down, and
+    // it only gives the process a few seconds to clean up.)
+    optional int32 incomplete_shutdown_count = 17;
+
+    // The number of times the program was able register with breakpad crash
+    // services.
+    optional int32 breakpad_registration_success_count = 18;
+
+    // The number of times the program failed to register with breakpad crash
+    // services.  If crash registration fails then when the program crashes no
+    // crash report will be generated.
+    optional int32 breakpad_registration_failure_count = 19;
+
+    // The number of times the program has run under a debugger.  This should
+    // be an exceptional condition.  Running under a debugger prevents crash
+    // dumps from being generated.
+    optional int32 debugger_present_count = 20;
+
+    // The number of times the program has run without a debugger attached.
+    // This should be most common scenario and should be very close to
+    // |launch_count|.
+    optional int32 debugger_not_present_count = 21;
+
+    // Stability information for all installed plugins.
+    message PluginStability {
+      // The relevant plugin's information (name, etc.)
+      optional Plugin plugin = 1;
+
+      // The number of times this plugin's process was launched.
+      optional int32 launch_count = 2;
+
+      // The number of times this plugin was instantiated on a web page.
+      // This will be >= |launch_count|.
+      // (A page load with multiple sections drawn by this plugin will
+      // increase this count multiple times.)
+      optional int32 instance_count = 3;
+
+      // The number of times this plugin process crashed.
+      // This value will be <= |launch_count|.
+      optional int32 crash_count = 4;
+
+      // The number of times this plugin could not be loaded.
+      optional int32 loading_error_count = 5;
+    }
+    repeated PluginStability plugin_stability = 22;
+  }
+  optional Stability stability = 8;
+
+  // Description of a field trial or experiment that the user is currently
+  // enrolled in.
+  // All metrics reported in this upload can potentially be influenced by the
+  // field trial.
+  message FieldTrial {
+    // The name of the field trial, as a 32-bit identifier.
+    // Currently, the identifier is a hash of the field trial's name.
+    optional fixed32 name_id = 1;
+
+    // The user's group within the field trial, as a 32-bit identifier.
+    // Currently, the identifier is a hash of the group's name.
+    optional fixed32 group_id = 2;
+  }
+  repeated FieldTrial field_trial = 9;
+}
diff --git a/chrome/common/metrics/proto/user_action_event.proto b/chrome/common/metrics/proto/user_action_event.proto
new file mode 100644
index 0000000..9801a07
--- /dev/null
+++ b/chrome/common/metrics/proto/user_action_event.proto
@@ -0,0 +1,19 @@
+// Copyright (c) 2012 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.
+//
+// Stores information about an event that occurs in response to a user action,
+// e.g. an interaction with a browser UI element.
+
+syntax = "proto2";
+
+package metrics;
+
+// Next tag: 3
+message UserActionEventProto {
+  // The name of the action, hashed.
+  optional fixed64 name_hash = 1;
+
+  // The timestamp for the event, in seconds since the epoch.
+  optional int64 time = 2;
+}
diff --git a/chrome/common/metrics/variations/OWNERS b/chrome/common/metrics/variations/OWNERS
new file mode 100644
index 0000000..9e09a4b
--- /dev/null
+++ b/chrome/common/metrics/variations/OWNERS
@@ -0,0 +1,2 @@
+asvitkine@chromium.org
+stevet@chromium.org
diff --git a/chrome/common/metrics/variations/variation_ids.h b/chrome/common/metrics/variations/variation_ids.h
new file mode 100644
index 0000000..e2f33e9
--- /dev/null
+++ b/chrome/common/metrics/variations/variation_ids.h
@@ -0,0 +1,153 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_METRICS_VARIATIONS_VARIATION_IDS_H_
+#define CHROME_COMMON_METRICS_VARIATIONS_VARIATION_IDS_H_
+
+namespace chrome_variations {
+
+// A list of Chrome Variation IDs. These IDs are associated with FieldTrials
+// for re-identification and analysis on Google servers.
+// These enums are to be used with the experiments_helper ID associoation API.
+//
+// The IDs are defined as part of an enum to prevent re-use. When adding your
+// own IDs, please respect the reserved IDs of other groups, as well as the
+// global range of permitted values.
+//
+// When you want to create a FieldTrial that needs to be recognized by Google
+// properties, reserve an ID by declaring them below. Please start with the name
+// of the FieldTrial followed a short description.
+//
+// Ex:
+// // Name: Instant-Field-Trial
+// // The Omnibox Instant Trial.
+// kInstantTrialOn  = 3300123,
+// kInstantTrialOff = 3300124,
+//
+// If you programatically generate FieldTrials, you can still use a loop to
+// create your IDs. Just be sure to reserve the range of IDs here with a clear
+// comment.
+//
+// Ex:
+// // Name: UMA-Uniformity-Trial-5-Percent
+// // Range: 330000 - 3300099
+// // The 5% Uniformity Trial. This is a reserved range.
+// kUniformityTrial5PercentStart = 330000,
+// kUniformirtTrial5PercentEnd   = 330099,
+//
+// Anything within the range of a uint32 should be castable to an ID, but
+// please ensure that they are within the range of the min and max values.
+enum VariationID {
+  // Used to represent no associated Chrome variation ID.
+  kEmptyID = 0,
+
+  // The smallest possible Chrome Variation ID in the reserved range. The
+  // first 10,000 values are reserved for internal variations infrastructure
+  // use. Please do not use values in this range.
+  kMinimumID = 3300000,
+
+  // Name: UMA-Uniformity-Trial-1-Percent
+  // Range: 3300000 - 3300099
+  kUniformity1PercentBase   = kMinimumID,
+  kUniformity1PercentLimit  = kUniformity1PercentBase + 100,
+  // Name: UMA-Uniformity-Trial-5-Percent
+  // Range: 3300100 - 3300119
+  kUniformity5PercentBase   = kUniformity1PercentLimit,
+  kUniformity5PercentLimit  = kUniformity5PercentBase + 20,
+  // Name: UMA-Uniformity-Trial-10-Percent
+  // Range: 3300120 - 3300129
+  kUniformity10PercentBase  = kUniformity5PercentLimit,
+  kUniformity10PercentLimit = kUniformity10PercentBase + 10,
+  // Name: UMA-Uniformity-Trial-20-Percent
+  // Range: 3300130 - 3300134
+  kUniformity20PercentBase  = kUniformity10PercentLimit,
+  kUniformity20PercentLimit = kUniformity20PercentBase + 5,
+  // Name: UMA-Uniformity-Trial-50-Percent
+  // Range: 3300135 - 3300136
+  kUniformity50PercentBase  = kUniformity20PercentLimit,
+  kUniformity50PercentLimit = kUniformity50PercentBase + 2,
+
+  // Name: UMA-Dynamic-Binary-Uniformity-Trial
+  // The dynamic uniformity trial is only specified on the server, this is just
+  // to reserve the id.
+  kDynamicUniformityDefault = 3300137,
+  kDynamicUniformityGroup01 = 3300138,
+
+  // Name: UMA-Session-Randomized-Uniformity-Trial-5-Percent
+  // Range: 3300139 - 3300158
+  // A uniformity trial used to compare one-time-randomized and
+  // session-randomized FieldTrials.
+  kUniformitySessionRandomized5PercentBase  = 3300139,
+  kUniformitySessionRandomized5PercentLimit =
+      kUniformitySessionRandomized5PercentBase + 20,
+
+  kUniformityTrialsMax      = 3300158,
+
+  // Some values reserved for unit and integration tests.
+  kTestValueA = 3300200,
+  kTestValueB = 3300201,
+
+  // USABLE IDs BEGIN HERE.
+  //
+  // The smallest possible Chrome Variation ID for use in real FieldTrials. If
+  // you are defining variation IDs for your own FieldTrials, NEVER use a value
+  // lower than this.
+  kMinimumUserID = 3310000,
+
+  // Add new variation IDs below.
+
+  // Name: OmniboxSearchSuggest
+  // Range: 3310000 - 3310019
+  // Suggest (Autocomplete) field trial, 20 IDs.
+  // Now retired.  But please don't reuse these IDs; they may taint
+  // your experiment results.
+  kSuggestIDMin = 3310000,
+  kSuggestIDMax = 3310019,
+
+  // Instant field trial.
+  kInstantIDControl = 3310020,
+  kInstantIDSilent  = 3310021,
+  kInstantIDHidden  = 3310022,
+  kInstantIDSuggest = 3310023,
+  kInstantIDInstant = 3310024,
+
+  // Instant dummy field trial.
+  kDummyInstantIDDefault         = 3310025,
+  kDummyInstantIDControl         = 3310026,
+  kDummyInstantIDExperimentOne   = 3310027,
+  kDummyInstantIDExperimentTwo   = 3310028,
+  kDummyInstantIDExperimentThree = 3310049,
+
+  // Name: OmniboxSearchSuggestStarted2012Q4
+  // Range: 3310029 - 3310048
+  // Suggest (Autocomplete) field trial, 20 IDs.  This differs from
+  // the earlier omnibox suggest field trial in this file because
+  // we created a new trial (with a new name) in order to shuffle IDs.
+  // We assign new experiment IDs because it's a good practice not to
+  // reuse experiment IDs.
+  kSuggestTrialStarted2012Q4IDMin = 3310029,
+  kSuggestTrialStarted2012Q4IDMax = 3310048,
+
+  // Name: Instant channel field trial.
+  // Range: 3310050 - 3310059
+  kChannelInstantIDBeta   = 3310050,
+  kChannelInstantIDDev    = 3310051,
+  kChannelInstantIDStable = 3310052,
+
+  // NEXT ID: When adding new IDs, please add them above this section, starting
+  // with the value of kNextID, and updating kNextID to (end of your reserved
+  // range) + 1.
+  kNextID = 3310060,
+
+
+  // USABLE IDs END HERE.
+  //
+  // The largest possible Chrome variation ID in the reserved range. When
+  // defining your variation IDs, DO NOT exceed this value.
+  kMaximumID = 3399999,
+};
+
+}  // namespace chrome_variations
+
+#endif  // CHROME_COMMON_METRICS_VARIATIONS_VARIATION_IDS_H_
diff --git a/chrome/common/metrics/variations/variations_util.cc b/chrome/common/metrics/variations/variations_util.cc
new file mode 100644
index 0000000..67e4406
--- /dev/null
+++ b/chrome/common/metrics/variations/variations_util.cc
@@ -0,0 +1,168 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/metrics/variations/variations_util.h"
+
+#include <map>
+#include <vector>
+
+#include "base/memory/singleton.h"
+#include "base/sha1.h"
+#include "base/string16.h"
+#include "base/stringprintf.h"
+#include "base/sys_byteorder.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/child_process_logging.h"
+#include "chrome/common/metrics/metrics_util.h"
+#include "chrome/common/metrics/variations/variation_ids.h"
+
+namespace chrome_variations {
+
+namespace {
+
+// The internal singleton accessor for the map, used to keep it thread-safe.
+class GroupMapAccessor {
+ public:
+  // Retrieve the singleton.
+  static GroupMapAccessor* GetInstance() {
+    return Singleton<GroupMapAccessor>::get();
+  }
+
+  GroupMapAccessor() {}
+  ~GroupMapAccessor() {}
+
+  // Note that this normally only sets the ID for a group the first time, unless
+  // |force| is set to true, in which case it will always override it.
+  void AssociateID(const ActiveGroupId& group_identifier,
+                   const VariationID id,
+                   const bool force) {
+    base::AutoLock scoped_lock(lock_);
+    if (force ||
+        group_to_id_map_.find(group_identifier) == group_to_id_map_.end())
+      group_to_id_map_[group_identifier] = id;
+  }
+
+  VariationID GetID(const ActiveGroupId& group_identifier) {
+    base::AutoLock scoped_lock(lock_);
+    GroupToIDMap::const_iterator it = group_to_id_map_.find(group_identifier);
+    if (it == group_to_id_map_.end())
+      return kEmptyID;
+    return it->second;
+  }
+
+ private:
+  typedef std::map<ActiveGroupId, VariationID, ActiveGroupIdCompare>
+      GroupToIDMap;
+
+  base::Lock lock_;
+  GroupToIDMap group_to_id_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(GroupMapAccessor);
+};
+
+ActiveGroupId MakeActiveGroupId(const std::string& trial_name,
+                                const std::string& group_name) {
+  ActiveGroupId id;
+  id.name = metrics::HashName(trial_name);
+  id.group = metrics::HashName(group_name);
+  return id;
+}
+
+// Populates |name_group_ids| based on |active_groups|.
+void GetFieldTrialActiveGroupIdsForActiveGroups(
+    const base::FieldTrial::ActiveGroups& active_groups,
+    std::vector<ActiveGroupId>* name_group_ids) {
+  DCHECK(name_group_ids->empty());
+  for (base::FieldTrial::ActiveGroups::const_iterator it =
+       active_groups.begin(); it != active_groups.end(); ++it) {
+    name_group_ids->push_back(MakeActiveGroupId(it->trial, it->group));
+  }
+}
+
+}  // namespace
+
+void GetFieldTrialActiveGroupIds(
+    std::vector<ActiveGroupId>* name_group_ids) {
+  DCHECK(name_group_ids->empty());
+  // A note on thread safety: Since GetActiveFieldTrialGroups() is thread
+  // safe, and we operate on a separate list of that data, this function is
+  // technically thread safe as well, with respect to the FieldTriaList data.
+  base::FieldTrial::ActiveGroups active_groups;
+  base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
+  GetFieldTrialActiveGroupIdsForActiveGroups(active_groups,
+                                             name_group_ids);
+}
+
+void GetFieldTrialActiveGroupIdsAsStrings(
+    std::vector<string16>* output) {
+  DCHECK(output->empty());
+  std::vector<ActiveGroupId> name_group_ids;
+  GetFieldTrialActiveGroupIds(&name_group_ids);
+  for (size_t i = 0; i < name_group_ids.size(); ++i) {
+    output->push_back(UTF8ToUTF16(base::StringPrintf(
+        "%x-%x", name_group_ids[i].name, name_group_ids[i].group)));
+  }
+}
+
+void AssociateGoogleVariationID(const std::string& trial_name,
+                                const std::string& group_name,
+                                chrome_variations::VariationID id) {
+  GroupMapAccessor::GetInstance()->AssociateID(
+      MakeActiveGroupId(trial_name, group_name), id, false);
+}
+
+void AssociateGoogleVariationIDForce(const std::string& trial_name,
+                                     const std::string& group_name,
+                                     chrome_variations::VariationID id) {
+  GroupMapAccessor::GetInstance()->AssociateID(
+      MakeActiveGroupId(trial_name, group_name), id, true);
+}
+
+chrome_variations::VariationID GetGoogleVariationID(
+    const std::string& trial_name,
+    const std::string& group_name) {
+  return GroupMapAccessor::GetInstance()->GetID(
+      MakeActiveGroupId(trial_name, group_name));
+}
+
+void GenerateVariationChunks(const std::vector<string16>& experiments,
+                             std::vector<string16>* chunks) {
+  string16 current_chunk;
+  for (size_t i = 0; i < experiments.size(); ++i) {
+    const size_t needed_length =
+        (current_chunk.empty() ? 1 : 0) + experiments[i].length();
+    if (current_chunk.length() + needed_length > kMaxVariationChunkSize) {
+      chunks->push_back(current_chunk);
+      current_chunk = experiments[i];
+    } else {
+      if (!current_chunk.empty())
+        current_chunk.push_back(',');
+      current_chunk += experiments[i];
+    }
+  }
+  if (!current_chunk.empty())
+    chunks->push_back(current_chunk);
+}
+
+void SetChildProcessLoggingVariationList() {
+  std::vector<string16> experiment_strings;
+  GetFieldTrialActiveGroupIdsAsStrings(&experiment_strings);
+  child_process_logging::SetExperimentList(experiment_strings);
+}
+
+// Functions below are exposed for testing explicitly behind this namespace.
+// They simply wrap existing functions in this file.
+namespace testing {
+
+void TestGetFieldTrialActiveGroupIds(
+    const base::FieldTrial::ActiveGroups& active_groups,
+    std::vector<ActiveGroupId>* name_group_ids) {
+  GetFieldTrialActiveGroupIdsForActiveGroups(active_groups,
+                                             name_group_ids);
+}
+
+}  // namespace testing
+
+}  // namespace chrome_variations
+
diff --git a/chrome/common/metrics/variations/variations_util.h b/chrome/common/metrics/variations/variations_util.h
new file mode 100644
index 0000000..46a93a0
--- /dev/null
+++ b/chrome/common/metrics/variations/variations_util.h
@@ -0,0 +1,131 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_METRICS_VARIATIONS_VARIATIONS_UTIL_H_
+#define CHROME_COMMON_METRICS_VARIATIONS_VARIATIONS_UTIL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/metrics/field_trial.h"
+#include "base/string16.h"
+#include "chrome/common/metrics/variations/variation_ids.h"
+
+// This namespace provides various helpers that extend the functionality around
+// base::FieldTrial.
+//
+// This includes a simple API used to handle getting and setting
+// data related to Google-specific variations in the browser. This is meant to
+// be an extension to the base::FieldTrial for Google-specific functionality.
+//
+// These calls are meant to be made directly after appending all your groups to
+// a FieldTrial (for associating IDs) and any time after the group selection has
+// been done (for retrieving IDs).
+//
+// Typical usage looks like this:
+//
+// // Set up your trial and groups as usual.
+// FieldTrial* trial = FieldTrialList::FactoryGetFieldTrial(
+//     "trial", 1000, "default", 2012, 12, 31, NULL);
+// const int kHighMemGroup = trial->AppendGroup("HighMem", 20);
+// const int kLowMemGroup = trial->AppendGroup("LowMem", 20);
+// // All groups are now created. We want to associate
+// // chrome_variation::VariationIDs with them, so do that now.
+// AssociateGoogleVariationID("trial", "default", chrome_variations::kValueA);
+// AssociateGoogleVariationID("trial", "HighMem", chrome_variations::kValueB);
+// AssociateGoogleVariationID("trial", "LowMem",  chrome_variations::kValueC);
+//
+// // Elsewhere, we are interested in retrieving the VariationID associated
+// // with |trial|.
+// chrome_variations::VariationID id =
+//     GetGoogleVariationID(trial->name(), trial->group_name());
+// // Do stuff with |id|...
+//
+// The AssociateGoogleVariationID and GetGoogleVariationID API methods are
+// thread safe.
+
+namespace chrome_variations {
+
+// The Unique ID of a trial and its active group, where the name and group
+// identifiers are hashes of the trial and group name strings.
+struct ActiveGroupId {
+  uint32 name;
+  uint32 group;
+};
+
+// We need to supply a Compare class for templates since ActiveGroupId is a
+// user-defined type.
+struct ActiveGroupIdCompare {
+  bool operator() (const ActiveGroupId& lhs,
+                   const ActiveGroupId& rhs) const {
+    // The group and name fields are just SHA-1 Hashes, so we just need to treat
+    // them as IDs and do a less-than comparison. We test group first, since
+    // name is more likely to collide.
+    if (lhs.group != rhs.group)
+      return lhs.group < rhs.group;
+    return lhs.name < rhs.name;
+  }
+};
+
+// Fills the supplied vector |name_group_ids| (which must be empty when called)
+// with unique ActiveGroupIds for each Field Trial that has a chosen group.
+// Field Trials for which a group has not been chosen yet are NOT returned in
+// this list.
+void GetFieldTrialActiveGroupIds(
+    std::vector<ActiveGroupId>* name_group_ids);
+
+// Fills the supplied vector |output| (which must be empty when called) with
+// unique string representations of ActiveGroupIds for each Field Trial that
+// has a chosen group. The strings are formatted as "<TrialName>-<GroupName>",
+// with the names as hex strings. Field Trials for which a group has not been
+// chosen yet are NOT returned in this list.
+void GetFieldTrialActiveGroupIdsAsStrings(std::vector<string16>* output);
+
+// Associate a chrome_variations::VariationID value with a FieldTrial group. If
+// an id was previously set for |trial_name| and |group_name|, this does
+// nothing. The group is denoted by |trial_name| and |group_name|. This must be
+// called whenever you prepare a FieldTrial (create the trial and append groups)
+// that needs to have a chrome_variations::VariationID associated with it so
+// Google servers can recognize the FieldTrial.
+void AssociateGoogleVariationID(const std::string& trial_name,
+                                const std::string& group_name,
+                                chrome_variations::VariationID id);
+
+// As above, but overwrites any previously set id.
+void AssociateGoogleVariationIDForce(const std::string& trial_name,
+                                     const std::string& group_name,
+                                     chrome_variations::VariationID id);
+
+// Retrieve the chrome_variations::VariationID associated with a FieldTrial
+// group. The group is denoted by |trial_name| and |group_name|. This will
+// return chrome_variations::kEmptyID if there is currently no associated ID
+// for the named group. This API can be nicely combined with
+// FieldTrial::GetActiveFieldTrialGroups() to enumerate the variation IDs for
+// all active FieldTrial groups.
+chrome_variations::VariationID GetGoogleVariationID(
+    const std::string& trial_name,
+    const std::string& group_name);
+
+// Generates variation chunks from |variation_strings| that are suitable for
+// crash reporting.
+void GenerateVariationChunks(const std::vector<string16>& variation_strings,
+                             std::vector<string16>* chunks);
+
+// Get the current set of chosen FieldTrial groups (aka variations) and send
+// them to the child process logging module so it can save it for crash dumps.
+void SetChildProcessLoggingVariationList();
+
+// Expose some functions for testing. These functions just wrap functionality
+// that is implemented above.
+namespace testing {
+
+void TestGetFieldTrialActiveGroupIds(
+    const base::FieldTrial::ActiveGroups& active_groups,
+    std::vector<ActiveGroupId>* name_group_ids);
+
+}  // namespace testing
+
+}  // namespace chrome_variations
+
+#endif  // CHROME_COMMON_METRICS_VARIATIONS_VARIATIONS_UTIL_H_
diff --git a/chrome/common/metrics/variations/variations_util_unittest.cc b/chrome/common/metrics/variations/variations_util_unittest.cc
new file mode 100644
index 0000000..8c3e09c
--- /dev/null
+++ b/chrome/common/metrics/variations/variations_util_unittest.cc
@@ -0,0 +1,242 @@
+// Copyright (c) 2012 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.
+
+// Tests for the Variations Helpers.
+
+#include <set>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/metrics/field_trial.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/metrics/metrics_util.h"
+#include "chrome/common/metrics/variations/variations_util.h"
+#include "content/public/test/test_browser_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_variations {
+
+namespace {
+
+// Convenience helper to retrieve the chrome_variations::VariationID for a
+// FieldTrial. Note that this will do the group assignment in |trial| if not
+// already done.
+VariationID GetIDForTrial(base::FieldTrial* trial) {
+  return GetGoogleVariationID(trial->name(), trial->group_name());
+}
+
+}  // namespace
+
+class VariationsUtilTest : public ::testing::Test {
+ public:
+  VariationsUtilTest() : field_trial_list_(NULL) {
+    // Since the API can only be called on the UI thread, we have to fake that
+    // we're on it.
+    ui_thread_.reset(new content::TestBrowserThread(
+        content::BrowserThread::UI, &message_loop_));
+
+    base::Time now = base::Time::NowFromSystemTime();
+    base::TimeDelta one_year = base::TimeDelta::FromDays(365);
+    base::Time::Exploded exploded;
+
+    base::Time next_year_time = now + one_year;
+    next_year_time.LocalExplode(&exploded);
+    next_year_ = exploded.year;
+  }
+
+ protected:
+  int next_year_;
+
+ private:
+  base::FieldTrialList field_trial_list_;
+  MessageLoop message_loop_;
+  scoped_ptr<content::TestBrowserThread> ui_thread_;
+};
+
+TEST_F(VariationsUtilTest, GetFieldTrialActiveGroups) {
+  typedef std::set<ActiveGroupId, ActiveGroupIdCompare> ActiveGroupIdSet;
+  std::string trial_one("trial one");
+  std::string group_one("group one");
+  std::string trial_two("trial two");
+  std::string group_two("group two");
+
+  base::FieldTrial::ActiveGroups active_groups;
+  base::FieldTrial::ActiveGroup active_group;
+  active_group.trial = trial_one;
+  active_group.group = group_one;
+  active_groups.push_back(active_group);
+
+  active_group.trial = trial_two;
+  active_group.group = group_two;
+  active_groups.push_back(active_group);
+
+  // Create our expected groups of IDs.
+  ActiveGroupIdSet expected_groups;
+  ActiveGroupId name_group_id;
+  name_group_id.name = metrics::HashName(trial_one);
+  name_group_id.group = metrics::HashName(group_one);
+  expected_groups.insert(name_group_id);
+  name_group_id.name = metrics::HashName(trial_two);
+  name_group_id.group = metrics::HashName(group_two);
+  expected_groups.insert(name_group_id);
+
+  std::vector<ActiveGroupId> active_group_ids;
+  testing::TestGetFieldTrialActiveGroupIds(active_groups, &active_group_ids);
+  EXPECT_EQ(2U, active_group_ids.size());
+  for (size_t i = 0; i < active_group_ids.size(); ++i) {
+    ActiveGroupIdSet::iterator expected_group =
+        expected_groups.find(active_group_ids[i]);
+    EXPECT_FALSE(expected_group == expected_groups.end());
+    expected_groups.erase(expected_group);
+  }
+  EXPECT_EQ(0U, expected_groups.size());
+}
+
+// Test that if the trial is immediately disabled, GetGoogleVariationID just
+// returns the empty ID.
+TEST_F(VariationsUtilTest, DisableImmediately) {
+  int default_group_number = -1;
+  scoped_refptr<base::FieldTrial> trial(
+      base::FieldTrialList::FactoryGetFieldTrial("trial", 100, "default",
+                                                 next_year_, 12, 12,
+                                                 &default_group_number));
+  trial->Disable();
+
+  ASSERT_EQ(default_group_number, trial->group());
+  ASSERT_EQ(kEmptyID, GetIDForTrial(trial.get()));
+}
+
+// Test that successfully associating the FieldTrial with some ID, and then
+// disabling the FieldTrial actually makes GetGoogleVariationID correctly
+// return the empty ID.
+TEST_F(VariationsUtilTest, DisableAfterInitialization) {
+  const std::string default_name = "default";
+  const std::string non_default_name = "non_default";
+
+  scoped_refptr<base::FieldTrial> trial(
+      base::FieldTrialList::FactoryGetFieldTrial("trial", 100, default_name,
+                                                 next_year_, 12, 12, NULL));
+  trial->AppendGroup(non_default_name, 100);
+  AssociateGoogleVariationID(trial->name(), default_name, kTestValueA);
+  AssociateGoogleVariationID(trial->name(), non_default_name, kTestValueB);
+  trial->Disable();
+  ASSERT_EQ(default_name, trial->group_name());
+  ASSERT_EQ(kTestValueA, GetIDForTrial(trial.get()));
+}
+
+// Test various successful association cases.
+TEST_F(VariationsUtilTest, AssociateGoogleVariationID) {
+  const std::string default_name1 = "default1";
+  scoped_refptr<base::FieldTrial> trial_true(
+      base::FieldTrialList::FactoryGetFieldTrial("d1", 10, default_name1,
+                                                 next_year_, 12, 31, NULL));
+  const std::string winner = "TheWinner";
+  int winner_group = trial_true->AppendGroup(winner, 10);
+
+  // Set GoogleVariationIDs so we can verify that they were chosen correctly.
+  AssociateGoogleVariationID(trial_true->name(), default_name1, kTestValueA);
+  AssociateGoogleVariationID(trial_true->name(), winner, kTestValueB);
+
+  EXPECT_EQ(winner_group, trial_true->group());
+  EXPECT_EQ(winner, trial_true->group_name());
+  EXPECT_EQ(kTestValueB, GetIDForTrial(trial_true.get()));
+
+  const std::string default_name2 = "default2";
+  scoped_refptr<base::FieldTrial> trial_false(
+      base::FieldTrialList::FactoryGetFieldTrial("d2", 10, default_name2,
+                                                 next_year_, 12, 31, NULL));
+  const std::string loser = "ALoser";
+  const int loser_group = trial_false->AppendGroup(loser, 0);
+
+  AssociateGoogleVariationID(trial_false->name(), default_name2, kTestValueA);
+  AssociateGoogleVariationID(trial_false->name(), loser, kTestValueB);
+
+  EXPECT_NE(loser_group, trial_false->group());
+  EXPECT_EQ(kTestValueA, GetIDForTrial(trial_false.get()));
+}
+
+// Test that not associating a FieldTrial with any IDs ensure that the empty ID
+// will be returned.
+TEST_F(VariationsUtilTest, NoAssociation) {
+  const std::string default_name = "default";
+  scoped_refptr<base::FieldTrial> no_id_trial(
+      base::FieldTrialList::FactoryGetFieldTrial("d3", 10, default_name,
+                                                 next_year_, 12, 31, NULL));
+  const std::string winner = "TheWinner";
+  const int winner_group = no_id_trial->AppendGroup(winner, 10);
+
+  // Ensure that despite the fact that a normal winner is elected, it does not
+  // have a valid VariationID associated with it.
+  EXPECT_EQ(winner_group, no_id_trial->group());
+  EXPECT_EQ(winner, no_id_trial->group_name());
+  EXPECT_EQ(kEmptyID, GetIDForTrial(no_id_trial.get()));
+}
+
+// Ensure that the AssociateGoogleVariationIDForce works as expected.
+TEST_F(VariationsUtilTest, ForceAssociation) {
+  EXPECT_EQ(kEmptyID, GetGoogleVariationID("trial", "group"));
+  AssociateGoogleVariationID("trial", "group", kTestValueA);
+  EXPECT_EQ(kTestValueA, GetGoogleVariationID("trial", "group"));
+  AssociateGoogleVariationID("trial", "group", kTestValueB);
+  EXPECT_EQ(kTestValueA, GetGoogleVariationID("trial", "group"));
+  AssociateGoogleVariationIDForce("trial", "group", kTestValueB);
+  EXPECT_EQ(kTestValueB, GetGoogleVariationID("trial", "group"));
+}
+
+TEST_F(VariationsUtilTest, GenerateExperimentChunks) {
+  const char* kExperimentStrings[] = {
+      "1d3048f1-9de009d0",
+      "cd73da34-cf196cb",
+      "6214fa18-9e6dc24d",
+      "4dcb0cd6-d31c4ca1",
+      "9d5bce6-30d7d8ac",
+  };
+  const char* kExpectedChunks1[] = {
+      "1d3048f1-9de009d0",
+  };
+  const char* kExpectedChunks2[] = {
+      "1d3048f1-9de009d0,cd73da34-cf196cb",
+  };
+  const char* kExpectedChunks3[] = {
+      "1d3048f1-9de009d0,cd73da34-cf196cb,6214fa18-9e6dc24d",
+  };
+  const char* kExpectedChunks4[] = {
+      "1d3048f1-9de009d0,cd73da34-cf196cb,6214fa18-9e6dc24d",
+      "4dcb0cd6-d31c4ca1",
+  };
+  const char* kExpectedChunks5[] = {
+      "1d3048f1-9de009d0,cd73da34-cf196cb,6214fa18-9e6dc24d",
+      "4dcb0cd6-d31c4ca1,9d5bce6-30d7d8ac",
+  };
+
+  struct {
+    size_t strings_length;
+    size_t expected_chunks_length;
+    const char** expected_chunks;
+  } cases[] = {
+    { 0, 0, NULL },
+    { 1, arraysize(kExpectedChunks1), kExpectedChunks1 },
+    { 2, arraysize(kExpectedChunks2), kExpectedChunks2 },
+    { 3, arraysize(kExpectedChunks3), kExpectedChunks3 },
+    { 4, arraysize(kExpectedChunks4), kExpectedChunks4 },
+    { 5, arraysize(kExpectedChunks5), kExpectedChunks5 },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+    ASSERT_LE(cases[i].strings_length, arraysize(kExperimentStrings));
+
+    std::vector<string16> experiments;
+    for (size_t j = 0; j < cases[i].strings_length; ++j)
+      experiments.push_back(UTF8ToUTF16(kExperimentStrings[j]));
+
+    std::vector<string16> chunks;
+    GenerateVariationChunks(experiments, &chunks);
+    ASSERT_EQ(cases[i].expected_chunks_length, chunks.size());
+    for (size_t j = 0; j < chunks.size(); ++j)
+      EXPECT_EQ(UTF8ToUTF16(cases[i].expected_chunks[j]), chunks[j]);
+  }
+}
+
+}  // namespace chrome_variations
diff --git a/chrome/common/multi_process_lock.h b/chrome/common/multi_process_lock.h
new file mode 100644
index 0000000..25e1b61
--- /dev/null
+++ b/chrome/common/multi_process_lock.h
@@ -0,0 +1,33 @@
+// 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.
+
+#ifndef CHROME_COMMON_MULTI_PROCESS_LOCK_H_
+#define CHROME_COMMON_MULTI_PROCESS_LOCK_H_
+
+#include <sys/types.h>
+#include <string>
+
+// Platform abstraction for a lock that can be shared between processes.
+// The process that owns the lock will release it on exit even if
+// the exit is due to a crash. Locks are not recursive.
+class MultiProcessLock {
+ public:
+
+  // Factory method for creating a multi-process lock.
+  // |name| is the name of the lock. The name has special meaning on Windows
+  // where the prefix can determine the namespace of the lock.
+  // See http://msdn.microsoft.com/en-us/library/aa382954(v=VS.85).aspx for
+  // details.
+  static MultiProcessLock* Create(const std::string& name);
+
+  virtual ~MultiProcessLock() { }
+
+  // Try to grab ownership of the lock.
+  virtual bool TryLock() = 0;
+
+  // Release ownership of the lock.
+  virtual void Unlock() = 0;
+};
+
+#endif  // CHROME_COMMON_MULTI_PROCESS_LOCK_H_
diff --git a/chrome/common/multi_process_lock_linux.cc b/chrome/common/multi_process_lock_linux.cc
new file mode 100644
index 0000000..f610237
--- /dev/null
+++ b/chrome/common/multi_process_lock_linux.cc
@@ -0,0 +1,106 @@
+// 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.
+
+#include "chrome/common/multi_process_lock.h"
+
+#include <stdio.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "base/eintr_wrapper.h"
+#include "base/logging.h"
+
+class MultiProcessLockLinux : public MultiProcessLock {
+ public:
+  explicit MultiProcessLockLinux(const std::string& name)
+      : name_(name), fd_(-1) { }
+
+  virtual ~MultiProcessLockLinux() {
+    if (fd_ != -1) {
+      Unlock();
+    }
+  }
+
+  virtual bool TryLock() {
+    struct sockaddr_un address;
+
+    // +1 for terminator, +1 for 0 in position 0 that makes it an
+    // abstract named socket.
+    const size_t max_len = sizeof(address.sun_path) - 2;
+
+    if (fd_ != -1) {
+      DLOG(ERROR) << "MultiProcessLock is already locked - " << name_;
+      return true;
+    }
+
+    if (name_.length() > max_len) {
+      LOG(ERROR) << "Socket name too long (" << name_.length()
+                 << " > " << max_len << ") - " << name_;
+      return false;
+    }
+
+    memset(&address, 0, sizeof(address));
+    int print_length = snprintf(&address.sun_path[1],
+                                max_len + 1,
+                                "%s", name_.c_str());
+
+    if (print_length < 0 ||
+        print_length > static_cast<int>(max_len)) {
+      PLOG(ERROR) << "Couldn't create sun_path - " << name_;
+      return false;
+    }
+
+    // Must set the first character of the path to something non-zero
+    // before we call SUN_LEN which depends on strcpy working.
+    address.sun_path[0] = '@';
+    size_t length = SUN_LEN(&address);
+
+    // Reset the first character of the path back to zero so that
+    // bind returns an abstract name socket.
+    address.sun_path[0] = 0;
+    address.sun_family = AF_LOCAL;
+
+    int socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+    if (socket_fd < 0) {
+      PLOG(ERROR) << "Couldn't create socket - " << name_;
+      return false;
+    }
+
+    if (bind(socket_fd,
+             reinterpret_cast<sockaddr *>(&address),
+             length) == 0) {
+      fd_ = socket_fd;
+      return true;
+    } else {
+      DVLOG(1) << "Couldn't bind socket - "
+               << &(address.sun_path[1])
+               << " Length: " << length;
+      if (HANDLE_EINTR(close(socket_fd)) < 0) {
+        PLOG(ERROR) << "close";
+      }
+      return false;
+    }
+  }
+
+  virtual void Unlock() {
+    if (fd_ == -1) {
+      DLOG(ERROR) << "Over-unlocked MultiProcessLock - " << name_;
+      return;
+    }
+    if (HANDLE_EINTR(close(fd_)) < 0) {
+      DPLOG(ERROR) << "close";
+    }
+    fd_ = -1;
+  }
+
+ private:
+  std::string name_;
+  int fd_;
+  DISALLOW_COPY_AND_ASSIGN(MultiProcessLockLinux);
+};
+
+MultiProcessLock* MultiProcessLock::Create(const std::string &name) {
+  return new MultiProcessLockLinux(name);
+}
diff --git a/chrome/common/multi_process_lock_mac.cc b/chrome/common/multi_process_lock_mac.cc
new file mode 100644
index 0000000..8a05b94
--- /dev/null
+++ b/chrome/common/multi_process_lock_mac.cc
@@ -0,0 +1,57 @@
+// 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.
+
+#include "chrome/common/multi_process_lock.h"
+
+#include "base/logging.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/sys_string_conversions.h"
+
+#include <servers/bootstrap.h>
+
+class MultiProcessLockMac : public MultiProcessLock {
+ public:
+  explicit MultiProcessLockMac(const std::string& name) : name_(name) { }
+
+  virtual ~MultiProcessLockMac() {
+    if (port_ != NULL) {
+      Unlock();
+    }
+  }
+
+  virtual bool TryLock() {
+    if (port_ != NULL) {
+      DLOG(ERROR) << "MultiProcessLock is already locked - " << name_;
+      return true;
+    }
+
+    if (name_.length() >= BOOTSTRAP_MAX_NAME_LEN) {
+      LOG(ERROR) << "Socket name too long (" << name_.length()
+                 << " >= " << BOOTSTRAP_MAX_NAME_LEN << ") - " << name_;
+      return false;
+    }
+
+    CFStringRef cf_name(base::SysUTF8ToCFStringRef(name_));
+    base::mac::ScopedCFTypeRef<CFStringRef> scoped_cf_name(cf_name);
+    port_.reset(CFMessagePortCreateLocal(NULL, cf_name, NULL, NULL, NULL));
+    return port_ != NULL;
+  }
+
+  virtual void Unlock() {
+    if (port_ == NULL) {
+      DLOG(ERROR) << "Over-unlocked MultiProcessLock - " << name_;
+      return;
+    }
+    port_.reset();
+  }
+
+ private:
+  std::string name_;
+  base::mac::ScopedCFTypeRef<CFMessagePortRef> port_;
+  DISALLOW_COPY_AND_ASSIGN(MultiProcessLockMac);
+};
+
+MultiProcessLock* MultiProcessLock::Create(const std::string &name) {
+  return new MultiProcessLockMac(name);
+}
diff --git a/chrome/common/multi_process_lock_unittest.cc b/chrome/common/multi_process_lock_unittest.cc
new file mode 100644
index 0000000..30c4d7b
--- /dev/null
+++ b/chrome/common/multi_process_lock_unittest.cc
@@ -0,0 +1,171 @@
+// 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.
+
+#include "base/basictypes.h"
+#include "base/environment.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/rand_util.h"
+#include "base/stringprintf.h"
+#include "base/test/multiprocess_test.h"
+#include "base/time.h"
+#include "chrome/common/multi_process_lock.h"
+#include "testing/multiprocess_func_list.h"
+
+class MultiProcessLockTest : public base::MultiProcessTest {
+ public:
+  static const char kLockEnviromentVarName[];
+
+  class ScopedEnvironmentVariable {
+   public:
+    ScopedEnvironmentVariable(const std::string &name,
+                              const std::string &value)
+        : name_(name), environment_(base::Environment::Create()) {
+      environment_->SetVar(name_.c_str(), value);
+    }
+    ~ScopedEnvironmentVariable() {
+      environment_->UnSetVar(name_.c_str());
+    }
+
+   private:
+    std::string name_;
+    scoped_ptr<base::Environment> environment_;
+    DISALLOW_COPY_AND_ASSIGN(ScopedEnvironmentVariable);
+  };
+
+  std::string GenerateLockName();
+  void ExpectLockIsLocked(const std::string &name);
+  void ExpectLockIsUnlocked(const std::string &name);
+};
+
+const char MultiProcessLockTest::kLockEnviromentVarName[]
+    = "MULTI_PROCESS_TEST_LOCK_NAME";
+
+std::string MultiProcessLockTest::GenerateLockName() {
+  base::Time now = base::Time::NowFromSystemTime();
+  return base::StringPrintf("multi_process_test_lock %lf%lf",
+                            now.ToDoubleT(), base::RandDouble());
+}
+
+void MultiProcessLockTest::ExpectLockIsLocked(const std::string &name) {
+  ScopedEnvironmentVariable var(kLockEnviromentVarName, name);
+  base::ProcessHandle handle = SpawnChild("MultiProcessLockTryFailMain", false);
+  ASSERT_TRUE(handle);
+  int exit_code = 0;
+  EXPECT_TRUE(base::WaitForExitCode(handle, &exit_code));
+  EXPECT_EQ(exit_code, 0);
+}
+
+void MultiProcessLockTest::ExpectLockIsUnlocked(
+    const std::string &name) {
+  ScopedEnvironmentVariable var(kLockEnviromentVarName, name);
+  base::ProcessHandle handle = SpawnChild("MultiProcessLockTrySucceedMain",
+                                          false);
+  ASSERT_TRUE(handle);
+  int exit_code = 0;
+  EXPECT_TRUE(base::WaitForExitCode(handle, &exit_code));
+  EXPECT_EQ(exit_code, 0);
+}
+
+TEST_F(MultiProcessLockTest, BasicCreationTest) {
+  // Test basic creation/destruction with no lock taken
+  std::string name = GenerateLockName();
+  scoped_ptr<MultiProcessLock> scoped(MultiProcessLock::Create(name));
+  ExpectLockIsUnlocked(name);
+  scoped.reset(NULL);
+}
+
+TEST_F(MultiProcessLockTest, LongNameTest) {
+  // Every platform has has it's own max path name size,
+  // so different checks are needed for them.
+  // POSIX: sizeof(address.sun_path) - 2
+  // Mac OS X: BOOTSTRAP_MAX_NAME_LEN
+  // Windows: MAX_PATH
+  LOG(INFO) << "Following error log due to long name is expected";
+#if defined(OS_MACOSX)
+  std::string name("This is a name that is longer than one hundred and "
+      "twenty-eight characters to make sure that we fail appropriately on "
+      "Mac OS X when we have a path that is too long for Mac OS X to handle");
+#elif defined(OS_POSIX)
+  std::string name("This is a name that is longer than one hundred and eight "
+      "characters to make sure that we fail appropriately on POSIX systems "
+      "when we have a path that is too long for the system to handle");
+#elif defined(OS_WIN)
+  std::string name("This is a name that is longer than two hundred and sixty "
+      "characters to make sure that we fail appropriately on Windows when we "
+      "have a path that is too long for Windows to handle "
+      "This limitation comes from the MAX_PATH definition which is obviously "
+      "defined to be a maximum of two hundred and sixty characters ");
+#endif
+  scoped_ptr<MultiProcessLock> test_lock(MultiProcessLock::Create(name));
+  EXPECT_FALSE(test_lock->TryLock());
+}
+
+TEST_F(MultiProcessLockTest, SimpleLock) {
+  std::string name = GenerateLockName();
+  scoped_ptr<MultiProcessLock> test_lock(MultiProcessLock::Create(name));
+  EXPECT_TRUE(test_lock->TryLock());
+  ExpectLockIsLocked(name);
+  test_lock->Unlock();
+  ExpectLockIsUnlocked(name);
+}
+
+TEST_F(MultiProcessLockTest, RecursiveLock) {
+  std::string name = GenerateLockName();
+  scoped_ptr<MultiProcessLock> test_lock(MultiProcessLock::Create(name));
+  EXPECT_TRUE(test_lock->TryLock());
+  ExpectLockIsLocked(name);
+  LOG(INFO) << "Following error log "
+            << "'MultiProcessLock is already locked' is expected";
+  EXPECT_TRUE(test_lock->TryLock());
+  ExpectLockIsLocked(name);
+  test_lock->Unlock();
+  ExpectLockIsUnlocked(name);
+  LOG(INFO) << "Following error log "
+            << "'Over-unlocked MultiProcessLock' is expected";
+  test_lock->Unlock();
+  ExpectLockIsUnlocked(name);
+  test_lock.reset();
+}
+
+TEST_F(MultiProcessLockTest, LockScope) {
+  // Check to see that lock is released when it goes out of scope.
+  std::string name = GenerateLockName();
+  {
+    scoped_ptr<MultiProcessLock> test_lock(MultiProcessLock::Create(name));
+    EXPECT_TRUE(test_lock->TryLock());
+    ExpectLockIsLocked(name);
+  }
+  ExpectLockIsUnlocked(name);
+}
+
+MULTIPROCESS_TEST_MAIN(MultiProcessLockTryFailMain) {
+  std::string name;
+  scoped_ptr<base::Environment> environment(base::Environment::Create());
+  EXPECT_TRUE(environment->GetVar(MultiProcessLockTest::kLockEnviromentVarName,
+                                  &name));
+#if defined(OS_MACOSX)
+  // OS X sends out a log if a lock fails.
+  // Hopefully this will keep people from panicking about it when they
+  // are perusing the build logs.
+  LOG(INFO) << "Following error log "
+            << "\"CFMessagePort: bootstrap_register(): failed 1100 (0x44c) "
+            << "'Permission denied'\" is expected";
+#endif  // defined(OS_MACOSX)
+  scoped_ptr<MultiProcessLock> test_lock(
+      MultiProcessLock::Create(name));
+  EXPECT_FALSE(test_lock->TryLock());
+  return 0;
+}
+
+MULTIPROCESS_TEST_MAIN(MultiProcessLockTrySucceedMain) {
+  std::string name;
+  scoped_ptr<base::Environment> environment(base::Environment::Create());
+  EXPECT_TRUE(environment->GetVar(MultiProcessLockTest::kLockEnviromentVarName,
+                                  &name));
+  scoped_ptr<MultiProcessLock> test_lock(
+      MultiProcessLock::Create(name));
+  EXPECT_TRUE(test_lock->TryLock());
+  return 0;
+}
diff --git a/chrome/common/multi_process_lock_win.cc b/chrome/common/multi_process_lock_win.cc
new file mode 100644
index 0000000..d35258e
--- /dev/null
+++ b/chrome/common/multi_process_lock_win.cc
@@ -0,0 +1,59 @@
+// 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.
+
+#include "chrome/common/multi_process_lock.h"
+
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+#include "base/win/scoped_handle.h"
+
+class MultiProcessLockWin : public MultiProcessLock {
+ public:
+  explicit MultiProcessLockWin(const std::string& name) : name_(name) { }
+
+  virtual ~MultiProcessLockWin() {
+    if (event_.Get() != NULL) {
+      Unlock();
+    }
+  }
+
+  virtual bool TryLock() {
+    if (event_.Get() != NULL) {
+      DLOG(ERROR) << "MultiProcessLock is already locked - " << name_;
+      return true;
+    }
+
+    if (name_.length() >= MAX_PATH) {
+      LOG(ERROR) << "Socket name too long (" << name_.length()
+                 << " >= " << MAX_PATH << ") - " << name_;
+      return false;
+    }
+
+    string16 wname = UTF8ToUTF16(name_);
+    event_.Set(CreateEvent(NULL, FALSE, FALSE, wname.c_str()));
+    if (event_.Get() && GetLastError() != ERROR_ALREADY_EXISTS) {
+      return true;
+    } else {
+      event_.Set(NULL);
+      return false;
+    }
+  }
+
+  virtual void Unlock() {
+    if (event_.Get() == NULL) {
+      DLOG(ERROR) << "Over-unlocked MultiProcessLock - " << name_;
+      return;
+    }
+    event_.Set(NULL);
+  }
+
+ private:
+  std::string name_;
+  base::win::ScopedHandle event_;
+  DISALLOW_COPY_AND_ASSIGN(MultiProcessLockWin);
+};
+
+MultiProcessLock* MultiProcessLock::Create(const std::string &name) {
+  return new MultiProcessLockWin(name);
+}
diff --git a/chrome/common/nacl_cmd_line.cc b/chrome/common/nacl_cmd_line.cc
new file mode 100644
index 0000000..b2e461b
--- /dev/null
+++ b/chrome/common/nacl_cmd_line.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 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.
+
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "chrome/common/chrome_switches.h"
+
+namespace nacl {
+
+void CopyNaClCommandLineArguments(CommandLine* cmd_line) {
+  const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
+
+  // Propagate the following switches to the NaCl loader command line (along
+  // with any associated values) if present in the browser command line.
+  // TODO(gregoryd): check which flags of those below can be supported.
+  static const char* const kSwitchNames[] = {
+    switches::kNoSandbox,
+    switches::kTestNaClSandbox,
+    switches::kDisableBreakpad,
+    switches::kFullMemoryCrashReport,
+    switches::kEnableLogging,
+    switches::kDisableLogging,
+    switches::kLoggingLevel,
+    switches::kEnableDCHECK,
+    switches::kSilentDumpOnDCHECK,
+    switches::kMemoryProfiling,
+    switches::kNoErrorDialogs,
+    switches::kEnableNaClSRPCProxy,
+#if defined(OS_MACOSX)
+    switches::kEnableSandboxLogging,
+#endif
+  };
+  cmd_line->CopySwitchesFrom(browser_command_line, kSwitchNames,
+                             arraysize(kSwitchNames));
+}
+
+}  // namespace nacl
diff --git a/chrome/common/nacl_cmd_line.h b/chrome/common/nacl_cmd_line.h
new file mode 100644
index 0000000..2975850
--- /dev/null
+++ b/chrome/common/nacl_cmd_line.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2010 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.
+
+#ifndef CHROME_COMMON_NACL_CMD_LINE_H_
+#define CHROME_COMMON_NACL_CMD_LINE_H_
+
+class CommandLine;
+
+namespace nacl {
+  // Copy all the relevant arguments from the command line of the current
+  // process to cmd_line that will be used for launching the NaCl loader/broker.
+  void CopyNaClCommandLineArguments(CommandLine* cmd_line);
+}
+
+#endif  // CHROME_COMMON_NACL_CMD_LINE_H_
diff --git a/chrome/common/nacl_debug_exception_handler_win.cc b/chrome/common/nacl_debug_exception_handler_win.cc
new file mode 100644
index 0000000..01206ec
--- /dev/null
+++ b/chrome/common/nacl_debug_exception_handler_win.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/nacl_debug_exception_handler_win.h"
+
+#include "base/bind.h"
+#include "base/process_util.h"
+#include "base/threading/platform_thread.h"
+#include "base/win/scoped_handle.h"
+#include "native_client/src/trusted/service_runtime/win/debug_exception_handler.h"
+
+namespace {
+
+class DebugExceptionHandler : public base::PlatformThread::Delegate {
+ public:
+  DebugExceptionHandler(base::ProcessHandle nacl_process,
+                        const std::string& startup_info,
+                        base::MessageLoopProxy* message_loop,
+                        const base::Callback<void(bool)>& on_connected)
+      : nacl_process_(nacl_process),
+        startup_info_(startup_info),
+        message_loop_(message_loop),
+        on_connected_(on_connected) {
+  }
+
+  virtual void ThreadMain() OVERRIDE {
+    // In the Windows API, the set of processes being debugged is
+    // thread-local, so we have to attach to the process (using
+    // DebugActiveProcess()) on the same thread on which
+    // NaClDebugExceptionHandlerRun() receives debug events for the
+    // process.
+    bool attached = false;
+    int pid = GetProcessId(nacl_process_);
+    if (pid == 0) {
+      LOG(ERROR) << "Invalid process handle";
+    } else {
+      if (!DebugActiveProcess(pid)) {
+        LOG(ERROR) << "Failed to connect to the process";
+      } else {
+        attached = true;
+      }
+    }
+    message_loop_->PostTask(FROM_HERE, base::Bind(on_connected_, attached));
+
+    if (attached) {
+      NaClDebugExceptionHandlerRun(
+          nacl_process_,
+          reinterpret_cast<const void*>(startup_info_.data()),
+          startup_info_.size());
+    }
+    delete this;
+  }
+
+ private:
+  base::win::ScopedHandle nacl_process_;
+  std::string startup_info_;
+  base::MessageLoopProxy* message_loop_;
+  base::Callback<void(bool)> on_connected_;
+
+  DISALLOW_COPY_AND_ASSIGN(DebugExceptionHandler);
+};
+
+}  // namespace
+
+void NaClStartDebugExceptionHandlerThread(
+    base::ProcessHandle nacl_process,
+    const std::string& startup_info,
+    base::MessageLoopProxy* message_loop,
+    const base::Callback<void(bool)>& on_connected) {
+  // The new PlatformThread will take ownership of the
+  // DebugExceptionHandler object, which will delete itself on exit.
+  DebugExceptionHandler* handler = new DebugExceptionHandler(
+      nacl_process, startup_info, message_loop, on_connected);
+  if (!base::PlatformThread::CreateNonJoinable(0, handler)) {
+    on_connected.Run(false);
+    delete handler;
+  }
+}
diff --git a/chrome/common/nacl_debug_exception_handler_win.h b/chrome/common/nacl_debug_exception_handler_win.h
new file mode 100644
index 0000000..9bc0129
--- /dev/null
+++ b/chrome/common/nacl_debug_exception_handler_win.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_NACL_DEBUG_EXCEPTION_HANDLER_WIN_H_
+#define CHROME_COMMON_NACL_DEBUG_EXCEPTION_HANDLER_WIN_H_
+
+#include "base/callback.h"
+#include "base/message_loop.h"
+#include "base/process.h"
+
+void NaClStartDebugExceptionHandlerThread(
+    base::ProcessHandle nacl_process,
+    const std::string& startup_info,
+    base::MessageLoopProxy* message_loop,
+    const base::Callback<void(bool)>& on_connected);
+
+#endif  // CHROME_COMMON_NACL_DEBUG_EXCEPTION_HANDLER_WIN_H_
diff --git a/chrome/common/nacl_helper_linux.h b/chrome/common/nacl_helper_linux.h
new file mode 100644
index 0000000..ec12e27
--- /dev/null
+++ b/chrome/common/nacl_helper_linux.h
@@ -0,0 +1,45 @@
+// 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.
+
+#ifndef CHROME_COMMON_NACL_HELPER_LINUX_H_
+#define CHROME_COMMON_NACL_HELPER_LINUX_H_
+
+// A mini-zygote specifically for Native Client. This file defines
+// constants used to implement communication between the nacl_helper
+// process and the Chrome zygote.
+
+// Used by Helper to tell Zygote it has started successfully.
+#define kNaClHelperStartupAck "NACLHELPER_OK"
+// Used by Zygote to ask Helper to fork a new NaCl loader.
+#define kNaClForkRequest "NACLFORK"
+
+// The next set of constants define global Linux file descriptors.
+// For communications between NaCl loader and browser.
+// See also content/common/zygote_main_linux.cc and
+// http://code.google.com/p/chromium/wiki/LinuxZygote
+#define kNaClBrowserDescriptor 3
+// For communications between NaCl loader and zygote.
+// We put the kNaClZygoteDescriptor on 3 together with the
+// kNaClBrowserDescriptor. They are never used at the same
+// time, and this prevents /dev/urandom from using the fd.
+#define kNaClZygoteDescriptor 3
+// For communications between the NaCl loader process and
+// the SUID sandbox.
+#define kNaClSandboxDescriptor 5
+// NOTE: kNaClBrowserDescriptor and kNaClSandboxDescriptor must match
+// content/browser/zygote_main_linux.cc kBrowserDescriptor and
+// kMagicSandboxIPCDescriptor.
+
+// A fork request from the Zygote to the helper includes an array
+// of three file descriptors. These constants are used as indicies
+// into the array.
+// Used to pass in the descriptor for talking to the Browser
+#define kNaClBrowserFDIndex 0
+// The next two are used in the protocol for discovering the
+// child processes real PID from within the SUID sandbox. See
+// http://code.google.com/p/chromium/wiki/LinuxZygote
+#define kNaClDummyFDIndex 1
+#define kNaClParentFDIndex 2
+
+#endif  // CHROME_COMMON_NACL_HELPER_LINUX_H_
diff --git a/chrome/common/nacl_loader.sb b/chrome/common/nacl_loader.sb
new file mode 100644
index 0000000..e881afe
--- /dev/null
+++ b/chrome/common/nacl_loader.sb
@@ -0,0 +1,14 @@
+;;
+;; 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.
+;;
+; This is the Sandbox configuration file used for safeguarding the user's
+; untrusted code within Native Client.
+;
+
+; *** The contents of content/common/common.sb are implicitly included here. ***
+
+; Allow a Native Client application to use semaphores, specifically
+; sem_init(), et.al.
+(allow ipc-posix-sem)
diff --git a/chrome/common/nacl_messages.cc b/chrome/common/nacl_messages.cc
new file mode 100644
index 0000000..66e4df2
--- /dev/null
+++ b/chrome/common/nacl_messages.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2010 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.
+
+// Get basic type definitions.
+#define IPC_MESSAGE_IMPL
+#include "chrome/common/nacl_messages.h"
+
+// Generate constructors.
+#include "ipc/struct_constructor_macros.h"
+#include "chrome/common/nacl_messages.h"
+
+// Generate destructors.
+#include "ipc/struct_destructor_macros.h"
+#include "chrome/common/nacl_messages.h"
+
+// Generate param traits write methods.
+#include "ipc/param_traits_write_macros.h"
+namespace IPC {
+#include "chrome/common/nacl_messages.h"
+}  // namespace IPC
+
+// Generate param traits read methods.
+#include "ipc/param_traits_read_macros.h"
+namespace IPC {
+#include "chrome/common/nacl_messages.h"
+}  // namespace IPC
+
+// Generate param traits log methods.
+#include "ipc/param_traits_log_macros.h"
+namespace IPC {
+#include "chrome/common/nacl_messages.h"
+}  // namespace IPC
+
diff --git a/chrome/common/nacl_messages.h b/chrome/common/nacl_messages.h
new file mode 100644
index 0000000..7ccba59
--- /dev/null
+++ b/chrome/common/nacl_messages.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2012 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.
+
+// Defines messages between the browser and NaCl process.
+
+// Multiply-included message file, no traditional include guard.
+#include "base/process.h"
+#include "chrome/common/nacl_types.h"
+#include "ipc/ipc_channel_handle.h"
+#include "ipc/ipc_message_macros.h"
+
+#define IPC_MESSAGE_START NaClMsgStart
+
+IPC_STRUCT_TRAITS_BEGIN(nacl::NaClStartParams)
+  IPC_STRUCT_TRAITS_MEMBER(handles)
+  IPC_STRUCT_TRAITS_MEMBER(debug_stub_server_bound_socket)
+  IPC_STRUCT_TRAITS_MEMBER(validation_cache_enabled)
+  IPC_STRUCT_TRAITS_MEMBER(validation_cache_key)
+  IPC_STRUCT_TRAITS_MEMBER(version)
+  IPC_STRUCT_TRAITS_MEMBER(enable_exception_handling)
+  IPC_STRUCT_TRAITS_MEMBER(enable_debug_stub)
+  IPC_STRUCT_TRAITS_MEMBER(enable_ipc_proxy)
+IPC_STRUCT_TRAITS_END()
+
+//-----------------------------------------------------------------------------
+// NaClProcess messages
+// These are messages sent between the browser and the NaCl process.
+// Tells the NaCl process to start.
+IPC_MESSAGE_CONTROL1(NaClProcessMsg_Start,
+                     nacl::NaClStartParams /* params */)
+
+#if defined(OS_WIN)
+// Tells the NaCl broker to launch a NaCl loader process.
+IPC_MESSAGE_CONTROL1(NaClProcessMsg_LaunchLoaderThroughBroker,
+                     std::string /* channel ID for the loader */)
+
+// Notify the browser process that the loader was launched successfully.
+IPC_MESSAGE_CONTROL2(NaClProcessMsg_LoaderLaunched,
+                     std::string,  /* channel ID for the loader */
+                     base::ProcessHandle /* loader process handle */)
+
+// Tells the NaCl broker to attach a debug exception handler to the
+// given NaCl loader process.
+IPC_MESSAGE_CONTROL3(NaClProcessMsg_LaunchDebugExceptionHandler,
+                     int32 /* pid of the NaCl process */,
+                     base::ProcessHandle /* handle of the NaCl process */,
+                     std::string /* NaCl internal process layout info */)
+
+// Notify the browser process that the broker process finished
+// attaching a debug exception handler to the given NaCl loader
+// process.
+IPC_MESSAGE_CONTROL2(NaClProcessMsg_DebugExceptionHandlerLaunched,
+                     int32 /* pid */,
+                     bool /* success */)
+
+// Notify the broker that all loader processes have been terminated and it
+// should shutdown.
+IPC_MESSAGE_CONTROL0(NaClProcessMsg_StopBroker)
+
+// Used by the NaCl process to request that a Windows debug exception
+// handler be attached to it.
+IPC_SYNC_MESSAGE_CONTROL1_1(NaClProcessMsg_AttachDebugExceptionHandler,
+                            std::string, /* Internal process info */
+                            bool /* Result */)
+#endif
+
+// Used by the NaCl process to query a database in the browser.  The database
+// contains the signatures of previously validated code chunks.
+IPC_SYNC_MESSAGE_CONTROL1_1(NaClProcessMsg_QueryKnownToValidate,
+                            std::string, /* A validation signature */
+                            bool /* Can validation be skipped? */)
+
+// Used by the NaCl process to add a validation signature to the validation
+// database in the browser.
+IPC_MESSAGE_CONTROL1(NaClProcessMsg_SetKnownToValidate,
+                     std::string /* A validation signature */)
+
+// Notify the browser process that the server side of the PPAPI channel was
+// created successfully.
+IPC_MESSAGE_CONTROL1(NaClProcessHostMsg_PpapiChannelCreated,
+                     IPC::ChannelHandle /* channel_handle */)
+
diff --git a/chrome/common/nacl_types.cc b/chrome/common/nacl_types.cc
new file mode 100644
index 0000000..4df0a7c
--- /dev/null
+++ b/chrome/common/nacl_types.cc
@@ -0,0 +1,17 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/nacl_types.h"
+
+namespace nacl {
+
+NaClStartParams::NaClStartParams()
+    : validation_cache_enabled(false),
+      enable_exception_handling(false) {
+}
+
+NaClStartParams::~NaClStartParams() {
+}
+
+}  // namespace nacl
diff --git a/chrome/common/nacl_types.h b/chrome/common/nacl_types.h
new file mode 100644
index 0000000..ca44e03
--- /dev/null
+++ b/chrome/common/nacl_types.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_NACL_TYPES_H_
+#define CHROME_COMMON_NACL_TYPES_H_
+
+#include <string>
+#include <vector>
+
+#include "build/build_config.h"
+
+#if defined(OS_POSIX)
+#include "base/file_descriptor_posix.h"
+#endif
+
+#if defined(OS_WIN)
+#include <windows.h>   // for HANDLE
+#endif
+
+// TODO(gregoryd): add a Windows definition for base::FileDescriptor
+namespace nacl {
+
+#if defined(OS_WIN)
+// We assume that HANDLE always uses less than 32 bits
+typedef int FileDescriptor;
+inline HANDLE ToNativeHandle(const FileDescriptor& desc) {
+  return reinterpret_cast<HANDLE>(desc);
+}
+#elif defined(OS_POSIX)
+typedef base::FileDescriptor FileDescriptor;
+inline int ToNativeHandle(const FileDescriptor& desc) {
+  return desc.fd;
+}
+#endif
+
+
+// Parameters sent to the NaCl process when we start it.
+//
+// If you change this, you will also need to update the IPC serialization in
+// nacl_messages.h.
+struct NaClStartParams {
+  NaClStartParams();
+  ~NaClStartParams();
+
+  std::vector<FileDescriptor> handles;
+  FileDescriptor debug_stub_server_bound_socket;
+
+  bool validation_cache_enabled;
+  std::string validation_cache_key;
+  // Chrome version string. Sending the version string over IPC avoids linkage
+  // issues in cases where NaCl is not compiled into the main Chromium
+  // executable or DLL.
+  std::string version;
+
+  bool enable_exception_handling;
+  bool enable_debug_stub;
+  bool enable_ipc_proxy;
+};
+
+}  // namespace nacl
+
+#endif  // CHROME_COMMON_NACL_TYPES_H_
diff --git a/chrome/common/net/DEPS b/chrome/common/net/DEPS
new file mode 100644
index 0000000..2304e31
--- /dev/null
+++ b/chrome/common/net/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  # Other libraries.
+  "+chrome/third_party/mozilla_security_manager",
+]
diff --git a/chrome/common/net/PRESUBMIT.py b/chrome/common/net/PRESUBMIT.py
new file mode 100644
index 0000000..01292ef
--- /dev/null
+++ b/chrome/common/net/PRESUBMIT.py
@@ -0,0 +1,16 @@
+# Copyright (c) 2012 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.
+
+"""Chromium presubmit script for src/chrome/common/net.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details on the presubmit API built into gcl.
+"""
+
+def GetPreferredTrySlaves():
+  return [
+    'linux_rel:sync_integration_tests',
+    'mac_rel:sync_integration_tests',
+    'win_rel:sync_integration_tests',
+  ]
diff --git a/chrome/common/net/net_resource_provider.cc b/chrome/common/net/net_resource_provider.cc
new file mode 100644
index 0000000..d83c9d6
--- /dev/null
+++ b/chrome/common/net/net_resource_provider.cc
@@ -0,0 +1,64 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/net/net_resource_provider.h"
+
+#include <string>
+
+#include "base/string_piece.h"
+#include "base/values.h"
+#include "chrome/common/jstemplate_builder.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/net_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/layout.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace {
+
+// The net module doesn't have access to this HTML or the strings that need to
+// be localized.  The Chrome locale will never change while we're running, so
+// it's safe to have a static string that we always return a pointer into.
+// This allows us to have the ResourceProvider return a pointer into the actual
+// resource (via a StringPiece), instead of always copying resources.
+struct LazyDirectoryListerCacher {
+  LazyDirectoryListerCacher() {
+    DictionaryValue value;
+    value.SetString("header",
+                    l10n_util::GetStringUTF16(IDS_DIRECTORY_LISTING_HEADER));
+    value.SetString("parentDirText",
+                    l10n_util::GetStringUTF16(IDS_DIRECTORY_LISTING_PARENT));
+    value.SetString("headerName",
+                    l10n_util::GetStringUTF16(IDS_DIRECTORY_LISTING_NAME));
+    value.SetString("headerSize",
+                    l10n_util::GetStringUTF16(IDS_DIRECTORY_LISTING_SIZE));
+    value.SetString("headerDateModified",
+        l10n_util::GetStringUTF16(IDS_DIRECTORY_LISTING_DATE_MODIFIED));
+    value.SetString("listingParsingErrorBoxText",
+        l10n_util::GetStringFUTF16(IDS_DIRECTORY_LISTING_PARSING_ERROR_BOX_TEXT,
+            l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
+    html_data = jstemplate_builder::GetI18nTemplateHtml(
+        ResourceBundle::GetSharedInstance().GetRawDataResource(
+            IDR_DIR_HEADER_HTML),
+        &value);
+  }
+
+  std::string html_data;
+};
+
+}  // namespace
+
+namespace chrome_common_net {
+
+base::StringPiece NetResourceProvider(int key) {
+  CR_DEFINE_STATIC_LOCAL(LazyDirectoryListerCacher, lazy_dir_lister, ());
+
+  if (IDR_DIR_HEADER_HTML == key)
+    return base::StringPiece(lazy_dir_lister.html_data);
+
+  return ResourceBundle::GetSharedInstance().GetRawDataResource(key);
+}
+
+}  // namespace chrome_common_net
diff --git a/chrome/common/net/net_resource_provider.h b/chrome/common/net/net_resource_provider.h
new file mode 100644
index 0000000..dbdd1cd
--- /dev/null
+++ b/chrome/common/net/net_resource_provider.h
@@ -0,0 +1,17 @@
+// 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.
+
+#ifndef CHROME_COMMON_NET_NET_RESOURCE_PROVIDER_H_
+#define CHROME_COMMON_NET_NET_RESOURCE_PROVIDER_H_
+
+#include "base/string_piece.h"
+
+namespace chrome_common_net {
+
+// This is called indirectly by the network layer to access resources.
+base::StringPiece NetResourceProvider(int key);
+
+}  // namespace chrome_common_net
+
+#endif  // CHROME_COMMON_NET_NET_RESOURCE_PROVIDER_H_
diff --git a/chrome/common/net/predictor_common.h b/chrome/common/net/predictor_common.h
new file mode 100644
index 0000000..c1d995f
--- /dev/null
+++ b/chrome/common/net/predictor_common.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2006-2008 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.
+
+// This file has shared types used across IPC between render_dns_master.cc
+// and dns_master.cc
+
+#ifndef CHROME_COMMON_NET_PREDICTOR_COMMON_H_
+#define CHROME_COMMON_NET_PREDICTOR_COMMON_H_
+
+#include <string>
+#include <vector>
+
+#include "googleurl/src/gurl.h"
+
+namespace chrome_common_net {
+
+// IPC messages are passed from the renderer to the browser in the form of
+// Namelist instances.
+// Each element of this vector is a hostname that needs to be looked up.
+// The hostnames should never be empty strings.
+typedef std::vector<std::string> NameList;
+// TODO(jar): We still need to switch to passing scheme/host/port in UrlList,
+// instead of NameList, from renderer (where content of pages are scanned for
+// links) to browser (where we perform predictive actions).
+typedef std::vector<GURL> UrlList;
+}
+
+#endif  // CHROME_COMMON_NET_PREDICTOR_COMMON_H_
diff --git a/chrome/common/net/test_server_locations.cc b/chrome/common/net/test_server_locations.cc
new file mode 100644
index 0000000..d029b7f
--- /dev/null
+++ b/chrome/common/net/test_server_locations.cc
@@ -0,0 +1,13 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/net/test_server_locations.h"
+
+namespace chrome_common_net {
+
+const char kEchoTestServerLocation[] = "chrome.googleechotest.com";
+const char kPipelineTestServerBaseUrl[] =
+    "http://pipelining.googleechotest.com/";
+
+}  // namespace chrome_common_net
diff --git a/chrome/common/net/test_server_locations.h b/chrome/common/net/test_server_locations.h
new file mode 100644
index 0000000..591f81f
--- /dev/null
+++ b/chrome/common/net/test_server_locations.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_NET_TEST_SERVER_LOCATIONS_H_
+#define CHROME_COMMON_NET_TEST_SERVER_LOCATIONS_H_
+
+namespace chrome_common_net {
+
+// Hostname used for the NetworkStats test. Should point to a TCP/UDP server
+// that simply echoes back the request.
+extern const char kEchoTestServerLocation[];
+
+// Base URL for the HttpPipeliningCompatibilityClient test. Should point to a
+// server running server.py from http://code.google.com/p/http-pipelining-test/
+extern const char kPipelineTestServerBaseUrl[];
+
+}  // namespace chrome_common_net
+
+#endif  // CHROME_COMMON_NET_TEST_SERVER_LOCATIONS_H_
diff --git a/chrome/common/net/url_util.cc b/chrome/common/net/url_util.cc
new file mode 100644
index 0000000..9d58317
--- /dev/null
+++ b/chrome/common/net/url_util.cc
@@ -0,0 +1,119 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/net/url_util.h"
+
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/url_constants.h"
+#include "googleurl/src/gurl.h"
+#include "googleurl/src/url_parse.h"
+#include "net/base/escape.h"
+#include "net/base/net_util.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
+
+namespace chrome_common_net {
+
+void WriteURLToClipboard(const GURL& url,
+                         const std::string& languages,
+                         ui::Clipboard *clipboard) {
+  if (url.is_empty() || !url.is_valid() || !clipboard)
+    return;
+
+  // Unescaping path and query is not a good idea because other applications
+  // may not encode non-ASCII characters in UTF-8.  See crbug.com/2820.
+  string16 text = url.SchemeIs(chrome::kMailToScheme) ?
+      ASCIIToUTF16(url.path()) :
+      net::FormatUrl(url, languages, net::kFormatUrlOmitNothing,
+                     net::UnescapeRule::NONE, NULL, NULL, NULL);
+
+  ui::ScopedClipboardWriter scw(clipboard, ui::Clipboard::BUFFER_STANDARD);
+  scw.WriteURL(text);
+}
+
+GURL AppendQueryParameter(const GURL& url,
+                          const std::string& name,
+                          const std::string& value) {
+  std::string query(url.query());
+
+  if (!query.empty())
+    query += "&";
+
+  query += (net::EscapeQueryParamValue(name, true) + "=" +
+            net::EscapeQueryParamValue(value, true));
+  GURL::Replacements replacements;
+  replacements.SetQueryStr(query);
+  return url.ReplaceComponents(replacements);
+}
+
+GURL AppendOrReplaceQueryParameter(const GURL& url,
+                                   const std::string& name,
+                                   const std::string& value) {
+  bool replaced = false;
+  std::string param_name = net::EscapeQueryParamValue(name, true);
+  std::string param_value = net::EscapeQueryParamValue(value, true);
+
+  const std::string input = url.query();
+  url_parse::Component cursor(0, input.size());
+  std::string output;
+  url_parse::Component key_range, value_range;
+  while (url_parse::ExtractQueryKeyValue(
+             input.data(), &cursor, &key_range, &value_range)) {
+    const base::StringPiece key(
+        input.data() + key_range.begin, key_range.len);
+    const base::StringPiece value(
+        input.data() + value_range.begin, value_range.len);
+    std::string key_value_pair;
+    // Check |replaced| as only the first pair should be replaced.
+    if (!replaced && key == param_name) {
+      replaced = true;
+      key_value_pair = (param_name + "=" + param_value);
+    } else {
+      key_value_pair.assign(input.data(),
+                            key_range.begin,
+                            value_range.end() - key_range.begin);
+    }
+    if (!output.empty())
+      output += "&";
+
+    output += key_value_pair;
+  }
+  if (!replaced) {
+    if (!output.empty())
+      output += "&";
+
+    output += (param_name + "=" + param_value);
+  }
+  GURL::Replacements replacements;
+  replacements.SetQueryStr(output);
+  return url.ReplaceComponents(replacements);
+}
+
+bool GetValueForKeyInQuery(const GURL& url,
+                           const std::string& search_key,
+                           std::string* out_value) {
+  url_parse::Component query = url.parsed_for_possibly_invalid_spec().query;
+  url_parse::Component key, value;
+  while (url_parse::ExtractQueryKeyValue(
+      url.spec().c_str(), &query, &key, &value)) {
+    if (key.is_nonempty()) {
+      std::string key_string = url.spec().substr(key.begin, key.len);
+      if (key_string == search_key) {
+        if (value.is_nonempty()) {
+          *out_value = net::UnescapeURLComponent(
+              url.spec().substr(value.begin, value.len),
+              net::UnescapeRule::SPACES |
+                  net::UnescapeRule::URL_SPECIAL_CHARS |
+                  net::UnescapeRule::REPLACE_PLUS_WITH_SPACE);
+        } else {
+          *out_value = "";
+        }
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+}  // namespace chrome_common_net
diff --git a/chrome/common/net/url_util.h b/chrome/common/net/url_util.h
new file mode 100644
index 0000000..f6bf296
--- /dev/null
+++ b/chrome/common/net/url_util.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_NET_URL_UTIL_H_
+#define CHROME_COMMON_NET_URL_UTIL_H_
+
+#include <string>
+
+class GURL;
+
+namespace ui {
+class Clipboard;
+}
+
+namespace chrome_common_net {
+
+// Writes a string representation of |url| to the system clipboard.
+void WriteURLToClipboard(const GURL& url,
+                         const std::string& languages,
+                         ui::Clipboard *clipboard);
+
+// Returns a new GURL by appending the given query parameter name and the
+// value. Unsafe characters in the name and the value are escaped like
+// %XX%XX. The original query component is preserved if it's present.
+//
+// Examples:
+//
+// AppendQueryParameter(GURL("http://example.com"), "name", "value").spec()
+// => "http://example.com?name=value"
+// AppendQueryParameter(GURL("http://example.com?x=y"), "name", "value").spec()
+// => "http://example.com?x=y&name=value"
+GURL AppendQueryParameter(const GURL& url,
+                          const std::string& name,
+                          const std::string& value);
+
+// Returns a new GURL by appending or replacing the given query parameter name
+// and the value. If |name| appears more than once, only the first name-value
+// pair is replaced. Unsafe characters in the name and the value are escaped
+// like %XX%XX. The original query component is preserved if it's present.
+//
+// Examples:
+//
+// AppendOrReplaceQueryParameter(
+//     GURL("http://example.com"), "name", "new").spec()
+// => "http://example.com?name=value"
+// AppendOrReplaceQueryParameter(
+//     GURL("http://example.com?x=y&name=old"), "name", "new").spec()
+// => "http://example.com?x=y&name=new"
+GURL AppendOrReplaceQueryParameter(const GURL& url,
+                                   const std::string& name,
+                                   const std::string& value);
+
+// Looks for |search_key| in the query portion of |url|. Returns true if the
+// key is found and sets |out_value| to the unescaped value for the key.
+// Returns false if the key is not found.
+bool GetValueForKeyInQuery(const GURL& url,
+                           const std::string& search_key,
+                           std::string* out_value);
+
+}  // namespace chrome_common_net
+
+#endif  // CHROME_COMMON_NET_URL_UTIL_H_
diff --git a/chrome/common/net/url_util_unittest.cc b/chrome/common/net/url_util_unittest.cc
new file mode 100644
index 0000000..1d7fdda
--- /dev/null
+++ b/chrome/common/net/url_util_unittest.cc
@@ -0,0 +1,102 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/net/url_util.h"
+
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_common_net {
+
+TEST(UrlUtilTest, AppendQueryParameter) {
+  // Appending a name-value pair to a URL without a query component.
+  EXPECT_EQ("http://example.com/path?name=value",
+            AppendQueryParameter(GURL("http://example.com/path"),
+                                 "name", "value").spec());
+
+  // Appending a name-value pair to a URL with a query component.
+  // The original component should be preserved, and the new pair should be
+  // appended with '&'.
+  EXPECT_EQ("http://example.com/path?existing=one&name=value",
+            AppendQueryParameter(GURL("http://example.com/path?existing=one"),
+                                 "name", "value").spec());
+
+  // Appending a name-value pair with unsafe characters included. The
+  // unsafe characters should be escaped.
+  EXPECT_EQ("http://example.com/path?existing=one&na+me=v.alue%3D",
+            AppendQueryParameter(GURL("http://example.com/path?existing=one"),
+                                 "na me", "v.alue=").spec());
+
+}
+
+TEST(UrlUtilTest, AppendOrReplaceQueryParameter) {
+  // Appending a name-value pair to a URL without a query component.
+  EXPECT_EQ("http://example.com/path?name=value",
+            AppendOrReplaceQueryParameter(GURL("http://example.com/path"),
+                                 "name", "value").spec());
+
+  // Appending a name-value pair to a URL with a query component.
+  // The original component should be preserved, and the new pair should be
+  // appended with '&'.
+  EXPECT_EQ("http://example.com/path?existing=one&name=value",
+      AppendOrReplaceQueryParameter(
+          GURL("http://example.com/path?existing=one"),
+          "name", "value").spec());
+
+  // Appending a name-value pair with unsafe characters included. The
+  // unsafe characters should be escaped.
+  EXPECT_EQ("http://example.com/path?existing=one&na+me=v.alue%3D",
+      AppendOrReplaceQueryParameter(
+          GURL("http://example.com/path?existing=one"),
+          "na me", "v.alue=").spec());
+
+  // Replace value of an existing paramater.
+  EXPECT_EQ("http://example.com/path?existing=one&name=new",
+      AppendOrReplaceQueryParameter(
+          GURL("http://example.com/path?existing=one&name=old"),
+          "name", "new").spec());
+
+  // Replace a name-value pair with unsafe characters included. The
+  // unsafe characters should be escaped.
+  EXPECT_EQ("http://example.com/path?na+me=n.ew%3D&existing=one",
+      AppendOrReplaceQueryParameter(
+          GURL("http://example.com/path?na+me=old&existing=one"),
+          "na me", "n.ew=").spec());
+
+  // Replace the value of first parameter with this name only.
+  EXPECT_EQ("http://example.com/path?name=new&existing=one&name=old",
+      AppendOrReplaceQueryParameter(
+          GURL("http://example.com/path?name=old&existing=one&name=old"),
+          "name", "new").spec());
+
+  // Preserve the content of the original params regarless of our failure to
+  // interpret them correctly.
+  EXPECT_EQ("http://example.com/path?bar&name=new&left=&"
+            "=right&=&&name=again",
+      AppendOrReplaceQueryParameter(
+          GURL("http://example.com/path?bar&name=old&left=&"
+                "=right&=&&name=again"),
+          "name", "new").spec());
+}
+
+TEST(BrowserUrlUtilTest, GetValueForKeyInQuery) {
+  GURL url("http://example.com/path?name=value&boolParam&"
+           "url=http://test.com/q?n1%3Dv1%26n2");
+  std::string value;
+
+  // False when getting a non-existent query param.
+  EXPECT_FALSE(GetValueForKeyInQuery(url, "non-exist", &value));
+
+  // True when query param exist.
+  EXPECT_TRUE(GetValueForKeyInQuery(url, "name", &value));
+  EXPECT_EQ("value", value);
+
+  EXPECT_TRUE(GetValueForKeyInQuery(url, "boolParam", &value));
+  EXPECT_EQ("", value);
+
+  EXPECT_TRUE(GetValueForKeyInQuery(url, "url", &value));
+  EXPECT_EQ("http://test.com/q?n1=v1&n2", value);
+}
+
+}  // namespace chrome_common_net.
diff --git a/chrome/common/net/x509_certificate_model.cc b/chrome/common/net/x509_certificate_model.cc
new file mode 100644
index 0000000..950b4f2
--- /dev/null
+++ b/chrome/common/net/x509_certificate_model.cc
@@ -0,0 +1,93 @@
+// 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.
+
+#include "chrome/common/net/x509_certificate_model.h"
+
+#include <unicode/uidna.h>
+
+#include "base/utf_string_conversions.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace x509_certificate_model {
+
+std::string ProcessIDN(const std::string& input) {
+  // Convert the ASCII input to a string16 for ICU.
+  string16 input16;
+  input16.reserve(input.length());
+  input16.insert(input16.end(), input.begin(), input.end());
+
+  string16 output16;
+  output16.resize(input.length());
+
+  UErrorCode status = U_ZERO_ERROR;
+  int output_chars = uidna_IDNToUnicode(input16.data(), input.length(),
+                                        &output16[0], output16.length(),
+                                        UIDNA_DEFAULT, NULL, &status);
+  if (status == U_ZERO_ERROR) {
+    output16.resize(output_chars);
+  } else if (status != U_BUFFER_OVERFLOW_ERROR) {
+    return input;
+  } else {
+    output16.resize(output_chars);
+    output_chars = uidna_IDNToUnicode(input16.data(), input.length(),
+                                      &output16[0], output16.length(),
+                                      UIDNA_DEFAULT, NULL, &status);
+    if (status != U_ZERO_ERROR)
+      return input;
+    DCHECK_EQ(static_cast<size_t>(output_chars), output16.length());
+    output16.resize(output_chars);  // Just to be safe.
+  }
+
+  if (input16 == output16)
+    return input;  // Input did not contain any encoded data.
+
+  // Input contained encoded data, return formatted string showing original and
+  // decoded forms.
+  return l10n_util::GetStringFUTF8(IDS_CERT_INFO_IDN_VALUE_FORMAT,
+                                   input16, output16);
+}
+
+std::string ProcessRawBytesWithSeparators(const unsigned char* data,
+                                          size_t data_length,
+                                          char hex_separator,
+                                          char line_separator) {
+  static const char kHexChars[] = "0123456789ABCDEF";
+
+  // Each input byte creates two output hex characters + a space or newline,
+  // except for the last byte.
+  std::string ret;
+  size_t kMin = 0U;
+
+  if (!data_length)
+    return "";
+
+  ret.reserve(std::max(kMin, data_length * 3 - 1));
+
+  for (size_t i = 0; i < data_length; ++i) {
+    unsigned char b = data[i];
+    ret.push_back(kHexChars[(b >> 4) & 0xf]);
+    ret.push_back(kHexChars[b & 0xf]);
+    if (i + 1 < data_length) {
+      if ((i + 1) % 16 == 0)
+        ret.push_back(line_separator);
+      else
+        ret.push_back(hex_separator);
+    }
+  }
+  return ret;
+}
+
+std::string ProcessRawBytes(const unsigned char* data, size_t data_length) {
+  return ProcessRawBytesWithSeparators(data, data_length, ' ', '\n');
+}
+
+#if defined(USE_NSS)
+std::string ProcessRawBits(const unsigned char* data, size_t data_length) {
+  return ProcessRawBytes(data, (data_length + 7) / 8);
+}
+#endif  // USE_NSS
+
+}  // x509_certificate_model
+
diff --git a/chrome/common/net/x509_certificate_model.h b/chrome/common/net/x509_certificate_model.h
new file mode 100644
index 0000000..6c4e31c
--- /dev/null
+++ b/chrome/common/net/x509_certificate_model.h
@@ -0,0 +1,150 @@
+// 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.
+
+#ifndef CHROME_COMMON_NET_X509_CERTIFICATE_MODEL_H_
+#define CHROME_COMMON_NET_X509_CERTIFICATE_MODEL_H_
+
+#include "net/base/cert_type.h"
+#include "net/base/x509_certificate.h"
+
+// This namespace defines a set of functions to be used in UI-related bits of
+// X509 certificates. It decouples the UI from the underlying crypto library
+// (currently NSS or OpenSSL - in development).
+// This is currently only used by linux, as mac / windows use their own native
+// certificate viewers and crypto libraries.
+namespace x509_certificate_model {
+
+std::string GetCertNameOrNickname(
+    net::X509Certificate::OSCertHandle cert_handle);
+
+std::string GetNickname(net::X509Certificate::OSCertHandle cert_handle);
+
+std::string GetTokenName(net::X509Certificate::OSCertHandle cert_handle);
+
+std::string GetVersion(net::X509Certificate::OSCertHandle cert_handle);
+
+net::CertType GetType(net::X509Certificate::OSCertHandle cert_handle);
+
+std::string GetEmailAddress(net::X509Certificate::OSCertHandle cert_handle);
+
+void GetUsageStrings(
+    net::X509Certificate::OSCertHandle cert_handle,
+    std::vector<std::string>* usages);
+
+std::string GetKeyUsageString(net::X509Certificate::OSCertHandle cert_handle);
+
+std::string GetSerialNumberHexified(
+    net::X509Certificate::OSCertHandle cert_handle,
+    const std::string& alternative_text);
+
+std::string GetIssuerCommonName(
+    net::X509Certificate::OSCertHandle cert_handle,
+    const std::string& alternative_text);
+
+std::string GetIssuerOrgName(
+    net::X509Certificate::OSCertHandle cert_handle,
+    const std::string& alternative_text);
+
+std::string GetIssuerOrgUnitName(
+    net::X509Certificate::OSCertHandle cert_handle,
+    const std::string& alternative_text);
+
+std::string GetSubjectOrgName(
+    net::X509Certificate::OSCertHandle cert_handle,
+    const std::string& alternative_text);
+
+std::string GetSubjectOrgUnitName(
+    net::X509Certificate::OSCertHandle cert_handle,
+    const std::string& alternative_text);
+
+std::string GetSubjectCommonName(
+    net::X509Certificate::OSCertHandle cert_handle,
+    const std::string& alternative_text);
+
+bool GetTimes(net::X509Certificate::OSCertHandle cert_handle,
+              base::Time* issued, base::Time* expires);
+
+std::string GetTitle(net::X509Certificate::OSCertHandle cert_handle);
+std::string GetIssuerName(net::X509Certificate::OSCertHandle cert_handle);
+std::string GetSubjectName(net::X509Certificate::OSCertHandle cert_handle);
+
+void GetEmailAddresses(net::X509Certificate::OSCertHandle cert_handle,
+                       std::vector<std::string>* email_addresses);
+
+void GetNicknameStringsFromCertList(const net::CertificateList& certs,
+                                    const std::string& cert_expired,
+                                    const std::string& cert_not_yet_valid,
+                                    std::vector<std::string>* nick_names);
+
+// Returns the PKCS#11 attribute CKA_ID for a certificate as an upper-case
+// hex string, or the empty string if none is found.
+std::string GetPkcs11Id(net::X509Certificate::OSCertHandle cert_handle);
+
+struct Extension {
+  std::string name;
+  std::string value;
+};
+
+typedef std::vector<Extension> Extensions;
+
+void GetExtensions(
+    const std::string& critical_label,
+    const std::string& non_critical_label,
+    net::X509Certificate::OSCertHandle cert_handle,
+    Extensions* extensions);
+
+// Hash a certificate using the given algorithm, return the result as a
+// colon-seperated hex string.
+std::string HashCertSHA256(net::X509Certificate::OSCertHandle cert_handle);
+std::string HashCertSHA1(net::X509Certificate::OSCertHandle cert_handle);
+
+// For host values, if they contain IDN Punycode-encoded A-labels, this will
+// return a string suitable for display that contains both the original and the
+// decoded U-label form.  Otherwise, the string will be returned as is.
+std::string ProcessIDN(const std::string& input);
+
+void GetCertChainFromCert(net::X509Certificate::OSCertHandle cert_handle,
+                          net::X509Certificate::OSCertHandles* cert_handles);
+void DestroyCertChain(net::X509Certificate::OSCertHandles* cert_handles);
+
+std::string GetDerString(net::X509Certificate::OSCertHandle cert_handle);
+std::string GetCMSString(const net::X509Certificate::OSCertHandles& cert_chain,
+                         size_t start, size_t end);
+
+std::string ProcessSecAlgorithmSignature(
+    net::X509Certificate::OSCertHandle cert_handle);
+std::string ProcessSecAlgorithmSubjectPublicKey(
+    net::X509Certificate::OSCertHandle cert_handle);
+std::string ProcessSecAlgorithmSignatureWrap(
+    net::X509Certificate::OSCertHandle cert_handle);
+
+std::string ProcessSubjectPublicKeyInfo(
+    net::X509Certificate::OSCertHandle cert_handle);
+
+std::string ProcessRawBitsSignatureWrap(
+    net::X509Certificate::OSCertHandle cert_handle);
+
+void RegisterDynamicOids();
+
+// Format a buffer as |hex_separator| separated string, with 16 bytes on each
+// line separated using |line_separator|.
+std::string ProcessRawBytesWithSeparators(const unsigned char* data,
+                                          size_t data_length,
+                                          char hex_separator,
+                                          char line_separator);
+
+// Format a buffer as a space separated string, with 16 bytes on each line.
+std::string ProcessRawBytes(const unsigned char* data,
+                            size_t data_length);
+
+#if defined(USE_NSS)
+// Format a buffer as a space separated string, with 16 bytes on each line.
+// |data_length| is the length in bits.
+std::string ProcessRawBits(const unsigned char* data,
+                           size_t data_length);
+#endif  // USE_NSS
+
+}  // namespace x509_certificate_model
+
+#endif  // CHROME_COMMON_NET_X509_CERTIFICATE_MODEL_H_
diff --git a/chrome/common/net/x509_certificate_model_nss.cc b/chrome/common/net/x509_certificate_model_nss.cc
new file mode 100644
index 0000000..63205ff
--- /dev/null
+++ b/chrome/common/net/x509_certificate_model_nss.cc
@@ -0,0 +1,416 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/net/x509_certificate_model.h"
+
+#include <cert.h>
+#include <cms.h>
+#include <hasht.h>
+#include <keyhi.h>  // SECKEY_DestroyPrivateKey
+#include <keythi.h>  // SECKEYPrivateKey
+#include <pk11pub.h>  // PK11_FindKeyByAnyCert
+#include <seccomon.h>  // SECItem
+#include <sechash.h>
+
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "crypto/nss_util.h"
+#include "crypto/scoped_nss_types.h"
+#include "net/base/x509_certificate.h"
+#include "chrome/third_party/mozilla_security_manager/nsNSSCertHelper.h"
+#include "chrome/third_party/mozilla_security_manager/nsNSSCertificate.h"
+#include "chrome/third_party/mozilla_security_manager/nsUsageArrayHelper.h"
+
+namespace psm = mozilla_security_manager;
+
+namespace {
+
+// Convert a char* return value from NSS into a std::string and free the NSS
+// memory.  If the arg is NULL, an empty string will be returned instead.
+std::string Stringize(char* nss_text, const std::string& alternative_text) {
+  if (!nss_text)
+    return alternative_text;
+
+  std::string s = nss_text;
+  PORT_Free(nss_text);
+  return s;
+}
+
+// Hash a certificate using the given algorithm, return the result as a
+// colon-seperated hex string.  The len specified is the number of bytes
+// required for storing the raw fingerprint.
+// (It's a bit redundant that the caller needs to specify len in addition to the
+// algorithm, but given the limited uses, not worth fixing.)
+std::string HashCert(CERTCertificate* cert, HASH_HashType algorithm, int len) {
+  unsigned char fingerprint[HASH_LENGTH_MAX];
+
+  DCHECK(NULL != cert->derCert.data);
+  DCHECK_NE(0U, cert->derCert.len);
+  DCHECK_LE(len, HASH_LENGTH_MAX);
+  memset(fingerprint, 0, len);
+  SECStatus rv = HASH_HashBuf(algorithm, fingerprint, cert->derCert.data,
+                              cert->derCert.len);
+  DCHECK_EQ(rv, SECSuccess);
+  return x509_certificate_model::ProcessRawBytes(fingerprint, len);
+}
+
+std::string ProcessSecAlgorithmInternal(SECAlgorithmID* algorithm_id) {
+  return psm::GetOIDText(&algorithm_id->algorithm);
+}
+
+std::string ProcessExtension(
+    const std::string& critical_label,
+    const std::string& non_critical_label,
+    CERTCertExtension* extension) {
+  std::string criticality =
+      extension->critical.data && extension->critical.data[0] ?
+          critical_label : non_critical_label;
+  return criticality + "\n" +
+      psm::ProcessExtensionData(SECOID_FindOIDTag(&extension->id),
+                                &extension->value);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NSS certificate export functions.
+
+class FreeNSSCMSMessage {
+ public:
+  inline void operator()(NSSCMSMessage* x) const {
+    NSS_CMSMessage_Destroy(x);
+  }
+};
+typedef scoped_ptr_malloc<NSSCMSMessage, FreeNSSCMSMessage>
+    ScopedNSSCMSMessage;
+
+class FreeNSSCMSSignedData {
+ public:
+  inline void operator()(NSSCMSSignedData* x) const {
+    NSS_CMSSignedData_Destroy(x);
+  }
+};
+typedef scoped_ptr_malloc<NSSCMSSignedData, FreeNSSCMSSignedData>
+    ScopedNSSCMSSignedData;
+
+}  // namespace
+
+namespace x509_certificate_model {
+
+using net::X509Certificate;
+using std::string;
+
+string GetCertNameOrNickname(X509Certificate::OSCertHandle cert_handle) {
+  string name = ProcessIDN(Stringize(CERT_GetCommonName(&cert_handle->subject),
+                                     ""));
+  if (!name.empty())
+    return name;
+  return GetNickname(cert_handle);
+}
+
+string GetNickname(X509Certificate::OSCertHandle cert_handle) {
+  string name;
+  if (cert_handle->nickname) {
+    name = cert_handle->nickname;
+    // Hack copied from mozilla: Cut off text before first :, which seems to
+    // just be the token name.
+    size_t colon_pos = name.find(':');
+    if (colon_pos != string::npos)
+      name = name.substr(colon_pos + 1);
+  }
+  return name;
+}
+
+string GetTokenName(X509Certificate::OSCertHandle cert_handle) {
+  return psm::GetCertTokenName(cert_handle);
+}
+
+string GetVersion(X509Certificate::OSCertHandle cert_handle) {
+  // If the version field is omitted from the certificate, the default
+  // value is v1(0).
+  unsigned long version = 0;
+  if (cert_handle->version.len == 0 ||
+      SEC_ASN1DecodeInteger(&cert_handle->version, &version) == SECSuccess) {
+    return base::UintToString(version + 1);
+  }
+  return "";
+}
+
+net::CertType GetType(X509Certificate::OSCertHandle cert_handle) {
+    return psm::GetCertType(cert_handle);
+}
+
+string GetEmailAddress(X509Certificate::OSCertHandle cert_handle) {
+  if (cert_handle->emailAddr)
+    return cert_handle->emailAddr;
+  return "";
+}
+
+void GetUsageStrings(X509Certificate::OSCertHandle cert_handle,
+                     std::vector<string>* usages) {
+  psm::GetCertUsageStrings(cert_handle, usages);
+}
+
+string GetKeyUsageString(X509Certificate::OSCertHandle cert_handle) {
+  SECItem key_usage;
+  key_usage.data = NULL;
+  string key_usage_str;
+  if (CERT_FindKeyUsageExtension(cert_handle, &key_usage) == SECSuccess) {
+    key_usage_str = psm::ProcessKeyUsageBitString(&key_usage, ',');
+    PORT_Free(key_usage.data);
+  }
+  return key_usage_str;
+}
+
+string GetSerialNumberHexified(X509Certificate::OSCertHandle cert_handle,
+                               const string& alternative_text) {
+  return Stringize(CERT_Hexify(&cert_handle->serialNumber, true),
+                   alternative_text);
+}
+
+string GetIssuerCommonName(X509Certificate::OSCertHandle cert_handle,
+                           const string& alternative_text) {
+  return Stringize(CERT_GetCommonName(&cert_handle->issuer), alternative_text);
+}
+
+string GetIssuerOrgName(X509Certificate::OSCertHandle cert_handle,
+                        const string& alternative_text) {
+  return Stringize(CERT_GetOrgName(&cert_handle->issuer), alternative_text);
+}
+
+string GetIssuerOrgUnitName(X509Certificate::OSCertHandle cert_handle,
+                            const string& alternative_text) {
+  return Stringize(CERT_GetOrgUnitName(&cert_handle->issuer), alternative_text);
+}
+
+string GetSubjectOrgName(X509Certificate::OSCertHandle cert_handle,
+                         const string& alternative_text) {
+  return Stringize(CERT_GetOrgName(&cert_handle->subject), alternative_text);
+}
+
+string GetSubjectOrgUnitName(X509Certificate::OSCertHandle cert_handle,
+                             const string& alternative_text) {
+  return Stringize(CERT_GetOrgUnitName(&cert_handle->subject),
+                   alternative_text);
+}
+
+string GetSubjectCommonName(X509Certificate::OSCertHandle cert_handle,
+                            const string& alternative_text) {
+  return Stringize(CERT_GetCommonName(&cert_handle->subject), alternative_text);
+}
+
+bool GetTimes(X509Certificate::OSCertHandle cert_handle,
+              base::Time* issued, base::Time* expires) {
+  PRTime pr_issued, pr_expires;
+  if (CERT_GetCertTimes(cert_handle, &pr_issued, &pr_expires) == SECSuccess) {
+    *issued = crypto::PRTimeToBaseTime(pr_issued);
+    *expires = crypto::PRTimeToBaseTime(pr_expires);
+    return true;
+  }
+  return false;
+}
+
+string GetTitle(X509Certificate::OSCertHandle cert_handle) {
+  return psm::GetCertTitle(cert_handle);
+}
+
+string GetIssuerName(X509Certificate::OSCertHandle cert_handle) {
+  return psm::ProcessName(&cert_handle->issuer);
+}
+
+string GetSubjectName(X509Certificate::OSCertHandle cert_handle) {
+  return psm::ProcessName(&cert_handle->subject);
+}
+
+void GetEmailAddresses(X509Certificate::OSCertHandle cert_handle,
+                       std::vector<string>* email_addresses) {
+  for (const char* addr = CERT_GetFirstEmailAddress(cert_handle);
+       addr; addr = CERT_GetNextEmailAddress(cert_handle, addr)) {
+    // The first email addr (from Subject) may be duplicated in Subject
+    // Alternative Name, so check subsequent addresses are not equal to the
+    // first one before adding to the list.
+    if (!email_addresses->size() || (*email_addresses)[0] != addr)
+      email_addresses->push_back(addr);
+  }
+}
+
+void GetNicknameStringsFromCertList(
+    const std::vector<scoped_refptr<X509Certificate> >& certs,
+    const string& cert_expired,
+    const string& cert_not_yet_valid,
+    std::vector<string>* nick_names) {
+  CERTCertList* cert_list = CERT_NewCertList();
+  for (size_t i = 0; i < certs.size(); ++i) {
+    CERT_AddCertToListTail(
+        cert_list,
+        CERT_DupCertificate(certs[i]->os_cert_handle()));
+  }
+  // Would like to use CERT_GetCertNicknameWithValidity on each cert
+  // individually instead of having to build a CERTCertList for this, but that
+  // function is not exported.
+  CERTCertNicknames* cert_nicknames = CERT_NicknameStringsFromCertList(
+      cert_list,
+      const_cast<char*>(cert_expired.c_str()),
+      const_cast<char*>(cert_not_yet_valid.c_str()));
+  DCHECK_EQ(cert_nicknames->numnicknames,
+            static_cast<int>(certs.size()));
+
+  for (int i = 0; i < cert_nicknames->numnicknames; ++i)
+    nick_names->push_back(cert_nicknames->nicknames[i]);
+
+  CERT_FreeNicknames(cert_nicknames);
+  CERT_DestroyCertList(cert_list);
+}
+
+// For background see this discussion on dev-tech-crypto.lists.mozilla.org:
+// http://web.archiveorange.com/archive/v/6JJW7E40sypfZGtbkzxX
+//
+// NOTE: This function relies on the convention that the same PKCS#11 ID
+// is shared between a certificate and its associated private and public
+// keys.  I tried to implement this with PK11_GetLowLevelKeyIDForCert(),
+// but that always returns NULL on Chrome OS for me.
+std::string GetPkcs11Id(net::X509Certificate::OSCertHandle cert_handle) {
+  std::string pkcs11_id;
+  SECKEYPrivateKey *priv_key = PK11_FindKeyByAnyCert(cert_handle,
+                                                     NULL /* wincx */);
+  if (priv_key) {
+    // Get the CKA_ID attribute for a key.
+    SECItem* sec_item = PK11_GetLowLevelKeyIDForPrivateKey(priv_key);
+    if (sec_item) {
+      pkcs11_id = base::HexEncode(sec_item->data, sec_item->len);
+      SECITEM_FreeItem(sec_item, PR_TRUE);
+    }
+    SECKEY_DestroyPrivateKey(priv_key);
+  }
+  return pkcs11_id;
+}
+
+void GetExtensions(
+    const string& critical_label,
+    const string& non_critical_label,
+    X509Certificate::OSCertHandle cert_handle,
+    Extensions* extensions) {
+  if (cert_handle->extensions) {
+    for (size_t i = 0; cert_handle->extensions[i] != NULL; ++i) {
+      Extension extension;
+      extension.name = psm::GetOIDText(&cert_handle->extensions[i]->id);
+      extension.value = ProcessExtension(
+          critical_label, non_critical_label, cert_handle->extensions[i]);
+      extensions->push_back(extension);
+    }
+  }
+}
+
+string HashCertSHA256(X509Certificate::OSCertHandle cert_handle) {
+  return HashCert(cert_handle, HASH_AlgSHA256, SHA256_LENGTH);
+}
+
+string HashCertSHA1(X509Certificate::OSCertHandle cert_handle) {
+  return HashCert(cert_handle, HASH_AlgSHA1, SHA1_LENGTH);
+}
+
+void GetCertChainFromCert(X509Certificate::OSCertHandle cert_handle,
+                          X509Certificate::OSCertHandles* cert_handles) {
+  CERTCertList* cert_list =
+      CERT_GetCertChainFromCert(cert_handle, PR_Now(), certUsageSSLServer);
+  CERTCertListNode* node;
+  for (node = CERT_LIST_HEAD(cert_list);
+       !CERT_LIST_END(node, cert_list);
+       node = CERT_LIST_NEXT(node)) {
+    cert_handles->push_back(CERT_DupCertificate(node->cert));
+  }
+  CERT_DestroyCertList(cert_list);
+}
+
+void DestroyCertChain(X509Certificate::OSCertHandles* cert_handles) {
+  for (X509Certificate::OSCertHandles::iterator i(cert_handles->begin());
+       i != cert_handles->end(); ++i)
+    CERT_DestroyCertificate(*i);
+  cert_handles->clear();
+}
+
+string GetDerString(X509Certificate::OSCertHandle cert_handle) {
+  return string(reinterpret_cast<const char*>(cert_handle->derCert.data),
+                cert_handle->derCert.len);
+}
+
+string GetCMSString(const X509Certificate::OSCertHandles& cert_chain,
+                    size_t start, size_t end) {
+  crypto::ScopedPLArenaPool arena(PORT_NewArena(1024));
+  DCHECK(arena.get());
+
+  ScopedNSSCMSMessage message(NSS_CMSMessage_Create(arena.get()));
+  DCHECK(message.get());
+
+  // First, create SignedData with the certificate only (no chain).
+  ScopedNSSCMSSignedData signed_data(NSS_CMSSignedData_CreateCertsOnly(
+      message.get(), cert_chain[start], PR_FALSE));
+  if (!signed_data.get()) {
+    DLOG(ERROR) << "NSS_CMSSignedData_Create failed";
+    return "";
+  }
+  // Add the rest of the chain (if any).
+  for (size_t i = start + 1; i < end; ++i) {
+    if (NSS_CMSSignedData_AddCertificate(signed_data.get(), cert_chain[i]) !=
+        SECSuccess) {
+      DLOG(ERROR) << "NSS_CMSSignedData_AddCertificate failed on " << i;
+      return "";
+    }
+  }
+
+  NSSCMSContentInfo *cinfo = NSS_CMSMessage_GetContentInfo(message.get());
+  if (NSS_CMSContentInfo_SetContent_SignedData(
+      message.get(), cinfo, signed_data.get()) == SECSuccess) {
+    ignore_result(signed_data.release());
+  } else {
+    DLOG(ERROR) << "NSS_CMSMessage_GetContentInfo failed";
+    return "";
+  }
+
+  SECItem cert_p7 = { siBuffer, NULL, 0 };
+  NSSCMSEncoderContext *ecx = NSS_CMSEncoder_Start(message.get(), NULL, NULL,
+                                                   &cert_p7, arena.get(), NULL,
+                                                   NULL, NULL, NULL, NULL,
+                                                   NULL);
+  if (!ecx) {
+    DLOG(ERROR) << "NSS_CMSEncoder_Start failed";
+    return "";
+  }
+
+  if (NSS_CMSEncoder_Finish(ecx) != SECSuccess) {
+    DLOG(ERROR) << "NSS_CMSEncoder_Finish failed";
+    return "";
+  }
+
+  return string(reinterpret_cast<const char*>(cert_p7.data), cert_p7.len);
+}
+
+string ProcessSecAlgorithmSignature(X509Certificate::OSCertHandle cert_handle) {
+  return ProcessSecAlgorithmInternal(&cert_handle->signature);
+}
+
+string ProcessSecAlgorithmSubjectPublicKey(
+    X509Certificate::OSCertHandle cert_handle) {
+  return ProcessSecAlgorithmInternal(
+      &cert_handle->subjectPublicKeyInfo.algorithm);
+}
+
+string ProcessSecAlgorithmSignatureWrap(
+    X509Certificate::OSCertHandle cert_handle) {
+  return ProcessSecAlgorithmInternal(
+      &cert_handle->signatureWrap.signatureAlgorithm);
+}
+
+string ProcessSubjectPublicKeyInfo(X509Certificate::OSCertHandle cert_handle) {
+  return psm::ProcessSubjectPublicKeyInfo(&cert_handle->subjectPublicKeyInfo);
+}
+
+string ProcessRawBitsSignatureWrap(X509Certificate::OSCertHandle cert_handle) {
+  return ProcessRawBits(cert_handle->signatureWrap.signature.data,
+                        cert_handle->signatureWrap.signature.len);
+}
+
+void RegisterDynamicOids() {
+  psm::RegisterDynamicOids();
+}
+
+}  // namespace x509_certificate_model
diff --git a/chrome/common/net/x509_certificate_model_openssl.cc b/chrome/common/net/x509_certificate_model_openssl.cc
new file mode 100644
index 0000000..114a242
--- /dev/null
+++ b/chrome/common/net/x509_certificate_model_openssl.cc
@@ -0,0 +1,271 @@
+// 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.
+
+#include "chrome/common/net/x509_certificate_model.h"
+
+#include <openssl/obj_mac.h>
+#include <openssl/sha.h>
+#include <openssl/x509v3.h>
+
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "net/base/x509_util_openssl.h"
+
+namespace x509_util = net::x509_util;
+
+namespace {
+
+std::string AlternativeWhenEmpty(const std::string& text,
+                                 const std::string& alternative) {
+  return text.empty() ? alternative : text;
+}
+
+std::string GetKeyValuesFromName(X509_NAME* name) {
+  std::string ret;
+  int rdns = X509_NAME_entry_count(name) - 1;
+  for (int i = rdns; i >= 0; --i) {
+    std::string key;
+    std::string value;
+    if (!x509_util::ParsePrincipalKeyAndValueByIndex(name, i, &key, &value))
+      break;
+    ret += key;
+    ret += " = ";
+    ret += value;
+    ret += '\n';
+  }
+  return ret;
+}
+
+}  // namepsace
+
+namespace x509_certificate_model {
+
+using net::X509Certificate;
+
+std::string GetCertNameOrNickname(X509Certificate::OSCertHandle cert_handle) {
+  // TODO(bulach): implement me.
+  return "";
+}
+
+std::string GetNickname(X509Certificate::OSCertHandle cert_handle) {
+  // TODO(jamescook): implement me.
+  return "";
+}
+
+std::string GetTokenName(X509Certificate::OSCertHandle cert_handle) {
+  // TODO(bulach): implement me.
+  return "";
+}
+
+std::string GetVersion(net::X509Certificate::OSCertHandle cert_handle) {
+  unsigned long version = X509_get_version(cert_handle);
+  if (version != ULONG_MAX)
+    return base::UintToString(version + 1);
+  return "";
+}
+
+net::CertType GetType(X509Certificate::OSCertHandle os_cert) {
+  // TODO(bulach): implement me.
+  return net::UNKNOWN_CERT;
+}
+
+std::string GetEmailAddress(X509Certificate::OSCertHandle os_cert) {
+  // TODO(bulach): implement me.
+  return "";
+}
+
+void GetUsageStrings(X509Certificate::OSCertHandle cert_handle,
+                         std::vector<std::string>* usages) {
+  // TODO(bulach): implement me.
+}
+
+std::string GetKeyUsageString(X509Certificate::OSCertHandle cert_handle) {
+  // TODO(bulach): implement me.
+  return "";
+}
+
+std::string GetSerialNumberHexified(
+    X509Certificate::OSCertHandle cert_handle,
+    const std::string& alternative_text) {
+  ASN1_INTEGER* num = X509_get_serialNumber(cert_handle);
+  const char kSerialNumberSeparator = ':';
+  std::string hex_string = ProcessRawBytesWithSeparators(
+      num->data, num->length, kSerialNumberSeparator, kSerialNumberSeparator);
+  return AlternativeWhenEmpty(hex_string, alternative_text);
+}
+
+std::string GetIssuerCommonName(
+    X509Certificate::OSCertHandle cert_handle,
+    const std::string& alternative_text) {
+  std::string ret;
+  x509_util::ParsePrincipalValueByNID(X509_get_issuer_name(cert_handle),
+                                      NID_commonName, &ret);
+  return AlternativeWhenEmpty(ret, alternative_text);
+}
+
+std::string GetIssuerOrgName(
+    X509Certificate::OSCertHandle cert_handle,
+    const std::string& alternative_text) {
+  std::string ret;
+  x509_util::ParsePrincipalValueByNID(X509_get_issuer_name(cert_handle),
+                                      NID_organizationName, &ret);
+  return AlternativeWhenEmpty(ret, alternative_text);
+}
+
+std::string GetIssuerOrgUnitName(
+    X509Certificate::OSCertHandle cert_handle,
+    const std::string& alternative_text) {
+  std::string ret;
+  x509_util::ParsePrincipalValueByNID(X509_get_issuer_name(cert_handle),
+                                      NID_organizationalUnitName, &ret);
+  return AlternativeWhenEmpty(ret, alternative_text);
+}
+
+std::string GetSubjectOrgName(
+    X509Certificate::OSCertHandle cert_handle,
+    const std::string& alternative_text) {
+  std::string ret;
+  x509_util::ParsePrincipalValueByNID(X509_get_subject_name(cert_handle),
+                                      NID_organizationName, &ret);
+  return AlternativeWhenEmpty(ret, alternative_text);
+}
+
+std::string GetSubjectOrgUnitName(
+    X509Certificate::OSCertHandle cert_handle,
+    const std::string& alternative_text) {
+  std::string ret;
+  x509_util::ParsePrincipalValueByNID(X509_get_subject_name(cert_handle),
+                                      NID_organizationalUnitName, &ret);
+  return AlternativeWhenEmpty(ret, alternative_text);
+}
+
+std::string GetSubjectCommonName(X509Certificate::OSCertHandle cert_handle,
+                                 const std::string& alternative_text) {
+  std::string ret;
+  x509_util::ParsePrincipalValueByNID(X509_get_subject_name(cert_handle),
+                                      NID_commonName, &ret);
+  return AlternativeWhenEmpty(ret, alternative_text);
+}
+
+bool GetTimes(X509Certificate::OSCertHandle cert_handle,
+              base::Time* issued, base::Time* expires) {
+  return x509_util::ParseDate(X509_get_notBefore(cert_handle), issued) &&
+         x509_util::ParseDate(X509_get_notAfter(cert_handle), expires);
+}
+
+std::string GetTitle(net::X509Certificate::OSCertHandle cert_handle) {
+  // TODO(bulach): implement me.
+  return "";
+}
+
+std::string GetIssuerName(net::X509Certificate::OSCertHandle cert_handle) {
+  return GetKeyValuesFromName(X509_get_issuer_name(cert_handle));
+}
+
+std::string GetSubjectName(net::X509Certificate::OSCertHandle cert_handle) {
+  return GetKeyValuesFromName(X509_get_subject_name(cert_handle));
+}
+
+void GetEmailAddresses(net::X509Certificate::OSCertHandle cert_handle,
+                       std::vector<std::string>* email_addresses) {
+  // TODO(bulach): implement me.
+}
+
+void GetNicknameStringsFromCertList(
+    const std::vector<scoped_refptr<net::X509Certificate> >& certs,
+    const std::string& cert_expired,
+    const std::string& cert_not_yet_valid,
+    std::vector<std::string>* nick_names) {
+  // TODO(bulach): implement me.
+}
+
+std::string GetPkcs11Id(net::X509Certificate::OSCertHandle cert_handle) {
+  // TODO(jamescook): implement me.
+  return "";
+}
+
+void GetExtensions(
+    const std::string& critical_label,
+    const std::string& non_critical_label,
+    net::X509Certificate::OSCertHandle cert_handle,
+    Extensions* extensions) {
+  // TODO(bulach): implement me.
+}
+
+std::string HashCertSHA256(net::X509Certificate::OSCertHandle cert_handle) {
+  unsigned char sha256_data[SHA256_DIGEST_LENGTH] = {0};
+  unsigned int sha256_size = sizeof(sha256_data);
+  int ret = X509_digest(cert_handle, EVP_sha256(), sha256_data, &sha256_size);
+  DCHECK(ret);
+  DCHECK_EQ(sha256_size, sizeof(sha256_data));
+  return ProcessRawBytes(sha256_data, sha256_size);
+}
+
+std::string HashCertSHA1(net::X509Certificate::OSCertHandle cert_handle) {
+  unsigned char sha1_data[SHA_DIGEST_LENGTH] = {0};
+  unsigned int sha1_size = sizeof(sha1_data);
+  int ret = X509_digest(cert_handle, EVP_sha1(), sha1_data, &sha1_size);
+  DCHECK(ret);
+  DCHECK_EQ(sha1_size, sizeof(sha1_data));
+  return ProcessRawBytes(sha1_data, sha1_size);
+}
+
+void GetCertChainFromCert(net::X509Certificate::OSCertHandle cert_handle,
+                          net::X509Certificate::OSCertHandles* cert_handles) {
+  // TODO(bulach): how to get the chain out of a certificate?
+  cert_handles->push_back(net::X509Certificate::DupOSCertHandle(cert_handle));
+}
+
+void DestroyCertChain(net::X509Certificate::OSCertHandles* cert_handles) {
+  for (net::X509Certificate::OSCertHandles::iterator i = cert_handles->begin();
+       i != cert_handles->end(); ++i)
+    X509_free(*i);
+  cert_handles->clear();
+}
+
+std::string GetDerString(net::X509Certificate::OSCertHandle cert_handle) {
+  // TODO(bulach): implement me.
+  return "";
+}
+
+std::string GetCMSString(const net::X509Certificate::OSCertHandles& cert_chain,
+                         size_t start, size_t end) {
+  // TODO(bulach): implement me.
+  return "";
+}
+
+std::string ProcessSecAlgorithmSignature(
+    net::X509Certificate::OSCertHandle cert_handle) {
+  // TODO(bulach): implement me.
+  return "";
+}
+
+std::string ProcessSecAlgorithmSubjectPublicKey(
+    net::X509Certificate::OSCertHandle cert_handle) {
+  // TODO(bulach): implement me.
+  return "";
+}
+
+std::string ProcessSecAlgorithmSignatureWrap(
+    net::X509Certificate::OSCertHandle cert_handle) {
+  // TODO(bulach): implement me.
+  return "";
+}
+
+std::string ProcessSubjectPublicKeyInfo(
+    net::X509Certificate::OSCertHandle cert_handle) {
+  // TODO(bulach): implement me.
+  return "";
+}
+
+std::string ProcessRawBitsSignatureWrap(
+    net::X509Certificate::OSCertHandle cert_handle) {
+  // TODO(bulach): implement me.
+  return "";
+}
+
+void RegisterDynamicOids() {
+}
+
+}  // namespace x509_certificate_model
diff --git a/chrome/common/net/x509_certificate_model_unittest.cc b/chrome/common/net/x509_certificate_model_unittest.cc
new file mode 100644
index 0000000..5b40b65
--- /dev/null
+++ b/chrome/common/net/x509_certificate_model_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/net/x509_certificate_model.h"
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "net/base/cert_test_util.h"
+#include "net/base/nss_cert_database.h"
+#include "net/base/test_data_directory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(X509CertificateModelTest, GetTypeCA) {
+  scoped_refptr<net::X509Certificate> cert(
+      net::ImportCertFromFile(net::GetTestCertsDirectory(),
+                              "root_ca_cert.crt"));
+  ASSERT_TRUE(cert.get());
+
+#if defined(USE_OPENSSL)
+  // Remove this when OpenSSL build implements the necessary functions.
+  EXPECT_EQ(net::UNKNOWN_CERT,
+            x509_certificate_model::GetType(cert->os_cert_handle()));
+#else
+  EXPECT_EQ(net::CA_CERT,
+            x509_certificate_model::GetType(cert->os_cert_handle()));
+
+  // Test that explicitly distrusted CA certs are still returned as CA_CERT
+  // type. See http://crbug.com/96654.
+  EXPECT_TRUE(net::NSSCertDatabase::GetInstance()->SetCertTrust(
+      cert, net::CA_CERT, net::NSSCertDatabase::DISTRUSTED_SSL));
+
+  EXPECT_EQ(net::CA_CERT,
+            x509_certificate_model::GetType(cert->os_cert_handle()));
+#endif
+}
+
+TEST(X509CertificateModelTest, GetTypeServer) {
+  scoped_refptr<net::X509Certificate> cert(
+      net::ImportCertFromFile(net::GetTestCertsDirectory(),
+                              "google.single.der"));
+  ASSERT_TRUE(cert.get());
+
+#if defined(USE_OPENSSL)
+  // Remove this when OpenSSL build implements the necessary functions.
+  EXPECT_EQ(net::UNKNOWN_CERT,
+            x509_certificate_model::GetType(cert->os_cert_handle()));
+#else
+  // Test mozilla_security_manager::GetCertType with server certs and default
+  // trust.  Currently this doesn't work.
+  // TODO(mattm): make mozilla_security_manager::GetCertType smarter so we can
+  // tell server certs even if they have no trust bits set.
+  EXPECT_EQ(net::UNKNOWN_CERT,
+            x509_certificate_model::GetType(cert->os_cert_handle()));
+
+  net::NSSCertDatabase* cert_db = net::NSSCertDatabase::GetInstance();
+  // Test GetCertType with server certs and explicit trust.
+  EXPECT_TRUE(cert_db->SetCertTrust(
+      cert, net::SERVER_CERT, net::NSSCertDatabase::TRUSTED_SSL));
+
+  EXPECT_EQ(net::SERVER_CERT,
+            x509_certificate_model::GetType(cert->os_cert_handle()));
+
+  // Test GetCertType with server certs and explicit distrust.
+  EXPECT_TRUE(cert_db->SetCertTrust(
+      cert, net::SERVER_CERT, net::NSSCertDatabase::DISTRUSTED_SSL));
+
+  EXPECT_EQ(net::SERVER_CERT,
+            x509_certificate_model::GetType(cert->os_cert_handle()));
+#endif
+}
+
+// An X.509 v1 certificate with the version field omitted should get
+// the default value v1.
+TEST(X509CertificateModelTest, GetVersionOmitted) {
+  scoped_refptr<net::X509Certificate> cert(
+      net::ImportCertFromFile(net::GetTestCertsDirectory(),
+                              "ndn.ca.crt"));
+  ASSERT_TRUE(cert.get());
+
+  EXPECT_EQ("1", x509_certificate_model::GetVersion(cert->os_cert_handle()));
+}
diff --git a/chrome/common/password_form_fill_data.cc b/chrome/common/password_form_fill_data.cc
new file mode 100644
index 0000000..c733719
--- /dev/null
+++ b/chrome/common/password_form_fill_data.cc
@@ -0,0 +1,45 @@
+// Copyright 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.
+
+#include "chrome/common/password_form_fill_data.h"
+
+#include "base/logging.h"
+#include "chrome/common/form_field_data.h"
+
+PasswordFormFillData::PasswordFormFillData() : wait_for_username(false) {
+}
+
+PasswordFormFillData::~PasswordFormFillData() {
+}
+
+void InitPasswordFormFillData(
+    const content::PasswordForm& form_on_page,
+    const content::PasswordFormMap& matches,
+    const content::PasswordForm* const preferred_match,
+    bool wait_for_username_before_autofill,
+    PasswordFormFillData* result) {
+  // Note that many of the |FormFieldData| members are not initialized for
+  // |username_field| and |password_field| because they are currently not used
+  // by the password autocomplete code.
+  FormFieldData username_field;
+  username_field.name = form_on_page.username_element;
+  username_field.value = preferred_match->username_value;
+  FormFieldData password_field;
+  password_field.name = form_on_page.password_element;
+  password_field.value = preferred_match->password_value;
+
+  // Fill basic form data.
+  result->basic_data.origin = form_on_page.origin;
+  result->basic_data.action = form_on_page.action;
+  result->basic_data.fields.push_back(username_field);
+  result->basic_data.fields.push_back(password_field);
+  result->wait_for_username = wait_for_username_before_autofill;
+
+  // Copy additional username/value pairs.
+  content::PasswordFormMap::const_iterator iter;
+  for (iter = matches.begin(); iter != matches.end(); iter++) {
+    if (iter->second != preferred_match)
+      result->additional_logins[iter->first] = iter->second->password_value;
+  }
+}
diff --git a/chrome/common/password_form_fill_data.h b/chrome/common/password_form_fill_data.h
new file mode 100644
index 0000000..00850a9
--- /dev/null
+++ b/chrome/common/password_form_fill_data.h
@@ -0,0 +1,46 @@
+// Copyright 2012 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.
+
+#ifndef CHROME_COMMON_PASSWORD_FORM_FILL_DATA_H_
+#define CHROME_COMMON_PASSWORD_FORM_FILL_DATA_H_
+
+#include <map>
+
+#include "base/memory/scoped_ptr.h"
+#include "chrome/common/form_data.h"
+#include "content/public/common/password_form.h"
+
+// Structure used for autofilling password forms.
+// basic_data identifies the HTML form on the page and preferred username/
+//            password for login, while
+// additional_logins is a list of other matching user/pass pairs for the form.
+// wait_for_username tells us whether we need to wait for the user to enter
+// a valid username before we autofill the password. By default, this is off
+// unless the PasswordManager determined there is an additional risk
+// associated with this form. This can happen, for example, if action URI's
+// of the observed form and our saved representation don't match up.
+struct PasswordFormFillData {
+  typedef std::map<string16, string16> LoginCollection;
+
+  FormData basic_data;
+  LoginCollection additional_logins;
+  bool wait_for_username;
+  PasswordFormFillData();
+  ~PasswordFormFillData();
+};
+
+// Create a FillData structure in preparation for autofilling a form,
+// from basic_data identifying which form to fill, and a collection of
+// matching stored logins to use as username/password values.
+// preferred_match should equal (address) one of matches.
+// wait_for_username_before_autofill is true if we should not autofill
+// anything until the user typed in a valid username and blurred the field.
+void InitPasswordFormFillData(
+    const content::PasswordForm& form_on_page,
+    const content::PasswordFormMap& matches,
+    const content::PasswordForm* const preferred_match,
+    bool wait_for_username_before_autofill,
+    PasswordFormFillData* result);
+
+#endif  // CHROME_COMMON_PASSWORD_FORM_FILL_DATA_H__
diff --git a/chrome/common/password_generation_util.cc b/chrome/common/password_generation_util.cc
new file mode 100644
index 0000000..f6f7027
--- /dev/null
+++ b/chrome/common/password_generation_util.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/password_generation_util.h"
+
+#include "base/metrics/histogram.h"
+
+namespace {
+
+// Enumerates user actions after password generation bubble is shown.
+enum UserAction {
+  // User closes the bubble without any meaningful actions (e.g. use backspace
+  // key, close the bubble, click outside the bubble, etc).
+  IGNORE_FEATURE,
+
+  // User navigates to the learn more page. Note that in the current
+  // implementation this will result in closing the bubble so this action
+  // doesn't overlap with the following two actions.
+  LEARN_MORE,
+
+  // User accepts the generated password without manually editing it (but
+  // including changing it through the regenerate button).
+  ACCEPT_ORIGINAL_PASSWORD,
+
+  // User accepts the gererated password after manually editing it.
+  ACCEPT_AFTER_EDITING,
+
+  // Number of enum entries, used for UMA histogram reporting macros.
+  ACTION_ENUM_COUNT
+};
+
+}  // anonymous namespace
+
+namespace password_generation {
+
+PasswordGenerationActions::PasswordGenerationActions()
+    : learn_more_visited(false),
+      password_accepted(false),
+      password_edited(false),
+      password_regenerated(false) {
+}
+
+PasswordGenerationActions::~PasswordGenerationActions() {
+}
+
+void LogUserActions(PasswordGenerationActions actions) {
+  UserAction action = IGNORE_FEATURE;
+  if (actions.password_accepted) {
+    if (actions.password_edited)
+      action = ACCEPT_AFTER_EDITING;
+    else
+      action = ACCEPT_ORIGINAL_PASSWORD;
+  } else if (actions.learn_more_visited) {
+    action = LEARN_MORE;
+  }
+  UMA_HISTOGRAM_ENUMERATION("PasswordGeneration.UserActions",
+                            action, ACTION_ENUM_COUNT);
+}
+
+void LogPasswordGenerationEvent(PasswordGenerationEvent event) {
+  UMA_HISTOGRAM_ENUMERATION("PasswordGeneration.Event",
+                            event, EVENT_ENUM_COUNT);
+}
+
+}  // namespace password_generation
diff --git a/chrome/common/password_generation_util.h b/chrome/common/password_generation_util.h
new file mode 100644
index 0000000..56d8967
--- /dev/null
+++ b/chrome/common/password_generation_util.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_PASSWORD_GENERATION_UTIL_H_
+#define CHROME_COMMON_PASSWORD_GENERATION_UTIL_H_
+
+namespace password_generation {
+
+// Enumerates various events related to the password generation process.
+enum PasswordGenerationEvent {
+  // No Account creation form is detected.
+  NO_SIGN_UP_DETECTED,
+
+  // Account creation form is detected.
+  SIGN_UP_DETECTED,
+
+  // Password generation icon is shown inside the first password field.
+  ICON_SHOWN,
+
+  // Password generation bubble is shown after user clicks on the icon.
+  BUBBLE_SHOWN,
+
+  // Number of enum entries, used for UMA histogram reporting macros.
+  EVENT_ENUM_COUNT
+};
+
+// Wrapper to store the user interactions with the password generation bubble.
+struct PasswordGenerationActions {
+  // Whether the user has clicked on the learn more link.
+  bool learn_more_visited;
+
+  // Whether the user has accepted the generated password.
+  bool password_accepted;
+
+  // Whether the user has manually edited password entry.
+  bool password_edited;
+
+  // Whether the user has clicked on the regenerate button.
+  bool password_regenerated;
+
+  PasswordGenerationActions();
+  ~PasswordGenerationActions();
+};
+
+void LogUserActions(PasswordGenerationActions actions);
+
+void LogPasswordGenerationEvent(PasswordGenerationEvent event);
+
+}  // namespace password_generation
+
+#endif  // CHROME_COMMON_PASSWORD_GENERATION_UTIL_H_
diff --git a/chrome/common/pepper_flash.cc b/chrome/common/pepper_flash.cc
new file mode 100644
index 0000000..d443625
--- /dev/null
+++ b/chrome/common/pepper_flash.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/pepper_flash.h"
+
+#include "ppapi/shared_impl/ppapi_permissions.h"
+
+bool ConductingPepperFlashFieldTrial() {
+#if defined(OS_WIN)
+  return true;
+#elif defined(OS_MACOSX)
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool IsPepperFlashEnabledByDefault() {
+#if defined(USE_AURA)
+  // Pepper Flash is required for Aura (on any OS).
+  return true;
+#elif defined(OS_WIN)
+  return true;
+#elif defined(OS_LINUX)
+  // For Linux, always try to use it (availability is checked elsewhere).
+  return true;
+#elif defined(OS_MACOSX)
+  return true;
+#else
+  return false;
+#endif
+}
+
+int32 kPepperFlashPermissions = ppapi::PERMISSION_DEV |
+                                ppapi::PERMISSION_PRIVATE |
+                                ppapi::PERMISSION_BYPASS_USER_GESTURE |
+                                ppapi::PERMISSION_FLASH;
+
+
diff --git a/chrome/common/pepper_flash.h b/chrome/common/pepper_flash.h
new file mode 100644
index 0000000..e73a198
--- /dev/null
+++ b/chrome/common/pepper_flash.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_PEPPER_FLASH_H_
+#define CHROME_COMMON_PEPPER_FLASH_H_
+
+#include "base/basictypes.h"
+
+// Whether a field trial for Pepper Flash is going on.
+bool ConductingPepperFlashFieldTrial();
+
+// True if Pepper Flash should be enabled by default.
+bool IsPepperFlashEnabledByDefault();
+
+// Permission bits for Pepper Flash.
+extern int32 kPepperFlashPermissions;
+
+#endif  // CHROME_COMMON_PEPPER_FLASH_H_
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
new file mode 100644
index 0000000..c14fad9
--- /dev/null
+++ b/chrome/common/pref_names.cc
@@ -0,0 +1,2140 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/pref_names.h"
+
+#include "base/basictypes.h"
+
+namespace prefs {
+
+// *************** PROFILE PREFS ***************
+// These are attached to the user profile
+
+// A string property indicating whether default apps should be installed
+// in this profile.  Use the value "install" to enable defaults apps, or
+// "noinstall" to disable them.  This property is usually set in the
+// master_preferences and copied into the profile preferences on first run.
+// Defaults apps are installed only when creating a new profile.
+const char kDefaultApps[] = "default_apps";
+
+// Whether we have installed default apps yet in this profile.
+const char kDefaultAppsInstalled[] = "default_apps_installed";
+
+// A boolean specifying whether the New Tab page is the home page or not.
+const char kHomePageIsNewTabPage[] = "homepage_is_newtabpage";
+
+// This is the URL of the page to load when opening new tabs.
+const char kHomePage[] = "homepage";
+
+// Did the user change the home page after install?
+const char kHomePageChanged[] = "homepage_changed";
+
+// Does this user have a Google+ Profile?
+const char kIsGooglePlusUser[] = "is_google_plus_user";
+
+// Used to determine if the last session exited cleanly. Set to false when
+// first opened, and to true when closing. On startup if the value is false,
+// it means the profile didn't exit cleanly.
+// DEPRECATED: this is replaced by kSessionExitType and exists for backwards
+// compatability.
+const char kSessionExitedCleanly[] = "profile.exited_cleanly";
+
+// A string pref whose values is one of the values defined by
+// |ProfileImpl::kPrefExitTypeXXX|. Set to |kPrefExitTypeCrashed| on startup and
+// one of |kPrefExitTypeNormal| or |kPrefExitTypeSessionEnded| during
+// shutdown. Used to determine the exit type the last time the profile was open.
+const char kSessionExitType[] = "profile.exit_type";
+
+// An integer pref. Holds one of several values:
+// 0: (deprecated) open the homepage on startup.
+// 1: restore the last session.
+// 2: this was used to indicate a specific session should be restored. It is
+//    no longer used, but saved to avoid conflict with old preferences.
+// 3: unused, previously indicated the user wants to restore a saved session.
+// 4: restore the URLs defined in kURLsToRestoreOnStartup.
+// 5: open the New Tab Page on startup.
+const char kRestoreOnStartup[] = "session.restore_on_startup";
+
+// The URLs to restore on startup or when the home button is pressed. The URLs
+// are only restored on startup if kRestoreOnStartup is 4.
+const char kURLsToRestoreOnStartup[] = "session.urls_to_restore_on_startup";
+
+// A preference to keep track of whether we have already checked whether we
+// need to migrate the user from kRestoreOnStartup=0 to kRestoreOnStartup=4.
+// We only need to do this check once, on upgrade from m18 or lower to m19 or
+// higher.
+const char kRestoreOnStartupMigrated[] = "session.restore_on_startup_migrated";
+
+// Disables screenshot accelerators and extension APIs.
+// This setting resides both in profile prefs and local state. Accelerator
+// handling code reads local state, while extension APIs use profile pref.
+const char kDisableScreenshots[] = "disable_screenshots";
+
+// The application locale.
+// For OS_CHROMEOS we maintain kApplicationLocale property in both local state
+// and user's profile.  Global property determines locale of login screen,
+// while user's profile determines his personal locale preference.
+const char kApplicationLocale[] = "intl.app_locale";
+#if defined(OS_CHROMEOS)
+// Locale preference of device' owner.  ChromeOS device appears in this locale
+// after startup/wakeup/signout.
+const char kOwnerLocale[] = "intl.owner_locale";
+// Locale accepted by user.  Non-syncable.
+// Used to determine whether we need to show Locale Change notification.
+const char kApplicationLocaleAccepted[] = "intl.app_locale_accepted";
+// Non-syncable item.
+// It is used in two distinct ways.
+// (1) Used for two-step initialization of locale in ChromeOS
+//     because synchronization of kApplicationLocale is not instant.
+// (2) Used to detect locale change.  Locale change is detected by
+//     LocaleChangeGuard in case values of kApplicationLocaleBackup and
+//     kApplicationLocale are both non-empty and differ.
+// Following is a table showing how state of those prefs may change upon
+// common real-life use cases:
+//                                  AppLocale Backup Accepted
+// Initial login                       -        A       -
+// Sync                                B        A       -
+// Accept (B)                          B        B       B
+// -----------------------------------------------------------
+// Initial login                       -        A       -
+// No sync and second login            A        A       -
+// Change options                      B        B       -
+// -----------------------------------------------------------
+// Initial login                       -        A       -
+// Sync                                A        A       -
+// Locale changed on login screen      A        C       -
+// Accept (A)                          A        A       A
+// -----------------------------------------------------------
+// Initial login                       -        A       -
+// Sync                                B        A       -
+// Revert                              A        A       -
+const char kApplicationLocaleBackup[] = "intl.app_locale_backup";
+#endif
+
+// The default character encoding to assume for a web page in the
+// absence of MIME charset specification
+const char kDefaultCharset[] = "intl.charset_default";
+
+// The value to use for Accept-Languages HTTP header when making an HTTP
+// request.
+const char kAcceptLanguages[] = "intl.accept_languages";
+
+// The value to use for showing locale-dependent encoding list for different
+// locale, it's initialized from the corresponding string resource that is
+// stored in non-translatable part of the resource bundle.
+const char kStaticEncodings[] = "intl.static_encodings";
+
+// Obselete WebKit prefs for migration.
+const char kGlobalDefaultCharset[] = "intl.global.charset_default";
+const char kWebKitGlobalDefaultFontSize[] =
+    "webkit.webprefs.global.default_font_size";
+const char kWebKitGlobalDefaultFixedFontSize[] =
+    "webkit.webprefs.global.default_fixed_font_size";
+const char kWebKitGlobalMinimumFontSize[] =
+    "webkit.webprefs.global.minimum_font_size";
+const char kWebKitGlobalMinimumLogicalFontSize[] =
+    "webkit.webprefs.global.minimum_logical_font_size";
+const char kWebKitGlobalJavascriptCanOpenWindowsAutomatically[] =
+    "webkit.webprefs.global.javascript_can_open_windows_automatically";
+const char kWebKitGlobalJavascriptEnabled[] =
+    "webkit.webprefs.global.javascript_enabled";
+const char kWebKitGlobalLoadsImagesAutomatically[] =
+    "webkit.webprefs.global.loads_images_automatically";
+const char kWebKitGlobalPluginsEnabled[] =
+    "webkit.webprefs.global.plugins_enabled";
+const char kWebKitGlobalStandardFontFamily[] =
+    "webkit.webprefs.global.standard_font_family";
+const char kWebKitGlobalFixedFontFamily[] =
+    "webkit.webprefs.global.fixed_font_family";
+const char kWebKitGlobalSerifFontFamily[] =
+    "webkit.webprefs.global.serif_font_family";
+const char kWebKitGlobalSansSerifFontFamily[] =
+    "webkit.webprefs.global.sansserif_font_family";
+const char kWebKitGlobalCursiveFontFamily[] =
+    "webkit.webprefs.global.cursive_font_family";
+const char kWebKitGlobalFantasyFontFamily[] =
+    "webkit.webprefs.global.fantasy_font_family";
+const char kWebKitOldStandardFontFamily[] =
+    "webkit.webprefs.standard_font_family";
+const char kWebKitOldFixedFontFamily[] = "webkit.webprefs.fixed_font_family";
+const char kWebKitOldSerifFontFamily[] = "webkit.webprefs.serif_font_family";
+const char kWebKitOldSansSerifFontFamily[] =
+    "webkit.webprefs.sansserif_font_family";
+const char kWebKitOldCursiveFontFamily[] =
+    "webkit.webprefs.cursive_font_family";
+const char kWebKitOldFantasyFontFamily[] =
+    "webkit.webprefs.fantasy_font_family";
+
+// If these change, the corresponding enums in the extension API
+// experimental.fontSettings.json must also change.
+const char* const kWebKitScriptsForFontFamilyMaps[] = {
+  "Afak", "Arab", "Armi", "Armn", "Avst", "Bali", "Bamu", "Bass", "Batk",
+  "Beng", "Blis", "Bopo", "Brah", "Brai", "Bugi", "Buhd", "Cakm", "Cans",
+  "Cari", "Cham", "Cher", "Cirt", "Copt", "Cprt", "Cyrl", "Cyrs", "Deva",
+  "Dsrt", "Dupl", "Egyd", "Egyh", "Egyp", "Elba", "Ethi", "Geor", "Geok",
+  "Glag", "Goth", "Gran", "Grek", "Gujr", "Guru", "Hang", "Hani", "Hano",
+  "Hans", "Hant", "Hebr", "Hluw", "Hmng", "Hung", "Inds", "Ital", "Java",
+  "Jpan", "Jurc", "Kali", "Khar", "Khmr", "Khoj", "Knda", "Kpel", "Kthi",
+  "Lana", "Laoo", "Latf", "Latg", "Latn", "Lepc", "Limb", "Lina", "Linb",
+  "Lisu", "Loma", "Lyci", "Lydi", "Mand", "Mani", "Maya", "Mend", "Merc",
+  "Mero", "Mlym", "Moon", "Mong", "Mroo", "Mtei", "Mymr", "Narb", "Nbat",
+  "Nkgb", "Nkoo", "Nshu", "Ogam", "Olck", "Orkh", "Orya", "Osma", "Palm",
+  "Perm", "Phag", "Phli", "Phlp", "Phlv", "Phnx", "Plrd", "Prti", "Rjng",
+  "Roro", "Runr", "Samr", "Sara", "Sarb", "Saur", "Sgnw", "Shaw", "Shrd",
+  "Sind", "Sinh", "Sora", "Sund", "Sylo", "Syrc", "Syre", "Syrj", "Syrn",
+  "Tagb", "Takr", "Tale", "Talu", "Taml", "Tang", "Tavt", "Telu", "Teng",
+  "Tfng", "Tglg", "Thaa", "Thai", "Tibt", "Tirh", "Ugar", "Vaii", "Visp",
+  "Wara", "Wole", "Xpeo", "Xsux", "Yiii", "Zmth", "Zsym", "Zyyy"
+};
+
+const size_t kWebKitScriptsForFontFamilyMapsLength =
+    arraysize(kWebKitScriptsForFontFamilyMaps);
+
+// Strings for WebKit font family preferences. If these change, the pref prefix
+// in pref_names_util.cc and the pref format in font_settings_api.cc must also
+// change.
+const char kWebKitStandardFontFamilyMap[] =
+    "webkit.webprefs.fonts.standard";
+const char kWebKitFixedFontFamilyMap[] =
+    "webkit.webprefs.fonts.fixed";
+const char kWebKitSerifFontFamilyMap[] =
+    "webkit.webprefs.fonts.serif";
+const char kWebKitSansSerifFontFamilyMap[] =
+    "webkit.webprefs.fonts.sansserif";
+const char kWebKitCursiveFontFamilyMap[] =
+    "webkit.webprefs.fonts.cursive";
+const char kWebKitFantasyFontFamilyMap[] =
+    "webkit.webprefs.fonts.fantasy";
+const char kWebKitPictographFontFamilyMap[] =
+    "webkit.webprefs.fonts.pictograph";
+const char kWebKitStandardFontFamilyArabic[] =
+    "webkit.webprefs.fonts.standard.Arab";
+const char kWebKitFixedFontFamilyArabic[] =
+    "webkit.webprefs.fonts.fixed.Arab";
+const char kWebKitSerifFontFamilyArabic[] =
+    "webkit.webprefs.fonts.serif.Arab";
+const char kWebKitSansSerifFontFamilyArabic[] =
+    "webkit.webprefs.fonts.sansserif.Arab";
+const char kWebKitStandardFontFamilyCyrillic[] =
+    "webkit.webprefs.fonts.standard.Cyrl";
+const char kWebKitFixedFontFamilyCyrillic[] =
+    "webkit.webprefs.fonts.fixed.Cyrl";
+const char kWebKitSerifFontFamilyCyrillic[] =
+    "webkit.webprefs.fonts.serif.Cyrl";
+const char kWebKitSansSerifFontFamilyCyrillic[] =
+    "webkit.webprefs.fonts.sansserif.Cyrl";
+const char kWebKitStandardFontFamilyGreek[] =
+    "webkit.webprefs.fonts.standard.Grek";
+const char kWebKitFixedFontFamilyGreek[] =
+    "webkit.webprefs.fonts.fixed.Grek";
+const char kWebKitSerifFontFamilyGreek[] =
+    "webkit.webprefs.fonts.serif.Grek";
+const char kWebKitSansSerifFontFamilyGreek[] =
+    "webkit.webprefs.fonts.sansserif.Grek";
+const char kWebKitStandardFontFamilyJapanese[] =
+    "webkit.webprefs.fonts.standard.Jpan";
+const char kWebKitFixedFontFamilyJapanese[] =
+    "webkit.webprefs.fonts.fixed.Jpan";
+const char kWebKitSerifFontFamilyJapanese[] =
+    "webkit.webprefs.fonts.serif.Jpan";
+const char kWebKitSansSerifFontFamilyJapanese[] =
+    "webkit.webprefs.fonts.sansserif.Jpan";
+const char kWebKitStandardFontFamilyKorean[] =
+    "webkit.webprefs.fonts.standard.Hang";
+const char kWebKitFixedFontFamilyKorean[] =
+    "webkit.webprefs.fonts.fixed.Hang";
+const char kWebKitSerifFontFamilyKorean[] =
+    "webkit.webprefs.fonts.serif.Hang";
+const char kWebKitSansSerifFontFamilyKorean[] =
+    "webkit.webprefs.fonts.sansserif.Hang";
+const char kWebKitCursiveFontFamilyKorean[] =
+    "webkit.webprefs.fonts.cursive.Hang";
+const char kWebKitStandardFontFamilySimplifiedHan[] =
+    "webkit.webprefs.fonts.standard.Hans";
+const char kWebKitFixedFontFamilySimplifiedHan[] =
+    "webkit.webprefs.fonts.fixed.Hans";
+const char kWebKitSerifFontFamilySimplifiedHan[] =
+    "webkit.webprefs.fonts.serif.Hans";
+const char kWebKitSansSerifFontFamilySimplifiedHan[] =
+    "webkit.webprefs.fonts.sansserif.Hans";
+const char kWebKitStandardFontFamilyTraditionalHan[] =
+    "webkit.webprefs.fonts.standard.Hant";
+const char kWebKitFixedFontFamilyTraditionalHan[] =
+    "webkit.webprefs.fonts.fixed.Hant";
+const char kWebKitSerifFontFamilyTraditionalHan[] =
+    "webkit.webprefs.fonts.serif.Hant";
+const char kWebKitSansSerifFontFamilyTraditionalHan[] =
+    "webkit.webprefs.fonts.sansserif.Hant";
+
+// WebKit preferences.
+const char kWebKitWebSecurityEnabled[] = "webkit.webprefs.web_security_enabled";
+const char kWebKitDomPasteEnabled[] = "webkit.webprefs.dom_paste_enabled";
+const char kWebKitShrinksStandaloneImagesToFit[] =
+    "webkit.webprefs.shrinks_standalone_images_to_fit";
+const char kWebKitInspectorSettings[] = "webkit.webprefs.inspector_settings";
+const char kWebKitUsesUniversalDetector[] =
+    "webkit.webprefs.uses_universal_detector";
+const char kWebKitTextAreasAreResizable[] =
+    "webkit.webprefs.text_areas_are_resizable";
+const char kWebKitJavaEnabled[] = "webkit.webprefs.java_enabled";
+const char kWebkitTabsToLinks[] = "webkit.webprefs.tabs_to_links";
+const char kWebKitAllowDisplayingInsecureContent[] =
+    "webkit.webprefs.allow_displaying_insecure_content";
+const char kWebKitAllowRunningInsecureContent[] =
+    "webkit.webprefs.allow_running_insecure_content";
+#if defined(OS_ANDROID)
+const char kWebKitFontScaleFactor[] = "webkit.webprefs.font_scale_factor";
+const char kWebKitForceEnableZoom[] = "webkit.webprefs.force_enable_zoom";
+#endif
+
+const char kWebKitCommonScript[] = "Zyyy";
+const char kWebKitStandardFontFamily[] = "webkit.webprefs.fonts.standard.Zyyy";
+const char kWebKitFixedFontFamily[] = "webkit.webprefs.fonts.fixed.Zyyy";
+const char kWebKitSerifFontFamily[] = "webkit.webprefs.fonts.serif.Zyyy";
+const char kWebKitSansSerifFontFamily[] =
+    "webkit.webprefs.fonts.sansserif.Zyyy";
+const char kWebKitCursiveFontFamily[] = "webkit.webprefs.fonts.cursive.Zyyy";
+const char kWebKitFantasyFontFamily[] = "webkit.webprefs.fonts.fantasy.Zyyy";
+const char kWebKitPictographFontFamily[] =
+    "webkit.webprefs.fonts.pictograph.Zyyy";
+const char kWebKitDefaultFontSize[] = "webkit.webprefs.default_font_size";
+const char kWebKitDefaultFixedFontSize[] =
+    "webkit.webprefs.default_fixed_font_size";
+const char kWebKitMinimumFontSize[] = "webkit.webprefs.minimum_font_size";
+const char kWebKitMinimumLogicalFontSize[] =
+    "webkit.webprefs.minimum_logical_font_size";
+const char kWebKitJavascriptEnabled[] = "webkit.webprefs.javascript_enabled";
+const char kWebKitJavascriptCanOpenWindowsAutomatically[] =
+    "webkit.webprefs.javascript_can_open_windows_automatically";
+const char kWebKitLoadsImagesAutomatically[] =
+    "webkit.webprefs.loads_images_automatically";
+const char kWebKitPluginsEnabled[] = "webkit.webprefs.plugins_enabled";
+
+// Boolean which specifies whether the bookmark bar is visible on all tabs.
+const char kShowBookmarkBar[] = "bookmark_bar.show_on_all_tabs";
+
+// Boolean which specifies the ids of the bookmark nodes that are expanded in
+// the bookmark editor.
+const char kBookmarkEditorExpandedNodes[] = "bookmark_editor.expanded_nodes";
+
+// Boolean that is true if the password manager is on (will record new
+// passwords and fill in known passwords).
+const char kPasswordManagerEnabled[] = "profile.password_manager_enabled";
+
+// Boolean controlling whether the password manager allows to retrieve passwords
+// in clear text.
+const char kPasswordManagerAllowShowPasswords[] =
+    "profile.password_manager_allow_show_passwords";
+
+// Boolean that is true when password generation is enabled.
+const char kPasswordGenerationEnabled[] = "password_generation.enabled";
+
+// Booleans identifying whether normal and reverse auto-logins are enabled.
+const char kAutologinEnabled[] = "autologin.enabled";
+const char kReverseAutologinEnabled[] = "reverse_autologin.enabled";
+
+// List to keep track of emails for which the user has rejected one-click
+// sign-in.
+const char kReverseAutologinRejectedEmailList[] =
+    "reverse_autologin.rejected_email_list";
+
+// Boolean that is true when SafeBrowsing is enabled.
+const char kSafeBrowsingEnabled[] = "safebrowsing.enabled";
+
+// Boolean that is true when SafeBrowsing Malware Report is enabled.
+const char kSafeBrowsingReportingEnabled[] =
+    "safebrowsing.reporting_enabled";
+
+// Boolean that is true when the SafeBrowsing interstitial should not allow
+// users to proceed anyway.
+const char kSafeBrowsingProceedAnywayDisabled[] =
+    "safebrowsing.proceed_anyway_disabled";
+
+// Enum that specifies whether Incognito mode is:
+// 0 - Enabled. Default behaviour. Default mode is available on demand.
+// 1 - Disabled. Used cannot browse pages in Incognito mode.
+// 2 - Forced. All pages/sessions are forced into Incognito.
+const char kIncognitoModeAvailability[] = "incognito.mode_availability";
+
+// Boolean that is true when Suggest support is enabled.
+const char kSearchSuggestEnabled[] = "search.suggest_enabled";
+
+// Boolean that indicates whether the browser should put up a confirmation
+// window when the user is attempting to quit. Mac only.
+const char kConfirmToQuitEnabled[] = "browser.confirm_to_quit";
+
+// OBSOLETE.  Enum that specifies whether to enforce a third-party cookie
+// blocking policy.  This has been superseded by kDefaultContentSettings +
+// kBlockThirdPartyCookies.
+// 0 - allow all cookies.
+// 1 - block third-party cookies
+// 2 - block all cookies
+const char kCookieBehavior[] = "security.cookie_behavior";
+
+// The GUID of the synced default search provider. Note that this acts like a
+// pointer to which synced search engine should be the default, rather than the
+// prefs below which describe the locally saved default search provider details
+// (and are not synced). This is ignored in the case of the default search
+// provider being managed by policy.
+const char kSyncedDefaultSearchProviderGUID[] =
+    "default_search_provider.synced_guid";
+
+// Whether having a default search provider is enabled.
+const char kDefaultSearchProviderEnabled[] =
+    "default_search_provider.enabled";
+
+// The URL (as understood by TemplateURLRef) the default search provider uses
+// for searches.
+const char kDefaultSearchProviderSearchURL[] =
+    "default_search_provider.search_url";
+
+// The URL (as understood by TemplateURLRef) the default search provider uses
+// for suggestions.
+const char kDefaultSearchProviderSuggestURL[] =
+    "default_search_provider.suggest_url";
+
+// The URL (as understood by TemplateURLRef) the default search provider uses
+// for instant results.
+const char kDefaultSearchProviderInstantURL[] =
+    "default_search_provider.instant_url";
+
+// The Favicon URL (as understood by TemplateURLRef) of the default search
+// provider.
+const char kDefaultSearchProviderIconURL[] =
+    "default_search_provider.icon_url";
+
+// The input encoding (as understood by TemplateURLRef) supported by the default
+// search provider.  The various encodings are separated by ';'
+const char kDefaultSearchProviderEncodings[] =
+    "default_search_provider.encodings";
+
+// The name of the default search provider.
+const char kDefaultSearchProviderName[] = "default_search_provider.name";
+
+// The keyword of the default search provider.
+const char kDefaultSearchProviderKeyword[] = "default_search_provider.keyword";
+
+// The id of the default search provider.
+const char kDefaultSearchProviderID[] = "default_search_provider.id";
+
+// The prepopulate id of the default search provider.
+const char kDefaultSearchProviderPrepopulateID[] =
+    "default_search_provider.prepopulate_id";
+
+// The alternate urls of the default search provider.
+const char kDefaultSearchProviderAlternateURLs[] =
+    "default_search_provider.alternate_urls";
+
+// The dictionary key used when the default search providers are given
+// in the preferences file. Normally they are copied from the master
+// preferences file.
+const char kSearchProviderOverrides[] = "search_provider_overrides";
+// The format version for the dictionary above.
+const char kSearchProviderOverridesVersion[] =
+    "search_provider_overrides_version";
+
+// Boolean which specifies whether we should ask the user if we should download
+// a file (true) or just download it automatically.
+const char kPromptForDownload[] = "download.prompt_for_download";
+
+// A boolean pref set to true if we're using Link Doctor error pages.
+const char kAlternateErrorPagesEnabled[] = "alternate_error_pages.enabled";
+
+// OBSOLETE: new pref now stored with user prefs instead of profile, as
+// kDnsPrefetchingStartupList.
+const char kDnsStartupPrefetchList[] = "StartupDNSPrefetchList";
+
+// An adaptively identified list of domain names to be pre-fetched during the
+// next startup, based on what was actually needed during this startup.
+const char kDnsPrefetchingStartupList[] = "dns_prefetching.startup_list";
+
+// OBSOLETE: new pref now stored with user prefs instead of profile, as
+// kDnsPrefetchingHostReferralList.
+const char kDnsHostReferralList[] = "HostReferralList";
+
+// A list of host names used to fetch web pages, and their commonly used
+// sub-resource hostnames (and expected latency benefits from pre-resolving, or
+// preconnecting to, such sub-resource hostnames).
+// This list is adaptively grown and pruned.
+const char kDnsPrefetchingHostReferralList[] =
+    "dns_prefetching.host_referral_list";
+
+// Disables the SPDY protocol.
+const char kDisableSpdy[] = "spdy.disabled";
+
+// Prefs for persisting HttpServerProperties.
+const char kHttpServerProperties[] = "net.http_server_properties";
+
+// Prefs for server names that support SPDY protocol.
+const char kSpdyServers[] = "spdy.servers";
+
+// Prefs for servers that support Alternate-Protocol.
+const char kAlternateProtocolServers[] = "spdy.alternate_protocol";
+
+// Disables the listed protocol schemes.
+const char kDisabledSchemes[] = "protocol.disabled_schemes";
+
+// Blocks access to the listed host patterns.
+const char kUrlBlacklist[] = "policy.url_blacklist";
+
+// Allows access to the listed host patterns, as exceptions to the blacklist.
+const char kUrlWhitelist[] = "policy.url_whitelist";
+
+// Boolean pref indicating whether the instant confirm dialog has been shown.
+const char kInstantConfirmDialogShown[] = "instant.confirm_dialog_shown";
+
+// Boolean pref indicating if instant is enabled.
+const char kInstantEnabled[] = "instant.enabled";
+
+// Prefix URL for the experimental Instant ZeroSuggest provider.
+const char kInstantUIZeroSuggestUrlPrefix[] =
+    "instant_ui.zero_suggest_url_prefix";
+
+// Used to migrate preferences from local state to user preferences to
+// enable multiple profiles.
+// BITMASK with possible values (see browser_prefs.cc for enum):
+// 0: No preferences migrated.
+// 1: DNS preferences migrated: kDnsPrefetchingStartupList and HostReferralList
+// 2: Browser window preferences migrated: kDevToolsSplitLocation and
+//    kBrowserWindowPlacement
+const char kMultipleProfilePrefMigration[] =
+    "local_state.multiple_profile_prefs_version";
+
+// A boolean pref set to true if prediction of network actions is allowed.
+// Actions include DNS prefetching, TCP and SSL preconnection, and prerendering
+// of web pages.
+// NOTE: The "dns_prefetching.enabled" value is used so that historical user
+// preferences are not lost.
+const char kNetworkPredictionEnabled[] = "dns_prefetching.enabled";
+
+// An integer representing the state of the default apps installation process.
+// This value is persisted in the profile's user preferences because the process
+// is async, and the user may have stopped chrome in the middle.  The next time
+// the profile is opened, the process will continue from where it left off.
+//
+// See possible values in external_provider_impl.cc.
+const char kDefaultAppsInstallState[] = "default_apps_install_state";
+
+#if defined(OS_CHROMEOS)
+// An integer pref to initially mute volume if 1. This pref is ignored if
+// |kAudioOutputAllowed| is set to false, but its value is preserved, therefore
+// when the policy is lifted the original mute state is restored.
+const char kAudioMute[] = "settings.audio.mute";
+
+// A double pref storing the user-requested volume.
+const char kAudioVolumePercent[] = "settings.audio.volume_percent";
+
+// A boolean pref set to true if touchpad tap-to-click is enabled.
+const char kTapToClickEnabled[] = "settings.touchpad.enable_tap_to_click";
+
+// A boolean pref set to true if touchpad tap-dragging is enabled.
+const char kTapDraggingEnabled[] = "settings.touchpad.enable_tap_dragging";
+
+// A boolean pref set to true if touchpad three-finger-click is enabled.
+const char kEnableTouchpadThreeFingerClick[] =
+    "settings.touchpad.enable_three_finger_click";
+
+// A boolean pref set to true if touchpad three-finger swipe is enabled.
+const char kEnableTouchpadThreeFingerSwipe[] =
+    "settings.touchpad.enable_three_finger_swipe";
+
+// A boolean pref set to true if touchpad natural scrolling is enabled.
+const char kNaturalScroll[] = "settings.touchpad.natural_scroll";
+
+// A boolean pref set to true if primary mouse button is the left button.
+const char kPrimaryMouseButtonRight[] = "settings.mouse.primary_right";
+
+// A integer pref for the touchpad sensitivity.
+const char kMouseSensitivity[] = "settings.mouse.sensitivity2";
+
+// A integer pref for the touchpad sensitivity.
+const char kTouchpadSensitivity[] = "settings.touchpad.sensitivity2";
+
+// A boolean pref set to true if time should be displayed in 24-hour clock.
+const char kUse24HourClock[] = "settings.clock.use_24hour_clock";
+
+// A boolean pref to disable Google Drive integration.
+// The pref prefix should remain as "gdata" for backward compatibility.
+const char kDisableDrive[] = "gdata.disabled";
+
+// A boolean pref to disable Drive over cellular connections.
+// The pref prefix should remain as "gdata" for backward compatibility.
+const char kDisableDriveOverCellular[] = "gdata.cellular.disabled";
+
+// A boolean pref to disable hosted files on Drive.
+// The pref prefix should remain as "gdata" for backward compatibility.
+const char kDisableDriveHostedFiles[] = "gdata.hosted_files.disabled";
+
+// A string pref set to the current input method.
+const char kLanguageCurrentInputMethod[] =
+    "settings.language.current_input_method";
+
+// A string pref set to the previous input method.
+const char kLanguagePreviousInputMethod[] =
+    "settings.language.previous_input_method";
+
+// A string pref (comma-separated list) set to the "next engine in menu"
+// hot-key lists.
+const char kLanguageHotkeyNextEngineInMenu[] =
+    "settings.language.hotkey_next_engine_in_menu";
+
+// A string pref (comma-separated list) set to the "previous engine"
+// hot-key lists.
+const char kLanguageHotkeyPreviousEngine[] =
+    "settings.language.hotkey_previous_engine";
+
+// A string pref (comma-separated list) set to the preferred language IDs
+// (ex. "en-US,fr,ko").
+const char kLanguagePreferredLanguages[] =
+    "settings.language.preferred_languages";
+
+// A string pref (comma-separated list) set to the preloaded (active) input
+// method IDs (ex. "pinyin,mozc").
+const char kLanguagePreloadEngines[] = "settings.language.preload_engines";
+
+// A List pref (comma-separated list) set to the extension IMEs to filter out.
+const char kLanguageFilteredExtensionImes[] =
+    "settings.language.filtered_extension_imes";
+
+// Boolean prefs for ibus-chewing Chinese input method.
+const char kLanguageChewingAutoShiftCur[] =
+    "settings.language.chewing_auto_shift_cur";
+const char kLanguageChewingAddPhraseDirection[] =
+    "settings.language.chewing_add_phrase_direction";
+const char kLanguageChewingEasySymbolInput[] =
+    "settings.language.chewing_easy_symbol_input";
+const char kLanguageChewingEscCleanAllBuf[] =
+    "settings.language.chewing_esc_clean_all_buf";
+const char kLanguageChewingForceLowercaseEnglish[] =
+    "settings.language.chewing_force_lowercase_english";
+const char kLanguageChewingPlainZhuyin[] =
+    "settings.language.chewing_plain_zhuyin";
+const char kLanguageChewingPhraseChoiceRearward[] =
+    "settings.language.chewing_phrase_choice_rearward";
+const char kLanguageChewingSpaceAsSelection[] =
+    "settings.language.chewing_space_as_selection";
+
+// Integer prefs for ibus-chewing Chinese input method.
+const char kLanguageChewingMaxChiSymbolLen[] =
+    "settings.language.chewing_max_chi_symbol_len";
+const char kLanguageChewingCandPerPage[] =
+    "settings.language.chewing_cand_per_page";
+
+// String prefs for ibus-chewing Chinese input method.
+const char kLanguageChewingKeyboardType[] =
+    "settings.language.chewing_keyboard_type";
+const char kLanguageChewingSelKeys[] =
+    "settings.language.chewing_sel_keys";
+
+const char kLanguageChewingHsuSelKeyType[] =
+    "settings.language.chewing_hsu_sel_key_type";
+
+// A string pref which determines the keyboard layout for Hangul input method.
+const char kLanguageHangulKeyboard[] = "settings.language.hangul_keyboard";
+const char kLanguageHangulHanjaBindingKeys[] =
+    "settings.language.hangul_hanja_binding_keys";
+
+// A boolean prefs for ibus-pinyin Chinese input method.
+const char kLanguagePinyinCorrectPinyin[] =
+    "settings.language.pinyin_correct_pinyin";
+const char kLanguagePinyinFuzzyPinyin[] =
+    "settings.language.pinyin_fuzzy_pinyin";
+const char kLanguagePinyinShiftSelectCandidate[] =
+    "settings.language.pinyin_shift_select_candidate";
+const char kLanguagePinyinMinusEqualPage[] =
+    "settings.language.pinyin_minus_equal_page";
+const char kLanguagePinyinCommaPeriodPage[] =
+    "settings.language.pinyin_comma_period_page";
+const char kLanguagePinyinAutoCommit[] =
+    "settings.language.pinyin_auto_commit";
+const char kLanguagePinyinDoublePinyin[] =
+    "settings.language.pinyin_double_pinyin";
+const char kLanguagePinyinInitChinese[] =
+    "settings.language.pinyin_init_chinese";
+const char kLanguagePinyinInitFull[] =
+    "settings.language.pinyin_init_full";
+const char kLanguagePinyinInitFullPunct[] =
+    "settings.language.pinyin_init_full_punct";
+const char kLanguagePinyinInitSimplifiedChinese[] =
+    "settings.language.pinyin_init_simplified_chinese";
+const char kLanguagePinyinTradCandidate[] =
+    "settings.language.pinyin_trad_candidate";
+
+// A integer prefs for ibus-pinyin Chinese input method.
+const char kLanguagePinyinDoublePinyinSchema[] =
+    "settings.language.pinyin_double_pinyin_schema";
+const char kLanguagePinyinLookupTablePageSize[] =
+    "settings.language.pinyin_lookup_table_page_size";
+
+// A string prefs for ibus-mozc Japanese input method.
+// ibus-mozc converts the string values to protobuf enum values defined in
+// third_party/ibus-mozc/files/src/session/config.proto.
+const char kLanguageMozcPreeditMethod[] =
+    "settings.language.mozc_preedit_method";
+const char kLanguageMozcSessionKeymap[] =
+    "settings.language.mozc_session_keymap";
+const char kLanguageMozcPunctuationMethod[] =
+    "settings.language.mozc_punctuation_method";
+const char kLanguageMozcSymbolMethod[] =
+    "settings.language.mozc_symbol_method";
+const char kLanguageMozcSpaceCharacterForm[] =
+    "settings.language.mozc_space_character_form";
+const char kLanguageMozcHistoryLearningLevel[] =
+    "settings.language.mozc_history_learning_level";
+const char kLanguageMozcSelectionShortcut[] =
+    "settings.language.mozc_selection_shortcut";
+const char kLanguageMozcShiftKeyModeSwitch[] =
+    "settings.language.mozc_shift_key_mode_switch";
+const char kLanguageMozcNumpadCharacterForm[] =
+    "settings.language.mozc_numpad_character_form";
+const char kLanguageMozcIncognitoMode[] =
+    "settings.language.mozc_incognito_mode";
+const char kLanguageMozcUseAutoImeTurnOff[] =
+    "settings.language.mozc_use_auto_ime_turn_off";
+const char kLanguageMozcUseHistorySuggest[] =
+    "settings.language.mozc_use_history_suggest";
+const char kLanguageMozcUseDictionarySuggest[] =
+    "settings.language.mozc_use_dictionary_suggest";
+const char kLanguageMozcSuggestionsSize[] =
+    "settings.language.mozc_suggestions_size";
+
+// A integer prefs which determine how we remap modifier keys (e.g. swap Alt and
+// Control.) Possible values for these prefs are 0-4. See ModifierKey enum in
+// src/chrome/browser/chromeos/input_method/xkeyboard.h
+const char kLanguageRemapSearchKeyTo[] =
+    // Note: we no longer use XKB for remapping these keys, but we can't change
+    // the pref names since the names are already synced with the cloud.
+    "settings.language.xkb_remap_search_key_to";
+const char kLanguageRemapControlKeyTo[] =
+    "settings.language.xkb_remap_control_key_to";
+const char kLanguageRemapAltKeyTo[] =
+    "settings.language.xkb_remap_alt_key_to";
+const char kLanguageRemapCapsLockKeyTo[] =
+    "settings.language.remap_caps_lock_key_to";
+
+// A boolean pref which determines whether key repeat is enabled.
+const char kLanguageXkbAutoRepeatEnabled[] =
+    "settings.language.xkb_auto_repeat_enabled_r2";
+// A integer pref which determines key repeat delay (in ms).
+const char kLanguageXkbAutoRepeatDelay[] =
+    "settings.language.xkb_auto_repeat_delay_r2";
+// A integer pref which determines key repeat interval (in ms).
+const char kLanguageXkbAutoRepeatInterval[] =
+    "settings.language.xkb_auto_repeat_interval_r2";
+// "_r2" suffixes are added to the three prefs above when we change the
+// preferences not user-configurable, not to sync them with cloud.
+
+// A boolean pref which determines whether spoken feedback is enabled.
+const char kSpokenFeedbackEnabled[] = "settings.accessibility";
+// A boolean pref which determines whether high conrast is enabled.
+const char kHighContrastEnabled[] = "settings.a11y.high_contrast_enabled";
+// A boolean pref which determines whether screen magnifier is enabled.
+const char kScreenMagnifierEnabled[] = "settings.a11y.screen_magnifier";
+// A double pref which determines a zooming scale of the screen magnifier.
+const char kScreenMagnifierScale[] = "settings.a11y.screen_magnifier_scale";
+// A boolean pref which determines whether virtual keyboard is enabled.
+// TODO(hashimoto): Remove this pref.
+const char kVirtualKeyboardEnabled[] = "settings.a11y.virtual_keyboard";
+
+// A boolean pref which turns on Advanced Filesystem
+// (USB support, SD card, etc).
+const char kLabsAdvancedFilesystemEnabled[] =
+    "settings.labs.advanced_filesystem";
+
+// A boolean pref which turns on the mediaplayer.
+const char kLabsMediaplayerEnabled[] = "settings.labs.mediaplayer";
+
+// A boolean pref that turns on screen locker.
+const char kEnableScreenLock[] = "settings.enable_screen_lock";
+
+// A boolean pref of whether to show mobile plan notifications.
+const char kShowPlanNotifications[] =
+    "settings.internet.mobile.show_plan_notifications";
+
+// A boolean pref of whether to show 3G promo notification.
+const char kShow3gPromoNotification[] =
+    "settings.internet.mobile.show_3g_promo_notification";
+
+// A string pref that contains version where "What's new" promo was shown.
+const char kChromeOSReleaseNotesVersion[] = "settings.release_notes.version";
+
+// A boolean pref that uses shared proxies.
+const char kUseSharedProxies[] = "settings.use_shared_proxies";
+
+// A string prefs for OAuth1 token.
+const char kOAuth1Token[] = "settings.account.oauth1_token";
+
+// A string prefs for OAuth1 secret.
+const char kOAuth1Secret[] = "settings.account.oauth1_secret";
+
+// A boolean pref that enables the (private) pepper GetID() call.
+const char kEnableCrosDRM[] = "settings.privacy.drm_enabled";
+
+// A dictionary pref that specifies per-display overscan data.  Its key is the
+// display's ID and its value is a dictionary of canceling overscan pixels for
+// 'top', 'right', 'bottom', 'left'.
+const char kDisplayOverscans[] = "settings.display.overscans";
+
+// A 64bit integer pref that specifies the name of the primary display device.
+const char kPrimaryDisplayID[] = "settings.display.primary_id";
+
+// An enumeration that specifies the layout of the secondary display.
+//  0 - The secondary display is at the top of the primary display.
+//  1 - The secondary display is at the right of the primary display.
+//  2 - The secondary display is at the bottom of the primary display.
+//  3 - The secondary display is at the left of the primary display.
+// TODO(mukai,oshima): update the format of the multi-display settings.
+const char kSecondaryDisplayLayout[] = "settings.display.secondary_layout";
+
+// An integer pref that specifies how far the secondary display is positioned
+// from the edge of the primary display.
+const char kSecondaryDisplayOffset[] = "settings.display.secondary_offset";
+
+// A dictionary pref that specifies per-display layout/offset information.
+// Its key is the ID of the display and its value is a dictionary for the
+// layout/offset information.
+const char kSecondaryDisplays[] = "settings.display.secondary_displays";
+#endif  // defined(OS_CHROMEOS)
+
+// The disabled messages in IPC logging.
+const char kIpcDisabledMessages[] = "ipc_log_disabled_messages";
+
+// A boolean pref set to true if a Home button to open the Home pages should be
+// visible on the toolbar.
+const char kShowHomeButton[] = "browser.show_home_button";
+
+// A string value which saves short list of recently user selected encodings
+// separated with comma punctuation mark.
+const char kRecentlySelectedEncoding[] = "profile.recently_selected_encodings";
+
+// Clear Browsing Data dialog preferences.
+const char kDeleteBrowsingHistory[] = "browser.clear_data.browsing_history";
+const char kDeleteDownloadHistory[] = "browser.clear_data.download_history";
+const char kDeleteCache[] = "browser.clear_data.cache";
+const char kDeleteCookies[] = "browser.clear_data.cookies";
+const char kDeletePasswords[] = "browser.clear_data.passwords";
+const char kDeleteFormData[] = "browser.clear_data.form_data";
+const char kDeleteHostedAppsData[] = "browser.clear_data.hosted_apps_data";
+const char kDeauthorizeContentLicenses[] =
+    "browser.clear_data.content_licenses";
+const char kDeleteTimePeriod[] = "browser.clear_data.time_period";
+
+// Boolean pref to define the default values for using spellchecker.
+const char kEnableSpellCheck[] = "browser.enable_spellchecking";
+
+// List of names of the enabled labs experiments (see chrome/browser/labs.cc).
+const char kEnabledLabsExperiments[] = "browser.enabled_labs_experiments";
+
+// Boolean pref to define the default values for using auto spell correct.
+const char kEnableAutoSpellCorrect[] = "browser.enable_autospellcorrect";
+
+// Boolean pref to define the default setting for "block offensive words".
+// The old key value is kept to avoid unnecessary migration code.
+const char kSpeechRecognitionFilterProfanities[] =
+    "browser.speechinput_censor_results";
+
+// List of speech recognition context names (extensions or websites) for which
+// the tray notification balloon has already been shown.
+const char kSpeechRecognitionTrayNotificationShownContexts[] =
+    "browser.speechinput_tray_notification_shown_contexts";
+
+// Boolean controlling whether history saving is disabled.
+const char kSavingBrowserHistoryDisabled[] = "history.saving_disabled";
+
+// Boolean controlling whether SafeSearch is mandatory for Google Web Searches.
+const char kForceSafeSearch[] = "settings.force_safesearch";
+
+#if defined(TOOLKIT_GTK)
+// GTK specific preference on whether we should match the system GTK theme.
+const char kUsesSystemTheme[] = "extensions.theme.use_system";
+#endif
+const char kCurrentThemePackFilename[] = "extensions.theme.pack";
+const char kCurrentThemeID[] = "extensions.theme.id";
+const char kCurrentThemeImages[] = "extensions.theme.images";
+const char kCurrentThemeColors[] = "extensions.theme.colors";
+const char kCurrentThemeTints[] = "extensions.theme.tints";
+const char kCurrentThemeDisplayProperties[] = "extensions.theme.properties";
+
+// Boolean pref which persists whether the extensions_ui is in developer mode
+// (showing developer packing tools and extensions details)
+const char kExtensionsUIDeveloperMode[] = "extensions.ui.developer_mode";
+
+// Integer pref that tracks the number of browser actions visible in the browser
+// actions toolbar.
+const char kExtensionToolbarSize[] = "extensions.toolbarsize";
+
+// Dictionary pref that tracks which command belongs to which
+// extension + named command pair.
+const char kExtensionCommands[] = "extensions.commands";
+
+// Integer pref that tracks how often the bubble has been shown to the user.
+const char kExtensionsSideloadWipeoutBubbleShown[] =
+    "extensions.sideload_wipeout_bubble_shown";
+
+// Pref containing the directory for internal plugins as written to the plugins
+// list (below).
+const char kPluginsLastInternalDirectory[] = "plugins.last_internal_directory";
+
+// List pref containing information (dictionaries) on plugins.
+const char kPluginsPluginsList[] = "plugins.plugins_list";
+
+// List pref containing names of plugins that are disabled by policy.
+const char kPluginsDisabledPlugins[] = "plugins.plugins_disabled";
+
+// List pref containing exceptions to the list of plugins disabled by policy.
+const char kPluginsDisabledPluginsExceptions[] =
+    "plugins.plugins_disabled_exceptions";
+
+// List pref containing names of plugins that are enabled by policy.
+const char kPluginsEnabledPlugins[] = "plugins.plugins_enabled";
+
+// When first shipped, the pdf plugin will be disabled by default.  When we
+// enable it by default, we'll want to do so only once.
+const char kPluginsEnabledInternalPDF[] = "plugins.enabled_internal_pdf3";
+
+// When first shipped, the nacl plugin will be disabled by default.  When we
+// enable it by default, we'll want to do so only once.
+const char kPluginsEnabledNaCl[] = "plugins.enabled_nacl";
+
+// When bundled NPAPI Flash is removed, if at that point it is enabled while
+// Pepper Flash is disabled, we would like to turn on Pepper Flash. And we will
+// want to do so only once.
+const char kPluginsMigratedToPepperFlash[] = "plugins.migrated_to_pepper_flash";
+
+#if !defined(OS_ANDROID)
+// Whether about:plugins is shown in the details mode or not.
+const char kPluginsShowDetails[] = "plugins.show_details";
+#endif
+
+// Boolean that indicates whether outdated plugins are allowed or not.
+const char kPluginsAllowOutdated[] = "plugins.allow_outdated";
+
+// Boolean that indicates whether plugins that require authorization should
+// be always allowed or not.
+const char kPluginsAlwaysAuthorize[] = "plugins.always_authorize";
+
+#if defined(ENABLE_PLUGIN_INSTALLATION)
+// Dictionary holding plug-ins metadata.
+const char kPluginsMetadata[] = "plugins.metadata";
+
+// Last update time of plug-ins resource cache.
+const char kPluginsResourceCacheUpdate[] = "plugins.resource_cache_update";
+#endif
+
+#if defined(ENABLE_WEB_INTENTS)
+// Boolean that is true if Web Intents is enabled.
+const char kWebIntentsEnabled[] = "webintents.enabled";
+#endif
+
+// Boolean that indicates whether we should check if we are the default browser
+// on start-up.
+const char kCheckDefaultBrowser[] = "browser.check_default_browser";
+
+#if defined(OS_WIN)
+// By default, setting Chrome as default during first run on Windows 8 will
+// trigger shutting down the current instance and spawning a new (Metro)
+// Chrome. This boolean preference supresses this behaviour.
+const char kSuppressSwitchToMetroModeOnSetDefault[] =
+    "browser.suppress_switch_to_metro_mode_on_set_default";
+#endif
+
+// Policy setting whether default browser check should be disabled and default
+// browser registration should take place.
+const char kDefaultBrowserSettingEnabled[] =
+    "browser.default_browser_setting_enabled";
+
+#if defined(OS_MACOSX)
+// Boolean that indicates whether the application should show the info bar
+// asking the user to set up automatic updates when Keystone promotion is
+// required.
+const char kShowUpdatePromotionInfoBar[] =
+    "browser.show_update_promotion_info_bar";
+#endif
+
+// Boolean that is false if we should show window manager decorations.  If
+// true, we draw a custom chrome frame (thicker title bar and blue border).
+const char kUseCustomChromeFrame[] = "browser.custom_chrome_frame";
+
+// Boolean that indicates whether the infobar explaining that search can be
+// done directly from the omnibox should be shown.
+const char kShowOmniboxSearchHint[] = "browser.show_omnibox_search_hint";
+
+// The list of origins which are allowed|denied to show desktop notifications.
+const char kDesktopNotificationDefaultContentSetting[] =
+    "profile.notifications_default_content_setting";
+const char kDesktopNotificationAllowedOrigins[] =
+    "profile.notification_allowed_sites";
+const char kDesktopNotificationDeniedOrigins[] =
+    "profile.notification_denied_sites";
+
+// The preferred position (which corner of screen) for desktop notifications.
+const char kDesktopNotificationPosition[] =
+    "browser.desktop_notification_position";
+
+// Dictionary of content settings applied to all hosts by default.
+const char kDefaultContentSettings[] = "profile.default_content_settings";
+
+// Boolean indicating whether the clear on exit pref was migrated to content
+// settings yet.
+const char kContentSettingsClearOnExitMigrated[] =
+    "profile.content_settings.clear_on_exit_migrated";
+
+// Version of the pattern format used to define content settings.
+const char kContentSettingsVersion[] = "profile.content_settings.pref_version";
+
+// Patterns for mapping hostnames to content related settings. Default settings
+// will be applied to hosts that don't match any of the patterns. Replaces
+// kPerHostContentSettings. The pattern format used is defined by
+// kContentSettingsVersion.
+const char kContentSettingsPatterns[] = "profile.content_settings.patterns";
+
+const char kContentSettingsPatternPairs[] =
+    "profile.content_settings.pattern_pairs";
+
+// Version of the content settings whitelist.
+const char kContentSettingsDefaultWhitelistVersion[] =
+    "profile.content_settings.whitelist_version";
+
+#if !defined(OS_ANDROID)
+// Which plugins have been whitelisted manually by the user.
+const char kContentSettingsPluginWhitelist[] =
+    "profile.content_settings.plugin_whitelist";
+#endif
+
+// Boolean that is true if we should unconditionally block third-party cookies,
+// regardless of other content settings.
+const char kBlockThirdPartyCookies[] = "profile.block_third_party_cookies";
+
+// Boolean that is true when all locally stored site data (e.g. cookies, local
+// storage, etc..) should be deleted on exit.
+const char kClearSiteDataOnExit[] = "profile.clear_site_data_on_exit";
+
+// Double that indicates the default zoom level.
+const char kDefaultZoomLevel[] = "profile.default_zoom_level";
+
+// Dictionary that maps hostnames to zoom levels.  Hosts not in this pref will
+// be displayed at the default zoom level.
+const char kPerHostZoomLevels[] = "profile.per_host_zoom_levels";
+
+// Boolean that is true if Autofill is enabled and allowed to save profile data.
+const char kAutofillEnabled[] = "autofill.enabled";
+
+// Boolean that is true when auxiliary Autofill profiles are enabled.
+// Currently applies to Address Book "me" card on Mac.  False on Win and Linux.
+const char kAutofillAuxiliaryProfilesEnabled[] =
+    "autofill.auxiliary_profiles_enabled";
+
+// Double that indicates positive (for matched forms) upload rate.
+const char kAutofillPositiveUploadRate[] = "autofill.positive_upload_rate";
+
+// Double that indicates negative (for not matched forms) upload rate.
+const char kAutofillNegativeUploadRate[] = "autofill.negative_upload_rate";
+
+// Boolean option set to true on the first run. Non-persistent.
+const char kAutofillPersonalDataManagerFirstRun[] = "autofill.pdm.first_run";
+
+// Modifying bookmarks is completely disabled when this is set to false.
+const char kEditBookmarksEnabled[] = "bookmarks.editing_enabled";
+
+// Boolean that is true when the translate feature is enabled.
+const char kEnableTranslate[] = "translate.enabled";
+
+#if !defined(OS_ANDROID)
+const char kPinnedTabs[] = "pinned_tabs";
+#endif
+
+// Integer containing the default Geolocation content setting.
+const char kGeolocationDefaultContentSetting[] =
+    "geolocation.default_content_setting";
+
+#if defined(OS_ANDROID)
+// Boolean that controls the enabled-state of Geolocation.
+const char kGeolocationEnabled[] = "geolocation.enabled";
+#endif
+
+// Dictionary that maps [frame, toplevel] to their Geolocation content setting.
+const char kGeolocationContentSettings[] = "geolocation.content_settings";
+
+// Preference to disable 3D APIs (WebGL, Pepper 3D).
+const char kDisable3DAPIs[] = "disable_3d_apis";
+
+// Whether to enable hyperlink auditing ("<a ping>").
+const char kEnableHyperlinkAuditing[] = "enable_a_ping";
+
+// Whether to enable sending referrers.
+const char kEnableReferrers[] = "enable_referrers";
+
+// Whether to send the DNT header.
+const char kEnableDoNotTrack[] = "enable_do_not_track";
+
+// Boolean to enable reporting memory info to page.
+const char kEnableMemoryInfo[] = "enable_memory_info";
+
+// Boolean that specifies whether to import bookmarks from the default browser
+// on first run.
+const char kImportBookmarks[] = "import_bookmarks";
+
+// Boolean that specifies whether to import the browsing history from the
+// default browser on first run.
+const char kImportHistory[] = "import_history";
+
+// Boolean that specifies whether to import the homepage from the default
+// browser on first run.
+const char kImportHomepage[] = "import_home_page";
+
+// Boolean that specifies whether to import the search engine from the default
+// browser on first run.
+const char kImportSearchEngine[] = "import_search_engine";
+
+// Boolean that specifies whether to import the saved passwords from the default
+// browser on first run.
+const char kImportSavedPasswords[] = "import_saved_passwords";
+
+// The URL of the enterprise web store, which is a site trusted by the
+// enterprise admin. Users can install apps & extensions from this site
+// without scary warnings.
+const char kEnterpriseWebStoreURL[] = "webstore.enterprise_store_url";
+
+// The name of the enterprise web store, to be shown to the user.
+const char kEnterpriseWebStoreName[] = "webstore.enterprise_store_name";
+
+#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) && defined(OS_POSIX)
+// The local profile id for this profile.
+const char kLocalProfileId[] = "profile.local_profile_id";
+
+// Whether passwords in external services (e.g. GNOME Keyring) have been tagged
+// with the local profile id yet. (Used for migrating to tagged passwords.)
+const char kPasswordsUseLocalProfileId[] =
+    "profile.passwords_use_local_profile_id";
+#endif
+
+// Profile avatar and name
+const char kProfileAvatarIndex[] = "profile.avatar_index";
+const char kProfileName[] = "profile.name";
+
+// Indicates if we've already shown a notification that high contrast
+// mode is on, recommending high-contrast extensions and themes.
+const char kInvertNotificationShown[] = "invert_notification_version_2_shown";
+
+// Boolean controlling whether printing is enabled.
+const char kPrintingEnabled[] = "printing.enabled";
+
+// Boolean controlling whether print preview is disabled.
+const char kPrintPreviewDisabled[] = "printing.print_preview_disabled";
+
+// *************** LOCAL STATE ***************
+// These are attached to the machine/installation
+
+// Directory of the last profile used.
+const char kProfileLastUsed[] = "profile.last_used";
+
+// List of directories of the profiles last active.
+const char kProfilesLastActive[] = "profile.last_active_profiles";
+
+// Total number of profiles created for this Chrome build. Used to tag profile
+// directories.
+const char kProfilesNumCreated[] = "profile.profiles_created";
+
+// String containing the version of Chrome that the profile was created by.
+// If profile was created before this feature was added, this pref will default
+// to "1.0.0.0".
+const char kProfileCreatedByVersion[] = "profile.created_by_version";
+
+// A map of profile data directory to cached information. This cache can be
+// used to display information about profiles without actually having to load
+// them.
+const char kProfileInfoCache[] = "profile.info_cache";
+
+// Prefs for SSLConfigServicePref.
+const char kCertRevocationCheckingEnabled[] = "ssl.rev_checking.enabled";
+const char kSSLVersionMin[] = "ssl.version_min";
+const char kSSLVersionMax[] = "ssl.version_max";
+const char kCipherSuiteBlacklist[] = "ssl.cipher_suites.blacklist";
+const char kEnableOriginBoundCerts[] = "ssl.origin_bound_certs.enabled";
+const char kDisableSSLRecordSplitting[] = "ssl.ssl_record_splitting.disabled";
+
+// The metrics client GUID, entropy source and session ID.
+const char kMetricsClientID[] = "user_experience_metrics.client_id";
+const char kMetricsSessionID[] = "user_experience_metrics.session_id";
+const char kMetricsLowEntropySource[] =
+    "user_experience_metrics.low_entropy_source";
+
+// Date/time when the current metrics profile ID was created
+// (which hopefully corresponds to first run).
+const char kMetricsClientIDTimestamp[] =
+    "user_experience_metrics.client_id_timestamp";
+
+// Boolean that specifies whether or not crash reporting and metrics reporting
+// are sent over the network for analysis.
+const char kMetricsReportingEnabled[] =
+    "user_experience_metrics.reporting_enabled";
+
+// Array of strings that are each UMA logs that were supposed to be sent in the
+// first minute of a browser session. These logs include things like crash count
+// info, etc.  Currently we store both XML and protobuf versions of these logs.
+const char kMetricsInitialLogsXml[] =
+    "user_experience_metrics.initial_logs";
+const char kMetricsInitialLogsProto[] =
+    "user_experience_metrics.initial_logs_as_protobufs";
+
+// Array of strings that are each UMA logs that were not sent because the
+// browser terminated before these accumulated metrics could be sent.  These
+// logs typically include histograms and memory reports, as well as ongoing
+// user activities.  Currently we store both XML and protobuf versions.
+const char kMetricsOngoingLogsXml[] =
+    "user_experience_metrics.ongoing_logs";
+const char kMetricsOngoingLogsProto[] =
+    "user_experience_metrics.ongoing_logs_as_protobufs";
+
+// Boolean that is true when bookmark prompt is enabled.
+const char kBookmarkPromptEnabled[] = "bookmark_prompt_enabled";
+
+// Number of times bookmark prompt displayed.
+const char kBookmarkPromptImpressionCount[] =
+    "bookmark_prompt_impression_count";
+
+// String serialized form of variations seed protobuf.
+const char kVariationsSeed[] = "variations_seed";
+
+// 64-bit integer serialization of the base::Time from the last seed received.
+const char kVariationsSeedDate[] = "variations_seed_date";
+
+// Where profile specific metrics are placed.
+const char kProfileMetrics[] = "user_experience_metrics.profiles";
+
+// The metrics for a profile are stored as dictionary values under the
+// path kProfileMetrics. The individual metrics are placed under the path
+// kProfileMetrics.kProfilePrefix<hashed-profile-id>.
+const char kProfilePrefix[] = "profile-";
+
+// True if the previous run of the program exited cleanly.
+const char kStabilityExitedCleanly[] =
+    "user_experience_metrics.stability.exited_cleanly";
+
+// Version string of previous run, which is used to assure that stability
+// metrics reported under current version reflect stability of the same version.
+const char kStabilityStatsVersion[] =
+    "user_experience_metrics.stability.stats_version";
+
+// Build time, in seconds since an epoch, which is used to assure that stability
+// metrics reported reflect stability of the same build.
+const char kStabilityStatsBuildTime[] =
+    "user_experience_metrics.stability.stats_buildtime";
+
+// False if we received a session end and either we crashed during processing
+// the session end or ran out of time and windows terminated us.
+const char kStabilitySessionEndCompleted[] =
+    "user_experience_metrics.stability.session_end_completed";
+
+// Number of times the application was launched since last report.
+const char kStabilityLaunchCount[] =
+    "user_experience_metrics.stability.launch_count";
+
+// Number of times the application exited uncleanly since the last report.
+const char kStabilityCrashCount[] =
+    "user_experience_metrics.stability.crash_count";
+
+// Number of times the session end did not complete.
+const char kStabilityIncompleteSessionEndCount[] =
+    "user_experience_metrics.stability.incomplete_session_end_count";
+
+// Number of times a page load event occurred since the last report.
+const char kStabilityPageLoadCount[] =
+    "user_experience_metrics.stability.page_load_count";
+
+// Number of times a renderer process crashed since the last report.
+const char kStabilityRendererCrashCount[] =
+    "user_experience_metrics.stability.renderer_crash_count";
+
+// Number of times an extension renderer process crashed since the last report.
+const char kStabilityExtensionRendererCrashCount[] =
+    "user_experience_metrics.stability.extension_renderer_crash_count";
+
+// Time when the app was last launched, in seconds since the epoch.
+const char kStabilityLaunchTimeSec[] =
+    "user_experience_metrics.stability.launch_time_sec";
+
+// Time when the app was last known to be running, in seconds since
+// the epoch.
+const char kStabilityLastTimestampSec[] =
+    "user_experience_metrics.stability.last_timestamp_sec";
+
+// This is the location of a list of dictionaries of plugin stability stats.
+const char kStabilityPluginStats[] =
+    "user_experience_metrics.stability.plugin_stats2";
+
+// Number of times the renderer has become non-responsive since the last
+// report.
+const char kStabilityRendererHangCount[] =
+    "user_experience_metrics.stability.renderer_hang_count";
+
+// Total number of child process crashes (other than renderer / extension
+// renderer ones, and plugin children, which are counted separately) since the
+// last report.
+const char kStabilityChildProcessCrashCount[] =
+    "user_experience_metrics.stability.child_process_crash_count";
+
+// On Chrome OS, total number of non-Chrome user process crashes
+// since the last report.
+const char kStabilityOtherUserCrashCount[] =
+    "user_experience_metrics.stability.other_user_crash_count";
+
+// On Chrome OS, total number of kernel crashes since the last report.
+const char kStabilityKernelCrashCount[] =
+    "user_experience_metrics.stability.kernel_crash_count";
+
+// On Chrome OS, total number of unclean system shutdowns since the
+// last report.
+const char kStabilitySystemUncleanShutdownCount[] =
+    "user_experience_metrics.stability.system_unclean_shutdowns";
+
+// Number of times the browser has been able to register crash reporting.
+const char kStabilityBreakpadRegistrationSuccess[] =
+    "user_experience_metrics.stability.breakpad_registration_ok";
+
+// Number of times the browser has failed to register crash reporting.
+const char kStabilityBreakpadRegistrationFail[] =
+    "user_experience_metrics.stability.breakpad_registration_fail";
+
+// Number of times the browser has been run under a debugger.
+const char kStabilityDebuggerPresent[] =
+    "user_experience_metrics.stability.debugger_present";
+
+// Number of times the browser has not been run under a debugger.
+const char kStabilityDebuggerNotPresent[] =
+    "user_experience_metrics.stability.debugger_not_present";
+
+// The keys below are used for the dictionaries in the
+// kStabilityPluginStats list.
+const char kStabilityPluginName[] = "name";
+const char kStabilityPluginLaunches[] = "launches";
+const char kStabilityPluginInstances[] = "instances";
+const char kStabilityPluginCrashes[] = "crashes";
+const char kStabilityPluginLoadingErrors[] = "loading_errors";
+
+// The keys below are strictly increasing counters over the lifetime of
+// a chrome installation. They are (optionally) sent up to the uninstall
+// survey in the event of uninstallation.
+const char kUninstallMetricsPageLoadCount[] =
+    "uninstall_metrics.page_load_count";
+const char kUninstallLaunchCount[] = "uninstall_metrics.launch_count";
+const char kUninstallMetricsInstallDate[] =
+    "uninstall_metrics.installation_date2";
+const char kUninstallMetricsUptimeSec[] = "uninstall_metrics.uptime_sec";
+const char kUninstallLastLaunchTimeSec[] =
+    "uninstall_metrics.last_launch_time_sec";
+const char kUninstallLastObservedRunTimeSec[] =
+    "uninstall_metrics.last_observed_running_time_sec";
+
+// A collection of position, size, and other data relating to the browser
+// window to restore on startup.
+const char kBrowserWindowPlacement[] = "browser.window_placement";
+
+// A collection of position, size, and other data relating to the task
+// manager window to restore on startup.
+const char kTaskManagerWindowPlacement[] = "task_manager.window_placement";
+
+// A collection of position, size, and other data relating to the keyword
+// editor window to restore on startup.
+const char kKeywordEditorWindowPlacement[] = "keyword_editor.window_placement";
+
+// A collection of position, size, and other data relating to the preferences
+// window to restore on startup.
+const char kPreferencesWindowPlacement[] = "preferences.window_placement";
+
+// An integer specifying the total number of bytes to be used by the
+// renderer's in-memory cache of objects.
+const char kMemoryCacheSize[] = "renderer.memory_cache.size";
+
+// String which specifies where to download files to by default.
+const char kDownloadDefaultDirectory[] = "download.default_directory";
+
+// Boolean that records if the download directory was changed by an
+// upgrade a unsafe location to a safe location.
+const char kDownloadDirUpgraded[] = "download.directory_upgrade";
+
+// String which specifies where to save html files to by default.
+const char kSaveFileDefaultDirectory[] = "savefile.default_directory";
+
+// The type used to save the page. See the enum SavePackage::SavePackageType in
+// the chrome/browser/download/save_package.h for the possible values.
+const char kSaveFileType[] = "savefile.type";
+
+// String which specifies the last directory that was chosen for uploading
+// or opening a file.
+const char kSelectFileLastDirectory[] = "selectfile.last_directory";
+
+// Boolean that specifies if file selection dialogs are shown.
+const char kAllowFileSelectionDialogs[] = "select_file_dialogs.allowed";
+
+// Map of default tasks, associated by MIME type.
+const char kDefaultTasksByMimeType[] =
+    "filebrowser.tasks.default_by_mime_type";
+
+// Map of default tasks, associated by file suffix.
+const char kDefaultTasksBySuffix[] =
+    "filebrowser.tasks.default_by_suffix";
+
+// Extensions which should be opened upon completion.
+const char kDownloadExtensionsToOpen[] = "download.extensions_to_open";
+
+// Integer which specifies the frequency in milliseconds for detecting whether
+// plugin windows are hung.
+const char kHungPluginDetectFrequency[] = "browser.hung_plugin_detect_freq";
+
+// Integer which specifies the timeout value to be used for SendMessageTimeout
+// to detect a hung plugin window.
+const char kPluginMessageResponseTimeout[] =
+    "browser.plugin_message_response_timeout";
+
+// String which represents the dictionary name for our spell-checker.
+const char kSpellCheckDictionary[] = "spellcheck.dictionary";
+
+// Boolean pref indicating whether the spelling confirm dialog has been shown.
+const char kSpellCheckConfirmDialogShown[] = "spellcheck.confirm_dialog_shown";
+
+// String which represents whether we use the spelling service.
+const char kSpellCheckUseSpellingService[] = "spellcheck.use_spelling_service";
+
+// Dictionary of schemes used by the external protocol handler.
+// The value is true if the scheme must be ignored.
+const char kExcludedSchemes[] = "protocol_handler.excluded_schemes";
+
+// Keys used for MAC handling of SafeBrowsing requests.
+const char kSafeBrowsingClientKey[] = "safe_browsing.client_key";
+const char kSafeBrowsingWrappedKey[] = "safe_browsing.wrapped_key";
+
+// Integer that specifies the index of the tab the user was on when they
+// last visited the options window.
+const char kOptionsWindowLastTabIndex[] = "options_window.last_tab_index";
+
+// Integer that specifies the index of the tab the user was on when they
+// last visited the content settings window.
+const char kContentSettingsWindowLastTabIndex[] =
+    "content_settings_window.last_tab_index";
+
+// Integer that specifies the index of the tab the user was on when they
+// last visited the Certificate Manager window.
+const char kCertificateManagerWindowLastTabIndex[] =
+    "certificate_manager_window.last_tab_index";
+
+// Boolean that specifies if the first run bubble should be shown.
+// This preference is only registered by the first-run procedure.
+const char kShouldShowFirstRunBubble[] = "show-first-run-bubble";
+
+// Signal that we should show the welcome page when we launch Chrome.
+const char kShouldShowWelcomePage[] = "show-welcome-page";
+
+// String containing the last known Google URL.  We re-detect this on startup in
+// most cases, and use it to send traffic to the correct Google host or with the
+// correct Google domain/country code for whatever location the user is in.
+const char kLastKnownGoogleURL[] = "browser.last_known_google_url";
+
+// String containing the last prompted Google URL to the user.
+// If the user is using .x TLD for Google URL and gets prompted about .y TLD
+// for Google URL, and says "no", we should leave the search engine set to .x
+// but not prompt again until the domain changes away from .y.
+const char kLastPromptedGoogleURL[] = "browser.last_prompted_google_url";
+
+// String containing the last known intranet redirect URL, if any.  See
+// intranet_redirect_detector.h for more information.
+const char kLastKnownIntranetRedirectOrigin[] = "browser.last_redirect_origin";
+
+// Integer containing the system Country ID the first time we checked the
+// template URL prepopulate data.  This is used to avoid adding a whole bunch of
+// new search engine choices if prepopulation runs when the user's Country ID
+// differs from their previous Country ID.  This pref does not exist until
+// prepopulation has been run at least once.
+const char kCountryIDAtInstall[] = "countryid_at_install";
+// OBSOLETE. Same as above, but uses the Windows-specific GeoID value instead.
+// Updated if found to the above key.
+const char kGeoIDAtInstall[] = "geoid_at_install";
+
+// An enum value of how the browser was shut down (see browser_shutdown.h).
+const char kShutdownType[] = "shutdown.type";
+// Number of processes that were open when the user shut down.
+const char kShutdownNumProcesses[] = "shutdown.num_processes";
+// Number of processes that were shut down using the slow path.
+const char kShutdownNumProcessesSlow[] = "shutdown.num_processes_slow";
+
+// Whether to restart the current Chrome session automatically as the last thing
+// before shutting everything down.
+const char kRestartLastSessionOnShutdown[] = "restart.last.session.on.shutdown";
+
+// Set before autorestarting Chrome, cleared on clean exit.
+const char kWasRestarted[] = "was.restarted";
+
+#if defined(OS_WIN)
+// On Windows 8 chrome can restart in desktop or in metro mode.
+const char kRestartSwitchMode[] = "restart.switch_mode";
+#endif
+
+// Number of bookmarks/folders on the bookmark bar/other bookmark folder.
+const char kNumBookmarksOnBookmarkBar[] =
+    "user_experience_metrics.num_bookmarks_on_bookmark_bar";
+const char kNumFoldersOnBookmarkBar[] =
+    "user_experience_metrics.num_folders_on_bookmark_bar";
+const char kNumBookmarksInOtherBookmarkFolder[] =
+    "user_experience_metrics.num_bookmarks_in_other_bookmark_folder";
+const char kNumFoldersInOtherBookmarkFolder[] =
+    "user_experience_metrics.num_folders_in_other_bookmark_folder";
+
+// Number of keywords.
+const char kNumKeywords[] = "user_experience_metrics.num_keywords";
+
+// Placeholder preference for disabling voice / video chat if it is ever added.
+// Currently, this does not change any behavior.
+const char kDisableVideoAndChat[] = "disable_video_chat";
+
+// Whether Extensions are enabled.
+const char kDisableExtensions[] = "extensions.disabled";
+
+// Whether the plugin finder that lets you install missing plug-ins is enabled.
+const char kDisablePluginFinder[] = "plugins.disable_plugin_finder";
+
+// Integer boolean representing the width (in pixels) of the container for
+// browser actions.
+const char kBrowserActionContainerWidth[] =
+    "extensions.browseractions.container.width";
+
+// The sites that are allowed to install extensions. These sites should be
+// allowed to install extensions without the scary dangerous downloads bar.
+// Also, when off-store-extension installs are disabled, these sites are exempt.
+const char kExtensionAllowedInstallSites[] = "extensions.allowed_install_sites";
+
+// A whitelist of extension ids the user can install: exceptions from the
+// following blacklist.
+const char kExtensionInstallAllowList[] = "extensions.install.allowlist";
+
+// A blacklist, containing extensions the user cannot install. This list can
+// contain "*" meaning all extensions. This list should not be confused with the
+// extension blacklist, which is Google controlled.
+const char kExtensionInstallDenyList[] = "extensions.install.denylist";
+
+// Whether we have run the extension-alert system (see ExtensionGlobalError)
+// at least once for this profile.
+const char kExtensionAlertsInitializedPref[] = "extensions.alerts.initialized";
+
+// A list containing extensions that Chrome will silently install
+// at startup time. It is a list of strings, each string contains
+// an extension ID and an update URL, delimited by a semicolon.
+// This preference is set by an admin policy, and meant to be only
+// accessed through extensions::ExternalPolicyProvider.
+const char kExtensionInstallForceList[] = "extensions.install.forcelist";
+
+// Time of the last, and next scheduled, extensions auto-update checks.
+const char kLastExtensionsUpdateCheck[] = "extensions.autoupdate.last_check";
+const char kNextExtensionsUpdateCheck[] = "extensions.autoupdate.next_check";
+// Version number of last blacklist check
+const char kExtensionBlacklistUpdateVersion[] =
+    "extensions.blacklistupdate.version";
+
+// Keeps track of which sessions are collapsed in the Other Devices menu.
+const char kNtpCollapsedForeignSessions[] = "ntp.collapsed_foreign_sessions";
+
+// New Tab Page URLs that should not be shown as most visited thumbnails.
+const char kNtpMostVisitedURLsBlacklist[] = "ntp.most_visited_blacklist";
+
+// Last time of update of promo_resource_cache.
+const char kNtpPromoResourceCacheUpdate[] = "ntp.promo_resource_cache_update";
+
+// Serves tips for the NTP.
+const char kNtpTipsResourceServer[] = "ntp.tips_resource_server";
+
+// Serves dates to determine display of elements on the NTP.
+const char kNtpDateResourceServer[] = "ntp.date_resource_server";
+
+// Which bookmarks folder should be visible on the new tab page v4.
+const char kNtpShownBookmarksFolder[] = "ntp.shown_bookmarks_folder";
+
+// Which page should be visible on the new tab page v4
+const char kNtpShownPage[] = "ntp.shown_page";
+
+// True if a desktop sync session was found for this user.
+const char kNtpPromoDesktopSessionFound[] = "ntp.promo_desktop_session_found";
+
+// Boolean indicating whether the web store is active for the current locale.
+const char kNtpWebStoreEnabled[] = "ntp.webstore_enabled";
+
+// The id of the last web store promo actually displayed on the NTP.
+const char kNtpWebStorePromoLastId[] = "ntp.webstore_last_promo_id";
+
+// The id of the current web store promo.
+const char kNtpWebStorePromoId[] = "ntp.webstorepromo.id";
+
+// The header line for the NTP web store promo.
+const char kNtpWebStorePromoHeader[] = "ntp.webstorepromo.header";
+
+// The button text for the NTP web store promo.
+const char kNtpWebStorePromoButton[] = "ntp.webstorepromo.button";
+
+// The button link for the NTP web store promo.
+const char kNtpWebStorePromoLink[] = "ntp.webstorepromo.link";
+
+// The image URL for the NTP web store promo logo.
+const char kNtpWebStorePromoLogo[] = "ntp.webstorepromo.logo";
+
+// The original URL for the NTP web store promo logo.
+const char kNtpWebStorePromoLogoSource[] = "ntp.webstorepromo.logo_source";
+
+// The "hide this" link text for the NTP web store promo.
+const char kNtpWebStorePromoExpire[] = "ntp.webstorepromo.expire";
+
+// Specifies what users should maximize the NTP web store promo.
+const char kNtpWebStorePromoUserGroup[] = "ntp.webstorepromo.usergroup";
+
+// Customized app page names that appear on the New Tab Page.
+const char kNtpAppPageNames[] = "ntp.app_page_names";
+
+const char kDevToolsDisabled[] = "devtools.disabled";
+
+// A string specifying the dock location (either 'bottom' or 'right').
+const char kDevToolsDockSide[] = "devtools.dock_side";
+
+// Maps of files edited locally using DevTools.
+const char kDevToolsEditedFiles[] = "devtools.edited_files";
+
+// Integer location of the horizontal split bar in the browser view.
+const char kDevToolsHSplitLocation[] = "devtools.split_location";
+
+// A boolean specifying whether dev tools window should be opened docked.
+const char kDevToolsOpenDocked[] = "devtools.open_docked";
+
+#if defined(OS_ANDROID)
+// A boolean specifying whether remote dev tools debugging is enabled.
+const char kDevToolsRemoteEnabled[] = "devtools.remote_enabled";
+#endif
+
+// Integer location of the vertical split bar in the browser view.
+const char kDevToolsVSplitLocation[] = "devtools.v_split_location";
+
+#if defined(OS_ANDROID)
+// A boolean specifying whether a SPDY proxy is enabled.
+const char kSpdyProxyEnabled[] = "spdy_proxy.enabled";
+#endif
+
+// 64-bit integer serialization of the base::Time when the last sync occurred.
+const char kSyncLastSyncedTime[] = "sync.last_synced_time";
+
+// Boolean specifying whether the user finished setting up sync.
+const char kSyncHasSetupCompleted[] = "sync.has_setup_completed";
+
+// Boolean specifying whether to automatically sync all data types (including
+// future ones, as they're added).  If this is true, the following preferences
+// (kSyncBookmarks, kSyncPasswords, etc.) can all be ignored.
+const char kSyncKeepEverythingSynced[] = "sync.keep_everything_synced";
+
+// Booleans specifying whether the user has selected to sync the following
+// datatypes.
+const char kSyncBookmarks[] = "sync.bookmarks";
+const char kSyncPasswords[] = "sync.passwords";
+const char kSyncPreferences[] = "sync.preferences";
+const char kSyncAppNotifications[] = "sync.app_notifications";
+const char kSyncAppSettings[] = "sync.app_settings";
+const char kSyncApps[] = "sync.apps";
+const char kSyncAutofill[] = "sync.autofill";
+const char kSyncAutofillProfile[] = "sync.autofill_profile";
+const char kSyncThemes[] = "sync.themes";
+const char kSyncTypedUrls[] = "sync.typed_urls";
+const char kSyncExtensions[] = "sync.extensions";
+const char kSyncExtensionSettings[] = "sync.extension_settings";
+const char kSyncSearchEngines[] = "sync.search_engines";
+const char kSyncSessions[] = "sync.sessions";
+const char kSyncHistoryDeleteDirectives[] = "sync.history_delete_directives";
+
+// Boolean used by enterprise configuration management in order to lock down
+// sync.
+const char kSyncManaged[] = "sync.managed";
+
+// Boolean to prevent sync from automatically starting up.  This is
+// used when sync is disabled by the user via the privacy dashboard.
+const char kSyncSuppressStart[] = "sync.suppress_start";
+
+// List of the currently acknowledged set of sync types, used to figure out
+// if a new sync type has rolled out so we can notify the user.
+const char kSyncAcknowledgedSyncTypes[] = "sync.acknowledged_types";
+
+// Dictionary from sync model type (as an int) to max invalidation
+// version (int64 represented as a string).
+const char kSyncMaxInvalidationVersions[] = "sync.max_invalidation_versions";
+
+// The GUID session sync will use to identify this client, even across sync
+// disable/enable events.
+const char kSyncSessionsGUID[] = "sync.session_sync_guid";
+
+// Opaque state from the invalidation subsystem that is persisted via prefs.
+// The value is base 64 encoded.
+const char kInvalidatorInvalidationState[] = "invalidator.invalidation_state";
+
+// List of {source, name, max invalidation version} tuples. source is an int,
+// while max invalidation version is an int64; both are stored as string
+// representations though.
+const char kInvalidatorMaxInvalidationVersions[] =
+    "invalidator.max_invalidation_versions";
+
+// A string that can be used to restore sync encryption infrastructure on
+// startup so that the user doesn't need to provide credentials on each start.
+const char kSyncEncryptionBootstrapToken[] =
+    "sync.encryption_bootstrap_token";
+
+// Same as kSyncEncryptionBootstrapToken, but derived from the keystore key,
+// so we don't have to do a GetKey command at restart.
+const char kSyncKeystoreEncryptionBootstrapToken[] =
+    "sync.keystore_encryption_bootstrap_token";
+
+// Boolean tracking whether the user chose to specify a secondary encryption
+// passphrase.
+const char kSyncUsingSecondaryPassphrase[] = "sync.using_secondary_passphrase";
+
+// String the identifies the last user that logged into sync and other
+// google services. As opposed to kGoogleServicesUsername, this value is not
+// cleared on signout, but while the user is signed in the two values will
+// be the same.
+const char kGoogleServicesLastUsername[] = "google.services.last_username";
+
+// String that identifies the current user logged into sync and other google
+// services.
+const char kGoogleServicesUsername[] = "google.services.username";
+
+// Local state pref containing a string regex that restricts which accounts
+// can be used to log in to chrome (e.g. "*@google.com"). If missing or blank,
+// all accounts are allowed (no restrictions).
+const char kGoogleServicesUsernamePattern[] =
+    "google.services.username_pattern";
+
+#if !defined(OS_ANDROID)
+// Tracks the number of times that we have shown the sync promo at startup.
+const char kSyncPromoStartupCount[] = "sync_promo.startup_count";
+
+// A counter to remember the number of times we've been to the sync promo page
+// (not at startup).
+const char kSyncPromoViewCount[] = "sync_promo.view_count";
+
+// Boolean tracking whether the user chose to skip the sync promo.
+const char kSyncPromoUserSkipped[] = "sync_promo.user_skipped";
+
+// Boolean that specifies if the sync promo is allowed to show on first run.
+// This preference is specified in the master preference file to suppress the
+// sync promo for some installations.
+const char kSyncPromoShowOnFirstRunAllowed[] =
+    "sync_promo.show_on_first_run_allowed";
+
+// Boolean that specifies if we should show a bubble in the new tab page.
+// The bubble is used to confirm that the user is signed into sync.
+const char kSyncPromoShowNTPBubble[] = "sync_promo.show_ntp_bubble";
+#endif
+
+// Time when the user's GAIA info was last updated (represented as an int64).
+const char kProfileGAIAInfoUpdateTime[] = "profile.gaia_info_update_time";
+
+// The URL from which the GAIA profile picture was downloaded. This is cached to
+// prevent the same picture from being downloaded multiple times.
+const char kProfileGAIAInfoPictureURL[] = "profile.gaia_info_picture_url";
+
+// Create web application shortcut dialog preferences.
+const char kWebAppCreateOnDesktop[] = "browser.web_app.create_on_desktop";
+const char kWebAppCreateInAppsMenu[] = "browser.web_app.create_in_apps_menu";
+const char kWebAppCreateInQuickLaunchBar[] =
+    "browser.web_app.create_in_quick_launch_bar";
+
+// Dictionary that maps Geolocation network provider server URLs to
+// corresponding access token.
+const char kGeolocationAccessToken[] = "geolocation.access_token";
+
+// Boolean that indicates whether to allow firewall traversal while trying to
+// establish the initial connection from the client or host.
+const char kRemoteAccessHostFirewallTraversal[] =
+    "remote_access.host_firewall_traversal";
+
+// Boolean controlling whether 2-factor auth should be required when connecting
+// to a host (instead of a PIN).
+const char kRemoteAccessHostRequireTwoFactor[] =
+    "remote_access.host_require_two_factor";
+
+// String containing the domain name that hosts must belong to. If blank, then
+// hosts can belong to any domain.
+const char kRemoteAccessHostDomain[] = "remote_access.host_domain";
+
+// String containing the domain name of the Chromoting Directory.
+// Used by Chromoting host and client.
+const char kRemoteAccessHostTalkGadgetPrefix[] =
+    "remote_access.host_talkgadget_prefix";
+
+// Boolean controlling whether curtaining is required when connecting to a host.
+const char kRemoteAccessHostRequireCurtain[] =
+    "remote_access.host_require_curtain";
+
+// The last used printer and its settings.
+const char kPrintPreviewStickySettings[] =
+    "printing.print_preview_sticky_settings";
+// The root URL of the cloud print service.
+const char kCloudPrintServiceURL[] = "cloud_print.service_url";
+
+// The URL to use to sign in to cloud print.
+const char kCloudPrintSigninURL[] = "cloud_print.signin_url";
+
+// The last requested size of the dialog as it was closed.
+const char kCloudPrintDialogWidth[] = "cloud_print.dialog_size.width";
+const char kCloudPrintDialogHeight[] = "cloud_print.dialog_size.height";
+const char kCloudPrintSigninDialogWidth[] =
+    "cloud_print.signin_dialog_size.width";
+const char kCloudPrintSigninDialogHeight[] =
+    "cloud_print.signin_dialog_size.height";
+
+#if !defined(OS_ANDROID)
+// The Chrome To Mobile service mobile device list pref.
+const char kChromeToMobileDeviceList[] = "chrome_to_mobile.device_list";
+#endif
+
+// The list of BackgroundContents that should be loaded when the browser
+// launches.
+const char kRegisteredBackgroundContents[] = "background_contents.registered";
+
+#if !defined(OS_ANDROID)
+// An int that stores how often we've shown the "Chrome is configured to
+// auto-launch" infobar.
+const char kShownAutoLaunchInfobar[] = "browser.shown_autolaunch_infobar";
+#endif
+
+// String that lists supported HTTP authentication schemes.
+const char kAuthSchemes[] = "auth.schemes";
+
+// Boolean that specifies whether to disable CNAME lookups when generating
+// Kerberos SPN.
+const char kDisableAuthNegotiateCnameLookup[] =
+    "auth.disable_negotiate_cname_lookup";
+
+// Boolean that specifies whether to include the port in a generated Kerberos
+// SPN.
+const char kEnableAuthNegotiatePort[] = "auth.enable_negotiate_port";
+
+// Whitelist containing servers for which Integrated Authentication is enabled.
+const char kAuthServerWhitelist[] = "auth.server_whitelist";
+
+// Whitelist containing servers Chrome is allowed to do Kerberos delegation
+// with.
+const char kAuthNegotiateDelegateWhitelist[] =
+    "auth.negotiate_delegate_whitelist";
+
+// String that specifies the name of a custom GSSAPI library to load.
+const char kGSSAPILibraryName[] = "auth.gssapi_library_name";
+
+// String that specifies the origin allowed to use SpdyProxy
+// authentication, if any.
+const char kSpdyProxyOrigin[] = "auth.spdyproxy.origin";
+
+// Boolean that specifies whether to allow basic auth prompting on cross-
+// domain sub-content requests.
+const char kAllowCrossOriginAuthPrompt[] = "auth.allow_cross_origin_prompt";
+
+// An int64 pref that contains the total size of all HTTP content that has been
+// received from the network.
+const char kHttpReceivedContentLength[] = "http_received_content_length";
+
+// An int64 pref that contains the total original size of all HTTP content that
+// was received over the network.
+const char kHttpOriginalContentLength[] = "http_original_content_length";
+
+#if defined(OS_CHROMEOS)
+// Dictionary for transient storage of settings that should go into device
+// settings storage before owner has been assigned.
+const char kDeviceSettingsCache[] = "signed_settings_cache";
+
+// The hardware keyboard layout of the device. This should look like
+// "xkb:us::eng".
+const char kHardwareKeyboardLayout[] = "intl.hardware_keyboard";
+
+// An integer pref which shows number of times carrier deal promo
+// notification has been shown to user.
+const char kCarrierDealPromoShown[] =
+    "settings.internet.mobile.carrier_deal_promo_shown";
+
+// A boolean pref of the auto-enrollment decision. Its value is only valid if
+// it's not the default value; otherwise, no auto-enrollment decision has been
+// made yet.
+const char kShouldAutoEnroll[] = "ShouldAutoEnroll";
+
+// An integer pref with the maximum number of bits used by the client in a
+// previous auto-enrollment request. If the client goes through an auto update
+// during OOBE and reboots into a version of the OS with a larger maximum
+// modulus, then it will retry auto-enrollment using the updated value.
+const char kAutoEnrollmentPowerLimit[] = "AutoEnrollmentPowerLimit";
+
+// The local state pref that stores device activity times before reporting
+// them to the policy server.
+const char kDeviceActivityTimes[] = "device_status.activity_times";
+
+// A pref holding the last known location when device location reporting is
+// enabled.
+const char kDeviceLocation[] = "device_status.location";
+
+// A string that is used to store first-time sync startup after once sync is
+// disabled. This will be refreshed every sign-in.
+const char kSyncSpareBootstrapToken[] = "sync.spare_bootstrap_token";
+
+// A pref holding the value of the policy used to disable mounting of external
+// storage for the user.
+const char kExternalStorageDisabled[] = "hardware.external_storage_disabled";
+
+// A pref holding the value of the policy used to disable playing audio on
+// ChromeOS devices. This pref overrides |kAudioMute| but does not overwrite
+// it, therefore when the policy is lifted the original mute state is restored.
+const char kAudioOutputAllowed[] = "hardware.audio_output_enabled";
+
+// A pref holding the value of the policy used to disable capturing audio on
+// ChromeOS devices.
+const char kAudioCaptureAllowed[] = "hardware.audio_capture_enabled";
+
+// A dictionary that maps usernames to wallpaper properties.
+const char kUsersWallpaperInfo[] = "user_wallpaper_info";
+
+// Copy of owner swap mouse buttons option to use on login screen.
+const char kOwnerPrimaryMouseButtonRight[] = "owner.mouse.primary_right";
+
+// Copy of owner tap-to-click option to use on login screen.
+const char kOwnerTapToClickEnabled[] = "owner.touchpad.enable_tap_to_click";
+#endif
+
+// Whether there is a Flash version installed that supports clearing LSO data.
+const char kClearPluginLSODataEnabled[] = "browser.clear_lso_data_enabled";
+
+// Whether we should show Pepper Flash-specific settings.
+const char kPepperFlashSettingsEnabled[] =
+    "browser.pepper_flash_settings_enabled";
+
+// String which specifies where to store the disk cache.
+const char kDiskCacheDir[] = "browser.disk_cache_dir";
+// Pref name for the policy specifying the maximal cache size.
+const char kDiskCacheSize[] = "browser.disk_cache_size";
+// Pref name for the policy specifying the maximal media cache size.
+const char kMediaCacheSize[] = "browser.media_cache_size";
+
+// Specifies the release channel that the device should be locked to.
+// Possible values: "stable-channel", "beta-channel", "dev-channel", or an
+// empty string, in which case the value will be ignored.
+// TODO(dubroy): This preference may not be necessary once
+// http://crosbug.com/17015 is implemented and the update engine can just
+// fetch the correct value from the policy.
+const char kChromeOsReleaseChannel[] = "cros.system.releaseChannel";
+
+// Value of the enums in TabStrip::LayoutType as an int.
+const char kTabStripLayoutType[] = "tab_strip_layout_type";
+
+// If true, cloud policy for the user is loaded once the user signs in.
+const char kLoadCloudPolicyOnSignin[] = "policy.load_cloud_policy_on_signin";
+
+// Indicates that factory reset was requested from options page.
+const char kFactoryResetRequested[] = "FactoryResetRequested";
+
+// *************** SERVICE PREFS ***************
+// These are attached to the service process.
+
+const char kCloudPrintRoot[] = "cloud_print";
+const char kCloudPrintProxyEnabled[] = "cloud_print.enabled";
+// The unique id for this instance of the cloud print proxy.
+const char kCloudPrintProxyId[] = "cloud_print.proxy_id";
+// The GAIA auth token for Cloud Print
+const char kCloudPrintAuthToken[] = "cloud_print.auth_token";
+// The GAIA auth token used by Cloud Print to authenticate with the XMPP server
+// This should eventually go away because the above token should work for both.
+const char kCloudPrintXMPPAuthToken[] = "cloud_print.xmpp_auth_token";
+// The email address of the account used to authenticate with the Cloud Print
+// server.
+const char kCloudPrintEmail[] = "cloud_print.email";
+// Settings specific to underlying print system.
+const char kCloudPrintPrintSystemSettings[] =
+    "cloud_print.print_system_settings";
+// A boolean indicating whether we should poll for print jobs when don't have
+// an XMPP connection (false by default).
+const char kCloudPrintEnableJobPoll[] = "cloud_print.enable_job_poll";
+const char kCloudPrintRobotRefreshToken[] = "cloud_print.robot_refresh_token";
+const char kCloudPrintRobotEmail[] = "cloud_print.robot_email";
+// A boolean indicating whether we should connect to cloud print new printers.
+const char kCloudPrintConnectNewPrinters[] = "cloud_print.connect_new_printers";
+// A boolean indicating whether we should ping XMPP connection.
+const char kCloudPrintXmppPingEnabled[] = "cloud_print.xmpp_ping_enabled";
+// An int value indicating the average timeout between xmpp pings.
+const char kCloudPrintXmppPingTimeout[] = "cloud_print.xmpp_ping_timeout_sec";
+// List of printers which should not be connected.
+const char kCloudPrintPrinterBlacklist[] = "cloud_print.printer_blacklist";
+// A boolean indicating whether submitting jobs to Google Cloud Print is
+// blocked by policy.
+const char kCloudPrintSubmitEnabled[] = "cloud_print.submit_enabled";
+
+// Preference to store proxy settings.
+const char kProxy[] = "proxy";
+const char kMaxConnectionsPerProxy[] = "net.max_connections_per_proxy";
+
+// Preferences that are exclusively used to store managed values for default
+// content settings.
+const char kManagedDefaultCookiesSetting[] =
+    "profile.managed_default_content_settings.cookies";
+const char kManagedDefaultImagesSetting[] =
+    "profile.managed_default_content_settings.images";
+const char kManagedDefaultJavaScriptSetting[] =
+    "profile.managed_default_content_settings.javascript";
+const char kManagedDefaultPluginsSetting[] =
+    "profile.managed_default_content_settings.plugins";
+const char kManagedDefaultPopupsSetting[] =
+    "profile.managed_default_content_settings.popups";
+const char kManagedDefaultGeolocationSetting[] =
+    "profile.managed_default_content_settings.geolocation";
+const char kManagedDefaultNotificationsSetting[] =
+    "profile.managed_default_content_settings.notifications";
+const char kManagedDefaultMediaStreamSetting[] =
+    "profile.managed_default_content_settings.media_stream";
+
+// Preferences that are exclusively used to store managed
+// content settings patterns.
+const char kManagedCookiesAllowedForUrls[] =
+    "profile.managed_cookies_allowed_for_urls";
+const char kManagedCookiesBlockedForUrls[] =
+    "profile.managed_cookies_blocked_for_urls";
+const char kManagedCookiesSessionOnlyForUrls[] =
+    "profile.managed_cookies_sessiononly_for_urls";
+const char kManagedImagesAllowedForUrls[] =
+    "profile.managed_images_allowed_for_urls";
+const char kManagedImagesBlockedForUrls[] =
+    "profile.managed_images_blocked_for_urls";
+const char kManagedJavaScriptAllowedForUrls[] =
+    "profile.managed_javascript_allowed_for_urls";
+const char kManagedJavaScriptBlockedForUrls[] =
+    "profile.managed_javascript_blocked_for_urls";
+const char kManagedPluginsAllowedForUrls[] =
+    "profile.managed_plugins_allowed_for_urls";
+const char kManagedPluginsBlockedForUrls[] =
+    "profile.managed_plugins_blocked_for_urls";
+const char kManagedPopupsAllowedForUrls[] =
+    "profile.managed_popups_allowed_for_urls";
+const char kManagedPopupsBlockedForUrls[] =
+    "profile.managed_popups_blocked_for_urls";
+const char kManagedNotificationsAllowedForUrls[] =
+    "profile.managed_notifications_allowed_for_urls";
+const char kManagedNotificationsBlockedForUrls[] =
+    "profile.managed_notifications_blocked_for_urls";
+const char kManagedAutoSelectCertificateForUrls[] =
+    "profile.managed_auto_select_certificate_for_urls";
+
+// Set to true if the user created a login item so we should not modify it when
+// uninstalling background apps.
+const char kUserCreatedLoginItem[] = "background_mode.user_created_login_item";
+
+// Set to true if the user removed our login item so we should not create a new
+// one when uninstalling background apps.
+const char kUserRemovedLoginItem[] = "background_mode.user_removed_login_item";
+
+// Set to true if background mode is enabled on this browser.
+const char kBackgroundModeEnabled[] = "background_mode.enabled";
+
+// List of protocol handlers.
+const char kRegisteredProtocolHandlers[] =
+  "custom_handlers.registered_protocol_handlers";
+
+// List of protocol handlers the user has requested not to be asked about again.
+const char kIgnoredProtocolHandlers[] =
+  "custom_handlers.ignored_protocol_handlers";
+
+// Whether user-specified handlers for protocols and content types can be
+// specified.
+const char kCustomHandlersEnabled[] = "custom_handlers.enabled";
+
+// Integers that specify the policy refresh rate for device- and user-policy in
+// milliseconds. Not all values are meaningful, so it is clamped to a sane range
+// by the cloud policy subsystem.
+const char kDevicePolicyRefreshRate[] = "policy.device_refresh_rate";
+const char kUserPolicyRefreshRate[] = "policy.user_refresh_rate";
+
+// String that represents the recovery component last downloaded version. This
+// takes the usual 'a.b.c.d' notation.
+const char kRecoveryComponentVersion[] = "recovery_component.version";
+
+// String that stores the component updater last known state. This is used for
+// troubleshooting.
+const char kComponentUpdaterState[] = "component_updater.state";
+
+// The next media gallery ID to assign.
+const char kMediaGalleriesUniqueId[] = "media_galleries.gallery_id";
+
+// A list of dictionaries, where each dictionary represents a known media
+// gallery.
+const char kMediaGalleriesRememberedGalleries[] =
+    "media_galleries.remembered_galleries";
+
+#if defined(USE_AURA)
+// |kShelfAlignment| and |kShelfAutoHideBehavior| have a local variant. The
+// local variant is not synced and is used if set. If the local variant is not
+// set its value is set from the synced value (once prefs have been
+// synced). This gives a per-machine setting that is initialized from the last
+// set value.
+// String value corresponding to ash::Shell::ShelfAlignment.
+const char kShelfAlignment[] = "shelf_alignment";
+const char kShelfAlignmentLocal[] = "shelf_alignment_local";
+// String value corresponding to ash::Shell::ShelfAutoHideBehavior.
+const char kShelfAutoHideBehavior[] = "auto_hide_behavior";
+const char kShelfAutoHideBehaviorLocal[] = "auto_hide_behavior_local";
+// Boolean value indicating whether to use default pinned apps.
+const char kUseDefaultPinnedApps[] = "use_default_pinned_apps";
+const char kPinnedLauncherApps[] =
+    "pinned_launcher_apps";
+// Boolean value indicating whether to show a logout button in the ash tray.
+const char kShowLogoutButtonInTray[] = "show_logout_button_in_tray";
+
+const char kLongPressTimeInSeconds[] =
+    "gesture.long_press_time_in_seconds";
+const char kMaxDistanceBetweenTapsForDoubleTap[] =
+    "gesture.max_distance_between_taps_for_double_tap";
+const char kMaxDistanceForTwoFingerTapInPixels[] =
+    "gesture.max_distance_for_two_finger_tap_in_pixels";
+const char kMaxSecondsBetweenDoubleClick[] =
+    "gesture.max_seconds_between_double_click";
+const char kMaxSeparationForGestureTouchesInPixels[] =
+    "gesture.max_separation_for_gesture_touches_in_pixels";
+const char kMaxSwipeDeviationRatio[] =
+    "gesture.max_swipe_deviation_ratio";
+const char kMaxTouchDownDurationInSecondsForClick[] =
+    "gesture.max_touch_down_duration_in_seconds_for_click";
+const char kMaxTouchMoveInPixelsForClick[] =
+    "gesture.max_touch_move_in_pixels_for_click";
+const char kMinDistanceForPinchScrollInPixels[] =
+    "gesture.min_distance_for_pinch_scroll_in_pixels";
+const char kMinFlickSpeedSquared[] =
+    "gesture.min_flick_speed_squared";
+const char kMinPinchUpdateDistanceInPixels[] =
+    "gesture.min_pinch_update_distance_in_pixels";
+const char kMinRailBreakVelocity[] =
+    "gesture.min_rail_break_velocity";
+const char kMinScrollDeltaSquared[] =
+    "gesture.min_scroll_delta_squared";
+const char kMinSwipeSpeed[] =
+    "gesture.min_swipe_speed";
+const char kMinTouchDownDurationInSecondsForClick[] =
+    "gesture.min_touch_down_duration_in_seconds_for_click";
+const char kPointsBufferedForVelocity[] =
+    "gesture.points_buffered_for_velocity";
+const char kRailBreakProportion[] =
+    "gesture.rail_break_proportion";
+const char kRailStartProportion[] =
+    "gesture.rail_start_proportion";
+const char kSemiLongPressTimeInSeconds[] =
+    "gesture.semi_long_press_time_in_seconds";
+const char kTouchScreenFlingAccelerationAdjustment[] =
+    "gesture.touchscreen_fling_acceleration_adjustment";
+#endif
+
+// Indicates whether the browser is in managed mode.
+const char kInManagedMode[] = "managed_mode";
+
+// Counts how many more times the 'profile on a network share' warning should be
+// shown to the user before the next silence period.
+const char kNetworkProfileWarningsLeft[] = "network_profile.warnings_left";
+// Tracks the time of the last shown warning. Used to reset
+// |network_profile.warnings_left| after a silence period.
+const char kNetworkProfileLastWarningTime[] =
+    "network_profile.last_warning_time";
+
+// 64-bit serialization of the time last policy usage statistics were collected
+// by UMA_HISTOGRAM_ENUMERATION.
+const char kLastPolicyStatisticsUpdate[] = "policy.last_statistics_update";
+
+}  // namespace prefs
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
new file mode 100644
index 0000000..6ad2377
--- /dev/null
+++ b/chrome/common/pref_names.h
@@ -0,0 +1,792 @@
+// Copyright (c) 2012 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.
+
+// Constants for the names of various preferences, for easier changing.
+
+#ifndef CHROME_COMMON_PREF_NAMES_H_
+#define CHROME_COMMON_PREF_NAMES_H_
+
+#include <stddef.h>
+
+#include "build/build_config.h"
+
+namespace prefs {
+
+// Profile prefs. Please add Local State prefs below instead.
+extern const char kDefaultApps[];
+extern const char kDefaultAppsInstalled[];
+extern const char kHomePageIsNewTabPage[];
+extern const char kHomePage[];
+extern const char kHomePageChanged[];
+extern const char kIsGooglePlusUser[];
+extern const char kSessionExitedCleanly[];
+extern const char kSessionExitType[];
+extern const char kRestoreOnStartup[];
+extern const char kURLsToRestoreOnStartup[];
+extern const char kRestoreOnStartupMigrated[];
+extern const char kDisableScreenshots[];
+
+// For OS_CHROMEOS we maintain kApplicationLocale property in both local state
+// and user's profile.  Global property determines locale of login screen,
+// while user's profile determines his personal locale preference.
+extern const char kApplicationLocale[];
+#if defined(OS_CHROMEOS)
+extern const char kApplicationLocaleBackup[];
+extern const char kApplicationLocaleAccepted[];
+extern const char kOwnerLocale[];
+#endif
+
+// Obselete keys, kept only for migration code to the new keys. See
+// http://crbug.com/123812
+extern const char kGlobalDefaultCharset[];
+extern const char kWebKitGlobalDefaultFontSize[];
+extern const char kWebKitGlobalDefaultFixedFontSize[];
+extern const char kWebKitGlobalMinimumFontSize[];
+extern const char kWebKitGlobalMinimumLogicalFontSize[];
+extern const char kWebKitGlobalJavascriptEnabled[];
+extern const char kWebKitGlobalJavascriptCanOpenWindowsAutomatically[];
+extern const char kWebKitGlobalLoadsImagesAutomatically[];
+extern const char kWebKitGlobalPluginsEnabled[];
+extern const char kWebKitGlobalStandardFontFamily[];
+extern const char kWebKitGlobalFixedFontFamily[];
+extern const char kWebKitGlobalSerifFontFamily[];
+extern const char kWebKitGlobalSansSerifFontFamily[];
+extern const char kWebKitGlobalCursiveFontFamily[];
+extern const char kWebKitGlobalFantasyFontFamily[];
+extern const char kWebKitOldStandardFontFamily[];
+extern const char kWebKitOldFixedFontFamily[];
+extern const char kWebKitOldSerifFontFamily[];
+extern const char kWebKitOldSansSerifFontFamily[];
+extern const char kWebKitOldCursiveFontFamily[];
+extern const char kWebKitOldFantasyFontFamily[];
+
+extern const char kDefaultCharset[];
+extern const char kAcceptLanguages[];
+extern const char kStaticEncodings[];
+extern const char kShowBookmarkBar[];
+extern const char kBookmarkEditorExpandedNodes[];
+extern const char kWebKitCommonScript[];
+extern const char kWebKitStandardFontFamily[];
+extern const char kWebKitFixedFontFamily[];
+extern const char kWebKitSerifFontFamily[];
+extern const char kWebKitSansSerifFontFamily[];
+extern const char kWebKitCursiveFontFamily[];
+extern const char kWebKitFantasyFontFamily[];
+extern const char kWebKitPictographFontFamily[];
+
+// ISO 15924 four-letter script codes that per-script font prefs are supported
+// for.
+extern const char* const kWebKitScriptsForFontFamilyMaps[];
+extern const size_t kWebKitScriptsForFontFamilyMapsLength;
+
+// Per-script font pref prefixes.
+extern const char kWebKitStandardFontFamilyMap[];
+extern const char kWebKitFixedFontFamilyMap[];
+extern const char kWebKitSerifFontFamilyMap[];
+extern const char kWebKitSansSerifFontFamilyMap[];
+extern const char kWebKitCursiveFontFamilyMap[];
+extern const char kWebKitFantasyFontFamilyMap[];
+extern const char kWebKitPictographFontFamilyMap[];
+
+// Per-script font prefs that have defaults, for easy reference when registering
+// the defaults.
+extern const char kWebKitStandardFontFamilyArabic[];
+extern const char kWebKitFixedFontFamilyArabic[];
+extern const char kWebKitSerifFontFamilyArabic[];
+extern const char kWebKitSansSerifFontFamilyArabic[];
+extern const char kWebKitStandardFontFamilyCyrillic[];
+extern const char kWebKitFixedFontFamilyCyrillic[];
+extern const char kWebKitSerifFontFamilyCyrillic[];
+extern const char kWebKitSansSerifFontFamilyCyrillic[];
+extern const char kWebKitStandardFontFamilyGreek[];
+extern const char kWebKitFixedFontFamilyGreek[];
+extern const char kWebKitSerifFontFamilyGreek[];
+extern const char kWebKitSansSerifFontFamilyGreek[];
+extern const char kWebKitStandardFontFamilyJapanese[];
+extern const char kWebKitFixedFontFamilyJapanese[];
+extern const char kWebKitSerifFontFamilyJapanese[];
+extern const char kWebKitSansSerifFontFamilyJapanese[];
+extern const char kWebKitStandardFontFamilyKorean[];
+extern const char kWebKitFixedFontFamilyKorean[];
+extern const char kWebKitSerifFontFamilyKorean[];
+extern const char kWebKitSansSerifFontFamilyKorean[];
+extern const char kWebKitCursiveFontFamilyKorean[];
+extern const char kWebKitStandardFontFamilySimplifiedHan[];
+extern const char kWebKitFixedFontFamilySimplifiedHan[];
+extern const char kWebKitSerifFontFamilySimplifiedHan[];
+extern const char kWebKitSansSerifFontFamilySimplifiedHan[];
+extern const char kWebKitStandardFontFamilyTraditionalHan[];
+extern const char kWebKitFixedFontFamilyTraditionalHan[];
+extern const char kWebKitSerifFontFamilyTraditionalHan[];
+extern const char kWebKitSansSerifFontFamilyTraditionalHan[];
+
+extern const char kWebKitDefaultFontSize[];
+extern const char kWebKitDefaultFixedFontSize[];
+extern const char kWebKitMinimumFontSize[];
+extern const char kWebKitMinimumLogicalFontSize[];
+extern const char kWebKitJavascriptEnabled[];
+extern const char kWebKitWebSecurityEnabled[];
+extern const char kWebKitJavascriptCanOpenWindowsAutomatically[];
+extern const char kWebKitLoadsImagesAutomatically[];
+extern const char kWebKitPluginsEnabled[];
+extern const char kWebKitDomPasteEnabled[];
+extern const char kWebKitShrinksStandaloneImagesToFit[];
+extern const char kWebKitInspectorSettings[];
+extern const char kWebKitUsesUniversalDetector[];
+extern const char kWebKitTextAreasAreResizable[];
+extern const char kWebKitJavaEnabled[];
+extern const char kWebkitTabsToLinks[];
+extern const char kWebKitAllowDisplayingInsecureContent[];
+extern const char kWebKitAllowRunningInsecureContent[];
+#if defined(OS_ANDROID)
+extern const char kWebKitFontScaleFactor[];
+extern const char kWebKitForceEnableZoom[];
+#endif
+extern const char kPasswordManagerEnabled[];
+extern const char kPasswordManagerAllowShowPasswords[];
+extern const char kPasswordGenerationEnabled[];
+extern const char kAutologinEnabled[];
+extern const char kReverseAutologinEnabled[];
+extern const char kReverseAutologinRejectedEmailList[];
+extern const char kSafeBrowsingEnabled[];
+extern const char kSafeBrowsingReportingEnabled[];
+extern const char kSafeBrowsingProceedAnywayDisabled[];
+extern const char kIncognitoModeAvailability[];
+extern const char kSearchSuggestEnabled[];
+extern const char kConfirmToQuitEnabled[];
+extern const char kCookieBehavior[];  // OBSOLETE
+extern const char kSyncedDefaultSearchProviderGUID[];
+extern const char kDefaultSearchProviderEnabled[];
+extern const char kDefaultSearchProviderSearchURL[];
+extern const char kDefaultSearchProviderSuggestURL[];
+extern const char kDefaultSearchProviderInstantURL[];
+extern const char kDefaultSearchProviderIconURL[];
+extern const char kDefaultSearchProviderEncodings[];
+extern const char kDefaultSearchProviderName[];
+extern const char kDefaultSearchProviderKeyword[];
+extern const char kDefaultSearchProviderID[];
+extern const char kDefaultSearchProviderPrepopulateID[];
+extern const char kDefaultSearchProviderAlternateURLs[];
+extern const char kSearchProviderOverrides[];
+extern const char kSearchProviderOverridesVersion[];
+extern const char kPromptForDownload[];
+extern const char kAlternateErrorPagesEnabled[];
+extern const char kDnsStartupPrefetchList[];  // OBSOLETE
+extern const char kDnsPrefetchingStartupList[];
+extern const char kDnsHostReferralList[];  // OBSOLETE
+extern const char kDnsPrefetchingHostReferralList[];
+extern const char kDisableSpdy[];
+extern const char kHttpServerProperties[];
+extern const char kSpdyServers[];
+extern const char kAlternateProtocolServers[];
+extern const char kDisabledSchemes[];
+extern const char kUrlBlacklist[];
+extern const char kUrlWhitelist[];
+extern const char kInstantConfirmDialogShown[];
+extern const char kInstantEnabled[];
+extern const char kInstantUIZeroSuggestUrlPrefix[];
+extern const char kMultipleProfilePrefMigration[];
+extern const char kNetworkPredictionEnabled[];
+extern const char kDefaultAppsInstallState[];
+#if defined(OS_CHROMEOS)
+extern const char kAudioMute[];
+extern const char kAudioVolumePercent[];
+extern const char kTapToClickEnabled[];
+extern const char kTapDraggingEnabled[];
+extern const char kEnableTouchpadThreeFingerClick[];
+extern const char kEnableTouchpadThreeFingerSwipe[];
+extern const char kNaturalScroll[];
+extern const char kPrimaryMouseButtonRight[];
+extern const char kMouseSensitivity[];
+extern const char kTouchpadSensitivity[];
+extern const char kUse24HourClock[];
+extern const char kDisableDrive[];
+extern const char kDisableDriveOverCellular[];
+extern const char kDisableDriveHostedFiles[];
+// TODO(yusukes): Change "kLanguageABC" to "kABC". The current form is too long
+// to remember and confusing. The prefs are actually for input methods and i18n
+// keyboards, not UI languages.
+extern const char kLanguageCurrentInputMethod[];
+extern const char kLanguagePreviousInputMethod[];
+extern const char kLanguageHotkeyNextEngineInMenu[];
+extern const char kLanguageHotkeyPreviousEngine[];
+extern const char kLanguagePreferredLanguages[];
+extern const char kLanguagePreloadEngines[];
+extern const char kLanguageFilteredExtensionImes[];
+extern const char kLanguageChewingAutoShiftCur[];
+extern const char kLanguageChewingAddPhraseDirection[];
+extern const char kLanguageChewingEasySymbolInput[];
+extern const char kLanguageChewingEscCleanAllBuf[];
+extern const char kLanguageChewingForceLowercaseEnglish[];
+extern const char kLanguageChewingPlainZhuyin[];
+extern const char kLanguageChewingPhraseChoiceRearward[];
+extern const char kLanguageChewingSpaceAsSelection[];
+extern const char kLanguageChewingMaxChiSymbolLen[];
+extern const char kLanguageChewingCandPerPage[];
+extern const char kLanguageChewingKeyboardType[];
+extern const char kLanguageChewingSelKeys[];
+extern const char kLanguageChewingHsuSelKeyType[];
+extern const char kLanguageHangulKeyboard[];
+extern const char kLanguageHangulHanjaBindingKeys[];
+extern const char kLanguagePinyinCorrectPinyin[];
+extern const char kLanguagePinyinFuzzyPinyin[];
+extern const char kLanguagePinyinLookupTablePageSize[];
+extern const char kLanguagePinyinShiftSelectCandidate[];
+extern const char kLanguagePinyinMinusEqualPage[];
+extern const char kLanguagePinyinCommaPeriodPage[];
+extern const char kLanguagePinyinAutoCommit[];
+extern const char kLanguagePinyinDoublePinyin[];
+extern const char kLanguagePinyinDoublePinyinSchema[];
+extern const char kLanguagePinyinInitChinese[];
+extern const char kLanguagePinyinInitFull[];
+extern const char kLanguagePinyinInitFullPunct[];
+extern const char kLanguagePinyinInitSimplifiedChinese[];
+extern const char kLanguagePinyinTradCandidate[];
+extern const char kLanguageMozcPreeditMethod[];
+extern const char kLanguageMozcSessionKeymap[];
+extern const char kLanguageMozcPunctuationMethod[];
+extern const char kLanguageMozcSymbolMethod[];
+extern const char kLanguageMozcSpaceCharacterForm[];
+extern const char kLanguageMozcHistoryLearningLevel[];
+extern const char kLanguageMozcSelectionShortcut[];
+extern const char kLanguageMozcShiftKeyModeSwitch[];
+extern const char kLanguageMozcNumpadCharacterForm[];
+extern const char kLanguageMozcIncognitoMode[];
+extern const char kLanguageMozcUseAutoImeTurnOff[];
+extern const char kLanguageMozcUseHistorySuggest[];
+extern const char kLanguageMozcUseDictionarySuggest[];
+extern const char kLanguageMozcSuggestionsSize[];
+extern const char kLanguageRemapCapsLockKeyTo[];
+extern const char kLanguageRemapSearchKeyTo[];
+extern const char kLanguageRemapControlKeyTo[];
+extern const char kLanguageRemapAltKeyTo[];
+extern const char kLanguageXkbAutoRepeatEnabled[];
+extern const char kLanguageXkbAutoRepeatDelay[];
+extern const char kLanguageXkbAutoRepeatInterval[];
+extern const char kSpokenFeedbackEnabled[];
+extern const char kHighContrastEnabled[];
+extern const char kScreenMagnifierEnabled[];
+extern const char kScreenMagnifierScale[];
+extern const char kVirtualKeyboardEnabled[];
+extern const char kLabsAdvancedFilesystemEnabled[];
+extern const char kLabsMediaplayerEnabled[];
+extern const char kEnableScreenLock[];
+extern const char kShowPlanNotifications[];
+extern const char kShow3gPromoNotification[];
+extern const char kChromeOSReleaseNotesVersion[];
+extern const char kUseSharedProxies[];
+extern const char kOAuth1Token[];
+extern const char kOAuth1Secret[];
+extern const char kEnableCrosDRM[];
+extern const char kDisplayOverscans[];
+extern const char kPrimaryDisplayID[];
+extern const char kSecondaryDisplayLayout[];
+extern const char kSecondaryDisplayOffset[];
+extern const char kSecondaryDisplays[];
+#endif  // defined(OS_CHROMEOS)
+extern const char kIpcDisabledMessages[];
+extern const char kShowHomeButton[];
+extern const char kRecentlySelectedEncoding[];
+extern const char kDeleteBrowsingHistory[];
+extern const char kDeleteDownloadHistory[];
+extern const char kDeleteCache[];
+extern const char kDeleteCookies[];
+extern const char kDeletePasswords[];
+extern const char kDeleteFormData[];
+extern const char kDeleteHostedAppsData[];
+extern const char kDeauthorizeContentLicenses[];
+extern const char kEnableSpellCheck[];
+extern const char kSpeechRecognitionFilterProfanities[];
+extern const char kSpeechRecognitionTrayNotificationShownContexts[];
+extern const char kEnabledLabsExperiments[];
+extern const char kEnableAutoSpellCorrect[];
+extern const char kSavingBrowserHistoryDisabled[];
+extern const char kForceSafeSearch[];
+extern const char kDeleteTimePeriod[];
+#if defined(TOOLKIT_GTK)
+extern const char kUsesSystemTheme[];
+#endif
+extern const char kCurrentThemePackFilename[];
+extern const char kCurrentThemeID[];
+extern const char kCurrentThemeImages[];
+extern const char kCurrentThemeColors[];
+extern const char kCurrentThemeTints[];
+extern const char kCurrentThemeDisplayProperties[];
+extern const char kExtensionsUIDeveloperMode[];
+extern const char kExtensionToolbarSize[];
+extern const char kExtensionCommands[];
+extern const char kExtensionsSideloadWipeoutBubbleShown[];
+extern const char kPluginsLastInternalDirectory[];
+extern const char kPluginsPluginsList[];
+extern const char kPluginsDisabledPlugins[];
+extern const char kPluginsDisabledPluginsExceptions[];
+extern const char kPluginsEnabledPlugins[];
+extern const char kPluginsEnabledInternalPDF[];
+extern const char kPluginsEnabledNaCl[];
+extern const char kPluginsMigratedToPepperFlash[];
+extern const char kPluginsShowDetails[];
+extern const char kPluginsAllowOutdated[];
+extern const char kPluginsAlwaysAuthorize[];
+#if defined(ENABLE_PLUGIN_INSTALLATION)
+extern const char kPluginsMetadata[];
+extern const char kPluginsResourceCacheUpdate[];
+#endif
+extern const char kCheckDefaultBrowser[];
+#if defined(OS_WIN)
+extern const char kSuppressSwitchToMetroModeOnSetDefault[];
+#endif
+extern const char kDefaultBrowserSettingEnabled[];
+#if defined(OS_MACOSX)
+extern const char kShowUpdatePromotionInfoBar[];
+#endif
+extern const char kUseCustomChromeFrame[];
+extern const char kShowOmniboxSearchHint[];
+extern const char kDesktopNotificationDefaultContentSetting[];  // OBSOLETE
+extern const char kDesktopNotificationAllowedOrigins[];  // OBSOLETE
+extern const char kDesktopNotificationDeniedOrigins[];  // OBSOLETE
+extern const char kDesktopNotificationPosition[];
+extern const char kDefaultContentSettings[];
+extern const char kContentSettingsClearOnExitMigrated[];
+extern const char kContentSettingsVersion[];
+extern const char kContentSettingsPatterns[];  // OBSOLETE
+extern const char kContentSettingsPatternPairs[];
+extern const char kContentSettingsDefaultWhitelistVersion[];
+extern const char kContentSettingsPluginWhitelist[];
+extern const char kBlockThirdPartyCookies[];
+extern const char kClearSiteDataOnExit[];
+extern const char kDefaultZoomLevel[];
+extern const char kPerHostZoomLevels[];
+extern const char kAutofillEnabled[];
+extern const char kAutofillAuxiliaryProfilesEnabled[];
+extern const char kAutofillPositiveUploadRate[];
+extern const char kAutofillNegativeUploadRate[];
+extern const char kAutofillPersonalDataManagerFirstRun[];
+extern const char kEditBookmarksEnabled[];
+
+extern const char kEnableTranslate[];
+extern const char kPinnedTabs[];
+
+extern const char kDisable3DAPIs[];
+extern const char kEnableHyperlinkAuditing[];
+extern const char kEnableReferrers[];
+extern const char kEnableDoNotTrack[];
+
+extern const char kImportBookmarks[];
+extern const char kImportHistory[];
+extern const char kImportHomepage[];
+extern const char kImportSearchEngine[];
+extern const char kImportSavedPasswords[];
+
+extern const char kEnterpriseWebStoreURL[];
+extern const char kEnterpriseWebStoreName[];
+
+#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) && defined(OS_POSIX)
+extern const char kLocalProfileId[];
+extern const char kPasswordsUseLocalProfileId[];
+#endif
+
+extern const char kProfileAvatarIndex[];
+extern const char kProfileName[];
+
+extern const char kInvertNotificationShown[];
+
+extern const char kPrintingEnabled[];
+extern const char kPrintPreviewDisabled[];
+
+// Local state prefs. Please add Profile prefs above instead.
+extern const char kCertRevocationCheckingEnabled[];
+extern const char kSSLVersionMin[];
+extern const char kSSLVersionMax[];
+extern const char kCipherSuiteBlacklist[];
+extern const char kEnableOriginBoundCerts[];
+extern const char kDisableSSLRecordSplitting[];
+extern const char kEnableMemoryInfo[];
+
+extern const char kMetricsClientID[];
+extern const char kMetricsSessionID[];
+extern const char kMetricsLowEntropySource[];
+extern const char kMetricsClientIDTimestamp[];
+extern const char kMetricsReportingEnabled[];
+extern const char kMetricsInitialLogsXml[];
+extern const char kMetricsInitialLogsProto[];
+extern const char kMetricsOngoingLogsXml[];
+extern const char kMetricsOngoingLogsProto[];
+
+extern const char kBookmarkPromptEnabled[];
+extern const char kBookmarkPromptImpressionCount[];
+
+extern const char kVariationsSeed[];
+extern const char kVariationsSeedDate[];
+
+extern const char kProfileLastUsed[];
+extern const char kProfilesLastActive[];
+extern const char kProfilesNumCreated[];
+extern const char kProfileInfoCache[];
+extern const char kProfileCreatedByVersion[];
+
+extern const char kProfileMetrics[];
+extern const char kProfilePrefix[];
+
+extern const char kStabilityExitedCleanly[];
+extern const char kStabilityStatsVersion[];
+extern const char kStabilityStatsBuildTime[];
+extern const char kStabilitySessionEndCompleted[];
+extern const char kStabilityLaunchCount[];
+extern const char kStabilityCrashCount[];
+extern const char kStabilityIncompleteSessionEndCount[];
+extern const char kStabilityPageLoadCount[];
+extern const char kStabilityRendererCrashCount[];
+extern const char kStabilityExtensionRendererCrashCount[];
+extern const char kStabilityLaunchTimeSec[];
+extern const char kStabilityLastTimestampSec[];
+extern const char kStabilityRendererHangCount[];
+extern const char kStabilityChildProcessCrashCount[];
+extern const char kStabilityOtherUserCrashCount[];
+extern const char kStabilityKernelCrashCount[];
+extern const char kStabilitySystemUncleanShutdownCount[];
+
+extern const char kStabilityBreakpadRegistrationSuccess[];
+extern const char kStabilityBreakpadRegistrationFail[];
+extern const char kStabilityDebuggerPresent[];
+extern const char kStabilityDebuggerNotPresent[];
+
+extern const char kStabilityPluginStats[];
+extern const char kStabilityPluginName[];
+extern const char kStabilityPluginLaunches[];
+extern const char kStabilityPluginInstances[];
+extern const char kStabilityPluginCrashes[];
+extern const char kStabilityPluginLoadingErrors[];
+
+extern const char kUninstallMetricsPageLoadCount[];
+extern const char kUninstallLaunchCount[];
+
+extern const char kUninstallMetricsInstallDate[];
+extern const char kUninstallMetricsUptimeSec[];
+extern const char kUninstallLastLaunchTimeSec[];
+extern const char kUninstallLastObservedRunTimeSec[];
+
+extern const char kBrowserWindowPlacement[];
+extern const char kTaskManagerWindowPlacement[];
+extern const char kKeywordEditorWindowPlacement[];
+extern const char kPreferencesWindowPlacement[];
+extern const char kMemoryCacheSize[];
+
+extern const char kDownloadDefaultDirectory[];
+extern const char kDownloadExtensionsToOpen[];
+extern const char kDownloadDirUpgraded[];
+
+extern const char kSaveFileDefaultDirectory[];
+extern const char kSaveFileType[];
+
+extern const char kAllowFileSelectionDialogs[];
+extern const char kDefaultTasksByMimeType[];
+extern const char kDefaultTasksBySuffix[];
+
+extern const char kSelectFileLastDirectory[];
+
+extern const char kHungPluginDetectFrequency[];
+extern const char kPluginMessageResponseTimeout[];
+
+extern const char kSpellCheckDictionary[];
+extern const char kSpellCheckConfirmDialogShown[];
+extern const char kSpellCheckUseSpellingService[];
+
+extern const char kExcludedSchemes[];
+
+extern const char kSafeBrowsingClientKey[];
+extern const char kSafeBrowsingWrappedKey[];
+
+extern const char kOptionsWindowLastTabIndex[];
+extern const char kContentSettingsWindowLastTabIndex[];
+extern const char kCertificateManagerWindowLastTabIndex[];
+extern const char kShouldShowFirstRunBubble[];
+extern const char kShouldShowWelcomePage[];
+
+extern const char kLastKnownGoogleURL[];
+extern const char kLastPromptedGoogleURL[];
+extern const char kLastKnownIntranetRedirectOrigin[];
+
+extern const char kCountryIDAtInstall[];
+extern const char kGeoIDAtInstall[];  // OBSOLETE
+
+extern const char kShutdownType[];
+extern const char kShutdownNumProcesses[];
+extern const char kShutdownNumProcessesSlow[];
+
+extern const char kRestartLastSessionOnShutdown[];
+extern const char kWasRestarted[];
+#if defined(OS_WIN)
+extern const char kRestartSwitchMode[];
+#endif
+
+extern const char kNumBookmarksOnBookmarkBar[];
+extern const char kNumFoldersOnBookmarkBar[];
+extern const char kNumBookmarksInOtherBookmarkFolder[];
+extern const char kNumFoldersInOtherBookmarkFolder[];
+
+extern const char kNumKeywords[];
+
+extern const char kDisableVideoAndChat[];
+
+extern const char kDisableExtensions[];
+extern const char kDisablePluginFinder[];
+extern const char kBrowserActionContainerWidth[];
+
+extern const char kLastExtensionsUpdateCheck[];
+extern const char kNextExtensionsUpdateCheck[];
+
+extern const char kExtensionAllowedInstallSites[];
+extern const char kExtensionInstallAllowList[];
+extern const char kExtensionInstallDenyList[];
+extern const char kExtensionAlertsInitializedPref[];
+extern const char kExtensionInstallForceList[];
+extern const char kExtensionBlacklistUpdateVersion[];
+
+extern const char kNtpTipsResourceServer[];
+
+extern const char kNtpCollapsedForeignSessions[];
+extern const char kNtpMostVisitedURLsBlacklist[];
+extern const char kNtpPromoResourceCacheUpdate[];
+extern const char kNtpDateResourceServer[];
+extern const char kNtpShownBookmarksFolder[];
+extern const char kNtpShownPage[];
+extern const char kNtpPromoDesktopSessionFound[];
+extern const char kNtpWebStoreEnabled[];
+extern const char kNtpAppPageNames[];
+
+extern const char kDevToolsDisabled[];
+extern const char kDevToolsDockSide[];
+extern const char kDevToolsEditedFiles[];
+extern const char kDevToolsHSplitLocation[];
+extern const char kDevToolsOpenDocked[];
+#if defined(OS_ANDROID)
+extern const char kDevToolsRemoteEnabled[];
+#endif
+extern const char kDevToolsVSplitLocation[];
+#if defined(OS_ANDROID)
+// Used by Chrome Mobile.
+extern const char kSpdyProxyEnabled[];
+#endif
+extern const char kSyncLastSyncedTime[];
+extern const char kSyncHasSetupCompleted[];
+extern const char kSyncKeepEverythingSynced[];
+extern const char kSyncBookmarks[];
+extern const char kSyncPasswords[];
+extern const char kSyncPreferences[];
+extern const char kSyncAppNotifications[];
+extern const char kSyncAppSettings[];
+extern const char kSyncApps[];
+extern const char kSyncAutofill[];
+extern const char kSyncAutofillProfile[];
+extern const char kSyncThemes[];
+extern const char kSyncTypedUrls[];
+extern const char kSyncExtensions[];
+extern const char kSyncExtensionSettings[];
+extern const char kSyncHistoryDeleteDirectives[];
+extern const char kSyncManaged[];
+extern const char kSyncSearchEngines[];
+extern const char kSyncSessions[];
+extern const char kSyncSuppressStart[];
+extern const char kGoogleServicesLastUsername[];
+extern const char kGoogleServicesUsername[];
+extern const char kGoogleServicesUsernamePattern[];
+extern const char kSyncUsingSecondaryPassphrase[];
+extern const char kSyncEncryptionBootstrapToken[];
+extern const char kSyncKeystoreEncryptionBootstrapToken[];
+extern const char kSyncAcknowledgedSyncTypes[];
+// Deprecated in favor of kInvalidatorMaxInvalidationVersions.
+extern const char kSyncMaxInvalidationVersions[];
+extern const char kSyncSessionsGUID[];
+
+extern const char kInvalidatorInvalidationState[];
+extern const char kInvalidatorMaxInvalidationVersions[];
+
+extern const char kSyncPromoStartupCount[];
+extern const char kSyncPromoViewCount[];
+extern const char kSyncPromoUserSkipped[];
+extern const char kSyncPromoShowOnFirstRunAllowed[];
+extern const char kSyncPromoShowNTPBubble[];
+
+extern const char kProfileGAIAInfoUpdateTime[];
+extern const char kProfileGAIAInfoPictureURL[];
+
+extern const char kWebAppCreateOnDesktop[];
+extern const char kWebAppCreateInAppsMenu[];
+extern const char kWebAppCreateInQuickLaunchBar[];
+
+extern const char kGeolocationAccessToken[];
+extern const char kGeolocationDefaultContentSetting[];
+extern const char kGeolocationContentSettings[];
+#if defined(OS_ANDROID)
+extern const char kGeolocationEnabled[];
+#endif
+
+extern const char kRemoteAccessHostFirewallTraversal[];
+extern const char kRemoteAccessHostRequireTwoFactor[];
+extern const char kRemoteAccessHostDomain[];
+extern const char kRemoteAccessHostTalkGadgetPrefix[];
+extern const char kRemoteAccessHostRequireCurtain[];
+
+extern const char kPrintPreviewStickySettings[];
+extern const char kCloudPrintRoot[];
+extern const char kCloudPrintServiceURL[];
+extern const char kCloudPrintSigninURL[];
+extern const char kCloudPrintDialogWidth[];
+extern const char kCloudPrintDialogHeight[];
+extern const char kCloudPrintSigninDialogWidth[];
+extern const char kCloudPrintSigninDialogHeight[];
+extern const char kCloudPrintProxyEnabled[];
+extern const char kCloudPrintProxyId[];
+extern const char kCloudPrintAuthToken[];
+extern const char kCloudPrintXMPPAuthToken[];
+extern const char kCloudPrintEmail[];
+extern const char kCloudPrintPrintSystemSettings[];
+extern const char kCloudPrintEnableJobPoll[];
+extern const char kCloudPrintRobotRefreshToken[];
+extern const char kCloudPrintRobotEmail[];
+extern const char kCloudPrintConnectNewPrinters[];
+extern const char kCloudPrintXmppPingEnabled[];
+extern const char kCloudPrintXmppPingTimeout[];
+extern const char kCloudPrintPrinterBlacklist[];
+extern const char kCloudPrintSubmitEnabled[];
+
+#if !defined(OS_ANDROID)
+extern const char kChromeToMobileDeviceList[];
+#endif
+
+extern const char kProxy[];
+extern const char kMaxConnectionsPerProxy[];
+
+extern const char kManagedDefaultCookiesSetting[];
+extern const char kManagedDefaultImagesSetting[];
+extern const char kManagedDefaultJavaScriptSetting[];
+extern const char kManagedDefaultPluginsSetting[];
+extern const char kManagedDefaultPopupsSetting[];
+extern const char kManagedDefaultGeolocationSetting[];
+extern const char kManagedDefaultNotificationsSetting[];
+extern const char kManagedDefaultMediaStreamSetting[];
+
+extern const char kManagedCookiesAllowedForUrls[];
+extern const char kManagedCookiesBlockedForUrls[];
+extern const char kManagedCookiesSessionOnlyForUrls[];
+extern const char kManagedImagesAllowedForUrls[];
+extern const char kManagedImagesBlockedForUrls[];
+extern const char kManagedJavaScriptAllowedForUrls[];
+extern const char kManagedJavaScriptBlockedForUrls[];
+extern const char kManagedPluginsAllowedForUrls[];
+extern const char kManagedPluginsBlockedForUrls[];
+extern const char kManagedPopupsAllowedForUrls[];
+extern const char kManagedPopupsBlockedForUrls[];
+extern const char kManagedNotificationsAllowedForUrls[];
+extern const char kManagedNotificationsBlockedForUrls[];
+extern const char kManagedAutoSelectCertificateForUrls[];
+
+#if defined(OS_CHROMEOS)
+extern const char kDeviceSettingsCache[];
+extern const char kHardwareKeyboardLayout[];
+extern const char kCarrierDealPromoShown[];
+extern const char kShouldAutoEnroll[];
+extern const char kAutoEnrollmentPowerLimit[];
+extern const char kDeviceActivityTimes[];
+extern const char kDeviceLocation[];
+extern const char kSyncSpareBootstrapToken[];
+extern const char kExternalStorageDisabled[];
+extern const char kUsersWallpaperInfo[];
+extern const char kAudioOutputAllowed[];
+extern const char kAudioCaptureAllowed[];
+extern const char kOwnerPrimaryMouseButtonRight[];
+extern const char kOwnerTapToClickEnabled[];
+#endif
+
+extern const char kClearPluginLSODataEnabled[];
+extern const char kPepperFlashSettingsEnabled[];
+extern const char kDiskCacheDir[];
+extern const char kDiskCacheSize[];
+extern const char kMediaCacheSize[];
+
+extern const char kChromeOsReleaseChannel[];
+
+extern const char kTabStripLayoutType[];
+
+extern const char kRegisteredBackgroundContents[];
+
+extern const char kShownAutoLaunchInfobar[];
+
+extern const char kAuthSchemes[];
+extern const char kDisableAuthNegotiateCnameLookup[];
+extern const char kEnableAuthNegotiatePort[];
+extern const char kAuthServerWhitelist[];
+extern const char kAuthNegotiateDelegateWhitelist[];
+extern const char kGSSAPILibraryName[];
+extern const char kSpdyProxyOrigin[];
+extern const char kAllowCrossOriginAuthPrompt[];
+
+extern const char kHttpReceivedContentLength[];
+extern const char kHttpOriginalContentLength[];
+
+extern const char kRegisteredProtocolHandlers[];
+extern const char kIgnoredProtocolHandlers[];
+extern const char kCustomHandlersEnabled[];
+
+extern const char kUserCreatedLoginItem[];
+extern const char kUserRemovedLoginItem[];
+extern const char kBackgroundModeEnabled[];
+
+extern const char kDevicePolicyRefreshRate[];
+extern const char kUserPolicyRefreshRate[];
+extern const char kLoadCloudPolicyOnSignin[];
+
+extern const char kFactoryResetRequested[];
+
+extern const char kRecoveryComponentVersion[];
+extern const char kComponentUpdaterState[];
+
+#if defined(ENABLE_WEB_INTENTS)
+extern const char kWebIntentsEnabled[];
+#endif
+
+extern const char kMediaGalleriesUniqueId[];
+extern const char kMediaGalleriesRememberedGalleries[];
+
+#if defined(USE_AURA)
+extern const char kShelfAlignment[];
+extern const char kShelfAlignmentLocal[];
+extern const char kShelfAutoHideBehavior[];
+extern const char kShelfAutoHideBehaviorLocal[];
+extern const char kUseDefaultPinnedApps[];
+extern const char kPinnedLauncherApps[];
+extern const char kShowLogoutButtonInTray[];
+
+extern const char kLongPressTimeInSeconds[];
+extern const char kMaxDistanceBetweenTapsForDoubleTap[];
+extern const char kMaxDistanceForTwoFingerTapInPixels[];
+extern const char kMaxSecondsBetweenDoubleClick[];
+extern const char kMaxSeparationForGestureTouchesInPixels[];
+extern const char kMaxSwipeDeviationRatio[];
+extern const char kMaxTouchDownDurationInSecondsForClick[];
+extern const char kMaxTouchMoveInPixelsForClick[];
+extern const char kMinDistanceForPinchScrollInPixels[];
+extern const char kMinFlickSpeedSquared[];
+extern const char kMinPinchUpdateDistanceInPixels[];
+extern const char kMinRailBreakVelocity[];
+extern const char kMinScrollDeltaSquared[];
+extern const char kMinSwipeSpeed[];
+extern const char kMinTouchDownDurationInSecondsForClick[];
+extern const char kPointsBufferedForVelocity[];
+extern const char kRailBreakProportion[];
+extern const char kRailStartProportion[];
+extern const char kSemiLongPressTimeInSeconds[];
+extern const char kTouchScreenFlingAccelerationAdjustment[];
+#endif
+
+extern const char kInManagedMode[];
+
+extern const char kNetworkProfileWarningsLeft[];
+extern const char kNetworkProfileLastWarningTime[];
+
+extern const char kLastPolicyStatisticsUpdate[];
+
+}  // namespace prefs
+
+#endif  // CHROME_COMMON_PREF_NAMES_H_
diff --git a/chrome/common/pref_names_util.cc b/chrome/common/pref_names_util.cc
new file mode 100644
index 0000000..1b1e4dd
--- /dev/null
+++ b/chrome/common/pref_names_util.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/pref_names_util.h"
+
+#include "base/string_util.h"
+
+namespace pref_names_util {
+
+const char kWebKitFontPrefPrefix[] = "webkit.webprefs.fonts.";
+
+bool ParseFontNamePrefPath(const std::string& pref_path,
+                           std::string* generic_family,
+                           std::string* script) {
+  if (!StartsWithASCII(pref_path, kWebKitFontPrefPrefix, true))
+    return false;
+
+  size_t start = strlen(kWebKitFontPrefPrefix);
+  size_t pos = pref_path.find('.', start);
+  if (pos == std::string::npos || pos + 1 == pref_path.length())
+    return false;
+  if (generic_family)
+    *generic_family = pref_path.substr(start, pos - start);
+  if (script)
+    *script = pref_path.substr(pos + 1);
+  return true;
+}
+
+}  // namespace pref_names_util
diff --git a/chrome/common/pref_names_util.h b/chrome/common/pref_names_util.h
new file mode 100644
index 0000000..d418e9b
--- /dev/null
+++ b/chrome/common/pref_names_util.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_PREF_NAMES_UTIL_H_
+#define CHROME_COMMON_PREF_NAMES_UTIL_H_
+
+#include <string>
+
+namespace pref_names_util {
+
+// Extracts the generic family and script from font name pref path |pref_path|.
+// For example, if |pref_path| is "webkit.webprefs.fonts.serif.Hang", returns
+// true and sets |generic_family| to "serif" and |script| to "Hang".
+bool ParseFontNamePrefPath(const std::string& pref_path,
+                           std::string* generic_family,
+                           std::string* script);
+
+}  // namespace pref_names_util
+
+#endif  // CHROME_COMMON_PREF_NAMES_UTIL_H_
diff --git a/chrome/common/pref_names_util_unittest.cc b/chrome/common/pref_names_util_unittest.cc
new file mode 100644
index 0000000..2d1356a
--- /dev/null
+++ b/chrome/common/pref_names_util_unittest.cc
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/pref_names_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void ExpectNoParse(const std::string& path) {
+  EXPECT_FALSE(pref_names_util::ParseFontNamePrefPath(path, NULL, NULL));
+}
+
+void ExpectParse(const std::string& path,
+                 const std::string& expected_generic_family,
+                 const std::string& expected_script)
+{
+  std::string generic_family;
+  std::string script;
+
+  ASSERT_TRUE(pref_names_util::ParseFontNamePrefPath(path, &generic_family,
+                                                     &script));
+  EXPECT_EQ(expected_generic_family, generic_family);
+  EXPECT_EQ(expected_script, script);
+}
+
+}  // namespace
+
+TEST(PrefNamesUtilTest, Basic) {
+  ExpectNoParse("");
+  ExpectNoParse(".");
+  ExpectNoParse(".....");
+  ExpectNoParse("webkit.webprefs.fonts.");
+  ExpectNoParse("webkit.webprefs.fonts..");
+  ExpectNoParse("webkit.webprefs.fontsfoobar.standard.Hrkt");
+  ExpectNoParse("foobar.webprefs.fonts.standard.Hrkt");
+  ExpectParse("webkit.webprefs.fonts.standard.Hrkt", "standard", "Hrkt");
+  ExpectParse("webkit.webprefs.fonts.standard.Hrkt.", "standard", "Hrkt.");
+  ExpectParse("webkit.webprefs.fonts.standard.Hrkt.Foobar", "standard",
+              "Hrkt.Foobar");
+
+  // We don't particularly care about the parsed family and script for these
+  // inputs, but just want to make sure it does something reasonable. Returning
+  // false may also be an option.
+  ExpectParse("webkit.webprefs.fonts...", "", ".");
+  ExpectParse("webkit.webprefs.fonts....", "", "..");
+
+  // Check that passing NULL output params is okay.
+  EXPECT_TRUE(pref_names_util::ParseFontNamePrefPath(
+      "webkit.webprefs.fonts.standard.Hrkt", NULL, NULL));
+}
diff --git a/chrome/common/prerender_messages.h b/chrome/common/prerender_messages.h
new file mode 100644
index 0000000..848476e
--- /dev/null
+++ b/chrome/common/prerender_messages.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 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.
+
+// Multiply-included message file, no traditional include guard.
+#include "content/public/common/common_param_traits.h"
+#include "content/public/common/referrer.h"
+#include "googleurl/src/gurl.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_param_traits.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebReferrerPolicy.h"
+#include "ui/gfx/size.h"
+
+#define IPC_MESSAGE_START PrerenderMsgStart
+
+// PrerenderLinkManager Messages
+// These are messages sent from the renderer to the browser in
+// relation to <link rel=prerender> elements.
+
+// Notifies of the insertion of a <link rel=prerender> element in the
+// document.
+IPC_MESSAGE_CONTROL5(PrerenderHostMsg_AddLinkRelPrerender,
+                     int /* prerender_id, assigned by WebPrerendererClient */,
+                     GURL /* url */,
+                     content::Referrer,
+                     gfx::Size,
+                     int /* render_view_route_id of launcher */)
+
+// Notifies on removal of a <link rel=prerender> element from the document.
+IPC_MESSAGE_CONTROL1(PrerenderHostMsg_CancelLinkRelPrerender,
+                     int /* prerender_id, assigned by WebPrerendererClient */)
+
+// Notifies on unloading a <link rel=prerender> element from a frame.
+IPC_MESSAGE_CONTROL1(PrerenderHostMsg_AbandonLinkRelPrerender,
+                     int /* prerender_id, assigned by WebPrerendererClient */)
+
+// PrerenderDispatcher Messages
+// These are messages sent from the browser to the renderer in relation to
+// running prerenders.
+
+// Tells a renderer if it's currently being prerendered.  Must only be set
+// to true before any navigation occurs, and only set to false at most once
+// after that.
+IPC_MESSAGE_ROUTED1(PrerenderMsg_SetIsPrerendering,
+                    bool /* whether the RenderView is prerendering */)
+
+// Specifies that a URL is currently being prerendered.
+IPC_MESSAGE_CONTROL1(PrerenderMsg_AddPrerenderURL,
+                     GURL /* url */)
+
+// Specifies that a URL is no longer being prerendered.
+IPC_MESSAGE_CONTROL1(PrerenderMsg_RemovePrerenderURL,
+                     GURL /* url */)
diff --git a/chrome/common/print_messages.cc b/chrome/common/print_messages.cc
new file mode 100644
index 0000000..126e248
--- /dev/null
+++ b/chrome/common/print_messages.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/print_messages.h"
+
+#include "base/basictypes.h"
+#include "base/string16.h"
+#include "ui/gfx/size.h"
+
+PrintMsg_Print_Params::PrintMsg_Print_Params()
+  : page_size(),
+    content_size(),
+    printable_area(),
+    margin_top(0),
+    margin_left(0),
+    dpi(0),
+    min_shrink(0),
+    max_shrink(0),
+    desired_dpi(0),
+    document_cookie(0),
+    selection_only(false),
+    supports_alpha_blend(false),
+    preview_ui_id(-1),
+    preview_request_id(0),
+    is_first_request(false),
+    print_scaling_option(WebKit::WebPrintScalingOptionSourceSize),
+    print_to_pdf(false),
+    display_header_footer(false),
+    date(),
+    title(),
+    url() {
+}
+
+PrintMsg_Print_Params::~PrintMsg_Print_Params() {}
+
+void PrintMsg_Print_Params::Reset() {
+  page_size = gfx::Size();
+  content_size = gfx::Size();
+  printable_area = gfx::Rect();
+  margin_top = 0;
+  margin_left = 0;
+  dpi = 0;
+  min_shrink = 0;
+  max_shrink = 0;
+  desired_dpi = 0;
+  document_cookie = 0;
+  selection_only = false;
+  supports_alpha_blend = false;
+  preview_ui_id = -1;
+  preview_request_id = 0;
+  is_first_request = false;
+  print_scaling_option = WebKit::WebPrintScalingOptionSourceSize;
+  print_to_pdf = false;
+  display_header_footer = false;
+  date = string16();
+  title = string16();
+  url = string16();
+}
+
+PrintMsg_PrintPages_Params::PrintMsg_PrintPages_Params()
+  : pages() {
+}
+
+PrintMsg_PrintPages_Params::~PrintMsg_PrintPages_Params() {}
+
+void PrintMsg_PrintPages_Params::Reset() {
+  params.Reset();
+  pages = std::vector<int>();
+}
diff --git a/chrome/common/print_messages.h b/chrome/common/print_messages.h
new file mode 100644
index 0000000..bdd1fbe
--- /dev/null
+++ b/chrome/common/print_messages.h
@@ -0,0 +1,424 @@
+// Copyright (c) 2012 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.
+
+// IPC messages for printing.
+// Multiply-included message file, hence no include guard.
+
+#include <string>
+#include <vector>
+
+#include "base/values.h"
+#include "base/shared_memory.h"
+#include "ipc/ipc_message_macros.h"
+#include "printing/page_size_margins.h"
+#include "printing/print_job_constants.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebPrintScalingOption.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/rect.h"
+
+#ifndef CHROME_COMMON_PRINT_MESSAGES_H_
+#define CHROME_COMMON_PRINT_MESSAGES_H_
+
+struct PrintMsg_Print_Params {
+  PrintMsg_Print_Params();
+  ~PrintMsg_Print_Params();
+
+  // Resets the members of the struct to 0.
+  void Reset();
+
+  gfx::Size page_size;
+  gfx::Size content_size;
+  gfx::Rect printable_area;
+  int margin_top;
+  int margin_left;
+  double dpi;
+  double min_shrink;
+  double max_shrink;
+  int desired_dpi;
+  int document_cookie;
+  bool selection_only;
+  bool supports_alpha_blend;
+  int32 preview_ui_id;
+  int preview_request_id;
+  bool is_first_request;
+  WebKit::WebPrintScalingOption print_scaling_option;
+  bool print_to_pdf;
+  bool display_header_footer;
+  string16 date;
+  string16 title;
+  string16 url;
+};
+
+struct PrintMsg_PrintPages_Params {
+  PrintMsg_PrintPages_Params();
+  ~PrintMsg_PrintPages_Params();
+
+  // Resets the members of the struct to 0.
+  void Reset();
+
+  PrintMsg_Print_Params params;
+  std::vector<int> pages;
+};
+
+#endif  // CHROME_COMMON_PRINT_MESSAGES_H_
+
+#define IPC_MESSAGE_START PrintMsgStart
+
+IPC_ENUM_TRAITS(printing::MarginType)
+IPC_ENUM_TRAITS(WebKit::WebPrintScalingOption)
+
+// Parameters for a render request.
+IPC_STRUCT_TRAITS_BEGIN(PrintMsg_Print_Params)
+  // Physical size of the page, including non-printable margins,
+  // in pixels according to dpi.
+  IPC_STRUCT_TRAITS_MEMBER(page_size)
+
+  // In pixels according to dpi_x and dpi_y.
+  IPC_STRUCT_TRAITS_MEMBER(content_size)
+
+  // Physical printable area of the page in pixels according to dpi.
+  IPC_STRUCT_TRAITS_MEMBER(printable_area)
+
+  // The y-offset of the printable area, in pixels according to dpi.
+  IPC_STRUCT_TRAITS_MEMBER(margin_top)
+
+  // The x-offset of the printable area, in pixels according to dpi.
+  IPC_STRUCT_TRAITS_MEMBER(margin_left)
+
+  // Specifies dots per inch.
+  IPC_STRUCT_TRAITS_MEMBER(dpi)
+
+  // Minimum shrink factor. See PrintSettings::min_shrink for more information.
+  IPC_STRUCT_TRAITS_MEMBER(min_shrink)
+
+  // Maximum shrink factor. See PrintSettings::max_shrink for more information.
+  IPC_STRUCT_TRAITS_MEMBER(max_shrink)
+
+  // Desired apparent dpi on paper.
+  IPC_STRUCT_TRAITS_MEMBER(desired_dpi)
+
+  // Cookie for the document to ensure correctness.
+  IPC_STRUCT_TRAITS_MEMBER(document_cookie)
+
+  // Should only print currently selected text.
+  IPC_STRUCT_TRAITS_MEMBER(selection_only)
+
+  // Does the printer support alpha blending?
+  IPC_STRUCT_TRAITS_MEMBER(supports_alpha_blend)
+
+  // *** Parameters below are used only for print preview. ***
+
+  // The print preview ui associated with this request.
+  IPC_STRUCT_TRAITS_MEMBER(preview_ui_id)
+
+  // The id of the preview request.
+  IPC_STRUCT_TRAITS_MEMBER(preview_request_id)
+
+  // True if this is the first preview request.
+  IPC_STRUCT_TRAITS_MEMBER(is_first_request)
+
+  // Specifies the page scaling option for preview printing.
+  IPC_STRUCT_TRAITS_MEMBER(print_scaling_option)
+
+  // True if print to pdf is requested.
+  IPC_STRUCT_TRAITS_MEMBER(print_to_pdf)
+
+  // Specifies if the header and footer should be rendered.
+  IPC_STRUCT_TRAITS_MEMBER(display_header_footer)
+
+  // Date string to be printed as header if requested by the user.
+  IPC_STRUCT_TRAITS_MEMBER(date)
+
+  // Title string to be printed as header if requested by the user.
+  IPC_STRUCT_TRAITS_MEMBER(title)
+
+  // URL string to be printed as footer if requested by the user.
+  IPC_STRUCT_TRAITS_MEMBER(url)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_BEGIN(PrintMsg_PrintPage_Params)
+  // Parameters to render the page as a printed page. It must always be the same
+  // value for all the document.
+  IPC_STRUCT_MEMBER(PrintMsg_Print_Params, params)
+
+  // The page number is the indicator of the square that should be rendered
+  // according to the layout specified in PrintMsg_Print_Params.
+  IPC_STRUCT_MEMBER(int, page_number)
+IPC_STRUCT_END()
+
+IPC_STRUCT_TRAITS_BEGIN(printing::PageSizeMargins)
+  IPC_STRUCT_TRAITS_MEMBER(content_width)
+  IPC_STRUCT_TRAITS_MEMBER(content_height)
+  IPC_STRUCT_TRAITS_MEMBER(margin_left)
+  IPC_STRUCT_TRAITS_MEMBER(margin_right)
+  IPC_STRUCT_TRAITS_MEMBER(margin_top)
+  IPC_STRUCT_TRAITS_MEMBER(margin_bottom)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(PrintMsg_PrintPages_Params)
+  // Parameters to render the page as a printed page. It must always be the same
+  // value for all the document.
+  IPC_STRUCT_TRAITS_MEMBER(params)
+
+  // If empty, this means a request to render all the printed pages.
+  IPC_STRUCT_TRAITS_MEMBER(pages)
+IPC_STRUCT_TRAITS_END()
+
+// Parameters to describe a rendered document.
+IPC_STRUCT_BEGIN(PrintHostMsg_DidPreviewDocument_Params)
+  // True when we can reuse existing preview data. |metafile_data_handle| and
+  // |data_size| should not be used when this is true.
+  IPC_STRUCT_MEMBER(bool, reuse_existing_data)
+
+  // A shared memory handle to metafile data.
+  IPC_STRUCT_MEMBER(base::SharedMemoryHandle, metafile_data_handle)
+
+  // Size of metafile data.
+  IPC_STRUCT_MEMBER(uint32, data_size)
+
+  // Cookie for the document to ensure correctness.
+  IPC_STRUCT_MEMBER(int, document_cookie)
+
+  // Store the expected pages count.
+  IPC_STRUCT_MEMBER(int, expected_pages_count)
+
+  // Whether the preview can be modified.
+  IPC_STRUCT_MEMBER(bool, modifiable)
+
+  // The id of the preview request.
+  IPC_STRUCT_MEMBER(int, preview_request_id)
+IPC_STRUCT_END()
+
+// Parameters to describe a rendered preview page.
+IPC_STRUCT_BEGIN(PrintHostMsg_DidPreviewPage_Params)
+  // A shared memory handle to metafile data for a draft document of the page.
+  IPC_STRUCT_MEMBER(base::SharedMemoryHandle, metafile_data_handle)
+
+  // Size of metafile data.
+  IPC_STRUCT_MEMBER(uint32, data_size)
+
+  // |page_number| is zero-based and can be |printing::INVALID_PAGE_INDEX| if it
+  // is just a check.
+  IPC_STRUCT_MEMBER(int, page_number)
+
+  // The id of the preview request.
+  IPC_STRUCT_MEMBER(int, preview_request_id)
+IPC_STRUCT_END()
+
+// Parameters sent along with the page count.
+IPC_STRUCT_BEGIN(PrintHostMsg_DidGetPreviewPageCount_Params)
+  // Cookie for the document to ensure correctness.
+  IPC_STRUCT_MEMBER(int, document_cookie)
+
+  // Total page count.
+  IPC_STRUCT_MEMBER(int, page_count)
+
+  // Indicates whether the previewed document is modifiable.
+  IPC_STRUCT_MEMBER(bool, is_modifiable)
+
+  // The id of the preview request.
+  IPC_STRUCT_MEMBER(int, preview_request_id)
+
+  // Indicates whether the existing preview data needs to be cleared or not.
+  IPC_STRUCT_MEMBER(bool, clear_preview_data)
+IPC_STRUCT_END()
+
+// Parameters to describe a rendered page.
+IPC_STRUCT_BEGIN(PrintHostMsg_DidPrintPage_Params)
+  // A shared memory handle to the EMF data. This data can be quite large so a
+  // memory map needs to be used.
+  IPC_STRUCT_MEMBER(base::SharedMemoryHandle, metafile_data_handle)
+
+  // Size of the metafile data.
+  IPC_STRUCT_MEMBER(uint32, data_size)
+
+  // Cookie for the document to ensure correctness.
+  IPC_STRUCT_MEMBER(int, document_cookie)
+
+  // Page number.
+  IPC_STRUCT_MEMBER(int, page_number)
+
+  // Shrink factor used to render this page.
+  IPC_STRUCT_MEMBER(double, actual_shrink)
+
+  // The size of the page the page author specified.
+  IPC_STRUCT_MEMBER(gfx::Size, page_size)
+
+  // The printable area the page author specified.
+  IPC_STRUCT_MEMBER(gfx::Rect, content_area)
+IPC_STRUCT_END()
+
+// Parameters for the IPC message ViewHostMsg_ScriptedPrint
+IPC_STRUCT_BEGIN(PrintHostMsg_ScriptedPrint_Params)
+  IPC_STRUCT_MEMBER(int, cookie)
+  IPC_STRUCT_MEMBER(int, expected_pages_count)
+  IPC_STRUCT_MEMBER(bool, has_selection)
+  IPC_STRUCT_MEMBER(printing::MarginType, margin_type)
+IPC_STRUCT_END()
+
+
+// Messages sent from the browser to the renderer.
+
+// Tells the render view to initiate print preview for the entire document.
+IPC_MESSAGE_ROUTED0(PrintMsg_InitiatePrintPreview)
+
+// Tells the render view to initiate printing or print preview for a particular
+// node, depending on which mode the render view is in.
+IPC_MESSAGE_ROUTED0(PrintMsg_PrintNodeUnderContextMenu)
+
+// Tells the renderer to print the print preview tab's PDF plugin without
+// showing the print dialog. (This is the final step in the print preview
+// workflow.)
+IPC_MESSAGE_ROUTED1(PrintMsg_PrintForPrintPreview,
+                    DictionaryValue /* settings */)
+
+// Tells the render view to switch the CSS to print media type, renders every
+// requested pages and switch back the CSS to display media type.
+IPC_MESSAGE_ROUTED0(PrintMsg_PrintPages)
+
+// Tells the render view that printing is done so it can clean up.
+IPC_MESSAGE_ROUTED1(PrintMsg_PrintingDone,
+                    bool /* success */)
+
+// Tells the render view whether scripted printing is blocked or not.
+IPC_MESSAGE_ROUTED1(PrintMsg_SetScriptedPrintingBlocked,
+                    bool /* blocked */)
+
+// Tells the render view to switch the CSS to print media type, renders every
+// requested pages for print preview using the given |settings|. This gets
+// called multiple times as the user updates settings.
+IPC_MESSAGE_ROUTED1(PrintMsg_PrintPreview,
+                    DictionaryValue /* settings */)
+
+// Like PrintMsg_PrintPages, but using the print preview document's frame/node.
+IPC_MESSAGE_ROUTED0(PrintMsg_PrintForSystemDialog)
+
+// Tells a renderer to stop blocking script initiated printing.
+IPC_MESSAGE_ROUTED0(PrintMsg_ResetScriptedPrintCount)
+
+// Messages sent from the renderer to the browser.
+
+#if defined(OS_WIN)
+// Duplicates a shared memory handle from the renderer to the browser. Then
+// the renderer can flush the handle.
+IPC_SYNC_MESSAGE_ROUTED1_1(PrintHostMsg_DuplicateSection,
+                           base::SharedMemoryHandle /* renderer handle */,
+                           base::SharedMemoryHandle /* browser handle */)
+#endif
+
+// Tells the browser that the renderer is done calculating the number of
+// rendered pages according to the specified settings.
+IPC_MESSAGE_ROUTED2(PrintHostMsg_DidGetPrintedPagesCount,
+                    int /* rendered document cookie */,
+                    int /* number of rendered pages */)
+
+// Sends the document cookie of the current printer query to the browser.
+IPC_MESSAGE_ROUTED1(PrintHostMsg_DidGetDocumentCookie,
+                    int /* rendered document cookie */)
+
+// Tells the browser that the print dialog has been shown.
+IPC_MESSAGE_ROUTED0(PrintHostMsg_DidShowPrintDialog)
+
+// Sends back to the browser the rendered "printed page" that was requested by
+// a ViewMsg_PrintPage message or from scripted printing. The memory handle in
+// this message is already valid in the browser process.
+IPC_MESSAGE_ROUTED1(PrintHostMsg_DidPrintPage,
+                    PrintHostMsg_DidPrintPage_Params /* page content */)
+
+// The renderer wants to know the default print settings.
+IPC_SYNC_MESSAGE_ROUTED0_1(PrintHostMsg_GetDefaultPrintSettings,
+                           PrintMsg_Print_Params /* default_settings */)
+
+// The renderer wants to update the current print settings with new
+// |job_settings|.
+IPC_SYNC_MESSAGE_ROUTED2_1(PrintHostMsg_UpdatePrintSettings,
+                           int /* document_cookie */,
+                           DictionaryValue /* job_settings */,
+                           PrintMsg_PrintPages_Params /* current_settings */)
+
+// It's the renderer that controls the printing process when it is generated
+// by javascript. This step is about showing UI to the user to select the
+// final print settings. The output parameter is the same as
+// ViewMsg_PrintPages which is executed implicitly.
+IPC_SYNC_MESSAGE_ROUTED1_1(PrintHostMsg_ScriptedPrint,
+                           PrintHostMsg_ScriptedPrint_Params,
+                           PrintMsg_PrintPages_Params
+                               /* settings chosen by the user*/)
+
+#if defined(USE_X11)
+// Asks the browser to create a temporary file for the renderer to fill
+// in resulting NativeMetafile in printing.
+IPC_SYNC_MESSAGE_CONTROL0_2(PrintHostMsg_AllocateTempFileForPrinting,
+                            base::FileDescriptor /* temp file fd */,
+                            int /* fd in browser*/)
+IPC_MESSAGE_CONTROL2(PrintHostMsg_TempFileForPrintingWritten,
+                     int /* render_view_id */,
+                     int /* fd in browser */)
+#endif
+
+// Asks the browser to do print preview.
+// |is_modifiable| is set to true when the request is for a web page, and false
+// for a PDF.
+// |webnode_only| is set to true if the document being printed is a specific
+// WebNode, and false if the document is a full WebFrame.
+IPC_MESSAGE_ROUTED2(PrintHostMsg_RequestPrintPreview,
+                    bool /* is_modifiable */,
+                    bool /* webnode_only */)
+
+// Notify the browser the number of pages in the print preview document.
+IPC_MESSAGE_ROUTED1(PrintHostMsg_DidGetPreviewPageCount,
+                    PrintHostMsg_DidGetPreviewPageCount_Params /* params */)
+
+// Notify the browser of the default page layout according to the currently
+// selected printer and page size.
+// |printable_area_in_points| Specifies the printable area in points.
+// |has_custom_page_size_style| is true when the printing frame has a custom
+// page size css otherwise false.
+IPC_MESSAGE_ROUTED3(PrintHostMsg_DidGetDefaultPageLayout,
+                    printing::PageSizeMargins /* page layout in points */,
+                    gfx::Rect /* printable area in points */,
+                    bool /* has custom page size style */)
+
+// Notify the browser a print preview page has been rendered.
+IPC_MESSAGE_ROUTED1(PrintHostMsg_DidPreviewPage,
+                    PrintHostMsg_DidPreviewPage_Params /* params */)
+
+// Asks the browser whether the print preview has been cancelled.
+IPC_SYNC_MESSAGE_ROUTED2_1(PrintHostMsg_CheckForCancel,
+                           int32 /* PrintPreviewUI ID */,
+                           int /* request id */,
+                           bool /* print preview cancelled */)
+
+// Sends back to the browser the complete rendered document (non-draft mode,
+// used for printing) that was requested by a PrintMsg_PrintPreview message.
+// The memory handle in this message is already valid in the browser process.
+IPC_MESSAGE_ROUTED1(PrintHostMsg_MetafileReadyForPrinting,
+                    PrintHostMsg_DidPreviewDocument_Params /* params */)
+
+// Tell the browser printing failed.
+IPC_MESSAGE_ROUTED1(PrintHostMsg_PrintingFailed,
+                    int /* document cookie */)
+
+// Tell the browser print preview failed.
+IPC_MESSAGE_ROUTED1(PrintHostMsg_PrintPreviewFailed,
+                    int /* document cookie */)
+
+// Tell the browser print preview was cancelled.
+IPC_MESSAGE_ROUTED1(PrintHostMsg_PrintPreviewCancelled,
+                    int /* document cookie */)
+
+// Tell the browser print preview found the selected printer has invalid
+// settings (which typically caused by disconnected network printer or printer
+// driver is bogus).
+IPC_MESSAGE_ROUTED1(PrintHostMsg_PrintPreviewInvalidPrinterSettings,
+                    int /* document cookie */)
+
+// Run a nested message loop in the renderer until print preview for
+// window.print() finishes.
+IPC_SYNC_MESSAGE_ROUTED1_0(PrintHostMsg_ScriptedPrintPreview,
+                           bool /* is_modifiable */)
+
+// Notify the browser that the PDF in the initiator renderer has disabled print
+// scaling option.
+IPC_MESSAGE_ROUTED0(PrintHostMsg_PrintPreviewScalingDisabled)
diff --git a/chrome/common/profiling.cc b/chrome/common/profiling.cc
new file mode 100644
index 0000000..bc67873
--- /dev/null
+++ b/chrome/common/profiling.cc
@@ -0,0 +1,154 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/profiling.h"
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/debug/profiler.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "base/threading/thread.h"
+#include "chrome/common/chrome_switches.h"
+#include "v8/include/v8.h"
+
+namespace {
+std::string GetProfileName() {
+  static const char kDefaultProfileName[] = "chrome-profile-{type}-{pid}";
+  CR_DEFINE_STATIC_LOCAL(std::string, profile_name, ());
+
+  if (profile_name.empty()) {
+    const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+    if (command_line.HasSwitch(switches::kProfilingFile))
+      profile_name = command_line.GetSwitchValueASCII(switches::kProfilingFile);
+    else
+      profile_name = std::string(kDefaultProfileName);
+    std::string process_type =
+        command_line.GetSwitchValueASCII(switches::kProcessType);
+    std::string type = process_type.empty() ?
+        std::string("browser") : std::string(process_type);
+    ReplaceSubstringsAfterOffset(&profile_name, 0, "{type}", type.c_str());
+  }
+  return profile_name;
+}
+
+void FlushProfilingData(base::Thread* thread) {
+  static const int kProfilingFlushSeconds = 10;
+
+  if (!Profiling::BeingProfiled())
+    return;
+
+  base::debug::FlushProfiling();
+  static int flush_seconds;
+  if (!flush_seconds) {
+    const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+    std::string profiling_flush =
+        command_line.GetSwitchValueASCII(switches::kProfilingFlush);
+    if (!profiling_flush.empty()) {
+      flush_seconds = atoi(profiling_flush.c_str());
+      DCHECK(flush_seconds > 0);
+    } else {
+      flush_seconds = kProfilingFlushSeconds;
+    }
+  }
+  thread->message_loop()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&FlushProfilingData, thread),
+      base::TimeDelta::FromSeconds(flush_seconds));
+}
+
+class ProfilingThreadControl {
+ public:
+  ProfilingThreadControl() : thread_(NULL) {}
+
+  void Start() {
+    base::AutoLock locked(lock_);
+
+    if (thread_ && thread_->IsRunning())
+      return;
+    thread_ = new base::Thread("Profiling_Flush");
+    thread_->Start();
+    thread_->message_loop()->PostTask(
+        FROM_HERE, base::Bind(&FlushProfilingData, thread_));
+  }
+
+  void Stop() {
+    base::AutoLock locked(lock_);
+
+    if (!thread_ || !thread_->IsRunning())
+      return;
+    thread_->Stop();
+    delete thread_;
+    thread_ = NULL;
+  }
+
+ private:
+  base::Thread* thread_;
+  base::Lock lock_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProfilingThreadControl);
+};
+
+base::LazyInstance<ProfilingThreadControl>::Leaky
+    g_flush_thread_control = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// static
+void Profiling::ProcessStarted() {
+  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+  std::string process_type =
+      command_line.GetSwitchValueASCII(switches::kProcessType);
+
+  // Establish the V8 return address resolution hook if we're
+  // an instrumented binary.
+  if (base::debug::IsBinaryInstrumented()) {
+    base::debug::ReturnAddressLocationResolver resolve_func =
+        base::debug::GetProfilerReturnAddrResolutionFunc();
+
+    if (resolve_func != NULL) {
+      v8::V8::SetReturnAddressLocationResolver(resolve_func);
+    }
+  }
+
+  if (command_line.HasSwitch(switches::kProfilingAtStart)) {
+    std::string process_type_to_start =
+        command_line.GetSwitchValueASCII(switches::kProfilingAtStart);
+    if (process_type == process_type_to_start)
+      Start();
+  }
+}
+
+// static
+void Profiling::Start() {
+  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+  bool flush = command_line.HasSwitch(switches::kProfilingFlush);
+  base::debug::StartProfiling(GetProfileName());
+
+  // Schedule profile data flushing for single process because it doesn't
+  // get written out correctly on exit.
+  if (flush)
+    g_flush_thread_control.Get().Start();
+}
+
+// static
+void Profiling::Stop() {
+  g_flush_thread_control.Get().Stop();
+  base::debug::StopProfiling();
+}
+
+// static
+bool Profiling::BeingProfiled() {
+  return base::debug::BeingProfiled();
+}
+
+// static
+void Profiling::Toggle() {
+  if (BeingProfiled())
+    Stop();
+  else
+    Start();
+}
diff --git a/chrome/common/profiling.h b/chrome/common/profiling.h
new file mode 100644
index 0000000..80e092d
--- /dev/null
+++ b/chrome/common/profiling.h
@@ -0,0 +1,42 @@
+// 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.
+
+#ifndef CHROME_COMMON_PROFILING_H_
+#define CHROME_COMMON_PROFILING_H_
+
+#include "build/build_config.h"
+
+#include "base/basictypes.h"
+#include "base/debug/profiler.h"
+
+// The Profiling class manages the interaction with a sampling based profiler.
+// Its function is controlled by the kProfilingAtStart, kProfilingFile, and
+// kProfilingFlush command line values.
+// All of the API should only be called from the main thread of the process.
+class Profiling {
+ public:
+  // Called early in a process' life to allow profiling of startup time.
+  // the presence of kProfilingAtStart is checked.
+  static void ProcessStarted();
+
+  // Start profiling.
+  static void Start();
+
+  // Stop profiling and write out profiling file.
+  static void Stop();
+
+  // Returns true if the process is being profiled.
+  static bool BeingProfiled();
+
+  // Toggle profiling on/off.
+  static void Toggle();
+
+ private:
+  // Do not instantiate this class.
+  Profiling();
+
+  DISALLOW_COPY_AND_ASSIGN(Profiling);
+};
+
+#endif  // CHROME_COMMON_PROFILING_H_
diff --git a/chrome/common/ref_counted_util.h b/chrome/common/ref_counted_util.h
new file mode 100644
index 0000000..65b0346
--- /dev/null
+++ b/chrome/common/ref_counted_util.h
@@ -0,0 +1,26 @@
+// 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.
+
+#ifndef CHROME_COMMON_REF_COUNTED_UTIL_H__
+#define CHROME_COMMON_REF_COUNTED_UTIL_H__
+
+#include "base/memory/ref_counted.h"
+#include <vector>
+
+// RefCountedVector is just a vector wrapped up with
+// RefCountedThreadSafe.
+template<class T>
+class RefCountedVector
+    : public base::RefCountedThreadSafe<RefCountedVector<T> > {
+ public:
+  RefCountedVector() {}
+  explicit RefCountedVector(const std::vector<T>& initializer)
+      : data(initializer) {}
+
+  std::vector<T> data;
+
+  DISALLOW_COPY_AND_ASSIGN(RefCountedVector<T>);
+};
+
+#endif  // CHROME_COMMON_REF_COUNTED_UTIL_H__
diff --git a/chrome/common/render_messages.cc b/chrome/common/render_messages.cc
new file mode 100644
index 0000000..ce17fa3
--- /dev/null
+++ b/chrome/common/render_messages.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/render_messages.h"
+
+namespace IPC {
+
+void ParamTraits<ContentSettingsPattern>::Write(
+    Message* m, const ContentSettingsPattern& pattern) {
+  pattern.WriteToMessage(m);
+}
+
+bool ParamTraits<ContentSettingsPattern>::Read(
+    const Message* m, PickleIterator* iter, ContentSettingsPattern* pattern) {
+  return pattern->ReadFromMessage(m, iter);
+}
+
+void ParamTraits<ContentSettingsPattern>::Log(
+    const ContentSettingsPattern& p, std::string* l) {
+  l->append("<ContentSettingsPattern: ");
+  l->append(p.ToString());
+  l->append(">");
+}
+
+}  // namespace IPC
diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h
new file mode 100644
index 0000000..79b051e
--- /dev/null
+++ b/chrome/common/render_messages.h
@@ -0,0 +1,664 @@
+// Copyright (c) 2012 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.
+
+// Multiply-included file, no traditional include guard.
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/process.h"
+#include "base/shared_memory.h"
+#include "base/string16.h"
+#include "base/stringprintf.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/common/common_param_traits.h"
+#include "chrome/common/content_settings.h"
+#include "chrome/common/content_settings_pattern.h"
+#include "chrome/common/instant_types.h"
+#include "chrome/common/nacl_types.h"
+#include "chrome/common/search_provider.h"
+#include "chrome/common/translate_errors.h"
+#include "content/public/common/common_param_traits.h"
+#include "ipc/ipc_channel_handle.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_platform_file.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebCache.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebConsoleMessage.h"
+#include "ui/gfx/rect.h"
+
+// Singly-included section for enums and custom IPC traits.
+#ifndef CHROME_COMMON_RENDER_MESSAGES_H_
+#define CHROME_COMMON_RENDER_MESSAGES_H_
+
+class SkBitmap;
+
+// Command values for the cmd parameter of the
+// ViewHost_JavaScriptStressTestControl message. For each command the parameter
+// passed has a different meaning:
+// For the command kJavaScriptStressTestSetStressRunType the parameter it the
+// type taken from the enumeration v8::Testing::StressType.
+// For the command kJavaScriptStressTestPrepareStressRun the parameter it the
+// number of the stress run about to take place.
+enum ViewHostMsg_JavaScriptStressTestControl_Commands {
+  kJavaScriptStressTestSetStressRunType = 0,
+  kJavaScriptStressTestPrepareStressRun = 1,
+};
+
+// This enum is inside a struct so that we can forward-declare the struct in
+// others headers without having to include this one.
+struct ChromeViewHostMsg_GetPluginInfo_Status {
+  enum Value {
+    kAllowed,
+    kBlocked,
+    kClickToPlay,
+    kDisabled,
+    kNotFound,
+    kNPAPINotSupported,
+    kOutdatedBlocked,
+    kOutdatedDisallowed,
+    kUnauthorized,
+  };
+
+  ChromeViewHostMsg_GetPluginInfo_Status() : value(kAllowed) {}
+
+  Value value;
+};
+
+namespace IPC {
+
+#if defined(OS_POSIX) && !defined(USE_AURA) && !defined(OS_ANDROID)
+
+// TODO(port): this shouldn't exist. However, the plugin stuff is really using
+// HWNDS (NativeView), and making Windows calls based on them. I've not figured
+// out the deal with plugins yet.
+// TODO(android): a gfx::NativeView is the same as a gfx::NativeWindow.
+template <>
+struct ParamTraits<gfx::NativeView> {
+  typedef gfx::NativeView param_type;
+  static void Write(Message* m, const param_type& p) {
+    NOTIMPLEMENTED();
+  }
+
+  static bool Read(const Message* m, PickleIterator* iter, param_type* p) {
+    NOTIMPLEMENTED();
+    *p = NULL;
+    return true;
+  }
+
+  static void Log(const param_type& p, std::string* l) {
+    l->append(base::StringPrintf("<gfx::NativeView>"));
+  }
+};
+
+#endif  // defined(OS_POSIX) && !defined(USE_AURA) && !defined(OS_ANDROID)
+
+template <>
+struct ParamTraits<ContentSettingsPattern> {
+  typedef ContentSettingsPattern param_type;
+  static void Write(Message* m, const param_type& p);
+  static bool Read(const Message* m, PickleIterator* iter, param_type* r);
+  static void Log(const param_type& p, std::string* l);
+};
+
+}  // namespace IPC
+
+#endif  // CHROME_COMMON_RENDER_MESSAGES_H_
+
+#define IPC_MESSAGE_START ChromeMsgStart
+
+IPC_ENUM_TRAITS(ChromeViewHostMsg_GetPluginInfo_Status::Value)
+IPC_ENUM_TRAITS(InstantCompleteBehavior)
+IPC_ENUM_TRAITS(InstantSizeUnits)
+IPC_ENUM_TRAITS(InstantSuggestionType)
+IPC_ENUM_TRAITS(InstantShownReason)
+IPC_ENUM_TRAITS(search_provider::OSDDType)
+IPC_ENUM_TRAITS(search_provider::InstallState)
+IPC_ENUM_TRAITS(TranslateErrors::Type)
+IPC_ENUM_TRAITS(WebKit::WebConsoleMessage::Level)
+
+IPC_STRUCT_TRAITS_BEGIN(ChromeViewHostMsg_GetPluginInfo_Status)
+IPC_STRUCT_TRAITS_MEMBER(value)
+IPC_STRUCT_TRAITS_END()
+
+// Output parameters for ChromeViewHostMsg_GetPluginInfo message.
+IPC_STRUCT_BEGIN(ChromeViewHostMsg_GetPluginInfo_Output)
+  IPC_STRUCT_MEMBER(ChromeViewHostMsg_GetPluginInfo_Status, status)
+  IPC_STRUCT_MEMBER(webkit::WebPluginInfo, plugin)
+  IPC_STRUCT_MEMBER(std::string, actual_mime_type)
+  IPC_STRUCT_MEMBER(std::string, group_identifier)
+  IPC_STRUCT_MEMBER(string16, group_name)
+IPC_STRUCT_END()
+
+IPC_STRUCT_TRAITS_BEGIN(ContentSettingsPattern::PatternParts)
+  IPC_STRUCT_TRAITS_MEMBER(scheme)
+  IPC_STRUCT_TRAITS_MEMBER(is_scheme_wildcard)
+  IPC_STRUCT_TRAITS_MEMBER(host)
+  IPC_STRUCT_TRAITS_MEMBER(has_domain_wildcard)
+  IPC_STRUCT_TRAITS_MEMBER(port)
+  IPC_STRUCT_TRAITS_MEMBER(is_port_wildcard)
+  IPC_STRUCT_TRAITS_MEMBER(path)
+  IPC_STRUCT_TRAITS_MEMBER(is_path_wildcard)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(ContentSettingPatternSource)
+  IPC_STRUCT_TRAITS_MEMBER(primary_pattern)
+  IPC_STRUCT_TRAITS_MEMBER(secondary_pattern)
+  IPC_STRUCT_TRAITS_MEMBER(setting)
+  IPC_STRUCT_TRAITS_MEMBER(source)
+  IPC_STRUCT_TRAITS_MEMBER(incognito)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(InstantAutocompleteResult)
+  IPC_STRUCT_TRAITS_MEMBER(provider)
+  IPC_STRUCT_TRAITS_MEMBER(is_search)
+  IPC_STRUCT_TRAITS_MEMBER(contents)
+  IPC_STRUCT_TRAITS_MEMBER(destination_url)
+  IPC_STRUCT_TRAITS_MEMBER(relevance)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(InstantSuggestion)
+  IPC_STRUCT_TRAITS_MEMBER(text)
+  IPC_STRUCT_TRAITS_MEMBER(behavior)
+  IPC_STRUCT_TRAITS_MEMBER(type)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(RendererContentSettingRules)
+  IPC_STRUCT_TRAITS_MEMBER(image_rules)
+  IPC_STRUCT_TRAITS_MEMBER(script_rules)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(WebKit::WebCache::ResourceTypeStat)
+  IPC_STRUCT_TRAITS_MEMBER(count)
+  IPC_STRUCT_TRAITS_MEMBER(size)
+  IPC_STRUCT_TRAITS_MEMBER(liveSize)
+  IPC_STRUCT_TRAITS_MEMBER(decodedSize)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(WebKit::WebCache::ResourceTypeStats)
+  IPC_STRUCT_TRAITS_MEMBER(images)
+  IPC_STRUCT_TRAITS_MEMBER(cssStyleSheets)
+  IPC_STRUCT_TRAITS_MEMBER(scripts)
+  IPC_STRUCT_TRAITS_MEMBER(xslStyleSheets)
+  IPC_STRUCT_TRAITS_MEMBER(fonts)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(WebKit::WebCache::UsageStats)
+  IPC_STRUCT_TRAITS_MEMBER(minDeadCapacity)
+  IPC_STRUCT_TRAITS_MEMBER(maxDeadCapacity)
+  IPC_STRUCT_TRAITS_MEMBER(capacity)
+  IPC_STRUCT_TRAITS_MEMBER(liveSize)
+  IPC_STRUCT_TRAITS_MEMBER(deadSize)
+IPC_STRUCT_TRAITS_END()
+
+//-----------------------------------------------------------------------------
+// RenderView messages
+// These are messages sent from the browser to the renderer process.
+
+// Tells the renderer to set its maximum cache size to the supplied value.
+IPC_MESSAGE_CONTROL3(ChromeViewMsg_SetCacheCapacities,
+                     size_t /* min_dead_capacity */,
+                     size_t /* max_dead_capacity */,
+                     size_t /* capacity */)
+
+// Tells the renderer to clear the cache.
+IPC_MESSAGE_CONTROL1(ChromeViewMsg_ClearCache,
+                     bool /* on_navigation */)
+
+// Tells the renderer to dump as much memory as it can, perhaps because we
+// have memory pressure or the renderer is (or will be) paged out.  This
+// should only result in purging objects we can recalculate, e.g. caches or
+// JS garbage, not in purging irreplaceable objects.
+IPC_MESSAGE_CONTROL0(ChromeViewMsg_PurgeMemory)
+
+// For WebUI testing, this message stores parameters to do ScriptEvalRequest at
+// a time which is late enough to not be thrown out, and early enough to be
+// before onload events are fired.
+IPC_MESSAGE_ROUTED4(ChromeViewMsg_WebUIJavaScript,
+                    string16,  /* frame_xpath */
+                    string16,  /* jscript_url */
+                    int,  /* ID */
+                    bool  /* If true, result is sent back. */)
+
+// Tells the render view to capture a thumbnail image of the page. The
+// render view responds with a ChromeViewHostMsg_Snapshot.
+IPC_MESSAGE_ROUTED0(ChromeViewMsg_CaptureSnapshot)
+
+// History system notification that the visited link database has been
+// replaced. It has one SharedMemoryHandle argument consisting of the table
+// handle. This handle is valid in the context of the renderer
+IPC_MESSAGE_CONTROL1(ChromeViewMsg_VisitedLink_NewTable,
+                     base::SharedMemoryHandle)
+
+// History system notification that a link has been added and the link
+// coloring state for the given hash must be re-calculated.
+IPC_MESSAGE_CONTROL1(ChromeViewMsg_VisitedLink_Add, std::vector<uint64>)
+
+// History system notification that one or more history items have been
+// deleted, which at this point means that all link coloring state must be
+// re-calculated.
+IPC_MESSAGE_CONTROL0(ChromeViewMsg_VisitedLink_Reset)
+
+// Set the content setting rules stored by the renderer.
+IPC_MESSAGE_CONTROL1(ChromeViewMsg_SetContentSettingRules,
+                     RendererContentSettingRules /* rules */)
+
+// Tells the render view to load all blocked plugins with the given identifier.
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_LoadBlockedPlugins,
+                    std::string /* identifier */)
+
+// Asks the renderer to send back stats on the WebCore cache broken down by
+// resource types.
+IPC_MESSAGE_CONTROL0(ChromeViewMsg_GetCacheResourceStats)
+
+// Tells the renderer to create a FieldTrial, and by using a 100% probability
+// for the FieldTrial, forces the FieldTrial to have assigned group name.
+IPC_MESSAGE_CONTROL2(ChromeViewMsg_SetFieldTrialGroup,
+                     std::string /* field trial name */,
+                     std::string /* group name that was assigned. */)
+
+// Asks the renderer to send back V8 heap stats.
+IPC_MESSAGE_CONTROL0(ChromeViewMsg_GetV8HeapStats)
+
+// Posts a message to the renderer.
+IPC_MESSAGE_ROUTED3(ChromeViewMsg_HandleMessageFromExternalHost,
+                    std::string /* The message */,
+                    std::string /* The origin */,
+                    std::string /* The target*/)
+
+IPC_MESSAGE_ROUTED4(ChromeViewMsg_SearchBoxChange,
+                    string16 /* value */,
+                    bool /* verbatim */,
+                    size_t /* selection_start */,
+                    size_t /* selection_end */)
+
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_SearchBoxSubmit,
+                    string16 /* value */)
+
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_SearchBoxCancel,
+                    string16 /* value */)
+
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_SearchBoxResize,
+                    gfx::Rect /* search_box_bounds */)
+
+IPC_MESSAGE_ROUTED0(ChromeViewMsg_DetermineIfPageSupportsInstant)
+
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_SearchBoxAutocompleteResults,
+                    std::vector<InstantAutocompleteResult>
+                        /* native_suggestions */)
+
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_SearchBoxUpOrDownKeyPressed,
+                    int /* count */)
+
+IPC_MESSAGE_ROUTED0(ChromeViewMsg_SearchBoxFocus)
+
+IPC_MESSAGE_ROUTED0(ChromeViewMsg_SearchBoxBlur)
+
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_SearchBoxActiveTabModeChanged,
+                    bool /* active_tab_is_ntp */)
+
+// Toggles visual muting of the render view area. This is on when a constrained
+// window is showing.
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_SetVisuallyDeemphasized,
+                    bool /* deemphazied */)
+
+// Tells the renderer to translate the page contents from one language to
+// another.
+IPC_MESSAGE_ROUTED4(ChromeViewMsg_TranslatePage,
+                    int /* page id */,
+                    std::string, /* the script injected in the page */
+                    std::string, /* BCP 47/RFC 5646 language code the page
+                                    is in */
+                    std::string /* BCP 47/RFC 5646 language code to translate
+                                   to */)
+
+// Tells the renderer to revert the text of translated page to its original
+// contents.
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_RevertTranslation,
+                    int /* page id */)
+
+// Sent on process startup to indicate whether this process is running in
+// incognito mode.
+IPC_MESSAGE_CONTROL1(ChromeViewMsg_SetIsIncognitoProcess,
+                     bool /* is_incognito_processs */)
+
+// Sent in response to ViewHostMsg_DidBlockDisplayingInsecureContent.
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_SetAllowDisplayingInsecureContent,
+                    bool /* allowed */)
+
+// Sent in response to ViewHostMsg_DidBlockRunningInsecureContent.
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_SetAllowRunningInsecureContent,
+                    bool /* allowed */)
+
+// Tells renderer to always enforce mixed content blocking for this host.
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_AddStrictSecurityHost,
+                    std::string /* host */)
+
+// Sent when the profile changes the kSafeBrowsingEnabled preference.
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_SetClientSidePhishingDetection,
+                    bool /* enable_phishing_detection */)
+
+// This message asks frame sniffer start.
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_StartFrameSniffer,
+                    string16 /* frame-name */)
+
+// JavaScript related messages -----------------------------------------------
+
+// Notify the JavaScript engine in the render to change its parameters
+// while performing stress testing.
+IPC_MESSAGE_ROUTED2(ChromeViewMsg_JavaScriptStressTestControl,
+                    int /* cmd */,
+                    int /* param */)
+
+// Asks the renderer to send back FPS.
+IPC_MESSAGE_ROUTED0(ChromeViewMsg_GetFPS)
+
+// Tells the view it is displaying an interstitial page.
+IPC_MESSAGE_ROUTED0(ChromeViewMsg_SetAsInterstitial)
+
+// Tells the renderer to suspend/resume the webkit timers.
+IPC_MESSAGE_CONTROL1(ChromeViewMsg_ToggleWebKitSharedTimer,
+                     bool /* suspend */)
+
+//-----------------------------------------------------------------------------
+// Misc messages
+// These are messages sent from the renderer to the browser process.
+
+// Provides the contents for the given page that was loaded recently.
+IPC_MESSAGE_ROUTED3(ChromeViewHostMsg_PageContents,
+                    GURL         /* URL of the page */,
+                    int32        /* page id */,
+                    string16     /* page contents */)
+
+// Notification that the language for the tab has been determined.
+IPC_MESSAGE_ROUTED2(ChromeViewHostMsg_TranslateLanguageDetermined,
+                    std::string  /* page ISO639_1 language code */,
+                    bool         /* whether the page can be translated */)
+
+IPC_MESSAGE_CONTROL1(ChromeViewHostMsg_UpdatedCacheStats,
+                     WebKit::WebCache::UsageStats /* stats */)
+
+// Tells the browser that content in the current page was blocked due to the
+// user's content settings.
+IPC_MESSAGE_ROUTED2(ChromeViewHostMsg_ContentBlocked,
+                    ContentSettingsType, /* type of blocked content */
+                    std::string /* resource identifier */)
+
+// Sent by the renderer process to check whether access to web databases is
+// granted by content settings.
+IPC_SYNC_MESSAGE_CONTROL5_1(ChromeViewHostMsg_AllowDatabase,
+                            int /* render_view_id */,
+                            GURL /* origin_url */,
+                            GURL /* top origin url */,
+                            string16 /* database name */,
+                            string16 /* database display name */,
+                            bool /* allowed */)
+
+// Sent by the renderer process to check whether access to DOM Storage is
+// granted by content settings.
+IPC_SYNC_MESSAGE_CONTROL4_1(ChromeViewHostMsg_AllowDOMStorage,
+                            int /* render_view_id */,
+                            GURL /* origin_url */,
+                            GURL /* top origin url */,
+                            bool /* if true local storage, otherwise session */,
+                            bool /* allowed */)
+
+// Sent by the renderer process to check whether access to FileSystem is
+// granted by content settings.
+IPC_SYNC_MESSAGE_CONTROL3_1(ChromeViewHostMsg_AllowFileSystem,
+                            int /* render_view_id */,
+                            GURL /* origin_url */,
+                            GURL /* top origin url */,
+                            bool /* allowed */)
+
+// Sent by the renderer process to check whether access to Indexed DBis
+// granted by content settings.
+IPC_SYNC_MESSAGE_CONTROL4_1(ChromeViewHostMsg_AllowIndexedDB,
+                            int /* render_view_id */,
+                            GURL /* origin_url */,
+                            GURL /* top origin url */,
+                            string16 /* database name */,
+                            bool /* allowed */)
+
+// Return information about a plugin for the given URL and MIME type.
+// In contrast to ViewHostMsg_GetPluginInfo in content/, this IPC call knows
+// about specific reasons why a plug-in can't be used, for example because it's
+// disabled.
+IPC_SYNC_MESSAGE_CONTROL4_1(ChromeViewHostMsg_GetPluginInfo,
+                            int /* render_view_id */,
+                            GURL /* url */,
+                            GURL /* top origin url */,
+                            std::string /* mime_type */,
+                            ChromeViewHostMsg_GetPluginInfo_Output /* output */)
+
+#if defined(ENABLE_PLUGIN_INSTALLATION)
+// Tells the browser to search for a plug-in that can handle the given MIME
+// type. The result will be sent asynchronously to the routing ID
+// |placeholder_id|.
+IPC_MESSAGE_ROUTED2(ChromeViewHostMsg_FindMissingPlugin,
+                    int /* placeholder_id */,
+                    std::string /* mime_type */)
+
+// Notifies the browser that a missing plug-in placeholder has been removed, so
+// the corresponding PluginPlaceholderHost can be deleted.
+IPC_MESSAGE_ROUTED1(ChromeViewHostMsg_RemovePluginPlaceholderHost,
+                    int /* placeholder_id */)
+
+// Notifies a missing plug-in placeholder that a plug-in with name |plugin_name|
+// has been found.
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_FoundMissingPlugin,
+                    string16 /* plugin_name */)
+
+// Notifies a missing plug-in placeholder that no plug-in has been found.
+IPC_MESSAGE_ROUTED0(ChromeViewMsg_DidNotFindMissingPlugin)
+
+// Notifies a missing plug-in placeholder that we have started downloading
+// the plug-in.
+IPC_MESSAGE_ROUTED0(ChromeViewMsg_StartedDownloadingPlugin)
+
+// Notifies a missing plug-in placeholder that we have finished downloading
+// the plug-in.
+IPC_MESSAGE_ROUTED0(ChromeViewMsg_FinishedDownloadingPlugin)
+
+// Notifies a missing plug-in placeholder that there was an error downloading
+// the plug-in.
+IPC_MESSAGE_ROUTED1(ChromeViewMsg_ErrorDownloadingPlugin,
+                    std::string /* message */)
+#endif  // defined(ENABLE_PLUGIN_INSTALLATION)
+
+// Notifies a missing plug-in placeholder that the user cancelled downloading
+// the plug-in.
+IPC_MESSAGE_ROUTED0(ChromeViewMsg_CancelledDownloadingPlugin)
+
+// Tells the browser to open chrome://plugins in a new tab. We use a separate
+// message because renderer processes aren't allowed to directly navigate to
+// chrome:// URLs.
+IPC_MESSAGE_ROUTED0(ChromeViewHostMsg_OpenAboutPlugins)
+
+// Tells the browser that there was an error loading a plug-in.
+IPC_MESSAGE_ROUTED1(ChromeViewHostMsg_CouldNotLoadPlugin,
+                    FilePath /* plugin_path */)
+
+// Tells the browser that we blocked a plug-in because NPAPI is not supported.
+IPC_MESSAGE_ROUTED1(ChromeViewHostMsg_NPAPINotSupported,
+                    std::string /* identifer */)
+
+// Send a snapshot of the tab contents to the render host.
+IPC_MESSAGE_ROUTED1(ChromeViewHostMsg_Snapshot,
+                    SkBitmap /* bitmap */)
+
+// A message for an external host.
+IPC_MESSAGE_ROUTED3(ChromeViewHostMsg_ForwardMessageToExternalHost,
+                    std::string  /* message */,
+                    std::string  /* origin */,
+                    std::string  /* target */)
+
+// A renderer sends this to the browser process when it wants to start
+// a new instance of the Native Client process. The browser will launch
+// the process and return an IPC channel handle. This handle will only
+// be valid if the NaCl IPC proxy is enabled.
+IPC_SYNC_MESSAGE_CONTROL4_3(ChromeViewHostMsg_LaunchNaCl,
+                            GURL /* manifest_url */,
+                            int /* render_view_id */,
+                            uint32 /* permission_bits */,
+                            int /* socket count */,
+                            std::vector<nacl::FileDescriptor>
+                                /* imc channel handles */,
+                            IPC::ChannelHandle /* ipc_channel_handle */,
+                            int /* plugin_child_id */)
+
+// A renderer sends this to the browser process when it wants to
+// open a file for from the Pnacl component directory.
+IPC_SYNC_MESSAGE_CONTROL1_1(ChromeViewHostMsg_GetReadonlyPnaclFD,
+                            std::string /* name of requested PNaCl file */,
+                            IPC::PlatformFileForTransit /* output file */)
+
+// A renderer sends this to the browser process when it wants to
+// create a temporary file.
+IPC_SYNC_MESSAGE_CONTROL0_1(ChromeViewHostMsg_NaClCreateTemporaryFile,
+                            IPC::PlatformFileForTransit /* out file */)
+
+// Notification that the page has an OpenSearch description document
+// associated with it.
+IPC_MESSAGE_ROUTED3(ChromeViewHostMsg_PageHasOSDD,
+                    int32 /* page_id */,
+                    GURL /* url of OS description document */,
+                    search_provider::OSDDType)
+
+// Find out if the given url's security origin is installed as a search
+// provider.
+IPC_SYNC_MESSAGE_ROUTED2_1(ChromeViewHostMsg_GetSearchProviderInstallState,
+                           GURL /* page url */,
+                           GURL /* inquiry url */,
+                           search_provider::InstallState /* install */)
+
+// Send back histograms as vector of pickled-histogram strings.
+IPC_MESSAGE_CONTROL2(ChromeViewHostMsg_RendererHistograms,
+                     int, /* sequence number of Renderer Histograms. */
+                     std::vector<std::string>)
+
+// Sends back stats about the V8 heap.
+IPC_MESSAGE_CONTROL2(ChromeViewHostMsg_V8HeapStats,
+                     int /* size of heap (allocated from the OS) */,
+                     int /* bytes in use */)
+
+// Request for a DNS prefetch of the names in the array.
+// NameList is typedef'ed std::vector<std::string>
+IPC_MESSAGE_CONTROL1(ChromeViewHostMsg_DnsPrefetch,
+                     std::vector<std::string> /* hostnames */)
+
+// Notifies when a plugin couldn't be loaded because it's outdated.
+IPC_MESSAGE_ROUTED2(ChromeViewHostMsg_BlockedOutdatedPlugin,
+                    int /* placeholder ID */,
+                    std::string /* plug-in group identifier */)
+
+// Notifies when a plugin couldn't be loaded because it requires
+// user authorization.
+IPC_MESSAGE_ROUTED2(ChromeViewHostMsg_BlockedUnauthorizedPlugin,
+                    string16 /* name */,
+                    std::string /* plug-in group identifier */)
+
+// Provide the browser process with information about the WebCore resource
+// cache and current renderer framerate.
+IPC_MESSAGE_CONTROL1(ChromeViewHostMsg_ResourceTypeStats,
+                     WebKit::WebCache::ResourceTypeStats)
+
+
+// Notifies the browser of the language (ISO 639_1 code language, such as fr,
+// en, zh...) of the current page.
+IPC_MESSAGE_ROUTED1(ChromeViewHostMsg_PageLanguageDetermined,
+                    std::string /* the language */)
+
+// Notifies the browser that a page has been translated.
+IPC_MESSAGE_ROUTED4(ChromeViewHostMsg_PageTranslated,
+                    int,                  /* page id */
+                    std::string           /* the original language */,
+                    std::string           /* the translated language */,
+                    TranslateErrors::Type /* the error type if available */)
+
+// Message sent from the renderer to the browser to notify it of events which
+// may lead to the cancellation of a prerender. The message is sent only when
+// the renderer is prerendering.
+IPC_MESSAGE_ROUTED0(ChromeViewHostMsg_MaybeCancelPrerenderForHTML5Media)
+
+// Message sent from the renderer to the browser to notify it of a
+// window.print() call which should cancel the prerender. The message is sent
+// only when the renderer is prerendering.
+IPC_MESSAGE_ROUTED0(ChromeViewHostMsg_CancelPrerenderForPrinting)
+
+// Sent by the renderer to check if a URL has permission to trigger a clipboard
+// read/write operation from the DOM.
+IPC_SYNC_MESSAGE_ROUTED1_1(ChromeViewHostMsg_CanTriggerClipboardRead,
+                           GURL /* origin */,
+                           bool /* allowed */)
+IPC_SYNC_MESSAGE_ROUTED1_1(ChromeViewHostMsg_CanTriggerClipboardWrite,
+                           GURL /* origin */,
+                           bool /* allowed */)
+
+// Sent when the renderer was prevented from displaying insecure content in
+// a secure page by a security policy.  The page may appear incomplete.
+IPC_MESSAGE_ROUTED0(ChromeViewHostMsg_DidBlockDisplayingInsecureContent)
+
+// Sent when the renderer was prevented from running insecure content in
+// a secure origin by a security policy.  The page may appear incomplete.
+IPC_MESSAGE_ROUTED0(ChromeViewHostMsg_DidBlockRunningInsecureContent)
+
+// Message sent from renderer to the browser when the element that is focused
+// and currently accepts keyboard input inside the webpage has been touched.
+IPC_MESSAGE_ROUTED0(ChromeViewHostMsg_FocusedEditableNodeTouched)
+
+// Suggest results -----------------------------------------------------------
+
+// Sent by the Instant preview to populate the omnibox with query suggestions.
+IPC_MESSAGE_ROUTED2(ChromeViewHostMsg_SetSuggestions,
+                    int /* page_id */,
+                    std::vector<InstantSuggestion> /* suggestions */)
+
+// Sent by the Instant preview indicating whether the page supports the Instant
+// API or not (http://dev.chromium.org/searchbox).
+IPC_MESSAGE_ROUTED2(ChromeViewHostMsg_InstantSupportDetermined,
+                    int /* page_id */,
+                    bool /* result */)
+
+// Sent by the Instant preview asking to show itself with the given height.
+IPC_MESSAGE_ROUTED4(ChromeViewHostMsg_ShowInstantPreview,
+                    int /* page_id */,
+                    InstantShownReason /* reason */,
+                    int /* height */,
+                    InstantSizeUnits /* units */)
+
+// The currently displayed PDF has an unsupported feature.
+IPC_MESSAGE_ROUTED0(ChromeViewHostMsg_PDFHasUnsupportedFeature)
+
+// This message indicates the error appeared in the frame.
+IPC_MESSAGE_ROUTED1(ChromeViewHostMsg_FrameLoadingError,
+                    int /* error */)
+
+// This message indicates the monitored frame loading had completed.
+IPC_MESSAGE_ROUTED0(ChromeViewHostMsg_FrameLoadingCompleted)
+
+// The following messages are used to set and get cookies for ChromeFrame
+// processes.
+// Used to set a cookie. The cookie is set asynchronously, but will be
+// available to a subsequent ChromeViewHostMsg_GetCookies request.
+IPC_MESSAGE_ROUTED3(ChromeViewHostMsg_SetCookie,
+                    GURL /* url */,
+                    GURL /* first_party_for_cookies */,
+                    std::string /* cookie */)
+
+// Used to get cookies for the given URL. This may block waiting for a
+// previous SetCookie message to be processed.
+IPC_SYNC_MESSAGE_ROUTED2_1(ChromeViewHostMsg_GetCookies,
+                           GURL /* url */,
+                           GURL /* first_party_for_cookies */,
+                           std::string /* cookies */)
+
+// Provide the browser process with current renderer framerate.
+IPC_MESSAGE_CONTROL2(ChromeViewHostMsg_FPS,
+                     int /* routing id */,
+                     float /* frames per second */)
diff --git a/chrome/common/safe_browsing/OWNERS b/chrome/common/safe_browsing/OWNERS
new file mode 100644
index 0000000..881aaa5
--- /dev/null
+++ b/chrome/common/safe_browsing/OWNERS
@@ -0,0 +1,8 @@
+# For security review of IPC message files.
+per-file *_messages.h=set noparent
+per-file *_messages.h=cdn@chromium.org
+per-file *_messages.h=cevans@chromium.org
+per-file *_messages.h=inferno@chromium.org
+per-file *_messages.h=jschuh@chromium.org
+per-file *_messages.h=palmer@chromium.org
+per-file *_messages.h=tsepez@chromium.org
diff --git a/chrome/common/safe_browsing/client_model.proto b/chrome/common/safe_browsing/client_model.proto
new file mode 100644
index 0000000..863f8eb
--- /dev/null
+++ b/chrome/common/safe_browsing/client_model.proto
@@ -0,0 +1,89 @@
+// 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.
+//
+// This proto represents a machine learning model which is used to compute
+// the probability that a particular page visited by Chrome is phishing.
+//
+// Note: sine the machine learning model is trained on the server-side and then
+// downloaded onto the client it is important that this proto file stays in
+// sync with the server-side copy.  Otherwise, the client may not be able to
+// parse the server generated model anymore.  If you want to change this
+// protocol definition or you have questions regarding its format please contact
+// chrome-anti-phishing@googlegroups.com.
+
+syntax = "proto2";
+
+package safe_browsing;
+
+// This protocol buffer represents a machine learning model that is used in
+// client-side phishing detection (in Chrome).  The client extracts a set
+// of features from every website the user visits.  Extracted features map
+// feature names to floating point values (e.g., PageSecureLinksFreq -> 0.9).
+//
+// To compute the phishing score (i.e., the probability that the website is
+// phishing) a scorer will simply compute the sum of all rule scores for a
+// given set of extracted features.  The score of a particular rule corresponds
+// to the product of all feature values that are part of the rule times the
+// rule weight.  If a feature has no value (i.e., is not part of the extracted
+// features) its value will be set to zero.  The overall score is computed
+// by summing up all the rule scores.  This overall score is a logodds and can
+// be converted to a probability like this:
+// p = exp(logodds) / (exp(logodds) + 1).
+//
+// To make it harder for phishers to reverse engineer our machine learning model
+// all the features in the model are hashed with a sha256 hash function.  The
+// feature extractors also hash the extracted features before scoring happens.
+message ClientSideModel {
+  // In order to save some space we store all the hashed strings in a
+  // single repeated field and then the rules as well as page terms
+  // and page words refer to an index in that repeated field.  All
+  // hashes are sha256 hashes stored in binary format.
+  repeated bytes hashes = 1;
+
+  message Rule {
+    // List of indexes into hashes above which are basically hashed
+    // features that form the current rule.
+    repeated int32 feature = 1;
+
+    // The weight for this particular rule.
+    required float weight = 2;
+  }
+
+  // List of rules which make up the model
+  repeated Rule rule = 2;
+
+  // List of indexes that point to the hashed page terms that appear in
+  // the model.  The hashes are computed over page terms that are encoded
+  // as lowercase UTF-8 strings.
+  repeated int32 page_term = 3;
+
+  // List of hashed page words.  The page words correspond to all words that
+  // appear in page terms.  If the term "one two" is in the list of page terms
+  // then "one" and "two" will be in the list of page words.  For page words
+  // we don't use SHA256 because it is too expensive.  We use MurmurHash3
+  // instead.  See: http://code.google.com/p/smhasher.
+  repeated fixed32 page_word = 4;
+
+  // Page terms in page_term contain at most this many page words.
+  required int32 max_words_per_term = 5;
+
+  // Model version number.  Every model that we train should have a different
+  // version number and it should always be larger than the previous model
+  // version.
+  optional int32 version = 6;
+
+  // List of known bad IP subnets.
+  message IPSubnet {
+    // The subnet prefix is a valid 16-byte IPv6 address (in network order) that
+    // is hashed using sha256.
+    required bytes prefix = 1;
+
+    // Network prefix size in bits.  Default is an exact-host match.
+    optional int32 size = 2 [default = 128];
+  };
+  repeated IPSubnet bad_subnet = 7;
+
+  // Murmur hash seed that was used to hash the page words.
+  optional fixed32 murmur_hash_seed = 8;
+}
diff --git a/chrome/common/safe_browsing/csd.proto b/chrome/common/safe_browsing/csd.proto
new file mode 100644
index 0000000..14fb1a7
--- /dev/null
+++ b/chrome/common/safe_browsing/csd.proto
@@ -0,0 +1,242 @@
+// Copyright (c) 2012 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.
+//
+// Client side phishing and malware detection request and response
+// protocol buffers.  Those protocol messages should be kept in sync
+// with the server implementation.
+//
+// If you want to change this protocol definition or you have questions
+// regarding its format please contact chrome-anti-phishing@googlegroups.com.
+
+syntax = "proto2";
+
+package safe_browsing;
+
+message ClientPhishingRequest {
+  // URL that the client visited.  The CGI parameters are stripped by the
+  // client.
+  optional string url = 1;
+
+  // A 5-byte SHA-256 hash prefix of the URL.  Before hashing the URL is
+  // canonicalized, converted to a suffix-prefix expression and broadened
+  // (www prefix is removed and everything past the last '/' is stripped).
+  //
+  // Marked OBSOLETE because the URL is sent for all users, making the hash
+  // prefix unnecessary.
+  optional bytes OBSOLETE_hash_prefix = 10;
+
+  // Score that was computed on the client.  Value is between 0.0 and 1.0.
+  // The larger the value the more likely the url is phishing.
+  required float client_score = 2;
+
+  // Note: we're skipping tag 3 because it was previously used.
+
+  // Is true if the features for this URL were classified as phishing.
+  // Currently, this will always be true for all client-phishing requests
+  // that are sent to the server.
+  optional bool is_phishing = 4;
+
+  message Feature {
+    // Feature name.  E.g., 'PageHasForms'.
+    required string name = 1;
+
+    // Feature value is always in the range [0.0, 1.0].  Boolean features
+    // have value 1.0.
+    required double value = 2;
+  }
+
+  // List of features that were extracted.  Those are the features that were
+  // sent to the scorer and which resulted in client_score being computed.
+  repeated Feature feature_map = 5;
+
+  // The version number of the model that was used to compute the client-score.
+  // Copied from ClientSideModel.version().
+  optional int32 model_version = 6;
+
+  // Field 7 is only used on the server.
+
+  // List of features that are extracted in the client but are not used in the
+  // machine learning model.
+  repeated Feature non_model_feature_map = 8;
+
+  // The referrer URL.  This field might not be set, for example, in the case
+  // where the referrer uses HTTPs.
+  // OBSOLETE: Use feature 'Referrer=<referrer>' instead.
+  optional string OBSOLETE_referrer_url = 9;
+
+  // Field 11 is only used on the server.
+}
+
+message ClientPhishingResponse {
+  required bool phishy = 1;
+
+  // A list of SafeBrowsing host-suffix / path-prefix expressions that
+  // are whitelisted.  The client must match the current top-level URL
+  // against these whitelisted expressions and only apply a positive
+  // phishing verdict above if the URL does not match any expression
+  // on this whitelist.  The client must not cache these whitelisted
+  // expressions.  This whitelist will be empty for the vast majority
+  // of the responses but might contain up to 100 entries in emergency
+  // situations.
+  //
+  // Marked OBSOLETE because the URL is sent for all users, so the server
+  // can do whitelist matching.
+  repeated string OBSOLETE_whitelist_expression = 2;
+}
+
+message ClientDownloadRequest {
+  // The final URL of the download (after all redirects).
+  required string url = 1;
+
+  // This message contains various binary digests of the download payload.
+  message Digests {
+    optional bytes sha256 = 1;
+    optional bytes sha1 = 2;
+    optional bytes md5 = 3;
+  }
+  required Digests digests = 2;
+
+  // This is the length in bytes of the download payload.
+  required int64 length = 3;
+
+  // Type of the resources stored below.
+  enum ResourceType {
+    // The final URL of the download payload.  The resource URL should
+    // correspond to the URL field above.
+    DOWNLOAD_URL = 0;
+    // A redirect URL that was fetched before hitting the final DOWNLOAD_URL.
+    DOWNLOAD_REDIRECT = 1;
+    // The final top-level URL of the tab that triggered the download.
+    TAB_URL = 2;
+    // A redirect URL thas was fetched before hitting the final TAB_URL.
+    TAB_REDIRECT = 3;
+  }
+
+  message Resource {
+    required string url = 1;
+    required ResourceType type = 2;
+    optional bytes remote_ip = 3;
+    // This will only be set if the referrer is available and if the
+    // resource type is either TAB_URL or DOWNLOAD_URL.
+    optional string referrer = 4;
+
+    // TODO(noelutz): add the transition type?
+  }
+
+  // This repeated field will store all the redirects as well as the
+  // final URLs for the top-level tab URL (i.e., the URL that
+  // triggered the download) as well as for the download URL itself.
+  repeated Resource resources = 4;
+
+  // A trust chain of certificates.  Each chain begins with the signing
+  // certificate of the binary, and ends with a self-signed certificate,
+  // typically from a trusted root CA.  This structure is analogous to
+  // CERT_CHAIN_CONTEXT on Windows.
+  message CertificateChain {
+    // A single link in the chain.
+    message Element {
+      // DER-encoded X.509 representation of the certificate.
+      optional bytes certificate = 1;
+      // Fields 2 - 7 are only used on the server.
+    }
+    repeated Element element = 1;
+  }
+
+  message SignatureInfo {
+    // All of the certificate chains for the binary's signing certificate.
+    // If no chains are present, the binary is not signed.  Multiple chains
+    // may be present if any certificate has multiple signers.
+    repeated CertificateChain certificate_chain = 1;
+
+    // True if the signature was trusted on the client.
+    optional bool trusted = 2;
+  }
+
+  // This field will only be set if the binary is signed.
+  optional SignatureInfo signature = 5;
+
+  // True if the download was user initiated.
+  optional bool user_initiated = 6;
+
+  // Fields 7 and 8 are only used on the server.
+
+  // Name of the file where the download would be stored if the
+  // download completes.  E.g., "bla.exe".
+  optional string file_basename = 9;
+
+  // Starting with Chrome M19 we're also sending back pings for Chrome
+  // extensions that get downloaded by users.
+  enum DownloadType {
+    WIN_EXECUTABLE = 0;    // Currently all .exe, .cab and .msi files.
+    CHROME_EXTENSION = 1;  // .crx files.
+    ANDROID_APK = 2;       // .apk files.
+    // .zip files containing one of the above executable types.
+    ZIPPED_EXECUTABLE = 3;
+  }
+  optional DownloadType download_type = 10 [default = WIN_EXECUTABLE];
+
+  // Locale of the device, eg en, en_US.
+  optional string locale = 11;
+
+  // Field 12 is only used on the server.
+}
+
+message ClientDownloadResponse {
+  enum Verdict {
+    // Download is considered safe.
+    SAFE = 0;
+    // Download is considered dangerous.  Chrome should show a warning to the
+    // user.
+    DANGEROUS = 1;
+    // Download is unknown.  Chrome should display a less severe warning.
+    UNCOMMON = 2;
+    // The download is potentially unwanted.
+    POTENTIALLY_UNWANTED = 3;
+  }
+  required Verdict verdict = 1;
+
+  message MoreInfo {
+    // A human-readable string describing the nature of the warning.
+    // Only if verdict != SAFE. Localized based on request.locale.
+    optional string description = 1;
+
+    // A URL to get more information about this warning, if available.
+    optional string url = 2;
+  }
+  optional MoreInfo more_info = 2;
+
+  // An arbitrary token that should be sent along for further server requests.
+  optional bytes token = 3;
+}
+
+// The following protocol buffer holds the feedback report gathered
+// from the user regarding the download.
+message ClientDownloadReport {
+  // The information of user who provided the feedback.
+  // This is going to be useful for handling appeals.
+  message UserInformation {
+    optional string email = 1;
+  }
+
+  enum Reason {
+    SHARE = 0;
+    FALSE_POSITIVE = 1;
+    APPEAL = 2;
+  }
+
+  // The type of feedback for this report.
+  optional Reason reason = 1;
+
+  // The original download ping
+  optional ClientDownloadRequest download_request = 2;
+
+  // Stores the information of the user who provided the feedback.
+  optional UserInformation user_information = 3;
+
+  // Unstructed comments provided by the user.
+  optional bytes comment = 4;
+
+  // The original download response sent from the verdict server.
+  optional ClientDownloadResponse download_response = 5;
+}
diff --git a/chrome/common/safe_browsing/safebrowsing_messages.h b/chrome/common/safe_browsing/safebrowsing_messages.h
new file mode 100644
index 0000000..1a44e31
--- /dev/null
+++ b/chrome/common/safe_browsing/safebrowsing_messages.h
@@ -0,0 +1,58 @@
+// 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.
+
+// Multiply-included message file, so no include guard.
+
+#include "googleurl/src/gurl.h"
+#include "ipc/ipc_message_macros.h"
+
+#define IPC_MESSAGE_START SafeBrowsingMsgStart
+
+// A node is essentially a frame.
+IPC_STRUCT_BEGIN(SafeBrowsingHostMsg_MalwareDOMDetails_Node)
+  // URL of this resource. Can be empty.
+  IPC_STRUCT_MEMBER(GURL, url)
+
+  // If this resource was in the "src" attribute of a tag, this is the tagname
+  // (eg "IFRAME"). Can be empty.
+  IPC_STRUCT_MEMBER(std::string, tag_name)
+
+  // URL of the parent node. Can be empty.
+  IPC_STRUCT_MEMBER(GURL, parent)
+
+  // children of this node. Can be emtpy.
+  IPC_STRUCT_MEMBER(std::vector<GURL>, children)
+IPC_STRUCT_END()
+
+// SafeBrowsing client-side detection messages sent from the renderer to the
+// browser.
+
+// Inform the browser that the client-side phishing detector running in the
+// renderer is done classifying the current URL.  If the URL is phishing
+// the request proto will have |is_phishing()| set to true.
+// TODO(noelutz): we may want to create custom ParamTraits for MessageLite to
+// have a generic way to send protocol messages over IPC.
+IPC_MESSAGE_ROUTED1(SafeBrowsingHostMsg_PhishingDetectionDone,
+                    std::string /* encoded ClientPhishingRequest proto */)
+
+// Send part of the DOM to the browser, to be used in a malware report.
+IPC_MESSAGE_ROUTED1(SafeBrowsingHostMsg_MalwareDOMDetails,
+                    std::vector<SafeBrowsingHostMsg_MalwareDOMDetails_Node>)
+
+// SafeBrowsing client-side detection messages sent from the browser to the
+// renderer.
+
+// A classification model for client-side phishing detection.
+// The string is an encoded safe_browsing::ClientSideModel protocol buffer, or
+// empty to disable client-side phishing detection for this renderer.
+IPC_MESSAGE_CONTROL1(SafeBrowsingMsg_SetPhishingModel,
+                     std::string /* encoded ClientSideModel proto */)
+
+// Request a DOM tree when a malware interstitial is shown.
+IPC_MESSAGE_ROUTED0(SafeBrowsingMsg_GetMalwareDOMDetails)
+
+// Tells the renderer to begin phishing detection for the given toplevel URL
+// which it has started loading.
+IPC_MESSAGE_ROUTED1(SafeBrowsingMsg_StartPhishingDetection,
+                    GURL)
diff --git a/chrome/common/search_provider.h b/chrome/common/search_provider.h
new file mode 100644
index 0000000..c1daea6
--- /dev/null
+++ b/chrome/common/search_provider.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_SEARCH_PROVIDER_H_
+#define CHROME_COMMON_SEARCH_PROVIDER_H_
+
+namespace search_provider {
+
+// The type of OSDD that the renderer is giving to the browser.
+enum OSDDType {
+  // The Open Search Description URL was detected automatically.
+  AUTODETECTED_PROVIDER,
+
+  // The Open Search Description URL was given by Javascript.
+  EXPLICIT_PROVIDER,
+};
+
+// The install state of the search provider (not installed, installed, default).
+enum InstallState {
+  // Equates to an access denied error.
+  DENIED = -1,
+
+  // DON'T CHANGE THE VALUES BELOW.
+  // All of the following values are manidated by the
+  // spec for window.external.IsSearchProviderInstalled.
+
+  // The search provider is not installed.
+  NOT_INSTALLED = 0,
+
+  // The search provider is in the user's set but is not
+  INSTALLED_BUT_NOT_DEFAULT = 1,
+
+  // The search provider is set as the user's default.
+  INSTALLED_AS_DEFAULT = 2
+};
+
+}  // namespace search_provider
+
+#endif  // CHROME_COMMON_SEARCH_PROVIDER_H_
diff --git a/chrome/common/service_messages.h b/chrome/common/service_messages.h
new file mode 100644
index 0000000..8dd3157
--- /dev/null
+++ b/chrome/common/service_messages.h
@@ -0,0 +1,59 @@
+// 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.
+
+// Multiply-included message file, no traditional include guard.
+#include <string>
+#include <vector>
+
+#include "chrome/common/cloud_print/cloud_print_proxy_info.h"
+#include "ipc/ipc_channel_handle.h"
+#include "ipc/ipc_message_macros.h"
+
+#define IPC_MESSAGE_START ServiceMsgStart
+
+IPC_STRUCT_TRAITS_BEGIN(cloud_print::CloudPrintProxyInfo)
+  IPC_STRUCT_TRAITS_MEMBER(enabled)
+  IPC_STRUCT_TRAITS_MEMBER(email)
+  IPC_STRUCT_TRAITS_MEMBER(proxy_id)
+IPC_STRUCT_TRAITS_END()
+
+//-----------------------------------------------------------------------------
+// Service process messages:
+// These are messages from the browser to the service process.
+// Tell the service process to enable the cloud proxy passing in the lsid
+// of the account to be used.
+IPC_MESSAGE_CONTROL1(ServiceMsg_EnableCloudPrintProxy,
+                     std::string /* lsid */)
+
+// Tell the service process to enable the cloud proxy passing in the OAuth2
+// auth code of a robot account.
+IPC_MESSAGE_CONTROL5(ServiceMsg_EnableCloudPrintProxyWithRobot,
+                     std::string /* robot_auth_code */,
+                     std::string /* robot_email */,
+                     std::string /* user_email */,
+                     bool /* connect_new_printers */,
+                     std::vector<std::string> /* printer_blacklist */)
+
+// Tell the service process to disable the cloud proxy.
+IPC_MESSAGE_CONTROL0(ServiceMsg_DisableCloudPrintProxy)
+
+// Requests a message back on the current status of the cloud print proxy
+// (whether it is enabled, the email address and the proxy id).
+IPC_MESSAGE_CONTROL0(ServiceMsg_GetCloudPrintProxyInfo)
+
+// Tell the service process to shutdown.
+IPC_MESSAGE_CONTROL0(ServiceMsg_Shutdown)
+
+// Tell the service process that an update is available.
+IPC_MESSAGE_CONTROL0(ServiceMsg_UpdateAvailable)
+
+//-----------------------------------------------------------------------------
+// Service process host messages:
+// These are messages from the service process to the browser.
+// Sent when the cloud print proxy has an authentication error.
+IPC_MESSAGE_CONTROL0(ServiceHostMsg_CloudPrintProxy_AuthError)
+
+// Sent as a response to a request for cloud print proxy info
+IPC_MESSAGE_CONTROL1(ServiceHostMsg_CloudPrintProxy_Info,
+                     cloud_print::CloudPrintProxyInfo /* proxy info */)
diff --git a/chrome/common/service_process_util.cc b/chrome/common/service_process_util.cc
new file mode 100644
index 0000000..7161ca4
--- /dev/null
+++ b/chrome/common/service_process_util.cc
@@ -0,0 +1,268 @@
+// Copyright (c) 2012 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.
+
+#include <algorithm>
+
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/path_service.h"
+#include "base/process_util.h"
+#include "base/sha1.h"
+#include "base/string16.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "base/version.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/chrome_version_info.h"
+#include "chrome/common/service_process_util.h"
+#include "content/public/common/content_paths.h"
+
+#if !defined(OS_MACOSX)
+
+namespace {
+
+// This should be more than enough to hold a version string assuming each part
+// of the version string is an int64.
+const uint32 kMaxVersionStringLength = 256;
+
+// The structure that gets written to shared memory.
+struct ServiceProcessSharedData {
+  char service_process_version[kMaxVersionStringLength];
+  base::ProcessId service_process_pid;
+};
+
+// Gets the name of the shared memory used by the service process to write its
+// version. The name is not versioned.
+std::string GetServiceProcessSharedMemName() {
+  return GetServiceProcessScopedName("_service_shmem");
+}
+
+enum ServiceProcessRunningState {
+  SERVICE_NOT_RUNNING,
+  SERVICE_OLDER_VERSION_RUNNING,
+  SERVICE_SAME_VERSION_RUNNING,
+  SERVICE_NEWER_VERSION_RUNNING,
+};
+
+ServiceProcessRunningState GetServiceProcessRunningState(
+    std::string* service_version_out, base::ProcessId* pid_out) {
+  std::string version;
+  if (!GetServiceProcessData(&version, pid_out))
+    return SERVICE_NOT_RUNNING;
+
+#if defined(OS_POSIX)
+  // We only need to check for service running on POSIX because Windows cleans
+  // up shared memory files when an app crashes, so there isn't a chance of
+  // us reading bogus data from shared memory for an app that has died.
+  if (!CheckServiceProcessReady()) {
+    return SERVICE_NOT_RUNNING;
+  }
+#endif  // defined(OS_POSIX)
+
+  // At this time we have a version string. Set the out param if it exists.
+  if (service_version_out)
+    *service_version_out = version;
+
+  Version service_version(version);
+  // If the version string is invalid, treat it like an older version.
+  if (!service_version.IsValid())
+    return SERVICE_OLDER_VERSION_RUNNING;
+
+  // Get the version of the currently *running* instance of Chrome.
+  chrome::VersionInfo version_info;
+  if (!version_info.is_valid()) {
+    NOTREACHED() << "Failed to get current file version";
+    // Our own version is invalid. This is an error case. Pretend that we
+    // are out of date.
+    return SERVICE_NEWER_VERSION_RUNNING;
+  }
+  Version running_version(version_info.Version());
+  if (!running_version.IsValid()) {
+    NOTREACHED() << "Failed to parse version info";
+    // Our own version is invalid. This is an error case. Pretend that we
+    // are out of date.
+    return SERVICE_NEWER_VERSION_RUNNING;
+  }
+
+  if (running_version.CompareTo(service_version) > 0) {
+    return SERVICE_OLDER_VERSION_RUNNING;
+  } else if (service_version.CompareTo(running_version) > 0) {
+    return SERVICE_NEWER_VERSION_RUNNING;
+  }
+  return SERVICE_SAME_VERSION_RUNNING;
+}
+
+}  // namespace
+
+// Return a name that is scoped to this instance of the service process. We
+// use the hash of the user-data-dir as a scoping prefix. We can't use
+// the user-data-dir itself as we have limits on the size of the lock names.
+std::string GetServiceProcessScopedName(const std::string& append_str) {
+  FilePath user_data_dir;
+  PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
+#if defined(OS_WIN)
+  std::string user_data_dir_path = WideToUTF8(user_data_dir.value());
+#elif defined(OS_POSIX)
+  std::string user_data_dir_path = user_data_dir.value();
+#endif  // defined(OS_WIN)
+  std::string hash = base::SHA1HashString(user_data_dir_path);
+  std::string hex_hash = base::HexEncode(hash.c_str(), hash.length());
+  return hex_hash + "." + append_str;
+}
+
+// Return a name that is scoped to this instance of the service process. We
+// use the user-data-dir and the version as a scoping prefix.
+std::string GetServiceProcessScopedVersionedName(
+    const std::string& append_str) {
+  std::string versioned_str;
+  chrome::VersionInfo version_info;
+  DCHECK(version_info.is_valid());
+  versioned_str.append(version_info.Version());
+  versioned_str.append(append_str);
+  return GetServiceProcessScopedName(versioned_str);
+}
+
+// Reads the named shared memory to get the shared data. Returns false if no
+// matching shared memory was found.
+bool GetServiceProcessData(std::string* version, base::ProcessId* pid) {
+  scoped_ptr<base::SharedMemory> shared_mem_service_data;
+  shared_mem_service_data.reset(new base::SharedMemory());
+  ServiceProcessSharedData* service_data = NULL;
+  if (shared_mem_service_data.get() &&
+      shared_mem_service_data->Open(GetServiceProcessSharedMemName(), true) &&
+      shared_mem_service_data->Map(sizeof(ServiceProcessSharedData))) {
+    service_data = reinterpret_cast<ServiceProcessSharedData*>(
+        shared_mem_service_data->memory());
+    // Make sure the version in shared memory is null-terminated. If it is not,
+    // treat it as invalid.
+    if (version && memchr(service_data->service_process_version, '\0',
+                          sizeof(service_data->service_process_version)))
+      *version = service_data->service_process_version;
+    if (pid)
+      *pid = service_data->service_process_pid;
+    return true;
+  }
+  return false;
+}
+
+#endif  // !OS_MACOSX
+
+ServiceProcessState::ServiceProcessState() : state_(NULL) {
+  CreateAutoRunCommandLine();
+  CreateState();
+}
+
+ServiceProcessState::~ServiceProcessState() {
+#if !defined(OS_MACOSX)
+  if (shared_mem_service_data_.get()) {
+    shared_mem_service_data_->Delete(GetServiceProcessSharedMemName());
+  }
+#endif  // !OS_MACOSX
+  TearDownState();
+}
+
+void ServiceProcessState::SignalStopped() {
+  TearDownState();
+  shared_mem_service_data_.reset();
+}
+
+#if !defined(OS_MACOSX)
+bool ServiceProcessState::Initialize() {
+  if (!TakeSingletonLock()) {
+    return false;
+  }
+  // Now that we have the singleton, take care of killing an older version, if
+  // it exists.
+  if (!HandleOtherVersion())
+    return false;
+
+  // Write the version we are using to shared memory. This can be used by a
+  // newer service to signal us to exit.
+  return CreateSharedData();
+}
+
+bool ServiceProcessState::HandleOtherVersion() {
+  std::string running_version;
+  base::ProcessId process_id = 0;
+  ServiceProcessRunningState state =
+      GetServiceProcessRunningState(&running_version, &process_id);
+  switch (state) {
+    case SERVICE_SAME_VERSION_RUNNING:
+    case SERVICE_NEWER_VERSION_RUNNING:
+      return false;
+    case SERVICE_OLDER_VERSION_RUNNING:
+      // If an older version is running, kill it.
+      ForceServiceProcessShutdown(running_version, process_id);
+      break;
+    case SERVICE_NOT_RUNNING:
+      break;
+  }
+  return true;
+}
+
+bool ServiceProcessState::CreateSharedData() {
+  chrome::VersionInfo version_info;
+  if (!version_info.is_valid()) {
+    NOTREACHED() << "Failed to get current file version";
+    return false;
+  }
+  if (version_info.Version().length() >= kMaxVersionStringLength) {
+    NOTREACHED() << "Version string length is << " <<
+        version_info.Version().length() << "which is longer than" <<
+        kMaxVersionStringLength;
+    return false;
+  }
+
+  scoped_ptr<base::SharedMemory> shared_mem_service_data(
+      new base::SharedMemory());
+  if (!shared_mem_service_data.get())
+    return false;
+
+  uint32 alloc_size = sizeof(ServiceProcessSharedData);
+  if (!shared_mem_service_data->CreateNamed(GetServiceProcessSharedMemName(),
+                                            true, alloc_size))
+    return false;
+
+  if (!shared_mem_service_data->Map(alloc_size))
+    return false;
+
+  memset(shared_mem_service_data->memory(), 0, alloc_size);
+  ServiceProcessSharedData* shared_data =
+      reinterpret_cast<ServiceProcessSharedData*>(
+          shared_mem_service_data->memory());
+  memcpy(shared_data->service_process_version, version_info.Version().c_str(),
+         version_info.Version().length());
+  shared_data->service_process_pid = base::GetCurrentProcId();
+  shared_mem_service_data_.reset(shared_mem_service_data.release());
+  return true;
+}
+
+IPC::ChannelHandle ServiceProcessState::GetServiceProcessChannel() {
+  return ::GetServiceProcessChannel();
+}
+
+#endif  // !OS_MACOSX
+
+void ServiceProcessState::CreateAutoRunCommandLine() {
+  FilePath exe_path;
+  PathService::Get(content::CHILD_PROCESS_EXE, &exe_path);
+  DCHECK(!exe_path.empty()) << "Unable to get service process binary name.";
+  autorun_command_line_.reset(new CommandLine(exe_path));
+  autorun_command_line_->AppendSwitchASCII(switches::kProcessType,
+                                           switches::kServiceProcess);
+
+  // The user data directory is the only other flag we currently want to
+  // possibly store.
+  const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
+  FilePath user_data_dir =
+    browser_command_line.GetSwitchValuePath(switches::kUserDataDir);
+  if (!user_data_dir.empty())
+    autorun_command_line_->AppendSwitchPath(switches::kUserDataDir,
+                                            user_data_dir);
+}
diff --git a/chrome/common/service_process_util.h b/chrome/common/service_process_util.h
new file mode 100644
index 0000000..9d044d4
--- /dev/null
+++ b/chrome/common/service_process_util.h
@@ -0,0 +1,144 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_SERVICE_PROCESS_UTIL_H_
+#define CHROME_COMMON_SERVICE_PROCESS_UTIL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/process.h"
+#include "base/shared_memory.h"
+#include "ipc/ipc_channel_handle.h"
+
+class CommandLine;
+class MultiProcessLock;
+
+#if defined(OS_MACOSX)
+#ifdef __OBJC__
+@class NSString;
+#else
+class NSString;
+#endif
+#endif
+
+namespace base {
+class MessageLoopProxy;
+}
+
+// Return the IPC channel to connect to the service process.
+IPC::ChannelHandle GetServiceProcessChannel();
+
+#if !defined(OS_MACOSX)
+// Return a name that is scoped to this instance of the service process. We
+// use the user-data-dir as a scoping prefix.
+std::string GetServiceProcessScopedName(const std::string& append_str);
+
+// Return a name that is scoped to this instance of the service process. We
+// use the user-data-dir and the version as a scoping prefix.
+std::string GetServiceProcessScopedVersionedName(const std::string& append_str);
+#endif  // OS_MACOSX
+
+#if defined(OS_MACOSX)
+// Return the name that is used to extract the socket path out of the
+// dictionary provided by launchd.
+NSString* GetServiceProcessLaunchDSocketEnvVar();
+#endif
+
+#if defined(OS_POSIX)
+// Attempts to take a lock named |name|. If |waiting| is true then this will
+// make multiple attempts to acquire the lock.
+// Caller is responsible for ownership of the MultiProcessLock.
+MultiProcessLock* TakeNamedLock(const std::string& name, bool waiting);
+#endif
+
+// The following methods are used in a process that acts as a client to the
+// service process (typically the browser process).
+// --------------------------------------------------------------------------
+// This method checks that if the service process is ready to receive
+// IPC commands.
+bool CheckServiceProcessReady();
+
+// Returns the process id and version of the currently running service process.
+// Note: DO NOT use this check whether the service process is ready because
+// a true return value only means that some process shared data was available,
+// and not that the process is ready to receive IPC commands, or even running.
+// This method is only exposed for testing.
+bool GetServiceProcessData(std::string* version, base::ProcessId* pid);
+// --------------------------------------------------------------------------
+
+// Forces a service process matching the specified version to shut down.
+bool ForceServiceProcessShutdown(const std::string& version,
+                                 base::ProcessId process_id);
+
+// This is a class that is used by the service process to signal events and
+// share data with external clients. This class lives in this file because the
+// internal data structures and mechanisms used by the utility methods above
+// and this class are shared.
+class ServiceProcessState {
+ public:
+  ServiceProcessState();
+  ~ServiceProcessState();
+
+  // Tries to become the sole service process for the current user data dir.
+  // Returns false if another service process is already running.
+  bool Initialize();
+
+  // Signal that the service process is ready.
+  // This method is called when the service process is running and initialized.
+  // |terminate_task| is invoked when we get a terminate request from another
+  // process (in the same thread that called SignalReady). It can be NULL.
+  // |message_loop_proxy| must be of type IO and is the loop that POSIX uses
+  // to monitor the service process.
+  bool SignalReady(
+      base::MessageLoopProxy* message_loop_proxy,
+      const base::Closure& terminate_task);
+
+  // Signal that the service process is stopped.
+  void SignalStopped();
+
+  // Register the service process to run on startup.
+  bool AddToAutoRun();
+
+  // Unregister the service process to run on startup.
+  bool RemoveFromAutoRun();
+
+  // Return the channel handle used for communicating with the service.
+  IPC::ChannelHandle GetServiceProcessChannel();
+
+ private:
+#if !defined(OS_MACOSX)
+  // Create the shared memory data for the service process.
+  bool CreateSharedData();
+
+  // If an older version of the service process running, it should be shutdown.
+  // Returns false if this process needs to exit.
+  bool HandleOtherVersion();
+
+  // Acquires a singleton lock for the service process. A return value of false
+  // means that a service process instance is already running.
+  bool TakeSingletonLock();
+#endif  // !OS_MACOSX
+
+  // Creates the platform specific state.
+  void CreateState();
+
+  // Tear down the platform specific state.
+  void TearDownState();
+
+  // Initializes the command-line that can be used to autorun the service
+  // process.
+  void CreateAutoRunCommandLine();
+
+  // An opaque object that maintains state. The actual definition of this is
+  // platform dependent.
+  struct StateData;
+  StateData* state_;
+  scoped_ptr<base::SharedMemory> shared_mem_service_data_;
+  scoped_ptr<CommandLine> autorun_command_line_;
+};
+
+#endif  // CHROME_COMMON_SERVICE_PROCESS_UTIL_H_
diff --git a/chrome/common/service_process_util_linux.cc b/chrome/common/service_process_util_linux.cc
new file mode 100644
index 0000000..0e912d0
--- /dev/null
+++ b/chrome/common/service_process_util_linux.cc
@@ -0,0 +1,90 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/service_process_util_posix.h"
+
+#include <signal.h>
+#include <unistd.h>
+
+#include "base/base_paths.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/threading/platform_thread.h"
+#include "chrome/common/auto_start_linux.h"
+#include "chrome/common/multi_process_lock.h"
+
+namespace {
+
+MultiProcessLock* TakeServiceInitializingLock(bool waiting) {
+  std::string lock_name =
+      GetServiceProcessScopedName("_service_initializing");
+  return TakeNamedLock(lock_name, waiting);
+}
+
+std::string GetBaseDesktopName() {
+#if defined(GOOGLE_CHROME_BUILD)
+  return "google-chrome-service.desktop";
+#else  // CHROMIUM_BUILD
+  return "chromium-service.desktop";
+#endif
+}
+}  // namespace
+
+MultiProcessLock* TakeServiceRunningLock(bool waiting) {
+  std::string lock_name =
+      GetServiceProcessScopedName("_service_running");
+  return TakeNamedLock(lock_name, waiting);
+}
+
+bool ForceServiceProcessShutdown(const std::string& version,
+                                 base::ProcessId process_id) {
+  if (kill(process_id, SIGTERM) < 0) {
+    DPLOG(ERROR) << "kill";
+    return false;
+  }
+  return true;
+}
+
+// Gets the name of the service process IPC channel.
+// Returns an absolute path as required.
+IPC::ChannelHandle GetServiceProcessChannel() {
+  FilePath temp_dir;
+  PathService::Get(base::DIR_TEMP, &temp_dir);
+  std::string pipe_name = GetServiceProcessScopedVersionedName("_service_ipc");
+  std::string pipe_path = temp_dir.Append(pipe_name).value();
+  return pipe_path;
+}
+
+
+
+bool CheckServiceProcessReady() {
+  scoped_ptr<MultiProcessLock> running_lock(TakeServiceRunningLock(false));
+  return running_lock.get() == NULL;
+}
+
+bool ServiceProcessState::TakeSingletonLock() {
+  state_->initializing_lock_.reset(TakeServiceInitializingLock(true));
+  return state_->initializing_lock_.get();
+}
+
+bool ServiceProcessState::AddToAutoRun() {
+  DCHECK(autorun_command_line_.get());
+#if defined(GOOGLE_CHROME_BUILD)
+  std::string app_name = "Google Chrome Service";
+#else  // CHROMIUM_BUILD
+  std::string app_name = "Chromium Service";
+#endif
+  return AutoStart::AddApplication(
+      GetServiceProcessScopedName(GetBaseDesktopName()),
+      app_name,
+      autorun_command_line_->GetCommandLineString(),
+      false);
+}
+
+bool ServiceProcessState::RemoveFromAutoRun() {
+  return AutoStart::Remove(
+      GetServiceProcessScopedName(GetBaseDesktopName()));
+}
diff --git a/chrome/common/service_process_util_mac.mm b/chrome/common/service_process_util_mac.mm
new file mode 100644
index 0000000..858beec
--- /dev/null
+++ b/chrome/common/service_process_util_mac.mm
@@ -0,0 +1,445 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/service_process_util_posix.h"
+
+#import <Foundation/Foundation.h>
+#include <launch.h>
+
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/mac/bundle_locations.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/memory/scoped_nsobject.h"
+#include "base/path_service.h"
+#include "base/process_util.h"
+#include "base/stringprintf.h"
+#include "base/string_util.h"
+#include "base/sys_string_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/version.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/chrome_version_info.h"
+#include "chrome/common/mac/launchd.h"
+
+using ::base::files::FilePathWatcher;
+
+namespace {
+
+#define kServiceProcessSessionType "Aqua"
+
+CFStringRef CopyServiceProcessLaunchDName() {
+  base::mac::ScopedNSAutoreleasePool pool;
+  NSBundle* bundle = base::mac::FrameworkBundle();
+  return CFStringCreateCopy(kCFAllocatorDefault,
+                            base::mac::NSToCFCast([bundle bundleIdentifier]));
+}
+
+NSString* GetServiceProcessLaunchDLabel() {
+  scoped_nsobject<NSString> name(
+      base::mac::CFToNSCast(CopyServiceProcessLaunchDName()));
+  NSString *label = [name stringByAppendingString:@".service_process"];
+  FilePath user_data_dir;
+  PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
+  std::string user_data_dir_path = user_data_dir.value();
+  NSString *ns_path = base::SysUTF8ToNSString(user_data_dir_path);
+  ns_path = [ns_path stringByReplacingOccurrencesOfString:@" "
+                                               withString:@"_"];
+  label = [label stringByAppendingString:ns_path];
+  return label;
+}
+
+NSString* GetServiceProcessLaunchDSocketKey() {
+  return @"ServiceProcessSocket";
+}
+
+bool GetParentFSRef(const FSRef& child, FSRef* parent) {
+  return FSGetCatalogInfo(&child, 0, NULL, NULL, NULL, parent) == noErr;
+}
+
+bool RemoveFromLaunchd() {
+  // We're killing a file.
+  base::ThreadRestrictions::AssertIOAllowed();
+  base::mac::ScopedCFTypeRef<CFStringRef> name(CopyServiceProcessLaunchDName());
+  return Launchd::GetInstance()->DeletePlist(Launchd::User,
+                                             Launchd::Agent,
+                                             name);
+}
+
+class ExecFilePathWatcherDelegate : public FilePathWatcher::Delegate {
+ public:
+  ExecFilePathWatcherDelegate() {}
+
+  bool Init(const FilePath& path);
+  virtual void OnFilePathChanged(const FilePath& path) OVERRIDE;
+
+ private:
+  virtual ~ExecFilePathWatcherDelegate() {}
+
+  FSRef executable_fsref_;
+};
+
+}  // namespace
+
+NSString* GetServiceProcessLaunchDSocketEnvVar() {
+  NSString *label = GetServiceProcessLaunchDLabel();
+  NSString *env_var = [label stringByReplacingOccurrencesOfString:@"."
+                                                       withString:@"_"];
+  env_var = [env_var stringByAppendingString:@"_SOCKET"];
+  env_var = [env_var uppercaseString];
+  return env_var;
+}
+
+// Gets the name of the service process IPC channel.
+IPC::ChannelHandle GetServiceProcessChannel() {
+  base::mac::ScopedNSAutoreleasePool pool;
+  std::string socket_path;
+  scoped_nsobject<NSDictionary> dictionary(
+      base::mac::CFToNSCast(Launchd::GetInstance()->CopyExports()));
+  NSString *ns_socket_path =
+      [dictionary objectForKey:GetServiceProcessLaunchDSocketEnvVar()];
+  if (ns_socket_path) {
+    socket_path = base::SysNSStringToUTF8(ns_socket_path);
+  }
+  return IPC::ChannelHandle(socket_path);
+}
+
+bool ForceServiceProcessShutdown(const std::string& /* version */,
+                                 base::ProcessId /* process_id */) {
+  base::mac::ScopedNSAutoreleasePool pool;
+  CFStringRef label = base::mac::NSToCFCast(GetServiceProcessLaunchDLabel());
+  CFErrorRef err = NULL;
+  bool ret = Launchd::GetInstance()->RemoveJob(label, &err);
+  if (!ret) {
+    DLOG(ERROR) << "ForceServiceProcessShutdown: " << err << " "
+                << base::SysCFStringRefToUTF8(label);
+    CFRelease(err);
+  }
+  return ret;
+}
+
+bool GetServiceProcessData(std::string* version, base::ProcessId* pid) {
+  base::mac::ScopedNSAutoreleasePool pool;
+  CFStringRef label = base::mac::NSToCFCast(GetServiceProcessLaunchDLabel());
+  scoped_nsobject<NSDictionary> launchd_conf(base::mac::CFToNSCast(
+      Launchd::GetInstance()->CopyJobDictionary(label)));
+  if (!launchd_conf.get()) {
+    return false;
+  }
+  // Anything past here will return true in that there does appear
+  // to be a service process of some sort registered with launchd.
+  if (version) {
+    *version = "0";
+    NSString *exe_path = [launchd_conf objectForKey:@ LAUNCH_JOBKEY_PROGRAM];
+    if (exe_path) {
+      NSString *bundle_path = [[[exe_path stringByDeletingLastPathComponent]
+                                stringByDeletingLastPathComponent]
+                               stringByDeletingLastPathComponent];
+      NSBundle *bundle = [NSBundle bundleWithPath:bundle_path];
+      if (bundle) {
+        NSString *ns_version =
+            [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
+        if (ns_version) {
+          *version = base::SysNSStringToUTF8(ns_version);
+        } else {
+          DLOG(ERROR) << "Unable to get version at: "
+                      << reinterpret_cast<CFStringRef>(bundle_path);
+        }
+      } else {
+        // The bundle has been deleted out from underneath the registered
+        // job.
+        DLOG(ERROR) << "Unable to get bundle at: "
+                    << reinterpret_cast<CFStringRef>(bundle_path);
+      }
+    } else {
+      DLOG(ERROR) << "Unable to get executable path for service process";
+    }
+  }
+  if (pid) {
+    *pid = -1;
+    NSNumber* ns_pid = [launchd_conf objectForKey:@ LAUNCH_JOBKEY_PID];
+    if (ns_pid) {
+     *pid = [ns_pid intValue];
+    }
+  }
+  return true;
+}
+
+bool ServiceProcessState::Initialize() {
+  CFErrorRef err = NULL;
+  CFDictionaryRef dict =
+      Launchd::GetInstance()->CopyDictionaryByCheckingIn(&err);
+  if (!dict) {
+    DLOG(ERROR) << "ServiceProcess must be launched by launchd. "
+                << "CopyLaunchdDictionaryByCheckingIn: " << err;
+    CFRelease(err);
+    return false;
+  }
+  state_->launchd_conf_.reset(dict);
+  return true;
+}
+
+IPC::ChannelHandle ServiceProcessState::GetServiceProcessChannel() {
+  DCHECK(state_);
+  NSDictionary *ns_launchd_conf = base::mac::CFToNSCast(state_->launchd_conf_);
+  NSDictionary* socket_dict =
+      [ns_launchd_conf objectForKey:@ LAUNCH_JOBKEY_SOCKETS];
+  NSArray* sockets =
+      [socket_dict objectForKey:GetServiceProcessLaunchDSocketKey()];
+  DCHECK_EQ([sockets count], 1U);
+  int socket = [[sockets objectAtIndex:0] intValue];
+  base::FileDescriptor fd(socket, false);
+  return IPC::ChannelHandle(std::string(), fd);
+}
+
+bool CheckServiceProcessReady() {
+  std::string version;
+  pid_t pid;
+  if (!GetServiceProcessData(&version, &pid)) {
+    return false;
+  }
+  Version service_version(version);
+  bool ready = true;
+  if (!service_version.IsValid()) {
+    ready = false;
+  } else {
+    chrome::VersionInfo version_info;
+    if (!version_info.is_valid()) {
+      // Our own version is invalid. This is an error case. Pretend that we
+      // are out of date.
+      NOTREACHED();
+      ready = true;
+    }
+    else {
+      Version running_version(version_info.Version());
+      if (!running_version.IsValid()) {
+        // Our own version is invalid. This is an error case. Pretend that we
+        // are out of date.
+        NOTREACHED();
+        ready = true;
+      } else if (running_version.CompareTo(service_version) > 0) {
+        ready = false;
+      } else {
+        ready = true;
+      }
+    }
+  }
+  if (!ready) {
+    ForceServiceProcessShutdown(version, pid);
+  }
+  return ready;
+}
+
+CFDictionaryRef CreateServiceProcessLaunchdPlist(CommandLine* cmd_line,
+                                                 bool for_auto_launch) {
+  base::mac::ScopedNSAutoreleasePool pool;
+
+  NSString *program =
+      base::SysUTF8ToNSString(cmd_line->GetProgram().value());
+
+  std::vector<std::string> args = cmd_line->argv();
+  NSMutableArray *ns_args = [NSMutableArray arrayWithCapacity:args.size()];
+
+  for (std::vector<std::string>::iterator iter = args.begin();
+       iter < args.end();
+       ++iter) {
+    [ns_args addObject:base::SysUTF8ToNSString(*iter)];
+  }
+
+  NSDictionary *socket =
+      [NSDictionary dictionaryWithObject:GetServiceProcessLaunchDSocketEnvVar()
+                                  forKey:@ LAUNCH_JOBSOCKETKEY_SECUREWITHKEY];
+  NSDictionary *sockets =
+      [NSDictionary dictionaryWithObject:socket
+                                  forKey:GetServiceProcessLaunchDSocketKey()];
+
+  // See the man page for launchd.plist.
+  NSMutableDictionary *launchd_plist =
+      [[NSMutableDictionary alloc] initWithObjectsAndKeys:
+        GetServiceProcessLaunchDLabel(), @ LAUNCH_JOBKEY_LABEL,
+        program, @ LAUNCH_JOBKEY_PROGRAM,
+        ns_args, @ LAUNCH_JOBKEY_PROGRAMARGUMENTS,
+        sockets, @ LAUNCH_JOBKEY_SOCKETS,
+        nil];
+
+  if (for_auto_launch) {
+    // We want the service process to be able to exit if there are no services
+    // enabled. With a value of NO in the SuccessfulExit key, launchd will
+    // relaunch the service automatically in any other case than exiting
+    // cleanly with a 0 return code.
+    NSDictionary *keep_alive =
+      [NSDictionary
+        dictionaryWithObject:[NSNumber numberWithBool:NO]
+                      forKey:@ LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT];
+    NSDictionary *auto_launchd_plist =
+      [[NSDictionary alloc] initWithObjectsAndKeys:
+        [NSNumber numberWithBool:YES], @ LAUNCH_JOBKEY_RUNATLOAD,
+        keep_alive, @ LAUNCH_JOBKEY_KEEPALIVE,
+        @ kServiceProcessSessionType, @ LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE,
+        nil];
+    [launchd_plist addEntriesFromDictionary:auto_launchd_plist];
+  }
+  return reinterpret_cast<CFDictionaryRef>(launchd_plist);
+}
+
+// Writes the launchd property list into the user's LaunchAgents directory,
+// creating that directory if needed. This will cause the service process to be
+// auto launched on the next user login.
+bool ServiceProcessState::AddToAutoRun() {
+  // We're creating directories and writing a file.
+  base::ThreadRestrictions::AssertIOAllowed();
+  DCHECK(autorun_command_line_.get());
+  base::mac::ScopedCFTypeRef<CFStringRef> name(CopyServiceProcessLaunchDName());
+  base::mac::ScopedCFTypeRef<CFDictionaryRef> plist(
+      CreateServiceProcessLaunchdPlist(autorun_command_line_.get(), true));
+  return Launchd::GetInstance()->WritePlistToFile(Launchd::User,
+                                                  Launchd::Agent,
+                                                  name,
+                                                  plist);
+}
+
+bool ServiceProcessState::RemoveFromAutoRun() {
+  return RemoveFromLaunchd();
+}
+
+bool ServiceProcessState::StateData::WatchExecutable() {
+  base::mac::ScopedNSAutoreleasePool pool;
+  NSDictionary* ns_launchd_conf = base::mac::CFToNSCast(launchd_conf_);
+  NSString* exe_path = [ns_launchd_conf objectForKey:@ LAUNCH_JOBKEY_PROGRAM];
+  if (!exe_path) {
+    DLOG(ERROR) << "No " LAUNCH_JOBKEY_PROGRAM;
+    return false;
+  }
+
+  FilePath executable_path = FilePath([exe_path fileSystemRepresentation]);
+  scoped_refptr<ExecFilePathWatcherDelegate> delegate(
+      new ExecFilePathWatcherDelegate);
+  if (!delegate->Init(executable_path)) {
+    DLOG(ERROR) << "executable_watcher_.Init " << executable_path.value();
+    return false;
+  }
+  if (!executable_watcher_.Watch(executable_path, delegate)) {
+    DLOG(ERROR) << "executable_watcher_.watch " << executable_path.value();
+    return false;
+  }
+  return true;
+}
+
+bool ExecFilePathWatcherDelegate::Init(const FilePath& path) {
+  return base::mac::FSRefFromPath(path.value(), &executable_fsref_);
+}
+
+void ExecFilePathWatcherDelegate::OnFilePathChanged(const FilePath& path) {
+  base::mac::ScopedNSAutoreleasePool pool;
+  bool needs_shutdown = false;
+  bool needs_restart = false;
+  bool good_bundle = false;
+
+  FSRef macos_fsref;
+  if (GetParentFSRef(executable_fsref_, &macos_fsref)) {
+    FSRef contents_fsref;
+    if (GetParentFSRef(macos_fsref, &contents_fsref)) {
+      FSRef bundle_fsref;
+      if (GetParentFSRef(contents_fsref, &bundle_fsref)) {
+        base::mac::ScopedCFTypeRef<CFURLRef> bundle_url(
+            CFURLCreateFromFSRef(kCFAllocatorDefault, &bundle_fsref));
+        if (bundle_url.get()) {
+          base::mac::ScopedCFTypeRef<CFBundleRef> bundle(
+              CFBundleCreate(kCFAllocatorDefault, bundle_url));
+          // Check to see if the bundle still has a minimal structure.
+          good_bundle = CFBundleGetIdentifier(bundle) != NULL;
+        }
+      }
+    }
+  }
+  if (!good_bundle) {
+    needs_shutdown = true;
+  } else {
+    Boolean in_trash;
+    OSErr err = FSDetermineIfRefIsEnclosedByFolder(kOnAppropriateDisk,
+                                                   kTrashFolderType,
+                                                   &executable_fsref_,
+                                                   &in_trash);
+    if (err == noErr && in_trash) {
+      needs_shutdown = true;
+    } else {
+      bool was_moved = true;
+      FSRef path_ref;
+      if (base::mac::FSRefFromPath(path.value(), &path_ref)) {
+        if (FSCompareFSRefs(&path_ref, &executable_fsref_) == noErr) {
+          was_moved = false;
+        }
+      }
+      if (was_moved) {
+        needs_restart = true;
+      }
+    }
+  }
+  if (needs_shutdown || needs_restart) {
+    // First deal with the plist.
+    base::mac::ScopedCFTypeRef<CFStringRef> name(
+        CopyServiceProcessLaunchDName());
+    if (needs_restart) {
+      base::mac::ScopedCFTypeRef<CFMutableDictionaryRef> plist(
+         Launchd::GetInstance()->CreatePlistFromFile(Launchd::User,
+                                                     Launchd::Agent,
+                                                     name));
+      if (plist.get()) {
+        NSMutableDictionary* ns_plist = base::mac::CFToNSCast(plist);
+        std::string new_path = base::mac::PathFromFSRef(executable_fsref_);
+        NSString* ns_new_path = base::SysUTF8ToNSString(new_path);
+        [ns_plist setObject:ns_new_path forKey:@ LAUNCH_JOBKEY_PROGRAM];
+        scoped_nsobject<NSMutableArray> args(
+            [[ns_plist objectForKey:@ LAUNCH_JOBKEY_PROGRAMARGUMENTS]
+             mutableCopy]);
+        [args replaceObjectAtIndex:0 withObject:ns_new_path];
+        [ns_plist setObject:args forKey:@ LAUNCH_JOBKEY_PROGRAMARGUMENTS];
+        if (!Launchd::GetInstance()->WritePlistToFile(Launchd::User,
+                                                      Launchd::Agent,
+                                                      name,
+                                                      plist)) {
+          DLOG(ERROR) << "Unable to rewrite plist.";
+          needs_shutdown = true;
+        }
+      } else {
+        DLOG(ERROR) << "Unable to read plist.";
+        needs_shutdown = true;
+      }
+    }
+    if (needs_shutdown) {
+      if (!RemoveFromLaunchd()) {
+        DLOG(ERROR) << "Unable to RemoveFromLaunchd.";
+      }
+    }
+
+    // Then deal with the process.
+    CFStringRef session_type = CFSTR(kServiceProcessSessionType);
+    if (needs_restart) {
+      if (!Launchd::GetInstance()->RestartJob(Launchd::User,
+                                              Launchd::Agent,
+                                              name,
+                                              session_type)) {
+        DLOG(ERROR) << "RestartLaunchdJob";
+        needs_shutdown = true;
+      }
+    }
+    if (needs_shutdown) {
+      CFStringRef label =
+          base::mac::NSToCFCast(GetServiceProcessLaunchDLabel());
+      CFErrorRef err = NULL;
+      if (!Launchd::GetInstance()->RemoveJob(label, &err)) {
+        base::mac::ScopedCFTypeRef<CFErrorRef> scoped_err(err);
+        DLOG(ERROR) << "RemoveJob " << err;
+        // Exiting with zero, so launchd doesn't restart the process.
+        exit(0);
+      }
+    }
+  }
+}
diff --git a/chrome/common/service_process_util_posix.cc b/chrome/common/service_process_util_posix.cc
new file mode 100644
index 0000000..fa793a6
--- /dev/null
+++ b/chrome/common/service_process_util_posix.cc
@@ -0,0 +1,195 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/service_process_util_posix.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/eintr_wrapper.h"
+#include "base/message_loop_proxy.h"
+#include "base/synchronization/waitable_event.h"
+#include "chrome/common/multi_process_lock.h"
+
+namespace {
+int g_signal_socket = -1;
+}
+
+// Attempts to take a lock named |name|. If |waiting| is true then this will
+// make multiple attempts to acquire the lock.
+// Caller is responsible for ownership of the MultiProcessLock.
+MultiProcessLock* TakeNamedLock(const std::string& name, bool waiting) {
+  scoped_ptr<MultiProcessLock> lock(MultiProcessLock::Create(name));
+  if (lock == NULL) return NULL;
+  bool got_lock = false;
+  for (int i = 0; i < 10; ++i) {
+    if (lock->TryLock()) {
+      got_lock = true;
+      break;
+    }
+    if (!waiting) break;
+    base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100 * i));
+  }
+  if (!got_lock) {
+    lock.reset();
+  }
+  return lock.release();
+}
+
+ServiceProcessTerminateMonitor::ServiceProcessTerminateMonitor(
+    const base::Closure& terminate_task)
+    : terminate_task_(terminate_task) {
+}
+
+ServiceProcessTerminateMonitor::~ServiceProcessTerminateMonitor() {
+}
+
+void ServiceProcessTerminateMonitor::OnFileCanReadWithoutBlocking(int fd) {
+  if (!terminate_task_.is_null()) {
+    int buffer;
+    int length = read(fd, &buffer, sizeof(buffer));
+    if ((length == sizeof(buffer)) && (buffer == kTerminateMessage)) {
+      terminate_task_.Run();
+      terminate_task_.Reset();
+    } else if (length > 0) {
+      DLOG(ERROR) << "Unexpected read: " << buffer;
+    } else if (length == 0) {
+      DLOG(ERROR) << "Unexpected fd close";
+    } else if (length < 0) {
+      DPLOG(ERROR) << "read";
+    }
+  }
+}
+
+void ServiceProcessTerminateMonitor::OnFileCanWriteWithoutBlocking(int fd) {
+  NOTIMPLEMENTED();
+}
+
+// "Forced" Shutdowns on POSIX are done via signals. The magic signal for
+// a shutdown is SIGTERM. "write" is a signal safe function. PLOG(ERROR) is
+// not, but we don't ever expect it to be called.
+static void SigTermHandler(int sig, siginfo_t* info, void* uap) {
+  // TODO(dmaclach): add security here to make sure that we are being shut
+  //                 down by an appropriate process.
+  int message = ServiceProcessTerminateMonitor::kTerminateMessage;
+  if (write(g_signal_socket, &message, sizeof(message)) < 0) {
+    DPLOG(ERROR) << "write";
+  }
+}
+
+ServiceProcessState::StateData::StateData() : set_action_(false) {
+  memset(sockets_, -1, sizeof(sockets_));
+  memset(&old_action_, 0, sizeof(old_action_));
+}
+
+void ServiceProcessState::StateData::SignalReady(base::WaitableEvent* signal,
+                                                 bool* success) {
+  DCHECK_EQ(g_signal_socket, -1);
+  DCHECK(!signal->IsSignaled());
+   *success = MessageLoopForIO::current()->WatchFileDescriptor(
+      sockets_[0], true, MessageLoopForIO::WATCH_READ,
+      &watcher_, terminate_monitor_.get());
+  if (!*success) {
+    DLOG(ERROR) << "WatchFileDescriptor";
+    signal->Signal();
+    return;
+  }
+  g_signal_socket = sockets_[1];
+
+  // Set up signal handler for SIGTERM.
+  struct sigaction action;
+  memset(&action, 0, sizeof(action));
+  action.sa_sigaction = SigTermHandler;
+  sigemptyset(&action.sa_mask);
+  action.sa_flags = SA_SIGINFO;
+  *success = sigaction(SIGTERM, &action, &old_action_) == 0;
+  if (!*success) {
+    DPLOG(ERROR) << "sigaction";
+    signal->Signal();
+    return;
+  }
+
+  // If the old_action is not default, somebody else has installed a
+  // a competing handler. Our handler is going to override it so it
+  // won't be called. If this occurs it needs to be fixed.
+  DCHECK_EQ(old_action_.sa_handler, SIG_DFL);
+  set_action_ = true;
+
+#if defined(OS_MACOSX)
+  *success = WatchExecutable();
+  if (!*success) {
+    DLOG(ERROR) << "WatchExecutable";
+    signal->Signal();
+    return;
+  }
+#elif defined(OS_POSIX)
+  initializing_lock_.reset();
+#endif  // OS_POSIX
+  signal->Signal();
+}
+
+ServiceProcessState::StateData::~StateData() {
+  if (sockets_[0] != -1) {
+    if (HANDLE_EINTR(close(sockets_[0]))) {
+      DPLOG(ERROR) << "close";
+    }
+  }
+  if (sockets_[1] != -1) {
+    if (HANDLE_EINTR(close(sockets_[1]))) {
+      DPLOG(ERROR) << "close";
+    }
+  }
+  if (set_action_) {
+    if (sigaction(SIGTERM, &old_action_, NULL) < 0) {
+      DPLOG(ERROR) << "sigaction";
+    }
+  }
+  g_signal_socket = -1;
+}
+
+void ServiceProcessState::CreateState() {
+  DCHECK(!state_);
+  state_ = new StateData;
+
+  // Explicitly adding a reference here (and removing it in TearDownState)
+  // because StateData is refcounted on Mac and Linux so that methods can
+  // be called on other threads.
+  // It is not refcounted on Windows at this time.
+  state_->AddRef();
+}
+
+bool ServiceProcessState::SignalReady(
+    base::MessageLoopProxy* message_loop_proxy,
+    const base::Closure& terminate_task) {
+  DCHECK(state_);
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+  state_->running_lock_.reset(TakeServiceRunningLock(true));
+  if (state_->running_lock_.get() == NULL) {
+    return false;
+  }
+#endif
+  state_->terminate_monitor_.reset(
+      new ServiceProcessTerminateMonitor(terminate_task));
+  if (pipe(state_->sockets_) < 0) {
+    DPLOG(ERROR) << "pipe";
+    return false;
+  }
+  base::WaitableEvent signal_ready(true, false);
+  bool success = false;
+
+  message_loop_proxy->PostTask(FROM_HERE,
+      base::Bind(&ServiceProcessState::StateData::SignalReady,
+                 state_,
+                 &signal_ready,
+                 &success));
+  signal_ready.Wait();
+  return success;
+}
+
+void ServiceProcessState::TearDownState() {
+  if (state_) {
+    state_->Release();
+    state_ = NULL;
+  }
+}
diff --git a/chrome/common/service_process_util_posix.h b/chrome/common/service_process_util_posix.h
new file mode 100644
index 0000000..d247130
--- /dev/null
+++ b/chrome/common/service_process_util_posix.h
@@ -0,0 +1,90 @@
+// 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.
+
+#ifndef CHROME_COMMON_SERVICE_PROCESS_UTIL_POSIX_H_
+#define CHROME_COMMON_SERVICE_PROCESS_UTIL_POSIX_H_
+
+#include "service_process_util.h"
+
+#include <signal.h>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+#include "chrome/common/multi_process_lock.h"
+MultiProcessLock* TakeServiceRunningLock(bool waiting);
+#endif
+
+#if defined(OS_MACOSX)
+#include "base/files/file_path_watcher.h"
+#include "base/mac/scoped_cftyperef.h"
+
+class CommandLine;
+CFDictionaryRef CreateServiceProcessLaunchdPlist(CommandLine* cmd_line,
+                                                 bool for_auto_launch);
+#endif  // OS_MACOSX
+
+namespace base {
+class WaitableEvent;
+}
+
+// Watches for |kTerminateMessage| to be written to the file descriptor it is
+// watching. When it reads |kTerminateMessage|, it performs |terminate_task_|.
+// Used here to monitor the socket listening to g_signal_socket.
+class ServiceProcessTerminateMonitor
+    : public MessageLoopForIO::Watcher {
+ public:
+
+  enum {
+    kTerminateMessage = 0xdecea5e
+  };
+
+  explicit ServiceProcessTerminateMonitor(const base::Closure& terminate_task);
+  virtual ~ServiceProcessTerminateMonitor();
+
+  // MessageLoopForIO::Watcher overrides
+  virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+  virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
+
+ private:
+  base::Closure terminate_task_;
+};
+
+struct ServiceProcessState::StateData
+    : public base::RefCountedThreadSafe<ServiceProcessState::StateData> {
+  StateData();
+
+  // WatchFileDescriptor needs to be set up by the thread that is going
+  // to be monitoring it.
+  void SignalReady(base::WaitableEvent* signal, bool* success);
+
+
+  // TODO(jhawkins): Either make this a class or rename these public member
+  // variables to remove the trailing underscore.
+
+#if defined(OS_MACOSX)
+  bool WatchExecutable();
+
+  base::mac::ScopedCFTypeRef<CFDictionaryRef> launchd_conf_;
+  base::files::FilePathWatcher executable_watcher_;
+#endif  // OS_MACOSX
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+  scoped_ptr<MultiProcessLock> initializing_lock_;
+  scoped_ptr<MultiProcessLock> running_lock_;
+#endif
+  scoped_ptr<ServiceProcessTerminateMonitor> terminate_monitor_;
+  MessageLoopForIO::FileDescriptorWatcher watcher_;
+  int sockets_[2];
+  struct sigaction old_action_;
+  bool set_action_;
+
+ protected:
+  friend class base::RefCountedThreadSafe<ServiceProcessState::StateData>;
+  virtual ~StateData();
+};
+
+#endif  // CHROME_COMMON_SERVICE_PROCESS_UTIL_POSIX_H_
diff --git a/chrome/common/service_process_util_unittest.cc b/chrome/common/service_process_util_unittest.cc
new file mode 100644
index 0000000..f68064a
--- /dev/null
+++ b/chrome/common/service_process_util_unittest.cc
@@ -0,0 +1,418 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/service_process_util.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/file_path.h"
+#include "base/process_util.h"
+
+#if !defined(OS_MACOSX)
+#include "base/at_exit.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string_util.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/chrome_version_info.h"
+#include "testing/multiprocess_func_list.h"
+
+#if defined(OS_WIN)
+#include "base/win/win_util.h"
+#endif
+
+#if defined(OS_POSIX)
+#include "chrome/common/auto_start_linux.h"
+#include <glib.h>
+#endif
+
+#if defined(USE_AURA)
+// This test fails http://crbug.com/84854, and is very flaky on CrOS and
+// somewhat flaky on other Linux.
+#define MAYBE_ForceShutdown DISABLED_ForceShutdown
+#else
+#if defined(OS_LINUX) || defined(OS_WIN)
+#define MAYBE_ForceShutdown DISABLED_ForceShutdown
+#else
+#define MAYBE_ForceShutdown ForceShutdown
+#endif
+#endif
+
+namespace {
+
+bool g_good_shutdown = false;
+
+void ShutdownTask(MessageLoop* loop) {
+  // Quit the main message loop.
+  ASSERT_FALSE(g_good_shutdown);
+  g_good_shutdown = true;
+  loop->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+}
+
+}  // namespace
+
+TEST(ServiceProcessUtilTest, ScopedVersionedName) {
+  std::string test_str = "test";
+  std::string scoped_name = GetServiceProcessScopedVersionedName(test_str);
+  chrome::VersionInfo version_info;
+  DCHECK(version_info.is_valid());
+  EXPECT_TRUE(EndsWith(scoped_name, test_str, true));
+  EXPECT_NE(std::string::npos, scoped_name.find(version_info.Version()));
+}
+
+class ServiceProcessStateTest : public base::MultiProcessTest {
+ public:
+  ServiceProcessStateTest();
+  ~ServiceProcessStateTest();
+  virtual void SetUp();
+  base::MessageLoopProxy* IOMessageLoopProxy() {
+    return io_thread_.message_loop_proxy();
+  }
+  void LaunchAndWait(const std::string& name);
+
+ private:
+  // This is used to release the ServiceProcessState singleton after each test.
+  base::ShadowingAtExitManager at_exit_manager_;
+  base::Thread io_thread_;
+};
+
+ServiceProcessStateTest::ServiceProcessStateTest()
+    : io_thread_("ServiceProcessStateTestThread") {
+}
+
+ServiceProcessStateTest::~ServiceProcessStateTest() {
+}
+
+void ServiceProcessStateTest::SetUp() {
+  base::Thread::Options options(MessageLoop::TYPE_IO, 0);
+  ASSERT_TRUE(io_thread_.StartWithOptions(options));
+}
+
+void ServiceProcessStateTest::LaunchAndWait(const std::string& name) {
+  base::ProcessHandle handle = SpawnChild(name, false);
+  ASSERT_TRUE(handle);
+  int exit_code = 0;
+  ASSERT_TRUE(base::WaitForExitCode(handle, &exit_code));
+  ASSERT_EQ(exit_code, 0);
+}
+
+TEST_F(ServiceProcessStateTest, Singleton) {
+  ServiceProcessState state;
+  ASSERT_TRUE(state.Initialize());
+  LaunchAndWait("ServiceProcessStateTestSingleton");
+}
+
+TEST_F(ServiceProcessStateTest, ReadyState) {
+  ASSERT_FALSE(CheckServiceProcessReady());
+  ServiceProcessState state;
+  ASSERT_TRUE(state.Initialize());
+  ASSERT_TRUE(state.SignalReady(IOMessageLoopProxy(), base::Closure()));
+  LaunchAndWait("ServiceProcessStateTestReadyTrue");
+  state.SignalStopped();
+  LaunchAndWait("ServiceProcessStateTestReadyFalse");
+}
+
+TEST_F(ServiceProcessStateTest, AutoRun) {
+  ServiceProcessState state;
+  ASSERT_TRUE(state.AddToAutoRun());
+  scoped_ptr<CommandLine> autorun_command_line;
+#if defined(OS_WIN)
+  std::string value_name = GetServiceProcessScopedName("_service_run");
+  string16 value;
+  EXPECT_TRUE(base::win::ReadCommandFromAutoRun(HKEY_CURRENT_USER,
+                                                UTF8ToWide(value_name),
+                                                &value));
+  autorun_command_line.reset(new CommandLine(CommandLine::FromString(value)));
+#elif defined(OS_POSIX) && !defined(OS_MACOSX)
+#if defined(GOOGLE_CHROME_BUILD)
+  std::string base_desktop_name = "google-chrome-service.desktop";
+#else  // CHROMIUM_BUILD
+  std::string base_desktop_name = "chromium-service.desktop";
+#endif
+  std::string exec_value;
+  EXPECT_TRUE(AutoStart::GetAutostartFileValue(
+      GetServiceProcessScopedName(base_desktop_name), "Exec", &exec_value));
+  GError *error = NULL;
+  gchar **argv = NULL;
+  gint argc = 0;
+  if (g_shell_parse_argv(exec_value.c_str(), &argc, &argv, &error)) {
+    autorun_command_line.reset(new CommandLine(argc, argv));
+    g_strfreev(argv);
+  } else {
+    ADD_FAILURE();
+    g_error_free(error);
+  }
+#endif  // defined(OS_WIN)
+  if (autorun_command_line.get()) {
+    EXPECT_EQ(autorun_command_line->GetSwitchValueASCII(switches::kProcessType),
+              std::string(switches::kServiceProcess));
+  }
+  ASSERT_TRUE(state.RemoveFromAutoRun());
+#if defined(OS_WIN)
+  EXPECT_FALSE(base::win::ReadCommandFromAutoRun(HKEY_CURRENT_USER,
+                                                 UTF8ToWide(value_name),
+                                                 &value));
+#elif defined(OS_POSIX) && !defined(OS_MACOSX)
+  EXPECT_FALSE(AutoStart::GetAutostartFileValue(
+      GetServiceProcessScopedName(base_desktop_name), "Exec", &exec_value));
+#endif  // defined(OS_WIN)
+}
+
+TEST_F(ServiceProcessStateTest, SharedMem) {
+  std::string version;
+  base::ProcessId pid;
+#if defined(OS_WIN)
+  // On Posix, named shared memory uses a file on disk. This file
+  // could be lying around from previous crashes which could cause
+  // GetServiceProcessPid to lie. On Windows, we use a named event so we
+  // don't have this issue. Until we have a more stable shared memory
+  // implementation on Posix, this check will only execute on Windows.
+  ASSERT_FALSE(GetServiceProcessData(&version, &pid));
+#endif  // defined(OS_WIN)
+  ServiceProcessState state;
+  ASSERT_TRUE(state.Initialize());
+  ASSERT_TRUE(GetServiceProcessData(&version, &pid));
+  ASSERT_EQ(base::GetCurrentProcId(), pid);
+}
+
+TEST_F(ServiceProcessStateTest, MAYBE_ForceShutdown) {
+  base::ProcessHandle handle = SpawnChild("ServiceProcessStateTestShutdown",
+                                          true);
+  ASSERT_TRUE(handle);
+  for (int i = 0; !CheckServiceProcessReady() && i < 10; ++i) {
+    base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+  }
+  ASSERT_TRUE(CheckServiceProcessReady());
+  std::string version;
+  base::ProcessId pid;
+  ASSERT_TRUE(GetServiceProcessData(&version, &pid));
+  ASSERT_TRUE(ForceServiceProcessShutdown(version, pid));
+  int exit_code = 0;
+  ASSERT_TRUE(base::WaitForExitCodeWithTimeout(handle,
+      &exit_code, TestTimeouts::action_max_timeout()));
+  base::CloseProcessHandle(handle);
+  ASSERT_EQ(exit_code, 0);
+}
+
+MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestSingleton) {
+  ServiceProcessState state;
+  EXPECT_FALSE(state.Initialize());
+  return 0;
+}
+
+MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyTrue) {
+  EXPECT_TRUE(CheckServiceProcessReady());
+  return 0;
+}
+
+MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestReadyFalse) {
+  EXPECT_FALSE(CheckServiceProcessReady());
+  return 0;
+}
+
+MULTIPROCESS_TEST_MAIN(ServiceProcessStateTestShutdown) {
+  MessageLoop message_loop;
+  message_loop.set_thread_name("ServiceProcessStateTestShutdownMainThread");
+  base::Thread io_thread_("ServiceProcessStateTestShutdownIOThread");
+  base::Thread::Options options(MessageLoop::TYPE_IO, 0);
+  EXPECT_TRUE(io_thread_.StartWithOptions(options));
+  ServiceProcessState state;
+  EXPECT_TRUE(state.Initialize());
+  EXPECT_TRUE(state.SignalReady(io_thread_.message_loop_proxy(),
+                                base::Bind(&ShutdownTask,
+                                           MessageLoop::current())));
+  message_loop.PostDelayedTask(FROM_HERE,
+                               MessageLoop::QuitClosure(),
+                               TestTimeouts::action_max_timeout());
+  EXPECT_FALSE(g_good_shutdown);
+  message_loop.Run();
+  EXPECT_TRUE(g_good_shutdown);
+  return 0;
+}
+
+#else  // !OS_MACOSX
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/mac/mac_util.h"
+#include "base/scoped_temp_dir.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread.h"
+#include "chrome/common/mac/launchd.h"
+#include "chrome/common/mac/mock_launchd.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class ServiceProcessStateFileManipulationTest : public ::testing::Test {
+ protected:
+  ServiceProcessStateFileManipulationTest()
+      : io_thread_("ServiceProcessStateFileManipulationTest_IO") {
+  }
+  virtual ~ServiceProcessStateFileManipulationTest() { }
+
+  virtual void SetUp() {
+    base::Thread::Options options;
+    options.message_loop_type = MessageLoop::TYPE_IO;
+    ASSERT_TRUE(io_thread_.StartWithOptions(options));
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    ASSERT_TRUE(MockLaunchd::MakeABundle(GetTempDirPath(),
+                                         "Test",
+                                         &bundle_path_,
+                                         &executable_path_));
+    mock_launchd_.reset(new MockLaunchd(executable_path_, &loop_,
+                                        false, false));
+    scoped_launchd_instance_.reset(
+        new Launchd::ScopedInstance(mock_launchd_.get()));
+    ASSERT_TRUE(service_process_state_.Initialize());
+    ASSERT_TRUE(service_process_state_.SignalReady(
+        io_thread_.message_loop_proxy(),
+        base::Closure()));
+    loop_.PostDelayedTask(FROM_HERE,
+                          MessageLoop::QuitClosure(),
+                          TestTimeouts::action_max_timeout());
+  }
+
+  const MockLaunchd* mock_launchd() const { return mock_launchd_.get(); }
+  const FilePath& executable_path() const { return executable_path_; }
+  const FilePath& bundle_path() const { return bundle_path_; }
+  const FilePath& GetTempDirPath() const { return temp_dir_.path(); }
+
+  base::MessageLoopProxy* GetIOMessageLoopProxy() {
+    return io_thread_.message_loop_proxy().get();
+  }
+  void Run() { loop_.Run(); }
+
+ private:
+  ScopedTempDir temp_dir_;
+  MessageLoopForUI loop_;
+  base::Thread io_thread_;
+  FilePath executable_path_, bundle_path_;
+  scoped_ptr<MockLaunchd> mock_launchd_;
+  scoped_ptr<Launchd::ScopedInstance> scoped_launchd_instance_;
+  ServiceProcessState service_process_state_;
+};
+
+void DeleteFunc(const FilePath& file) {
+  EXPECT_TRUE(file_util::Delete(file, true));
+}
+
+void MoveFunc(const FilePath& from, const FilePath& to) {
+  EXPECT_TRUE(file_util::Move(from, to));
+}
+
+void ChangeAttr(const FilePath& from, int mode) {
+  EXPECT_EQ(chmod(from.value().c_str(), mode), 0);
+}
+
+class ScopedAttributesRestorer {
+ public:
+  ScopedAttributesRestorer(const FilePath& path, int mode)
+      : path_(path), mode_(mode) {
+  }
+  ~ScopedAttributesRestorer() {
+    ChangeAttr(path_, mode_);
+  }
+ private:
+  FilePath path_;
+  int mode_;
+};
+
+void TrashFunc(const FilePath& src) {
+  FSRef path_ref;
+  FSRef new_path_ref;
+  EXPECT_TRUE(base::mac::FSRefFromPath(src.value(), &path_ref));
+  OSStatus status = FSMoveObjectToTrashSync(&path_ref,
+                                            &new_path_ref,
+                                            kFSFileOperationDefaultOptions);
+  EXPECT_EQ(status, noErr) << "FSMoveObjectToTrashSync " << status;
+}
+
+TEST_F(ServiceProcessStateFileManipulationTest, VerifyLaunchD) {
+  // There have been problems where launchd has gotten into a bad state, usually
+  // because something had deleted all the files in /tmp. launchd depends on
+  // a Unix Domain Socket that it creates at /tmp/launchd*/sock.
+  // The symptom of this problem is that the service process connect fails
+  // on Mac and "launch_msg(): Socket is not connected" appears.
+  // This test is designed to make sure that launchd is working.
+  // http://crbug/75518
+
+  CommandLine cl(FilePath("/bin/launchctl"));
+  cl.AppendArg("list");
+  cl.AppendArg("com.apple.launchctl.Aqua");
+
+  std::string output;
+  int exit_code = -1;
+  ASSERT_TRUE(base::GetAppOutputWithExitCode(cl, &output, &exit_code)
+              && exit_code == 0)
+      << " exit_code:" << exit_code << " " << output;
+}
+
+TEST_F(ServiceProcessStateFileManipulationTest, DeleteFile) {
+  GetIOMessageLoopProxy()->PostTask(
+      FROM_HERE,
+      base::Bind(&DeleteFunc, executable_path()));
+  Run();
+  ASSERT_TRUE(mock_launchd()->remove_called());
+  ASSERT_TRUE(mock_launchd()->delete_called());
+}
+
+TEST_F(ServiceProcessStateFileManipulationTest, DeleteBundle) {
+  GetIOMessageLoopProxy()->PostTask(
+      FROM_HERE,
+      base::Bind(&DeleteFunc, bundle_path()));
+  Run();
+  ASSERT_TRUE(mock_launchd()->remove_called());
+  ASSERT_TRUE(mock_launchd()->delete_called());
+}
+
+TEST_F(ServiceProcessStateFileManipulationTest, MoveBundle) {
+  FilePath new_loc = GetTempDirPath().AppendASCII("MoveBundle");
+  GetIOMessageLoopProxy()->PostTask(
+      FROM_HERE,
+      base::Bind(&MoveFunc, bundle_path(), new_loc));
+  Run();
+  ASSERT_TRUE(mock_launchd()->restart_called());
+  ASSERT_TRUE(mock_launchd()->write_called());
+}
+
+TEST_F(ServiceProcessStateFileManipulationTest, MoveFile) {
+  FilePath new_loc = GetTempDirPath().AppendASCII("MoveFile");
+  GetIOMessageLoopProxy()->PostTask(
+      FROM_HERE,
+      base::Bind(&MoveFunc, executable_path(), new_loc));
+  Run();
+  ASSERT_TRUE(mock_launchd()->remove_called());
+  ASSERT_TRUE(mock_launchd()->delete_called());
+}
+
+TEST_F(ServiceProcessStateFileManipulationTest, TrashBundle) {
+  FSRef bundle_ref;
+  ASSERT_TRUE(base::mac::FSRefFromPath(bundle_path().value(), &bundle_ref));
+  GetIOMessageLoopProxy()->PostTask(
+      FROM_HERE,
+      base::Bind(&TrashFunc, bundle_path()));
+  Run();
+  ASSERT_TRUE(mock_launchd()->remove_called());
+  ASSERT_TRUE(mock_launchd()->delete_called());
+  std::string path(base::mac::PathFromFSRef(bundle_ref));
+  FilePath file_path(path);
+  ASSERT_TRUE(file_util::Delete(file_path, true));
+}
+
+TEST_F(ServiceProcessStateFileManipulationTest, ChangeAttr) {
+  ScopedAttributesRestorer restorer(bundle_path(), 0777);
+  GetIOMessageLoopProxy()->PostTask(
+      FROM_HERE,
+      base::Bind(&ChangeAttr, bundle_path(), 0222));
+  Run();
+  ASSERT_TRUE(mock_launchd()->remove_called());
+  ASSERT_TRUE(mock_launchd()->delete_called());
+}
+
+#endif  // !OS_MACOSX
diff --git a/chrome/common/service_process_util_win.cc b/chrome/common/service_process_util_win.cc
new file mode 100644
index 0000000..d06989b
--- /dev/null
+++ b/chrome/common/service_process_util_win.cc
@@ -0,0 +1,175 @@
+// 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.
+
+#include "chrome/common/service_process_util.h"
+
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/string16.h"
+#include "base/utf_string_conversions.h"
+#include "base/win/object_watcher.h"
+#include "base/win/scoped_handle.h"
+#include "base/win/win_util.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+
+namespace {
+
+const char* kTerminateEventSuffix = "_service_terminate_evt";
+
+string16 GetServiceProcessReadyEventName() {
+  return UTF8ToWide(
+      GetServiceProcessScopedVersionedName("_service_ready"));
+}
+
+string16 GetServiceProcessTerminateEventName() {
+  return UTF8ToWide(
+      GetServiceProcessScopedVersionedName(kTerminateEventSuffix));
+}
+
+std::string GetServiceProcessAutoRunKey() {
+  return GetServiceProcessScopedName("_service_run");
+}
+
+// Returns the name of the autotun reg value that we used to use for older
+// versions of Chrome.
+std::string GetObsoleteServiceProcessAutoRunKey() {
+  FilePath user_data_dir;
+  PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
+  std::string scoped_name = WideToUTF8(user_data_dir.value());
+  std::replace(scoped_name.begin(), scoped_name.end(), '\\', '!');
+  std::replace(scoped_name.begin(), scoped_name.end(), '/', '!');
+  scoped_name.append("_service_run");
+  return scoped_name;
+}
+
+class ServiceProcessTerminateMonitor
+    : public base::win::ObjectWatcher::Delegate {
+ public:
+  explicit ServiceProcessTerminateMonitor(const base::Closure& terminate_task)
+      : terminate_task_(terminate_task) {
+  }
+  void Start() {
+    string16 event_name = GetServiceProcessTerminateEventName();
+    DCHECK(event_name.length() <= MAX_PATH);
+    terminate_event_.Set(CreateEvent(NULL, TRUE, FALSE, event_name.c_str()));
+    watcher_.StartWatching(terminate_event_.Get(), this);
+  }
+
+  // base::ObjectWatcher::Delegate implementation.
+  virtual void OnObjectSignaled(HANDLE object) {
+    if (!terminate_task_.is_null()) {
+      terminate_task_.Run();
+      terminate_task_.Reset();
+    }
+  }
+
+ private:
+  base::win::ScopedHandle terminate_event_;
+  base::win::ObjectWatcher watcher_;
+  base::Closure terminate_task_;
+};
+
+}  // namespace
+
+// Gets the name of the service process IPC channel.
+IPC::ChannelHandle GetServiceProcessChannel() {
+  return GetServiceProcessScopedVersionedName("_service_ipc");
+}
+
+bool ForceServiceProcessShutdown(const std::string& version,
+                                 base::ProcessId process_id) {
+  base::win::ScopedHandle terminate_event;
+  std::string versioned_name = version;
+  versioned_name.append(kTerminateEventSuffix);
+  string16 event_name =
+      UTF8ToWide(GetServiceProcessScopedName(versioned_name));
+  terminate_event.Set(OpenEvent(EVENT_MODIFY_STATE, FALSE, event_name.c_str()));
+  if (!terminate_event.IsValid())
+    return false;
+  SetEvent(terminate_event.Get());
+  return true;
+}
+
+bool CheckServiceProcessReady() {
+  string16 event_name = GetServiceProcessReadyEventName();
+  base::win::ScopedHandle event(
+      OpenEvent(SYNCHRONIZE | READ_CONTROL, false, event_name.c_str()));
+  if (!event.IsValid())
+    return false;
+  // Check if the event is signaled.
+  return WaitForSingleObject(event, 0) == WAIT_OBJECT_0;
+}
+
+struct ServiceProcessState::StateData {
+  // An event that is signaled when a service process is ready.
+  base::win::ScopedHandle ready_event;
+  scoped_ptr<ServiceProcessTerminateMonitor> terminate_monitor;
+};
+
+void ServiceProcessState::CreateState() {
+  DCHECK(!state_);
+  state_ = new StateData;
+}
+
+bool ServiceProcessState::TakeSingletonLock() {
+  DCHECK(state_);
+  string16 event_name = GetServiceProcessReadyEventName();
+  DCHECK(event_name.length() <= MAX_PATH);
+  base::win::ScopedHandle service_process_ready_event;
+  service_process_ready_event.Set(
+      CreateEvent(NULL, TRUE, FALSE, event_name.c_str()));
+  DWORD error = GetLastError();
+  if ((error == ERROR_ALREADY_EXISTS) || (error == ERROR_ACCESS_DENIED))
+    return false;
+  DCHECK(service_process_ready_event.IsValid());
+  state_->ready_event.Set(service_process_ready_event.Take());
+  return true;
+}
+
+bool ServiceProcessState::SignalReady(
+    base::MessageLoopProxy* message_loop_proxy,
+    const base::Closure& terminate_task) {
+  DCHECK(state_);
+  DCHECK(state_->ready_event.IsValid());
+  if (!SetEvent(state_->ready_event.Get())) {
+    return false;
+  }
+  if (!terminate_task.is_null()) {
+    state_->terminate_monitor.reset(
+        new ServiceProcessTerminateMonitor(terminate_task));
+    state_->terminate_monitor->Start();
+  }
+  return true;
+}
+
+bool ServiceProcessState::AddToAutoRun() {
+  DCHECK(autorun_command_line_.get());
+  // Remove the old autorun value first because we changed the naming scheme
+  // for the autorun value name.
+  base::win::RemoveCommandFromAutoRun(
+      HKEY_CURRENT_USER, UTF8ToWide(GetObsoleteServiceProcessAutoRunKey()));
+  return base::win::AddCommandToAutoRun(
+      HKEY_CURRENT_USER,
+      UTF8ToWide(GetServiceProcessAutoRunKey()),
+      autorun_command_line_->GetCommandLineString());
+}
+
+bool ServiceProcessState::RemoveFromAutoRun() {
+  // Remove the old autorun value first because we changed the naming scheme
+  // for the autorun value name.
+  base::win::RemoveCommandFromAutoRun(
+      HKEY_CURRENT_USER, UTF8ToWide(GetObsoleteServiceProcessAutoRunKey()));
+  return base::win::RemoveCommandFromAutoRun(
+      HKEY_CURRENT_USER, UTF8ToWide(GetServiceProcessAutoRunKey()));
+}
+
+void ServiceProcessState::TearDownState() {
+  delete state_;
+  state_ = NULL;
+}
diff --git a/chrome/common/spellcheck_common.cc b/chrome/common/spellcheck_common.cc
new file mode 100644
index 0000000..d25e07a
--- /dev/null
+++ b/chrome/common/spellcheck_common.cc
@@ -0,0 +1,164 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/spellcheck_common.h"
+
+#include "base/file_path.h"
+
+namespace chrome {
+namespace spellcheck_common {
+
+static const struct {
+  // The language.
+  const char* language;
+
+  // The corresponding language and region, used by the dictionaries.
+  const char* language_region;
+} g_supported_spellchecker_languages[] = {
+  // Several languages are not to be included in the spellchecker list:
+  // th-TH, uk-UA
+  {"af", "af-ZA"},
+  {"bg", "bg-BG"},
+  {"ca", "ca-ES"},
+  {"cs", "cs-CZ"},
+  {"da", "da-DK"},
+  {"de", "de-DE"},
+  {"el", "el-GR"},
+  {"en-AU", "en-AU"},
+  {"en-CA", "en-CA"},
+  {"en-GB", "en-GB"},
+  {"en-US", "en-US"},
+  {"es", "es-ES"},
+  {"et", "et-EE"},
+  {"fo", "fo-FO"},
+  {"fr", "fr-FR"},
+  {"he", "he-IL"},
+  {"hi", "hi-IN"},
+  {"hr", "hr-HR"},
+  {"hu", "hu-HU"},
+  {"id", "id-ID"},
+  {"it", "it-IT"},
+  {"lt", "lt-LT"},
+  {"lv", "lv-LV"},
+  {"nb", "nb-NO"},
+  {"nl", "nl-NL"},
+  {"pl", "pl-PL"},
+  {"pt-BR", "pt-BR"},
+  {"pt-PT", "pt-PT"},
+  {"ro", "ro-RO"},
+  {"ru", "ru-RU"},
+  {"sk", "sk-SK"},
+  {"sl", "sl-SI"},
+  {"sh", "sh"},
+  {"sr", "sr"},
+  {"sv", "sv-SE"},
+  {"tr", "tr-TR"},
+  {"uk", "uk-UA"},
+  {"vi", "vi-VN"},
+};
+
+// This function returns the language-region version of language name.
+// e.g. returns hi-IN for hi.
+std::string GetSpellCheckLanguageRegion(const std::string& input_language) {
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages);
+       ++i) {
+    if (g_supported_spellchecker_languages[i].language == input_language) {
+      return std::string(
+          g_supported_spellchecker_languages[i].language_region);
+    }
+  }
+
+  return input_language;
+}
+
+FilePath GetVersionedFileName(const std::string& input_language,
+                              const FilePath& dict_dir) {
+  // The default dictionary version is 1-2. These versions have been augmented
+  // with additional words found by the translation team.
+  static const char kDefaultVersionString[] = "-1-2";
+
+  static const struct {
+    // The language input.
+    const char* language;
+
+    // The corresponding version.
+    const char* version;
+  } special_version_string[] = {
+    {"es-ES", "-1-1"},  // 1-1: Have not been augmented with addtional words.
+    {"nl-NL", "-1-1"},
+    {"sv-SE", "-1-1"},
+    {"he-IL", "-1-1"},
+    {"el-GR", "-1-1"},
+    {"hi-IN", "-1-1"},
+    {"tr-TR", "-1-1"},
+    {"et-EE", "-1-1"},
+    {"lt-LT", "-1-3"},  // 1-3 (Feb 2009): new words, as well as an upgraded
+                        // dictionary.
+    {"pl-PL", "-1-3"},
+    {"fr-FR", "-2-0"},  // 2-0 (2010): upgraded dictionaries.
+    {"hu-HU", "-2-0"},
+    {"ro-RO", "-2-0"},
+    {"ru-RU", "-2-0"},
+    {"bg-BG", "-2-0"},
+    {"sr",    "-2-0"},
+    {"uk-UA", "-2-0"},
+    {"pt-BR", "-2-2"},  // 2-2 (Mar 2011): upgraded a dictionary.
+    {"sh",    "-2-2"},  // 2-2 (Mar 2011): added a dictionary.
+    {"ca-ES", "-2-3"},  // 2-3 (May 2012): upgraded a dictionary.
+    {"sv-SE", "-2-3"},  // 2-3 (May 2012): upgraded a dictionary.
+    {"af-ZA", "-2-3"},  // 2-3 (May 2012): added a dictionary.
+    {"fo-FO", "-2-3"},  // 2-3 (May 2012): added a dictionary.
+    {"en-US", "-2-4"},  // 2-4 (October 2012): add more words.
+    {"en-CA", "-2-4"},
+    {"en-GB", "-2-4"},
+    {"en-AU", "-2-4"},
+
+  };
+
+  // Generate the bdict file name using default version string or special
+  // version string, depending on the language.
+  std::string language = GetSpellCheckLanguageRegion(input_language);
+  std::string versioned_bdict_file_name(language + kDefaultVersionString +
+                                        ".bdic");
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(special_version_string); ++i) {
+    if (language == special_version_string[i].language) {
+      versioned_bdict_file_name =
+          language + special_version_string[i].version + ".bdic";
+      break;
+    }
+  }
+
+  return dict_dir.AppendASCII(versioned_bdict_file_name);
+}
+
+std::string GetCorrespondingSpellCheckLanguage(const std::string& language) {
+  // Look for exact match in the Spell Check language list.
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages);
+       ++i) {
+    // First look for exact match in the language region of the list.
+    std::string spellcheck_language(
+        g_supported_spellchecker_languages[i].language);
+    if (spellcheck_language == language)
+      return language;
+
+    // Next, look for exact match in the language_region part of the list.
+    std::string spellcheck_language_region(
+        g_supported_spellchecker_languages[i].language_region);
+    if (spellcheck_language_region == language)
+      return g_supported_spellchecker_languages[i].language;
+  }
+
+  // No match found - return blank.
+  return std::string();
+}
+
+void SpellCheckLanguages(std::vector<std::string>* languages) {
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages);
+       ++i) {
+    languages->push_back(g_supported_spellchecker_languages[i].language);
+  }
+}
+
+}  // namespace spellcheck_common
+}  // namespace chrome
diff --git a/chrome/common/spellcheck_common.h b/chrome/common/spellcheck_common.h
new file mode 100644
index 0000000..d11dcfc
--- /dev/null
+++ b/chrome/common/spellcheck_common.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_SPELLCHECK_COMMON_H_
+#define CHROME_COMMON_SPELLCHECK_COMMON_H_
+
+#include <string>
+#include <vector>
+
+class FilePath;
+
+namespace chrome {
+namespace spellcheck_common {
+
+// Max number of dictionary suggestions.
+static const int kMaxSuggestions = 5;
+
+static const int kMaxAutoCorrectWordSize = 8;
+
+typedef std::vector<std::string> WordList;
+
+FilePath GetVersionedFileName(const std::string& input_language,
+                              const FilePath& dict_dir);
+
+std::string GetCorrespondingSpellCheckLanguage(const std::string& language);
+
+// Get SpellChecker supported languages.
+void SpellCheckLanguages(std::vector<std::string>* languages);
+
+}  // namespace spellcheck_common
+}  // namespace chrome
+
+#endif  // CHROME_COMMON_SPELLCHECK_COMMON_H_
diff --git a/chrome/common/spellcheck_messages.h b/chrome/common/spellcheck_messages.h
new file mode 100644
index 0000000..a617814
--- /dev/null
+++ b/chrome/common/spellcheck_messages.h
@@ -0,0 +1,129 @@
+// Copyright (c) 2012 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.
+
+// IPC messages for spellcheck.
+// Multiply-included message file, hence no include guard.
+
+#include "chrome/common/spellcheck_result.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_platform_file.h"
+
+
+#define IPC_MESSAGE_START SpellCheckMsgStart
+
+IPC_ENUM_TRAITS(SpellCheckResult::Type)
+
+IPC_STRUCT_TRAITS_BEGIN(SpellCheckResult)
+  IPC_STRUCT_TRAITS_MEMBER(type)
+  IPC_STRUCT_TRAITS_MEMBER(location)
+  IPC_STRUCT_TRAITS_MEMBER(length)
+  IPC_STRUCT_TRAITS_MEMBER(replacement)
+IPC_STRUCT_TRAITS_END()
+
+// Messages sent from the browser to the renderer.
+
+IPC_MESSAGE_ROUTED0(SpellCheckMsg_ToggleSpellCheck)
+
+// Passes some initialization params to the renderer's spellchecker. This can
+// be called directly after startup or in (async) response to a
+// RequestDictionary ViewHost message.
+IPC_MESSAGE_CONTROL4(SpellCheckMsg_Init,
+                     IPC::PlatformFileForTransit /* bdict_file */,
+                     std::vector<std::string> /* custom_dict_words */,
+                     std::string /* language */,
+                     bool /* auto spell correct */)
+
+// A word has been added to the custom dictionary; update the local custom
+// word list.
+IPC_MESSAGE_CONTROL1(SpellCheckMsg_WordAdded,
+                     std::string /* word */)
+
+// Toggle the auto spell correct functionality.
+IPC_MESSAGE_CONTROL1(SpellCheckMsg_EnableAutoSpellCorrect,
+                     bool /* enable */)
+
+#if !defined(OS_MACOSX)
+// Sends text-check results from the Spelling service when the service finishes
+// checking text reveived by a SpellCheckHostMsg_CallSpellingService message.
+// If the service is not available, the 4th parameter should be false and
+// the 5th parameter should contain the requested setence.
+IPC_MESSAGE_ROUTED5(SpellCheckMsg_RespondSpellingService,
+                    int         /* request identifier given by WebKit */,
+                    int         /* offset */,
+                    bool        /* succeeded calling serivce */,
+                    string16    /* sentence */,
+                    std::vector<SpellCheckResult>)
+#endif
+
+#if defined(OS_MACOSX)
+// This message tells the renderer to advance to the next misspelling. It is
+// sent when the user clicks the "Find Next" button on the spelling panel.
+IPC_MESSAGE_ROUTED0(SpellCheckMsg_AdvanceToNextMisspelling)
+
+// Sends when NSSpellChecker finishes checking text received by a preceeding
+// SpellCheckHostMsg_RequestTextCheck message.
+IPC_MESSAGE_ROUTED2(SpellCheckMsg_RespondTextCheck,
+                    int        /* request identifier given by WebKit */,
+                    std::vector<SpellCheckResult>)
+
+IPC_MESSAGE_ROUTED1(SpellCheckMsg_ToggleSpellPanel,
+                    bool)
+#endif
+
+// Messages sent from the renderer to the browser.
+
+// The renderer has tried to spell check a word, but couldn't because no
+// dictionary was available to load. Request that the browser find an
+// appropriate dictionary and return it.
+IPC_MESSAGE_CONTROL0(SpellCheckHostMsg_RequestDictionary)
+
+// Tracks spell checking occurrence to collect histogram.
+IPC_MESSAGE_ROUTED2(SpellCheckHostMsg_NotifyChecked,
+                    string16 /* word */,
+                    bool /* true if checked word is misspelled */)
+
+#if !defined(OS_MACOSX)
+// Asks the Spelling service to check text. When the service finishes checking
+// the input text, it sends a SpellingCheckMsg_RespondSpellingService with
+// text-check results.
+IPC_MESSAGE_CONTROL4(SpellCheckHostMsg_CallSpellingService,
+                     int /* route_id for response */,
+                     int /* request identifier given by WebKit */,
+                     int /* offset */,
+                     string16 /* sentence */)
+#endif
+
+#if defined(OS_MACOSX)
+// This message tells the spellchecker that a document has been closed and all
+// of the ignored words for that document can be forgotten.
+IPC_MESSAGE_ROUTED1(SpellCheckHostMsg_DocumentClosed,
+                    int /* route_id to identify document */)
+
+// Tells the browser to display or not display the SpellingPanel
+IPC_MESSAGE_ROUTED1(SpellCheckHostMsg_ShowSpellingPanel,
+                    bool /* if true, then show it, otherwise hide it*/)
+
+// Tells the browser to update the spelling panel with the given word.
+IPC_MESSAGE_ROUTED1(SpellCheckHostMsg_UpdateSpellingPanelWithMisspelledWord,
+                    string16 /* the word to update the panel with */)
+
+// TODO(groby): This needs to originate from SpellcheckProvider.
+IPC_SYNC_MESSAGE_CONTROL2_1(SpellCheckHostMsg_CheckSpelling,
+                            string16 /* word */,
+                            int /* route_id */,
+                            bool /* correct */)
+
+IPC_SYNC_MESSAGE_CONTROL1_1(SpellCheckHostMsg_FillSuggestionList,
+                            string16 /* word */,
+                            std::vector<string16> /* suggestions */)
+
+IPC_MESSAGE_CONTROL3(SpellCheckHostMsg_RequestTextCheck,
+                     int /* route_id for response */,
+                     int /* request identifier given by WebKit */,
+                     string16 /* sentence */)
+
+IPC_MESSAGE_ROUTED2(SpellCheckHostMsg_ToggleSpellCheck,
+                    bool /* enabled */,
+                    bool /* checked */)
+#endif  // OS_MACOSX
diff --git a/chrome/common/spellcheck_result.h b/chrome/common/spellcheck_result.h
new file mode 100644
index 0000000..38b96ee
--- /dev/null
+++ b/chrome/common/spellcheck_result.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_SPELLCHECK_RESULT_H_
+#define CHROME_COMMON_SPELLCHECK_RESULT_H_
+
+#include "base/string16.h"
+
+// This class mirrors WebKit::WebTextCheckingResult which holds a
+// misspelled range inside the checked text. It also contains a
+// possible replacement of the misspelling if it is available.
+//
+// Although SpellCheckResult::Type defines various values Chromium
+// only uses the |Spelling| type. otehr values are just reflecting the
+// enum definition in the original WebKit class.
+//
+struct SpellCheckResult {
+  enum Type {
+    SPELLING = 1 << 1,
+    GRAMMAR  = 1 << 2,
+    LINK = 1 << 5,
+    QUOTE = 1 << 6,
+    DASH = 1 << 7,
+    REPLACEMENT = 1 << 8,
+    CORRECTION = 1 << 9,
+    SHOWCORRECTIONPANEL = 1 << 10
+  };
+
+  explicit SpellCheckResult(
+      Type t = SPELLING,
+      int loc = 0,
+      int len = 0,
+      const string16& rep = string16())
+      : type(t), location(loc), length(len), replacement(rep) {
+  }
+
+  Type type;
+  int location;
+  int length;
+  string16 replacement;
+};
+
+#endif  // CHROME_COMMON_SPELLCHECK_RESULT_H_
diff --git a/chrome/common/startup_metric_utils.cc b/chrome/common/startup_metric_utils.cc
new file mode 100644
index 0000000..2292fff
--- /dev/null
+++ b/chrome/common/startup_metric_utils.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/startup_metric_utils.h"
+
+#include "base/logging.h"
+#include "base/time.h"
+
+namespace {
+
+// Mark as volatile to defensively make sure usage is thread-safe.
+// Note that at the time of this writing, access is only on the UI thread.
+static volatile bool g_non_browser_ui_displayed = false;
+
+const base::Time* MainEntryPointTimeInternal() {
+  static base::Time main_start_time = base::Time::Now();
+  return &main_start_time;
+}
+
+static bool g_main_entry_time_was_recorded = false;
+}  // namespace
+
+namespace startup_metric_utils {
+
+bool WasNonBrowserUIDisplayed() {
+  return g_non_browser_ui_displayed;
+}
+
+void SetNonBrowserUIDisplayed() {
+  g_non_browser_ui_displayed = true;
+}
+
+void RecordMainEntryPointTime() {
+  DCHECK(!g_main_entry_time_was_recorded);
+  g_main_entry_time_was_recorded = true;
+  MainEntryPointTimeInternal();
+}
+
+const base::Time MainEntryStartTime() {
+  DCHECK(g_main_entry_time_was_recorded);
+  return *MainEntryPointTimeInternal();
+}
+
+}  // namespace startup_metric_utils
diff --git a/chrome/common/startup_metric_utils.h b/chrome/common/startup_metric_utils.h
new file mode 100644
index 0000000..feeca61
--- /dev/null
+++ b/chrome/common/startup_metric_utils.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_STARTUP_METRIC_UTILS_H_
+#define CHROME_COMMON_STARTUP_METRIC_UTILS_H_
+
+#include "base/time.h"
+
+// Utility functions to support metric collection for browser startup.
+
+namespace startup_metric_utils {
+
+// Returns true if any UI other than the browser window has been displayed
+// so far.  Useful to test if UI has been displayed before the first browser
+// window was shown, which would invalidate any surrounding timing metrics.
+bool WasNonBrowserUIDisplayed();
+
+// Call this when displaying UI that might potentially delay the appearance
+// of the initial browser window on Chrome startup.
+//
+// Note on usage: This function is idempotent and its overhead is low enough
+// in comparison with UI display that it's OK to call it on every
+// UI invocation regardless of whether the browser window has already
+// been displayed or not.
+void SetNonBrowserUIDisplayed();
+
+// Call this as early as possible in the startup process to record a
+// timestamp.
+void RecordMainEntryPointTime();
+
+// Return the time recorded by RecordMainEntryPointTime().
+const base::Time MainEntryStartTime();
+
+}  // namespace startup_metric_utils
+
+#endif  // CHROME_COMMON_STARTUP_METRIC_UTILS_H_
diff --git a/chrome/common/switch_utils.cc b/chrome/common/switch_utils.cc
new file mode 100644
index 0000000..41752da
--- /dev/null
+++ b/chrome/common/switch_utils.cc
@@ -0,0 +1,33 @@
+// 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.
+
+#include "chrome/common/switch_utils.h"
+
+#include "base/basictypes.h"
+#include "chrome/common/chrome_switches.h"
+
+namespace switches {
+
+// Switches enumerated here will be removed when a background instance of
+// Chrome restarts itself. If your key is designed to only be used once,
+// or if it does not make sense when restarting a background instance to
+// pick up an automatic update, be sure to add it to this list.
+const char* const kSwitchesToRemoveOnAutorestart[] = {
+  switches::kApp,
+  switches::kAppId,
+  switches::kFirstRun,
+  switches::kImport,
+  switches::kImportFromFile,
+  switches::kMakeDefaultBrowser,
+  switches::kNoStartupWindow,
+  switches::kRestoreLastSession
+};
+
+void RemoveSwitchesForAutostart(
+    std::map<std::string, CommandLine::StringType>* switch_list) {
+  for (size_t i = 0; i < arraysize(kSwitchesToRemoveOnAutorestart); ++i)
+    switch_list->erase(kSwitchesToRemoveOnAutorestart[i]);
+}
+
+}  // namespace switches
diff --git a/chrome/common/switch_utils.h b/chrome/common/switch_utils.h
new file mode 100644
index 0000000..83594d1
--- /dev/null
+++ b/chrome/common/switch_utils.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2010 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.
+
+#ifndef CHROME_COMMON_SWITCH_UTILS_H_
+#define CHROME_COMMON_SWITCH_UTILS_H_
+
+#include <map>
+#include <string>
+
+#include "base/command_line.h"
+
+namespace switches {
+
+// Remove the keys that we shouldn't pass through during restart.
+void RemoveSwitchesForAutostart(
+    std::map<std::string, CommandLine::StringType>* switches);
+
+}  // namespace switches
+
+#endif  // CHROME_COMMON_SWITCH_UTILS_H_
diff --git a/chrome/common/switch_utils_unittest.cc b/chrome/common/switch_utils_unittest.cc
new file mode 100644
index 0000000..74bc8af
--- /dev/null
+++ b/chrome/common/switch_utils_unittest.cc
@@ -0,0 +1,59 @@
+// 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.
+
+#include "chrome/common/switch_utils.h"
+
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/file_path.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(SwitchUtilsTest, RemoveSwitches) {
+  const CommandLine::CharType* argv[] = {
+    FILE_PATH_LITERAL("program"),
+    FILE_PATH_LITERAL("--app=http://www.google.com/"),
+    FILE_PATH_LITERAL("--first-run"),
+    FILE_PATH_LITERAL("--import"),
+    FILE_PATH_LITERAL("--import-from-file=c:\\test.html"),
+    FILE_PATH_LITERAL("--make-default-browser"),
+    FILE_PATH_LITERAL("--foo"),
+    FILE_PATH_LITERAL("--bar")};
+  CommandLine cmd_line(arraysize(argv), argv);
+  EXPECT_FALSE(cmd_line.GetCommandLineString().empty());
+
+  std::map<std::string, CommandLine::StringType> switches =
+      cmd_line.GetSwitches();
+  EXPECT_EQ(7U, switches.size());
+
+  switches::RemoveSwitchesForAutostart(&switches);
+  EXPECT_EQ(2U, switches.size());
+  EXPECT_TRUE(cmd_line.HasSwitch("foo"));
+  EXPECT_TRUE(cmd_line.HasSwitch("bar"));
+}
+
+#if defined(OS_WIN)
+TEST(SwitchUtilsTest, RemoveSwitchesFromString) {
+  // All these command line args (except foo and bar) will
+  // be removed after RemoveSwitchesForAutostart:
+  CommandLine cmd_line = CommandLine::FromString(
+      L"program"
+      L" --app=http://www.google.com/"
+      L" --first-run"
+      L" --import"
+      L" --import-from-file=c:\\test.html"
+      L" --make-default-browser"
+      L" --foo"
+      L" --bar");
+  EXPECT_FALSE(cmd_line.GetCommandLineString().empty());
+
+  std::map<std::string, CommandLine::StringType> switches =
+      cmd_line.GetSwitches();
+  EXPECT_EQ(7U, switches.size());
+
+  switches::RemoveSwitchesForAutostart(&switches);
+  EXPECT_EQ(2U, switches.size());
+  EXPECT_TRUE(cmd_line.HasSwitch("foo"));
+  EXPECT_TRUE(cmd_line.HasSwitch("bar"));
+}
+#endif
diff --git a/chrome/common/thumbnail_score.cc b/chrome/common/thumbnail_score.cc
new file mode 100644
index 0000000..e2f41a0
--- /dev/null
+++ b/chrome/common/thumbnail_score.cc
@@ -0,0 +1,139 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/thumbnail_score.h"
+
+#include "base/logging.h"
+#include "base/stringprintf.h"
+
+using base::Time;
+using base::TimeDelta;
+
+const int64 ThumbnailScore::kUpdateThumbnailTimeDays = 1;
+const double ThumbnailScore::kThumbnailMaximumBoringness = 0.94;
+const double ThumbnailScore::kThumbnailDegradePerHour = 0.01;
+const double ThumbnailScore::kTooWideAspectRatio = 2.0;
+
+// Calculates a numeric score from traits about where a snapshot was
+// taken. The lower the better. We store the raw components in the
+// database because I'm sure this will evolve and I don't want to break
+// databases.
+static int GetThumbnailType(const ThumbnailScore& score) {
+  int type = 0;
+  if (!score.at_top)
+    type += 1;
+  if (!score.good_clipping)
+    type += 2;
+  if (!score.load_completed)
+    type += 3;
+  return type;
+}
+
+ThumbnailScore::ThumbnailScore()
+    : boring_score(1.0),
+      good_clipping(false),
+      at_top(false),
+      load_completed(false),
+      time_at_snapshot(Time::Now()),
+      redirect_hops_from_dest(0) {
+}
+
+ThumbnailScore::ThumbnailScore(double score, bool clipping, bool top)
+    : boring_score(score),
+      good_clipping(clipping),
+      at_top(top),
+      load_completed(false),
+      time_at_snapshot(Time::Now()),
+      redirect_hops_from_dest(0) {
+}
+
+ThumbnailScore::ThumbnailScore(double score, bool clipping, bool top,
+                               const Time& time)
+    : boring_score(score),
+      good_clipping(clipping),
+      at_top(top),
+      load_completed(false),
+      time_at_snapshot(time),
+      redirect_hops_from_dest(0) {
+}
+
+ThumbnailScore::~ThumbnailScore() {
+}
+
+bool ThumbnailScore::Equals(const ThumbnailScore& rhs) const {
+  return boring_score == rhs.boring_score &&
+      good_clipping == rhs.good_clipping &&
+      at_top == rhs.at_top &&
+      time_at_snapshot == rhs.time_at_snapshot &&
+      redirect_hops_from_dest == rhs.redirect_hops_from_dest;
+}
+
+std::string ThumbnailScore::ToString() const {
+  return StringPrintf("boring_score: %f, at_top %d, good_clipping %d, "
+                      "load_completed: %d, "
+                      "time_at_snapshot: %f, redirect_hops_from_dest: %d",
+                      boring_score,
+                      at_top,
+                      good_clipping,
+                      load_completed,
+                      time_at_snapshot.ToDoubleT(),
+                      redirect_hops_from_dest);
+}
+
+bool ShouldReplaceThumbnailWith(const ThumbnailScore& current,
+                                const ThumbnailScore& replacement) {
+  int current_type = GetThumbnailType(current);
+  int replacement_type = GetThumbnailType(replacement);
+  if (replacement_type < current_type) {
+    // If we have a better class of thumbnail, add it if it meets
+    // certain minimum boringness.
+    return replacement.boring_score <
+        ThumbnailScore::kThumbnailMaximumBoringness;
+  } else if (replacement_type == current_type) {
+    // It's much easier to do the scaling below when we're dealing with "higher
+    // is better." Then we can decrease the score by dividing by a fraction.
+    const double kThumbnailMinimumInterestingness =
+        1.0 - ThumbnailScore::kThumbnailMaximumBoringness;
+    double current_interesting_score = 1.0 - current.boring_score;
+    double replacement_interesting_score = 1.0 - replacement.boring_score;
+
+    // Degrade the score of each thumbnail to account for how many redirects
+    // they are away from the destination. 1/(x+1) gives a scaling factor of
+    // one for x = 0, and asymptotically approaches 0 for larger values of x.
+    current_interesting_score *=
+        1.0 / (current.redirect_hops_from_dest + 1);
+    replacement_interesting_score *=
+        1.0 / (replacement.redirect_hops_from_dest + 1);
+
+    // Degrade the score and prefer the newer one based on how long apart the
+    // two thumbnails were taken. This means we'll eventually replace an old
+    // good one with a new worse one assuming enough time has passed.
+    TimeDelta time_between_thumbnails =
+        replacement.time_at_snapshot - current.time_at_snapshot;
+    current_interesting_score -= time_between_thumbnails.InHours() *
+         ThumbnailScore::kThumbnailDegradePerHour;
+
+    if (current_interesting_score < kThumbnailMinimumInterestingness)
+      current_interesting_score = kThumbnailMinimumInterestingness;
+    if (replacement_interesting_score > current_interesting_score)
+      return true;
+  }
+
+  // If the current thumbnail doesn't meet basic boringness
+  // requirements, but the replacement does, always replace the
+  // current one even if we're using a worse thumbnail type.
+  return current.boring_score >= ThumbnailScore::kThumbnailMaximumBoringness &&
+      replacement.boring_score < ThumbnailScore::kThumbnailMaximumBoringness;
+}
+
+bool ThumbnailScore::ShouldConsiderUpdating() {
+  const TimeDelta time_elapsed = Time::Now() - time_at_snapshot;
+  if (time_elapsed < TimeDelta::FromDays(kUpdateThumbnailTimeDays) &&
+      good_clipping && at_top && load_completed) {
+    // The current thumbnail is new and has good properties.
+    return false;
+  }
+  // The current thumbnail should be updated.
+  return true;
+}
diff --git a/chrome/common/thumbnail_score.h b/chrome/common/thumbnail_score.h
new file mode 100644
index 0000000..b5e2df3
--- /dev/null
+++ b/chrome/common/thumbnail_score.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_THUMBNAIL_SCORE_H_
+#define CHROME_COMMON_THUMBNAIL_SCORE_H_
+
+#include <string>
+#include "base/time.h"
+
+// A set of metadata about a Thumbnail.
+struct ThumbnailScore {
+  // Initializes the ThumbnailScore to the absolute worst possible values
+  // except for time, which is set to Now(), and redirect_hops_from_dest which
+  // is set to 0.
+  ThumbnailScore();
+
+  // Builds a ThumbnailScore with the passed in values, and sets the
+  // thumbnail generation time to Now().
+  ThumbnailScore(double score, bool clipping, bool top);
+
+  // Builds a ThumbnailScore with the passed in values.
+  ThumbnailScore(double score, bool clipping, bool top,
+                 const base::Time& time);
+  ~ThumbnailScore();
+
+  // Tests for equivalence between two ThumbnailScore objects.
+  bool Equals(const ThumbnailScore& rhs) const;
+
+  // Returns string representation of this object.
+  std::string ToString() const;
+
+  // How "boring" a thumbnail is. The boring score is the 0,1 ranged
+  // percentage of pixels that are the most common luma. Higher boring
+  // scores indicate that a higher percentage of a bitmap are all the
+  // same brightness (most likely the same color).
+  //
+  // The score should only be used for comparing two thumbnails taken from
+  // the same page to see which one is more boring/interesting. The
+  // absolute score is not suitable for judging whether the thumbnail is
+  // actually boring or not. For instance, www.google.com is very
+  // succinct, so the boring score can be as high as 0.9, depending on the
+  // browser window size.
+  double boring_score;
+
+  // Whether the thumbnail was taken with height greater than
+  // width or width greater than height and the aspect ratio less than
+  // kTooWideAspectRatio. In cases where we don't have |good_clipping|,
+  // the thumbnails are either clipped from the horizontal center of the
+  // window, or are otherwise weirdly stretched.
+  bool good_clipping;
+
+  // Whether this thumbnail was taken while the renderer was
+  // displaying the top of the page. Most pages are more recognizable
+  // by their headers then by a set of random text half way down the
+  // page; i.e. most MediaWiki sites would be indistinguishable by
+  // thumbnails with |at_top| set to false.
+  bool at_top;
+
+  // Whether this thumbnail was taken after load was completed.
+  // Thumbnails taken while page loading may only contain partial
+  // contents.
+  bool load_completed;
+
+  // Record the time when a thumbnail was taken. This is used to make
+  // sure thumbnails are kept fresh.
+  base::Time time_at_snapshot;
+
+  // The number of hops from the final destination page that this thumbnail was
+  // taken at. When a thumbnail is taken, this will always be the redirect
+  // destination (value of 0).
+  //
+  // For the most visited view, we'll sometimes get thumbnails for URLs in the
+  // middle of a redirect chain. In this case, the top sites component will set
+  // this value so the distance from the destination can be taken into account
+  // by the comparison function.
+  //
+  // If "http://google.com/" redirected to "http://www.google.com/", then
+  // a thumbnail for the first would have a redirect hop of 1, and the second
+  // would have a redirect hop of 0.
+  int redirect_hops_from_dest;
+
+  // How bad a thumbnail needs to be before we completely ignore it.
+  static const double kThumbnailMaximumBoringness;
+
+  // We consider a thumbnail interesting enough if the boring score is
+  // lower than this.
+  static const double kThumbnailInterestingEnoughBoringness;
+
+  // Time before we take a worse thumbnail (subject to
+  // kThumbnailMaximumBoringness) over what's currently in the database
+  // for freshness.
+  static const int64 kUpdateThumbnailTimeDays;
+
+  // Penalty of how much more boring a thumbnail should be per hour.
+  static const double kThumbnailDegradePerHour;
+
+  // If a thumbnail is taken with the aspect ratio greater than or equal to
+  // this value, |good_clipping| is to false.
+  static const double kTooWideAspectRatio;
+
+  // Checks whether we should consider updating a new thumbnail based on
+  // this score. For instance, we don't have to update a new thumbnail
+  // if the current thumbnail is new and interesting enough.
+  bool ShouldConsiderUpdating();
+};
+
+// Checks whether we should replace one thumbnail with another.
+bool ShouldReplaceThumbnailWith(const ThumbnailScore& current,
+                                const ThumbnailScore& replacement);
+
+#endif  // CHROME_COMMON_THUMBNAIL_SCORE_H_
diff --git a/chrome/common/thumbnail_score_unittest.cc b/chrome/common/thumbnail_score_unittest.cc
new file mode 100644
index 0000000..7e17ada
--- /dev/null
+++ b/chrome/common/thumbnail_score_unittest.cc
@@ -0,0 +1,88 @@
+// 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.
+
+#include "chrome/common/thumbnail_score.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Tests that the different types of thumbnails are compared properly.
+TEST(ThumbnailScoreTest, ShouldReplaceThumbnailWithType) {
+  base::Time now = base::Time::Now();
+
+  ThumbnailScore nothing_good(0.5, false, false, now);
+  ThumbnailScore not_at_top(0.5, false, true, now);
+  ThumbnailScore bad_clipping(0.5, true, false, now);
+  ThumbnailScore life_is_awesome(0.5, true, true, now);
+
+  EXPECT_TRUE(ShouldReplaceThumbnailWith(nothing_good, not_at_top));
+  EXPECT_TRUE(ShouldReplaceThumbnailWith(nothing_good, bad_clipping));
+  EXPECT_TRUE(ShouldReplaceThumbnailWith(nothing_good, life_is_awesome));
+  EXPECT_TRUE(ShouldReplaceThumbnailWith(not_at_top, bad_clipping));
+  EXPECT_TRUE(ShouldReplaceThumbnailWith(not_at_top, life_is_awesome));
+  EXPECT_TRUE(ShouldReplaceThumbnailWith(bad_clipping, life_is_awesome));
+}
+
+// Tests that we'll replace old thumbnails will crappier but newer ones.
+TEST(ThumbnailScoreTest, ShouldReplaceThumbnailWithTime) {
+  // Use a really long time for the difference so we aren't sensitive to the
+  // degrading schedule.
+  base::Time now = base::Time::Now();
+  base::Time last_year = now - base::TimeDelta::FromDays(365);
+
+  ThumbnailScore oldie_but_goodie(0.1, true, true, last_year);
+  ThumbnailScore newie_but_crappie(0.9, true, true, now);
+
+  EXPECT_TRUE(ShouldReplaceThumbnailWith(oldie_but_goodie, newie_but_crappie));
+}
+
+// Having many redirects should age the thumbnail.
+TEST(ThumbnailScoreTest, RedirectCount) {
+  base::Time now = base::Time::Now();
+
+  ThumbnailScore no_redirects(0.5, true, true, now);
+  no_redirects.redirect_hops_from_dest = 0;
+  ThumbnailScore some_redirects(0.5, true, true, now);
+  some_redirects.redirect_hops_from_dest = 1;
+
+  EXPECT_TRUE(ShouldReplaceThumbnailWith(some_redirects, no_redirects));
+
+  // This one has a lot of redirects but a better score. It should still be
+  // rejected.
+  ThumbnailScore lotsa_redirects(0.4, true, true, now);
+  lotsa_redirects.redirect_hops_from_dest = 4;
+  EXPECT_FALSE(ShouldReplaceThumbnailWith(no_redirects, lotsa_redirects));
+}
+
+TEST(ThumbnailScoreTest, ShouldConsiderUpdating) {
+  ThumbnailScore score;
+  // By default, the score is low, thus we should generate a new thumbnail.
+  EXPECT_TRUE(score.ShouldConsiderUpdating());
+
+  // Make it very interesting, but this is not enough.
+  score.boring_score = 0.0;
+  EXPECT_TRUE(score.ShouldConsiderUpdating());
+
+  // good_clipping is important, but sill not enough.
+  score.good_clipping = true;
+  EXPECT_TRUE(score.ShouldConsiderUpdating());
+
+  // at_top is important, but still not enough.
+  score.at_top = true;
+  EXPECT_TRUE(score.ShouldConsiderUpdating());
+
+  // load_completed is important. Finally, the thumbnail is new and
+  // interesting enough.
+  score.load_completed = true;
+  EXPECT_FALSE(score.ShouldConsiderUpdating());
+
+  // Make it very boring, but it won't change the result. The boring score
+  // isn't used for judging whether we should update or not. See comments
+  // at boring_score in thumbnail_score.h for why.
+  score.boring_score = 1.0;
+  EXPECT_FALSE(score.ShouldConsiderUpdating());
+
+  // Make it old. Then, it's no longer new enough.
+  score.time_at_snapshot -=
+      base::TimeDelta::FromDays(ThumbnailScore::kUpdateThumbnailTimeDays);
+  EXPECT_TRUE(score.ShouldConsiderUpdating());
+}
diff --git a/chrome/common/time_format.cc b/chrome/common/time_format.cc
new file mode 100644
index 0000000..13554d3
--- /dev/null
+++ b/chrome/common/time_format.cc
@@ -0,0 +1,376 @@
+// 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.
+
+#include "chrome/common/time_format.h"
+
+#include <vector>
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/string16.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "unicode/datefmt.h"
+#include "unicode/locid.h"
+#include "unicode/plurfmt.h"
+#include "unicode/plurrule.h"
+#include "unicode/smpdtfmt.h"
+
+using base::Time;
+using base::TimeDelta;
+
+namespace {
+
+static const char kFallbackFormatSuffixShort[] = "}";
+static const char kFallbackFormatSuffixLeft[] = " left}";
+static const char kFallbackFormatSuffixAgo[] = " ago}";
+
+// Contains message IDs for various time units and pluralities.
+struct MessageIDs {
+  // There are 4 different time units and 6 different pluralities.
+  int ids[4][6];
+};
+
+// Message IDs for different time formats.
+static const MessageIDs kTimeShortMessageIDs = { {
+  {
+    IDS_TIME_SECS_DEFAULT, IDS_TIME_SEC_SINGULAR, IDS_TIME_SECS_ZERO,
+    IDS_TIME_SECS_TWO, IDS_TIME_SECS_FEW, IDS_TIME_SECS_MANY
+  },
+  {
+    IDS_TIME_MINS_DEFAULT, IDS_TIME_MIN_SINGULAR, IDS_TIME_MINS_ZERO,
+    IDS_TIME_MINS_TWO, IDS_TIME_MINS_FEW, IDS_TIME_MINS_MANY
+  },
+  {
+    IDS_TIME_HOURS_DEFAULT, IDS_TIME_HOUR_SINGULAR, IDS_TIME_HOURS_ZERO,
+    IDS_TIME_HOURS_TWO, IDS_TIME_HOURS_FEW, IDS_TIME_HOURS_MANY
+  },
+  {
+    IDS_TIME_DAYS_DEFAULT, IDS_TIME_DAY_SINGULAR, IDS_TIME_DAYS_ZERO,
+    IDS_TIME_DAYS_TWO, IDS_TIME_DAYS_FEW, IDS_TIME_DAYS_MANY
+  }
+} };
+
+static const MessageIDs kTimeRemainingMessageIDs = { {
+  {
+    IDS_TIME_REMAINING_SECS_DEFAULT, IDS_TIME_REMAINING_SEC_SINGULAR,
+    IDS_TIME_REMAINING_SECS_ZERO, IDS_TIME_REMAINING_SECS_TWO,
+    IDS_TIME_REMAINING_SECS_FEW, IDS_TIME_REMAINING_SECS_MANY
+  },
+  {
+    IDS_TIME_REMAINING_MINS_DEFAULT, IDS_TIME_REMAINING_MIN_SINGULAR,
+    IDS_TIME_REMAINING_MINS_ZERO, IDS_TIME_REMAINING_MINS_TWO,
+    IDS_TIME_REMAINING_MINS_FEW, IDS_TIME_REMAINING_MINS_MANY
+  },
+  {
+    IDS_TIME_REMAINING_HOURS_DEFAULT, IDS_TIME_REMAINING_HOUR_SINGULAR,
+    IDS_TIME_REMAINING_HOURS_ZERO, IDS_TIME_REMAINING_HOURS_TWO,
+    IDS_TIME_REMAINING_HOURS_FEW, IDS_TIME_REMAINING_HOURS_MANY
+  },
+  {
+    IDS_TIME_REMAINING_DAYS_DEFAULT, IDS_TIME_REMAINING_DAY_SINGULAR,
+    IDS_TIME_REMAINING_DAYS_ZERO, IDS_TIME_REMAINING_DAYS_TWO,
+    IDS_TIME_REMAINING_DAYS_FEW, IDS_TIME_REMAINING_DAYS_MANY
+  }
+} };
+
+static const MessageIDs kTimeRemainingLongMessageIDs = { {
+  {
+    IDS_TIME_REMAINING_SECS_DEFAULT, IDS_TIME_REMAINING_SEC_SINGULAR,
+    IDS_TIME_REMAINING_SECS_ZERO, IDS_TIME_REMAINING_SECS_TWO,
+    IDS_TIME_REMAINING_SECS_FEW, IDS_TIME_REMAINING_SECS_MANY
+  },
+  {
+    IDS_TIME_REMAINING_LONG_MINS_DEFAULT, IDS_TIME_REMAINING_LONG_MIN_SINGULAR,
+    IDS_TIME_REMAINING_LONG_MINS_ZERO, IDS_TIME_REMAINING_LONG_MINS_TWO,
+    IDS_TIME_REMAINING_LONG_MINS_FEW, IDS_TIME_REMAINING_LONG_MINS_MANY
+  },
+  {
+    IDS_TIME_REMAINING_HOURS_DEFAULT, IDS_TIME_REMAINING_HOUR_SINGULAR,
+    IDS_TIME_REMAINING_HOURS_ZERO, IDS_TIME_REMAINING_HOURS_TWO,
+    IDS_TIME_REMAINING_HOURS_FEW, IDS_TIME_REMAINING_HOURS_MANY
+  },
+  {
+    IDS_TIME_REMAINING_DAYS_DEFAULT, IDS_TIME_REMAINING_DAY_SINGULAR,
+    IDS_TIME_REMAINING_DAYS_ZERO, IDS_TIME_REMAINING_DAYS_TWO,
+    IDS_TIME_REMAINING_DAYS_FEW, IDS_TIME_REMAINING_DAYS_MANY
+  }
+} };
+
+static const MessageIDs kTimeElapsedMessageIDs = { {
+  {
+    IDS_TIME_ELAPSED_SECS_DEFAULT, IDS_TIME_ELAPSED_SEC_SINGULAR,
+    IDS_TIME_ELAPSED_SECS_ZERO, IDS_TIME_ELAPSED_SECS_TWO,
+    IDS_TIME_ELAPSED_SECS_FEW, IDS_TIME_ELAPSED_SECS_MANY
+  },
+  {
+    IDS_TIME_ELAPSED_MINS_DEFAULT, IDS_TIME_ELAPSED_MIN_SINGULAR,
+    IDS_TIME_ELAPSED_MINS_ZERO, IDS_TIME_ELAPSED_MINS_TWO,
+    IDS_TIME_ELAPSED_MINS_FEW, IDS_TIME_ELAPSED_MINS_MANY
+  },
+  {
+    IDS_TIME_ELAPSED_HOURS_DEFAULT, IDS_TIME_ELAPSED_HOUR_SINGULAR,
+    IDS_TIME_ELAPSED_HOURS_ZERO, IDS_TIME_ELAPSED_HOURS_TWO,
+    IDS_TIME_ELAPSED_HOURS_FEW, IDS_TIME_ELAPSED_HOURS_MANY
+  },
+  {
+    IDS_TIME_ELAPSED_DAYS_DEFAULT, IDS_TIME_ELAPSED_DAY_SINGULAR,
+    IDS_TIME_ELAPSED_DAYS_ZERO, IDS_TIME_ELAPSED_DAYS_TWO,
+    IDS_TIME_ELAPSED_DAYS_FEW, IDS_TIME_ELAPSED_DAYS_MANY
+  }
+} };
+
+// Different format types.
+enum FormatType {
+  FORMAT_SHORT,
+  FORMAT_REMAINING,
+  FORMAT_REMAINING_LONG,
+  FORMAT_ELAPSED,
+};
+
+}  // namespace
+
+class TimeFormatter {
+  public:
+    const std::vector<icu::PluralFormat*>& formatter(FormatType format_type) {
+      switch (format_type) {
+        case FORMAT_SHORT:
+          return short_formatter_;
+        case FORMAT_REMAINING:
+          return time_left_formatter_;
+        case FORMAT_REMAINING_LONG:
+          return time_left_long_formatter_;
+        case FORMAT_ELAPSED:
+          return time_elapsed_formatter_;
+        default:
+          NOTREACHED();
+          return short_formatter_;
+      }
+    }
+  private:
+    static const MessageIDs& GetMessageIDs(FormatType format_type) {
+      switch (format_type) {
+        case FORMAT_SHORT:
+          return kTimeShortMessageIDs;
+        case FORMAT_REMAINING:
+          return kTimeRemainingMessageIDs;
+        case FORMAT_REMAINING_LONG:
+          return kTimeRemainingLongMessageIDs;
+        case FORMAT_ELAPSED:
+          return kTimeElapsedMessageIDs;
+        default:
+          NOTREACHED();
+          return kTimeShortMessageIDs;
+      }
+    }
+
+    static const char* GetFallbackFormatSuffix(FormatType format_type) {
+      switch (format_type) {
+        case FORMAT_SHORT:
+          return kFallbackFormatSuffixShort;
+        case FORMAT_REMAINING:
+        case FORMAT_REMAINING_LONG:
+          return kFallbackFormatSuffixLeft;
+        case FORMAT_ELAPSED:
+          return kFallbackFormatSuffixAgo;
+        default:
+          NOTREACHED();
+          return kFallbackFormatSuffixShort;
+      }
+    }
+
+    TimeFormatter() {
+      BuildFormats(FORMAT_SHORT, &short_formatter_);
+      BuildFormats(FORMAT_REMAINING, &time_left_formatter_);
+      BuildFormats(FORMAT_REMAINING_LONG, &time_left_long_formatter_);
+      BuildFormats(FORMAT_ELAPSED, &time_elapsed_formatter_);
+    }
+    ~TimeFormatter() {
+      STLDeleteContainerPointers(short_formatter_.begin(),
+                                 short_formatter_.end());
+      STLDeleteContainerPointers(time_left_formatter_.begin(),
+                                 time_left_formatter_.end());
+      STLDeleteContainerPointers(time_left_long_formatter_.begin(),
+                                 time_left_long_formatter_.end());
+      STLDeleteContainerPointers(time_elapsed_formatter_.begin(),
+                                 time_elapsed_formatter_.end());
+    }
+    friend struct base::DefaultLazyInstanceTraits<TimeFormatter>;
+
+    std::vector<icu::PluralFormat*> short_formatter_;
+    std::vector<icu::PluralFormat*> time_left_formatter_;
+    std::vector<icu::PluralFormat*> time_left_long_formatter_;
+    std::vector<icu::PluralFormat*> time_elapsed_formatter_;
+    static void BuildFormats(FormatType format_type,
+                             std::vector<icu::PluralFormat*>* time_formats);
+    static icu::PluralFormat* createFallbackFormat(
+        const icu::PluralRules& rules, int index, FormatType format_type);
+
+    DISALLOW_COPY_AND_ASSIGN(TimeFormatter);
+};
+
+static base::LazyInstance<TimeFormatter> g_time_formatter =
+    LAZY_INSTANCE_INITIALIZER;
+
+void TimeFormatter::BuildFormats(
+    FormatType format_type, std::vector<icu::PluralFormat*>* time_formats) {
+  const icu::UnicodeString kKeywords[] = {
+    UNICODE_STRING_SIMPLE("other"), UNICODE_STRING_SIMPLE("one"),
+    UNICODE_STRING_SIMPLE("zero"), UNICODE_STRING_SIMPLE("two"),
+    UNICODE_STRING_SIMPLE("few"), UNICODE_STRING_SIMPLE("many")
+  };
+  UErrorCode err = U_ZERO_ERROR;
+  scoped_ptr<icu::PluralRules> rules(
+      icu::PluralRules::forLocale(icu::Locale::getDefault(), err));
+  if (U_FAILURE(err)) {
+    err = U_ZERO_ERROR;
+    icu::UnicodeString fallback_rules("one: n is 1", -1, US_INV);
+    rules.reset(icu::PluralRules::createRules(fallback_rules, err));
+    DCHECK(U_SUCCESS(err));
+  }
+
+  const MessageIDs& message_ids = GetMessageIDs(format_type);
+
+  for (int i = 0; i < 4; ++i) {
+    icu::UnicodeString pattern;
+    for (size_t j = 0; j < arraysize(kKeywords); ++j) {
+      int msg_id = message_ids.ids[i][j];
+      std::string sub_pattern = l10n_util::GetStringUTF8(msg_id);
+      // NA means this keyword is not used in the current locale.
+      // Even if a translator translated for this keyword, we do not
+      // use it unless it's 'other' (j=0) or it's defined in the rules
+      // for the current locale. Special-casing of 'other' will be removed
+      // once ICU's isKeyword is fixed to return true for isKeyword('other').
+      if (sub_pattern.compare("NA") != 0 &&
+          (j == 0 || rules->isKeyword(kKeywords[j]))) {
+          pattern += kKeywords[j];
+          pattern += UNICODE_STRING_SIMPLE("{");
+          pattern += icu::UnicodeString(sub_pattern.c_str(), "UTF-8");
+          pattern += UNICODE_STRING_SIMPLE("}");
+      }
+    }
+    icu::PluralFormat* format = new icu::PluralFormat(*rules, pattern, err);
+    if (U_SUCCESS(err)) {
+      time_formats->push_back(format);
+    } else {
+      delete format;
+      time_formats->push_back(createFallbackFormat(*rules, i, format_type));
+      // Reset it so that next ICU call can proceed.
+      err = U_ZERO_ERROR;
+    }
+  }
+}
+
+// Create a hard-coded fallback plural format. This will never be called
+// unless translators make a mistake.
+icu::PluralFormat* TimeFormatter::createFallbackFormat(
+    const icu::PluralRules& rules, int index, FormatType format_type) {
+  const icu::UnicodeString kUnits[4][2] = {
+    { UNICODE_STRING_SIMPLE("sec"), UNICODE_STRING_SIMPLE("secs") },
+    { UNICODE_STRING_SIMPLE("min"), UNICODE_STRING_SIMPLE("mins") },
+    { UNICODE_STRING_SIMPLE("hour"), UNICODE_STRING_SIMPLE("hours") },
+    { UNICODE_STRING_SIMPLE("day"), UNICODE_STRING_SIMPLE("days") }
+  };
+  icu::UnicodeString suffix(GetFallbackFormatSuffix(format_type), -1, US_INV);
+  icu::UnicodeString pattern;
+  if (rules.isKeyword(UNICODE_STRING_SIMPLE("one"))) {
+    pattern += UNICODE_STRING_SIMPLE("one{# ") + kUnits[index][0] + suffix;
+  }
+  pattern += UNICODE_STRING_SIMPLE(" other{# ") + kUnits[index][1] + suffix;
+  UErrorCode err = U_ZERO_ERROR;
+  icu::PluralFormat* format = new icu::PluralFormat(rules, pattern, err);
+  DCHECK(U_SUCCESS(err));
+  return format;
+}
+
+static string16 FormatTimeImpl(const TimeDelta& delta, FormatType format_type) {
+  if (delta.ToInternalValue() < 0) {
+    NOTREACHED() << "Negative duration";
+    return string16();
+  }
+
+  int number;
+
+  const std::vector<icu::PluralFormat*>& formatters =
+    g_time_formatter.Get().formatter(format_type);
+
+  UErrorCode error = U_ZERO_ERROR;
+  icu::UnicodeString time_string;
+  // Less than a minute gets "X seconds left"
+  if (delta.ToInternalValue() < Time::kMicrosecondsPerMinute) {
+    number = static_cast<int>(delta.ToInternalValue() /
+                              Time::kMicrosecondsPerSecond);
+    time_string = formatters[0]->format(number, error);
+
+  // Less than 1 hour gets "X minutes left".
+  } else if (delta.ToInternalValue() < Time::kMicrosecondsPerHour) {
+    number = static_cast<int>(delta.ToInternalValue() /
+                              Time::kMicrosecondsPerMinute);
+    time_string = formatters[1]->format(number, error);
+
+  // Less than 1 day remaining gets "X hours left"
+  } else if (delta.ToInternalValue() < Time::kMicrosecondsPerDay) {
+    number = static_cast<int>(delta.ToInternalValue() /
+                              Time::kMicrosecondsPerHour);
+    time_string = formatters[2]->format(number, error);
+
+  // Anything bigger gets "X days left"
+  } else {
+    number = static_cast<int>(delta.ToInternalValue() /
+                              Time::kMicrosecondsPerDay);
+    time_string = formatters[3]->format(number, error);
+  }
+
+  // With the fallback added, this should never fail.
+  DCHECK(U_SUCCESS(error));
+  int capacity = time_string.length() + 1;
+  DCHECK_GT(capacity, 1);
+  string16 result;
+  time_string.extract(static_cast<UChar*>(WriteInto(&result, capacity)),
+                      capacity, error);
+  DCHECK(U_SUCCESS(error));
+  return result;
+}
+
+// static
+string16 TimeFormat::TimeElapsed(const TimeDelta& delta) {
+  return FormatTimeImpl(delta, FORMAT_ELAPSED);
+}
+
+// static
+string16 TimeFormat::TimeRemaining(const TimeDelta& delta) {
+  return FormatTimeImpl(delta, FORMAT_REMAINING);
+}
+
+// static
+string16 TimeFormat::TimeRemainingLong(const TimeDelta& delta) {
+  return FormatTimeImpl(delta, FORMAT_REMAINING_LONG);
+}
+
+// static
+string16 TimeFormat::TimeRemainingShort(const TimeDelta& delta) {
+  return FormatTimeImpl(delta, FORMAT_SHORT);
+}
+
+// static
+string16 TimeFormat::RelativeDate(
+    const Time& time,
+    const Time* optional_midnight_today) {
+  Time midnight_today = optional_midnight_today ? *optional_midnight_today :
+      Time::Now().LocalMidnight();
+  TimeDelta day = TimeDelta::FromMicroseconds(Time::kMicrosecondsPerDay);
+  Time tomorrow = midnight_today + day;
+  Time yesterday = midnight_today - day;
+  if (time >= tomorrow)
+    return string16();
+  else if (time >= midnight_today)
+    return l10n_util::GetStringUTF16(IDS_PAST_TIME_TODAY);
+  else if (time >= yesterday)
+    return l10n_util::GetStringUTF16(IDS_PAST_TIME_YESTERDAY);
+  return string16();
+}
diff --git a/chrome/common/time_format.h b/chrome/common/time_format.h
new file mode 100644
index 0000000..dedfc37
--- /dev/null
+++ b/chrome/common/time_format.h
@@ -0,0 +1,59 @@
+// 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.
+
+#ifndef CHROME_COMMON_TIME_FORMAT_H_
+#define CHROME_COMMON_TIME_FORMAT_H_
+
+#include "base/basictypes.h"
+#include "base/string16.h"
+
+namespace base {
+class Time;
+class TimeDelta;
+}
+
+// Methods to format time values as strings.
+class TimeFormat {
+ public:
+  // TimeElapsed, TimeRemaining and TimeRemainingShort functions:
+  // These functions return a localized string of approximate time duration. The
+  // conditions are simpler than PastTime since these functions are used for
+  // in-progress operations and users have different expectations of units.
+
+  // Returns times in elapsed-format: "3 mins ago", "2 days ago".
+  static string16 TimeElapsed(const base::TimeDelta& delta);
+
+  // Returns times in remaining-format: "3 mins left", "2 days left".
+  static string16 TimeRemaining(const base::TimeDelta& delta);
+
+  // Returns times in remaining-long-format: "3 minutes left", "2 days left".
+  // Currently, this only affects the minutes in long format, the rest
+  // of the time units are formatted the same as TimeRemaining does.
+  static string16 TimeRemainingLong(const base::TimeDelta& delta);
+
+  // Returns times in short-format: "3 mins", "2 days".
+  static string16 TimeRemainingShort(const base::TimeDelta& delta);
+
+  // For displaying a relative time in the past.  This method returns either
+  // "Today", "Yesterday", or an empty string if it's older than that.  Returns
+  // the empty string for days in the future.
+  //
+  // TODO(brettw): This should be able to handle days in the future like
+  //    "Tomorrow".
+  // TODO(tc): This should be able to do things like "Last week".  This
+  //    requires handling singluar/plural for all languages.
+  //
+  // The second parameter is optional, it is midnight of "Now" for relative day
+  // computations: Time::Now().LocalMidnight()
+  // If NULL, the current day's midnight will be retrieved, which can be
+  // slow. If many items are being processed, it is best to get the current
+  // time once at the beginning and pass it for each computation.
+  static string16 RelativeDate(const base::Time& time,
+                               const base::Time* optional_midnight_today);
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(TimeFormat);
+};
+
+#endif  // CHROME_COMMON_TIME_FORMAT_H_
diff --git a/chrome/common/time_format_browsertest.cc b/chrome/common/time_format_browsertest.cc
new file mode 100644
index 0000000..c014a70
--- /dev/null
+++ b/chrome/common/time_format_browsertest.cc
@@ -0,0 +1,39 @@
+// 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.
+
+// This whole test runs as a separate browser_test because it depends on a
+// static initialization inside third_party/icu (gDecimal in digitlst.cpp).
+//
+// That initialization depends on the current locale, and on certain locales
+// will lead to wrong behavior. To make sure that the locale is set before
+// icu is used, and that the "wrong" static value doesn't affect other tests,
+// this test is executed on its own process.
+
+#include "base/string16.h"
+#include "base/test/scoped_locale.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/time_format.h"
+#include "chrome/test/base/in_process_browser_test.h"
+
+using base::TimeDelta;
+
+class TimeFormatBrowserTest : public InProcessBrowserTest {
+ public:
+  TimeFormatBrowserTest() : scoped_locale_("fr_FR.utf-8") {
+  }
+
+ private:
+  base::ScopedLocale scoped_locale_;
+};
+
+IN_PROC_BROWSER_TEST_F(TimeFormatBrowserTest, DecimalPointNotDot) {
+  // Some locales use a comma ',' instead of a dot '.' as the separator for
+  // decimal digits. The icu library wasn't handling this, leading to "1"
+  // being internally converted to "+1,0e00" and ultimately leading to "NaN".
+  // This showed up on the browser on estimated download time, for example.
+  // http://crbug.com/60476
+
+  string16 one_min = TimeFormat::TimeRemainingShort(TimeDelta::FromMinutes(1));
+  EXPECT_EQ(ASCIIToUTF16("1 min"), one_min);
+}
diff --git a/chrome/common/time_format_unittest.cc b/chrome/common/time_format_unittest.cc
new file mode 100644
index 0000000..cc30a48
--- /dev/null
+++ b/chrome/common/time_format_unittest.cc
@@ -0,0 +1,70 @@
+// 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.
+
+#include "chrome/common/time_format.h"
+
+#include "base/string16.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using base::TimeDelta;
+
+void TestTimeFormats(const TimeDelta& delta, const char* expected_ascii) {
+  string16 expected = ASCIIToUTF16(expected_ascii);
+  string16 expected_left = expected + ASCIIToUTF16(" left");
+  string16 expected_ago = expected + ASCIIToUTF16(" ago");
+  EXPECT_EQ(expected, TimeFormat::TimeRemainingShort(delta));
+  EXPECT_EQ(expected_left, TimeFormat::TimeRemaining(delta));
+  EXPECT_EQ(expected_ago, TimeFormat::TimeElapsed(delta));
+}
+
+TEST(TimeFormat, FormatTime) {
+  const TimeDelta one_day = TimeDelta::FromDays(1);
+  const TimeDelta three_days = TimeDelta::FromDays(3);
+  const TimeDelta one_hour = TimeDelta::FromHours(1);
+  const TimeDelta four_hours = TimeDelta::FromHours(4);
+  const TimeDelta one_min = TimeDelta::FromMinutes(1);
+  const TimeDelta three_mins = TimeDelta::FromMinutes(3);
+  const TimeDelta one_sec = TimeDelta::FromSeconds(1);
+  const TimeDelta five_secs = TimeDelta::FromSeconds(5);
+  const TimeDelta twohundred_millisecs = TimeDelta::FromMilliseconds(200);
+
+  // TODO(jungshik) : These test only pass when the OS locale is 'en'.
+  // We need to add SetUp() and TearDown() to set the locale to 'en'.
+  TestTimeFormats(twohundred_millisecs, "0 secs");
+  TestTimeFormats(one_sec - twohundred_millisecs, "0 secs");
+  TestTimeFormats(one_sec + twohundred_millisecs, "1 sec");
+  TestTimeFormats(five_secs + twohundred_millisecs, "5 secs");
+  TestTimeFormats(one_min + five_secs, "1 min");
+  TestTimeFormats(three_mins + twohundred_millisecs, "3 mins");
+  TestTimeFormats(one_hour + five_secs, "1 hour");
+  TestTimeFormats(four_hours + five_secs, "4 hours");
+  TestTimeFormats(one_day + five_secs, "1 day");
+  TestTimeFormats(three_days, "3 days");
+  TestTimeFormats(three_days + four_hours, "3 days");
+}
+
+// crbug.com/159388: This test fails when daylight savings time ends.
+TEST(TimeFormat, FLAKY_RelativeDate) {
+  base::Time now = base::Time::Now();
+  string16 today_str = TimeFormat::RelativeDate(now, NULL);
+  EXPECT_EQ(ASCIIToUTF16("Today"), today_str);
+
+  base::Time yesterday = now - TimeDelta::FromDays(1);
+  string16 yesterday_str = TimeFormat::RelativeDate(yesterday, NULL);
+  EXPECT_EQ(ASCIIToUTF16("Yesterday"), yesterday_str);
+
+  base::Time two_days_ago = now - TimeDelta::FromDays(2);
+  string16 two_days_ago_str = TimeFormat::RelativeDate(two_days_ago, NULL);
+  EXPECT_TRUE(two_days_ago_str.empty());
+
+  base::Time a_week_ago = now - TimeDelta::FromDays(7);
+  string16 a_week_ago_str = TimeFormat::RelativeDate(a_week_ago, NULL);
+  EXPECT_TRUE(a_week_ago_str.empty());
+}
+
+}  // namespace
diff --git a/chrome/common/translate_errors.h b/chrome/common/translate_errors.h
new file mode 100644
index 0000000..1c6ea8a
--- /dev/null
+++ b/chrome/common/translate_errors.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2010 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.
+
+#ifndef CHROME_COMMON_TRANSLATE_ERRORS_H_
+#define CHROME_COMMON_TRANSLATE_ERRORS_H_
+
+// This file consolidates all the error types for translation of a page.
+
+class TranslateErrors {
+ public:
+  enum Type {
+    NONE = 0,
+    NETWORK,  // No connectivity.
+    INITIALIZATION_ERROR,  // The translation script failed to initialize.
+    UNKNOWN_LANGUAGE,      // The page's language could not be detected.
+    UNSUPPORTED_LANGUAGE,  // The server detected a language that the browser
+                           // does not know.
+    IDENTICAL_LANGUAGES,   // The original and target languages are the same.
+    TRANSLATION_ERROR,     // An error was reported by the translation script
+                           // during translation.
+  };
+
+ private:
+  TranslateErrors() {}
+
+  DISALLOW_COPY_AND_ASSIGN(TranslateErrors);
+};
+
+#endif  // CHROME_COMMON_TRANSLATE_ERRORS_H_
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
new file mode 100644
index 0000000..75a06ef
--- /dev/null
+++ b/chrome/common/url_constants.cc
@@ -0,0 +1,508 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/url_constants.h"
+
+#include "googleurl/src/url_util.h"
+
+namespace chrome {
+
+#if defined(OS_CHROMEOS)
+const char kCrosScheme[] = "cros";
+const char kDriveScheme[] = "drive";
+#endif
+
+const char kAboutPluginsURL[] = "about:plugins";
+const char kAboutVersionURL[] = "about:version";
+
+// Add Chrome UI URLs as necessary, in alphabetical order.
+// Be sure to add the corresponding kChromeUI*Host constant below.
+// This is a WebUI page that lists other WebUI pages.
+const char kChromeUIAboutURL[] = "chrome://about/";
+const char kChromeUIBookmarksURL[] = "chrome://bookmarks/";
+const char kChromeUICertificateViewerURL[] = "chrome://view-cert/";
+const char kChromeUIChromeURLsURL[] = "chrome://chrome-urls/";
+const char kChromeUICloudPrintResourcesURL[] = "chrome://cloudprintresources/";
+const char kChromeUIConflictsURL[] = "chrome://conflicts/";
+const char kChromeUIConstrainedHTMLTestURL[] = "chrome://constrained-test/";
+const char kChromeUICrashesURL[] = "chrome://crashes/";
+const char kChromeUICreditsURL[] = "chrome://credits/";
+const char kChromeUIDevToolsURL[] = "chrome-devtools://devtools/";
+const char kChromeUIDownloadsURL[] = "chrome://downloads/";
+const char kChromeUIEditSearchEngineDialogURL[] = "chrome://editsearchengine/";
+const char kChromeUIExtensionActivityURL[] = "chrome://extension-activity/";
+const char kChromeUIExtensionIconURL[] = "chrome://extension-icon/";
+const char kChromeUIExtensionInfoURL[] = "chrome://extension-info/";
+const char kChromeUIExtensionsFrameURL[] = "chrome://extensions-frame/";
+const char kChromeUIExtensionsURL[] = "chrome://extensions/";
+const char kChromeUIFaviconURL[] = "chrome://favicon/";
+const char kChromeUIFeedbackURL[] = "chrome://feedback/";
+const char kChromeUIFlagsURL[] = "chrome://flags/";
+const char kChromeUIFlashURL[] = "chrome://flash/";
+const char kChromeUIHelpFrameURL[] = "chrome://help-frame/";
+const char kChromeUIHistoryURL[] = "chrome://history/";
+const char kChromeUIHistoryFrameURL[] = "chrome://history-frame/";
+const char kChromeUIInputWindowDialogURL[] = "chrome://input-window-dialog/";
+const char kChromeUIInspectURL[] = "chrome://inspect/";
+const char kChromeUIInstantURL[] = "chrome://instant/";
+const char kChromeUIIPCURL[] = "chrome://ipc/";
+const char kChromeUIKeyboardURL[] = "chrome://keyboard/";
+const char kChromeUIMemoryRedirectURL[] = "chrome://memory-redirect/";
+const char kChromeUIMemoryURL[] = "chrome://memory/";
+const char kChromeUIMetroFlowURL[] = "chrome://make-metro/";
+const char kChromeUINaClURL[] = "chrome://nacl/";
+const char kChromeUINetInternalsURL[] = "chrome://net-internals/";
+const char kChromeUINewProfile[] = "chrome://newprofile/";
+const char kChromeUINewTabURL[] = "chrome://newtab/";
+const char kChromeUIOmniboxURL[] = "chrome://omnibox/";
+const char kChromeUIPerformanceMonitorURL[] = "chrome://performance/";
+const char kChromeUIPluginsURL[] = "chrome://plugins/";
+const char kChromeUIPolicyURL[] = "chrome://policy/";
+const char kChromeUIPrintURL[] = "chrome://print/";
+const char kChromeUISessionFaviconURL[] = "chrome://session-favicon/";
+const char kChromeUISettingsURL[] = "chrome://settings/";
+const char kChromeUISettingsFrameURL[] = "chrome://settings-frame/";
+const char kChromeUISuggestionsInternalsURL[] =
+    "chrome://suggestions-internals/";
+const char kChromeUISSLClientCertificateSelectorURL[] = "chrome://select-cert/";
+const char kChromeUISyncPromoURL[] = "chrome://signin/";
+const char kChromeUITaskManagerURL[] = "chrome://tasks/";
+const char kChromeUITermsURL[] = "chrome://terms/";
+const char kChromeUIThemeURL[] = "chrome://theme/";
+const char kChromeUIThumbnailURL[] = "chrome://thumb/";
+const char kChromeUIUberURL[] = "chrome://chrome/";
+const char kChromeUIUberFrameURL[] = "chrome://uber-frame/";
+const char kChromeUIVersionURL[] = "chrome://version/";
+
+#if defined(OS_ANDROID)
+const char kChromeUIWelcomeURL[] = "chrome://welcome/";
+#endif
+
+#if defined(OS_CHROMEOS)
+const char kChromeUIActivationMessage[] = "chrome://activationmessage/";
+const char kChromeUIChooseMobileNetworkURL[] =
+    "chrome://choose-mobile-network/";
+const char kChromeUIDiagnosticsURL[] = "chrome://diagnostics/";
+const char kChromeUIDiscardsURL[] = "chrome://discards/";
+const char kChromeUIIdleLogoutDialogURL[] = "chrome://idle-logout/";
+const char kChromeUIImageBurnerURL[] = "chrome://imageburner/";
+const char kChromeUIKeyboardOverlayURL[] = "chrome://keyboardoverlay/";
+const char kChromeUILockScreenURL[] = "chrome://lock/";
+const char kChromeUIMediaplayerURL[] = "chrome://mediaplayer/";
+const char kChromeUIMobileSetupURL[] = "chrome://mobilesetup/";
+const char kChromeUIOobeURL[] = "chrome://oobe/";
+const char kChromeUIOSCreditsURL[] = "chrome://os-credits/";
+const char kChromeUIProxySettingsURL[] = "chrome://proxy-settings/";
+const char kChromeUIRegisterPageURL[] = "chrome://register/";
+const char kChromeUISimUnlockURL[] = "chrome://sim-unlock/";
+const char kChromeUISlideshowURL[] = "chrome://slideshow/";
+const char kChromeUISystemInfoURL[] = "chrome://system/";
+const char kChromeUITermsOemURL[] = "chrome://terms/oem";
+const char kChromeUIUserImageURL[] = "chrome://userimage/";
+const char kChromeUIWallpaperURL[] = "chrome://wallpapers/";
+const char kChromeUIWallpaperImageURL[] = "chrome://wallpaper/";
+const char kChromeUIWallpaperThumbnailURL[] = "chrome://wallpaper-thumb/";
+#endif
+
+#if defined(USE_ASH)
+const char kChromeUITransparencyURL[] = "chrome://transparency/";
+#endif
+
+#if defined(FILE_MANAGER_EXTENSION)
+const char kChromeUIFileManagerURL[] = "chrome://files/";
+#endif
+
+#if defined(USE_AURA)
+const char kChromeUIGestureConfigURL[] = "chrome://gesture/";
+const char kChromeUIGestureConfigHost[] = "gesture";
+#endif
+
+#if (defined(OS_LINUX) && defined(TOOLKIT_VIEWS)) || defined(USE_AURA)
+const char kChromeUICollectedCookiesURL[] = "chrome://collected-cookies/";
+const char kChromeUIHttpAuthURL[] = "chrome://http-auth/";
+const char kChromeUITabModalConfirmDialogURL[] =
+    "chrome://tab-modal-confirm-dialog/";
+#endif
+
+// Add Chrome UI hosts here, in alphabetical order.
+// Add hosts to kChromePaths in browser_about_handler.cc to be listed by
+// chrome://chrome-urls (about:about) and the built-in AutocompleteProvider.
+const char kChromeUIAboutHost[] = "about";
+const char kChromeUIBlankHost[] = "blank";
+const char kChromeUIBookmarksHost[] = "bookmarks";
+const char kChromeUICacheHost[] = "cache";
+const char kChromeUICertificateViewerHost[] = "view-cert";
+const char kChromeUIChromeURLsHost[] = "chrome-urls";
+const char kChromeUICloudPrintResourcesHost[] = "cloudprintresources";
+const char kChromeUICloudPrintSetupHost[] = "cloudprintsetup";
+const char kChromeUIConflictsHost[] = "conflicts";
+const char kChromeUIConstrainedHTMLTestHost[] = "constrained-test";
+const char kChromeUICrashesHost[] = "crashes";
+const char kChromeUICrashHost[] = "crash";
+const char kChromeUICreditsHost[] = "credits";
+const char kChromeUIDefaultHost[] = "version";
+const char kChromeUIDevToolsHost[] = "devtools";
+const char kChromeUIDialogHost[] = "dialog";
+const char kChromeUIDNSHost[] = "dns";
+const char kChromeUIDownloadsHost[] = "downloads";
+const char kChromeUIDriveInternalsHost[] = "drive-internals";
+const char kChromeUIEditSearchEngineDialogHost[] = "editsearchengine";
+const char kChromeUIExtensionActivityHost[] = "extension-activity";
+const char kChromeUIExtensionIconHost[] = "extension-icon";
+const char kChromeUIExtensionInfoHost[] = "extension-info";
+const char kChromeUIExtensionsFrameHost[] = "extensions-frame";
+const char kChromeUIExtensionsHost[] = "extensions";
+const char kChromeUIFaviconHost[] = "favicon";
+const char kChromeUIFeedbackHost[] = "feedback";
+const char kChromeUIFlagsHost[] = "flags";
+const char kChromeUIFlashHost[] = "flash";
+const char kChromeUIGpuHost[] = "gpu";
+const char kChromeUIGpuInternalsHost[] = "gpu-internals";
+const char kChromeUIHangHost[] = "hang";
+const char kChromeUIHelpFrameHost[] = "help-frame";
+const char kChromeUIHelpHost[] = "help";
+const char kChromeUIHistoryHost[] = "history";
+const char kChromeUIHistoryFrameHost[] = "history-frame";
+const char kChromeUIInputWindowDialogHost[] = "input-window-dialog";
+const char kChromeUIInspectHost[] = "inspect";
+const char kChromeUIInstantHost[] = "instant";
+const char kChromeUIIPCHost[] = "ipc";
+const char kChromeUIKeyboardHost[] = "keyboard";
+const char kChromeUIKillHost[] = "kill";
+const char kChromeUIMediaInternalsHost[] = "media-internals";
+const char kChromeUIMemoryHost[] = "memory";
+const char kChromeUIMemoryRedirectHost[] = "memory-redirect";
+const char kChromeUIMetroFlowHost[] = "make-metro";
+const char kChromeUINaClHost[] = "nacl";
+const char kChromeUINetInternalsHost[] = "net-internals";
+const char kChromeUINewTabHost[] = "newtab";
+const char kChromeUIOmniboxHost[] = "omnibox";
+const char kChromeUIPerformanceMonitorHost[] = "performance";
+const char kChromeUIPluginsHost[] = "plugins";
+const char kChromeUIPolicyHost[] = "policy";
+const char kChromeUIPredictorsHost[] = "predictors";
+const char kChromeUIPrintHost[] = "print";
+const char kChromeUIProfilerHost[] = "profiler";
+const char kChromeUIQuotaInternalsHost[] = "quota-internals";
+const char kChromeUIResourcesHost[] = "resources";
+const char kChromeUISessionFaviconHost[] = "session-favicon";
+const char kChromeUISettingsHost[] = "settings";
+const char kChromeUISettingsFrameHost[] = "settings-frame";
+const char kChromeUIShorthangHost[] = "shorthang";
+const char kChromeUISuggestionsInternalsHost[] = "suggestions-internals";
+const char kChromeUISSLClientCertificateSelectorHost[] = "select-cert";
+const char kChromeUIStatsHost[] = "stats";
+const char kChromeUISyncHost[] = "sync";
+const char kChromeUISyncInternalsHost[] = "sync-internals";
+const char kChromeUISyncPromoHost[] = "signin";
+const char kChromeUISyncResourcesHost[] = "syncresources";
+const char kChromeUITaskManagerHost[] = "tasks";
+const char kChromeUITermsHost[] = "terms";
+const char kChromeUIThemeHost[] = "theme";
+const char kChromeUIThumbnailHost[] = "thumb";
+const char kChromeUITouchIconHost[] = "touch-icon";
+const char kChromeUITracingHost[] = "tracing";
+const char kChromeUIUberFrameHost[] = "uber-frame";
+const char kChromeUIUberHost[] = "chrome";
+const char kChromeUIVersionHost[] = "version";
+const char kChromeUIWorkersHost[] = "workers";
+
+const char kChromeUIScreenshotPath[] = "screenshots";
+const char kChromeUIThemePath[] = "theme";
+
+#if defined(OS_ANDROID)
+const char kChromeUIWelcomeHost[] = "welcome";
+#endif
+
+#if defined(OS_LINUX) || defined(OS_OPENBSD)
+const char kChromeUILinuxProxyConfigHost[] = "linux-proxy-config";
+const char kChromeUISandboxHost[] = "sandbox";
+#endif
+
+#if defined(OS_CHROMEOS)
+const char kChromeUIActivationMessageHost[] = "activationmessage";
+const char kChromeUIChooseMobileNetworkHost[] = "choose-mobile-network";
+const char kChromeUICryptohomeHost[] = "cryptohome";
+const char kChromeUIDiagnosticsHost[] = "diagnostics";
+const char kChromeUIDiscardsHost[] = "discards";
+const char kChromeUIIdleLogoutDialogHost[] = "idle-logout";
+const char kChromeUIImageBurnerHost[] = "imageburner";
+const char kChromeUIKeyboardOverlayHost[] = "keyboardoverlay";
+const char kChromeUILockScreenHost[] = "lock";
+const char kChromeUILoginContainerHost[] = "login-container";
+const char kChromeUILoginHost[] = "login";
+const char kChromeUIMediaplayerHost[] = "mediaplayer";
+const char kChromeUIMobileSetupHost[] = "mobilesetup";
+const char kChromeUINetworkHost[] = "network";
+const char kChromeUIOobeHost[] = "oobe";
+const char kChromeUIOSCreditsHost[] = "os-credits";
+const char kChromeUIProxySettingsHost[] = "proxy-settings";
+const char kChromeUIRegisterPageHost[] = "register";
+const char kChromeUIRotateHost[] = "rotate";
+const char kChromeUISimUnlockHost[] = "sim-unlock";
+const char kChromeUISlideshowHost[] = "slideshow";
+const char kChromeUISystemInfoHost[] = "system";
+const char kChromeUIUserImageHost[] = "userimage";
+const char kChromeUIWallpaperHost[] = "wallpapers";
+const char kChromeUIWallpaperImageHost[] = "wallpaper";
+const char kChromeUIWallpaperThumbnailHost[] = "wallpaper-thumb";
+
+const char kChromeUIMenu[] = "menu";
+const char kChromeUINetworkMenu[] = "network-menu";
+const char kChromeUIWrenchMenu[] = "wrench-menu";
+
+const char kEULAPathFormat[] = "/usr/share/chromeos-assets/eula/%s/eula.html";
+const char kOemEulaURLPath[] = "oem";
+#endif
+
+#if defined(USE_ASH)
+const char kChromeUITransparencyHost[] = "transparency";
+#endif
+
+#if defined(FILE_MANAGER_EXTENSION)
+const char kChromeUIFileManagerHost[] = "files";
+#endif
+
+#if (defined(OS_LINUX) && defined(TOOLKIT_VIEWS)) || defined(USE_AURA)
+const char kChromeUICollectedCookiesHost[] = "collected-cookies";
+const char kChromeUIHttpAuthHost[] = "http-auth";
+const char kChromeUITabModalConfirmDialogHost[] = "tab-modal-confirm-dialog";
+#endif
+
+// Option sub pages.
+// Add sub page paths to kChromeSettingsSubPages in builtin_provider.cc to be
+// listed by the built-in AutocompleteProvider.
+const char kAutofillSubPage[] = "autofill";
+const char kClearBrowserDataSubPage[] = "clearBrowserData";
+const char kContentSettingsExceptionsSubPage[] = "contentExceptions";
+const char kContentSettingsSubPage[] = "content";
+const char kExtensionsSubPage[] = "extensions";
+const char kHandlerSettingsSubPage[] = "handlers";
+const char kImportDataSubPage[] = "importData";
+const char kLanguageOptionsSubPage[] = "languages";
+const char kManageProfileSubPage[] = "manageProfile";
+const char kPasswordManagerSubPage[] = "passwords";
+const char kSearchEnginesSubPage[] = "searchEngines";
+const char kSearchSubPage[] = "search";
+const char kSyncSetupSubPage[] = "syncSetup";
+const char kSyncSetupForceLoginSubPage[] = "syncSetup#forceLogin";
+#if defined(OS_CHROMEOS)
+const char kInternetOptionsSubPage[] = "internet";
+const char kBluetoothAddDeviceSubPage[] = "bluetooth";
+#endif
+
+// Extension sub pages.
+const char kExtensionConfigureCommandsSubPage[] = "configureCommands";
+
+const char kExtensionInvalidRequestURL[] = "chrome-extension://invalid/";
+const char kExtensionResourceInvalidRequestURL[] =
+    "chrome-extension-resource://invalid/";
+
+const char kSyncGoogleDashboardURL[] =
+    "https://www.google.com/settings/chrome/sync/";
+
+const char kAutoPasswordGenerationLearnMoreURL[] =
+    "https://support.google.com/chrome/?p=ui_generate_password";
+
+
+const char kPasswordManagerLearnMoreURL[] =
+#if defined(OS_CHROMEOS)
+    "https://support.google.com/chromeos/?p=settings_password";
+#else
+    "https://support.google.com/chrome/?p=settings_password";
+#endif
+
+const char kChromeHelpViaKeyboardURL[] =
+#if defined(OS_CHROMEOS)
+#if defined(OFFICIAL_BUILD)
+    "chrome-extension://honijodknafkokifofgiaalefdiedpko/main.html";
+#else
+    "https://support.google.com/chromeos/?p=help&ctx=keyboard";
+#endif  // defined(OFFICIAL_BUILD
+#else
+    "https://support.google.com/chrome/?p=help&ctx=keyboard";
+#endif  // defined(OS_CHROMEOS)
+
+const char kChromeHelpViaMenuURL[] =
+#if defined(OS_CHROMEOS)
+#if defined(OFFICIAL_BUILD)
+    "chrome-extension://honijodknafkokifofgiaalefdiedpko/main.html";
+#else
+    "https://support.google.com/chromeos/?p=help&ctx=menu";
+#endif  // defined(OFFICIAL_BUILD
+#else
+    "https://support.google.com/chrome/?p=help&ctx=menu";
+#endif  // defined(OS_CHROMEOS)
+
+const char kChromeHelpViaWebUIURL[] =
+#if defined(OS_CHROMEOS)
+#if defined(OFFICIAL_BUILD)
+    "chrome-extension://honijodknafkokifofgiaalefdiedpko/main.html";
+#else
+    "https://support.google.com/chromeos/?p=help&ctx=settings";
+#endif  // defined(OFFICIAL_BUILD
+#else
+    "https://support.google.com/chrome/?p=help&ctx=settings";
+#endif  // defined(OS_CHROMEOS)
+
+const char kChromeSyncLearnMoreURL[] =
+#if defined(OS_CHROMEOS)
+    "http://support.google.com/chromeos/bin/answer.py?answer=165139";
+#else
+    "http://support.google.com/chrome/bin/answer.py?answer=165139";
+#endif
+
+const char kSettingsSearchHelpURL[] =
+#if defined(OS_CHROMEOS)
+    "https://support.google.com/chromeos/?p=settings_search_help";
+#else
+    "https://support.google.com/chrome/?p=settings_search_help";
+#endif
+
+const char kAboutGoogleTranslateURL[] =
+#if defined(OS_CHROMEOS)
+    "https://support.google.com/chromeos/?p=ib_translation_bar";
+#else
+    "https://support.google.com/chrome/?p=ib_translation_bar";
+#endif
+
+const char kAutofillHelpURL[] =
+#if defined(OS_CHROMEOS)
+    "https://support.google.com/chromeos/?p=settings_autofill";
+#else
+    "https://support.google.com/chrome/?p=settings_autofill";
+#endif
+
+const char kOmniboxLearnMoreURL[] =
+#if defined(OS_CHROMEOS)
+    "https://support.google.com/chromeos/?p=settings_omnibox";
+#else
+    "https://support.google.com/chrome/?p=settings_omnibox";
+#endif
+
+const char kInstantLearnMoreURL[] =
+#if defined(OS_CHROMEOS)
+    "https://support.google.com/chromeos/?p=settings_instant_policy";
+#else
+    "https://support.google.com/chrome/?p=settings_instant_policy";
+#endif
+
+const char kPageInfoHelpCenterURL[] =
+#if defined(OS_CHROMEOS)
+    "https://support.google.com/chromeos/?p=ui_security_indicator";
+#else
+    "https://support.google.com/chrome/?p=ui_security_indicator";
+#endif
+
+const char kCrashReasonURL[] =
+#if defined(OS_CHROMEOS)
+    "https://support.google.com/chromeos/?p=e_awsnap";
+#else
+    "https://support.google.com/chrome/?p=e_awsnap";
+#endif
+
+const char kKillReasonURL[] =
+#if defined(OS_CHROMEOS)
+    "https://support.google.com/chromeos/?p=e_deadjim";
+#else
+    "https://support.google.com/chrome/?p=e_deadjim";
+#endif
+
+const char kPrivacyLearnMoreURL[] =
+#if defined(OS_CHROMEOS)
+    "https://support.google.com/chromeos/?p=settings_privacy";
+#else
+    "https://support.google.com/chrome/?p=settings_privacy";
+#endif
+
+const char kDoNotTrackLearnMoreURL[] =
+#if defined(OS_CHROMEOS)
+    "https://support.google.com/chromeos/?p=settings_do_not_track";
+#else
+    "https://support.google.com/chrome/?p=settings_do_not_track";
+#endif
+
+const char kChromiumProjectURL[] = "http://code.google.com/chromium/";
+
+const char kLearnMoreReportingURL[] =
+    "https://support.google.com/chrome/?p=ui_usagestat";
+
+const char kOutdatedPluginLearnMoreURL[] =
+    "https://support.google.com/chrome/?p=ib_outdated_plugin";
+
+const char kBlockedPluginLearnMoreURL[] =
+    "https://support.google.com/chrome/?p=ib_blocked_plugin";
+
+const char kSpeechInputAboutURL[] =
+    "https://support.google.com/chrome/?p=ui_speech_input";
+
+const char kLearnMoreRegisterProtocolHandlerURL[] =
+    "https://support.google.com/chrome/?p=ib_protocol_handler";
+
+const char kSyncLearnMoreURL[] =
+    "https://support.google.com/chrome/?p=settings_sign_in";
+
+const char kDownloadScanningLearnMoreURL[] =
+    "https://support.google.com/chrome/?p=ib_download_scan";
+
+const char kSyncEverythingLearnMoreURL[] =
+    "https://support.google.com/chrome/?p=settings_sync_all";
+
+const char kCloudPrintLearnMoreURL[] =
+#if defined(OS_CHROMEOS)
+    "https://support.google.com/chromeos/?p=settings_cloud_print";
+#else
+    "https://support.google.com/chrome/?p=settings_cloud_print";
+#endif
+
+const char kInvalidPasswordHelpURL[] =
+    "https://support.google.com/accounts/bin/answer.py?ctx=ch&answer=27444";
+
+const char kCanNotAccessAccountURL[] =
+    "https://support.google.com/accounts/bin/answer.py?answer=48598";
+
+const char kSyncEncryptionHelpURL[] =
+#if defined(OS_CHROMEOS)
+    "https://support.google.com/chromeos/?p=settings_encryption";
+#else
+    "https://support.google.com/chrome/?p=settings_encryption";
+#endif
+
+const char kSyncErrorsHelpURL[] =
+    "https://support.google.com/chrome/?p=settings_sync_error";
+
+const char kSyncCreateNewAccountURL[] =
+    "https://accounts.google.com/NewAccount?service=chromiumsync";
+
+const char kChromeToMobileLearnMoreURL[] =
+    "https://support.google.com/chrome/?p=ib_chrome_to_mobile";
+
+const char kSideloadWipeoutHelpURL[] =
+    "https://support.google.com/chrome/?p=ui_remove_non_cws_extensions";
+
+#if defined(OS_CHROMEOS)
+const char kNaturalScrollHelpURL[] =
+    "https://support.google.com/chromeos/?p=simple_scrolling";
+#endif
+
+const char* const kChromeDebugURLs[] = {
+  kChromeUICrashURL,
+  kChromeUIKillURL,
+  kChromeUIHangURL,
+  content::kChromeUIShorthangURL,
+  kChromeUIGpuCleanURL,
+  kChromeUIGpuCrashURL,
+  kChromeUIGpuHangURL,
+};
+const int kNumberOfChromeDebugURLs =
+    static_cast<int>(arraysize(kChromeDebugURLs));
+
+const char kExtensionScheme[] = "chrome-extension";
+const char kExtensionResourceScheme[] = "chrome-extension-resource";
+
+// Google SafeSearch query parameters.
+const char kSafeSearchSafeParameter[] = "safe=active";
+const char kSafeSearchSsuiParameter[] = "ssui=on";
+
+}  // namespace chrome
diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h
new file mode 100644
index 0000000..3b7dd38
--- /dev/null
+++ b/chrome/common/url_constants.h
@@ -0,0 +1,422 @@
+// Copyright (c) 2012 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.
+
+// Contains constants for known URLs and portions thereof.
+
+#ifndef CHROME_COMMON_URL_CONSTANTS_H_
+#define CHROME_COMMON_URL_CONSTANTS_H_
+
+#include "build/build_config.h"
+#include "content/public/common/url_constants.h"
+
+namespace chrome {
+
+// TODO(msw): Resolve chrome_frame dependency on these constants.
+extern const char kAboutPluginsURL[];
+extern const char kAboutVersionURL[];
+
+// chrome: URLs (including schemes). Should be kept in sync with the
+// components below.
+extern const char kChromeUIAboutURL[];
+extern const char kChromeUIBookmarksURL[];
+extern const char kChromeUICertificateViewerURL[];
+extern const char kChromeUIChromeURLsURL[];
+extern const char kChromeUICloudPrintResourcesURL[];
+extern const char kChromeUIConflictsURL[];
+extern const char kChromeUIConstrainedHTMLTestURL[];
+extern const char kChromeUICrashesURL[];
+extern const char kChromeUICreditsURL[];
+extern const char kChromeUIDevToolsURL[];
+extern const char kChromeUIDownloadsURL[];
+extern const char kChromeUIEditSearchEngineDialogURL[];
+extern const char kChromeUIExtensionActivityURL[];
+extern const char kChromeUIExtensionIconURL[];
+extern const char kChromeUIExtensionInfoURL[];
+extern const char kChromeUIExtensionsFrameURL[];
+extern const char kChromeUIExtensionsURL[];
+extern const char kChromeUIFaviconURL[];
+extern const char kChromeUIFeedbackURL[];
+extern const char kChromeUIFlagsURL[];
+extern const char kChromeUIFlashURL[];
+extern const char kChromeUIHelpFrameURL[];
+extern const char kChromeUIHistoryURL[];
+extern const char kChromeUIHistoryFrameURL[];
+extern const char kChromeUIInputWindowDialogURL[];
+extern const char kChromeUIInspectURL[];
+extern const char kChromeUIInstantURL[];
+extern const char kChromeUIIPCURL[];
+extern const char kChromeUIKeyboardURL[];
+extern const char kChromeUIMemoryRedirectURL[];
+extern const char kChromeUIMemoryURL[];
+extern const char kChromeUIMetroFlowURL[];
+extern const char kChromeUINaClURL[];
+extern const char kChromeUINetInternalsURL[];
+extern const char kChromeUINewProfile[];
+extern const char kChromeUINewTabURL[];
+extern const char kChromeUIOmniboxURL[];
+extern const char kChromeUIPerformanceMonitorURL[];
+extern const char kChromeUIPluginsURL[];
+extern const char kChromeUIPolicyURL[];
+extern const char kChromeUIPrintURL[];
+extern const char kChromeUISessionFaviconURL[];
+extern const char kChromeUISettingsURL[];
+extern const char kChromeUISettingsFrameURL[];
+extern const char kChromeUISuggestionsInternalsURL[];
+extern const char kChromeUISSLClientCertificateSelectorURL[];
+extern const char kChromeUISyncPromoURL[];
+extern const char kChromeUITaskManagerURL[];
+extern const char kChromeUITermsURL[];
+extern const char kChromeUIThemeURL[];
+extern const char kChromeUIThumbnailURL[];
+extern const char kChromeUIUberURL[];
+extern const char kChromeUIUberFrameURL[];
+extern const char kChromeUIVersionURL[];
+
+#if defined(OS_ANDROID)
+extern const char kChromeUIWelcomeURL[];
+#endif
+
+#if defined(OS_CHROMEOS)
+extern const char kChromeUIActivationMessage[];
+extern const char kChromeUIChooseMobileNetworkURL[];
+extern const char kChromeUIDiagnosticsURL[];
+extern const char kChromeUIDiscardsURL[];
+extern const char kChromeUIIdleLogoutDialogURL[];
+extern const char kChromeUIImageBurnerURL[];
+extern const char kChromeUIKeyboardOverlayURL[];
+extern const char kChromeUILockScreenURL[];
+extern const char kChromeUIMediaplayerURL[];
+extern const char kChromeUIMobileSetupURL[];
+extern const char kChromeUIOobeURL[];
+extern const char kChromeUIOSCreditsURL[];
+extern const char kChromeUIProxySettingsURL[];
+extern const char kChromeUIRegisterPageURL[];
+extern const char kChromeUISimUnlockURL[];
+extern const char kChromeUISlideshowURL[];
+extern const char kChromeUISystemInfoURL[];
+extern const char kChromeUITermsOemURL[];
+extern const char kChromeUIUserImageURL[];
+extern const char kChromeUIWallpaperURL[];
+extern const char kChromeUIWallpaperImageURL[];
+extern const char kChromeUIWallpaperThumbnailURL[];
+#endif
+
+#if defined(USE_ASH)
+extern const char kChromeUITransparencyURL[];
+#endif
+
+#if defined(FILE_MANAGER_EXTENSION)
+extern const char kChromeUIFileManagerURL[];
+#endif
+
+#if defined(USE_AURA)
+extern const char kChromeUIGestureConfigURL[];
+extern const char kChromeUIGestureConfigHost[];
+#endif
+
+#if (defined(OS_LINUX) && defined(TOOLKIT_VIEWS)) || defined(USE_AURA)
+extern const char kChromeUICollectedCookiesURL[];
+extern const char kChromeUIHttpAuthURL[];
+extern const char kChromeUITabModalConfirmDialogURL[];
+#endif
+
+// chrome components of URLs. Should be kept in sync with the full URLs above.
+extern const char kChromeUIAboutHost[];
+extern const char kChromeUIAboutPageFrameHost[];
+extern const char kChromeUIBlankHost[];
+extern const char kChromeUIBookmarksHost[];
+extern const char kChromeUICacheHost[];
+extern const char kChromeUICertificateViewerHost[];
+extern const char kChromeUIChromeURLsHost[];
+extern const char kChromeUICloudPrintResourcesHost[];
+extern const char kChromeUICloudPrintSetupHost[];
+extern const char kChromeUIConflictsHost[];
+extern const char kChromeUIConstrainedHTMLTestHost[];
+extern const char kChromeUICrashesHost[];
+extern const char kChromeUICrashHost[];
+extern const char kChromeUICreditsHost[];
+extern const char kChromeUIDefaultHost[];
+extern const char kChromeUIDevToolsHost[];
+extern const char kChromeUIDialogHost[];
+extern const char kChromeUIDNSHost[];
+extern const char kChromeUIDownloadsHost[];
+extern const char kChromeUIDriveInternalsHost[];
+extern const char kChromeUIEditSearchEngineDialogHost[];
+extern const char kChromeUIExtensionActivityHost[];
+extern const char kChromeUIExtensionIconHost[];
+extern const char kChromeUIExtensionInfoHost[];
+extern const char kChromeUIExtensionsFrameHost[];
+extern const char kChromeUIExtensionsHost[];
+extern const char kChromeUIFaviconHost[];
+extern const char kChromeUIFeedbackHost[];
+extern const char kChromeUIFlagsHost[];
+extern const char kChromeUIFlashHost[];
+extern const char kChromeUIHelpFrameHost[];
+extern const char kChromeUIHelpHost[];
+extern const char kChromeUIGpuHost[];
+extern const char kChromeUIGpuInternalsHost[];
+extern const char kChromeUIHangHost[];
+extern const char kChromeUIHistoryHost[];
+extern const char kChromeUIHistoryFrameHost[];
+extern const char kChromeUIInputWindowDialogHost[];
+extern const char kChromeUIInspectHost[];
+extern const char kChromeUIInstantHost[];
+extern const char kChromeUIIPCHost[];
+extern const char kChromeUIKeyboardHost[];
+extern const char kChromeUIKillHost[];
+extern const char kChromeUIMediaInternalsHost[];
+extern const char kChromeUIMemoryHost[];
+extern const char kChromeUIMemoryRedirectHost[];
+extern const char kChromeUIMetroFlowHost[];
+extern const char kChromeUINaClHost[];
+extern const char kChromeUINetInternalsHost[];
+extern const char kChromeUINewTabHost[];
+extern const char kChromeUIOmniboxHost[];
+extern const char kChromeUIPerformanceMonitorHost[];
+extern const char kChromeUIPluginsHost[];
+extern const char kChromeUIPolicyHost[];
+extern const char kChromeUIPredictorsHost[];
+extern const char kChromeUIPrintHost[];
+extern const char kChromeUIProfilerHost[];
+extern const char kChromeUIQuotaInternalsHost[];
+extern const char kChromeUIResourcesHost[];
+extern const char kChromeUISessionFaviconHost[];
+extern const char kChromeUISettingsHost[];
+extern const char kChromeUISettingsFrameHost[];
+extern const char kChromeUIShorthangHost[];
+extern const char kChromeUISuggestionsInternalsHost[];
+extern const char kChromeUISSLClientCertificateSelectorHost[];
+extern const char kChromeUIStatsHost[];
+extern const char kChromeUISyncHost[];
+extern const char kChromeUISyncInternalsHost[];
+extern const char kChromeUISyncPromoHost[];
+extern const char kChromeUISyncResourcesHost[];
+extern const char kChromeUITaskManagerHost[];
+extern const char kChromeUITermsHost[];
+extern const char kChromeUIThemeHost[];
+extern const char kChromeUIThumbnailHost[];
+extern const char kChromeUITouchIconHost[];
+extern const char kChromeUITracingHost[];
+extern const char kChromeUIUberFrameHost[];
+extern const char kChromeUIUberHost[];
+extern const char kChromeUIVersionHost[];
+extern const char kChromeUIWorkersHost[];
+
+extern const char kChromeUIScreenshotPath[];
+extern const char kChromeUIThemePath[];
+
+#if defined(OS_ANDROID)
+extern const char kChromeUIWelcomeHost[];
+#endif
+
+#if defined(OS_LINUX) || defined(OS_OPENBSD)
+extern const char kChromeUILinuxProxyConfigHost[];
+extern const char kChromeUISandboxHost[];
+#endif
+
+#if defined(OS_CHROMEOS)
+extern const char kChromeUIActivationMessageHost[];
+extern const char kChromeUIChooseMobileNetworkHost[];
+extern const char kChromeUICryptohomeHost[];
+extern const char kChromeUIDiagnosticsHost[];
+extern const char kChromeUIDiscardsHost[];
+extern const char kChromeUIIdleLogoutDialogHost[];
+extern const char kChromeUIImageBurnerHost[];
+extern const char kChromeUIKeyboardOverlayHost[];
+extern const char kChromeUILockScreenHost[];
+extern const char kChromeUILoginContainerHost[];
+extern const char kChromeUILoginHost[];
+extern const char kChromeUIMediaplayerHost[];
+extern const char kChromeUIMobileSetupHost[];
+extern const char kChromeUINetworkHost[];
+extern const char kChromeUIOobeHost[];
+extern const char kChromeUIOSCreditsHost[];
+extern const char kChromeUIProxySettingsHost[];
+extern const char kChromeUIRegisterPageHost[];
+extern const char kChromeUIRotateHost[];
+extern const char kChromeUISimUnlockHost[];
+extern const char kChromeUISlideshowHost[];
+extern const char kChromeUISystemInfoHost[];
+extern const char kChromeUIUserImageHost[];
+extern const char kChromeUIWallpaperHost[];
+extern const char kChromeUIWallpaperImageHost[];
+extern const char kChromeUIWallpaperThumbnailHost[];
+
+extern const char kChromeUIMenu[];
+extern const char kChromeUINetworkMenu[];
+extern const char kChromeUIWrenchMenu[];
+
+extern const char kEULAPathFormat[];
+extern const char kOemEulaURLPath[];
+#endif
+
+#if defined(USE_ASH)
+extern const char kChromeUITransparencyHost[];
+#endif
+
+#if defined(FILE_MANAGER_EXTENSION)
+extern const char kChromeUIFileManagerHost[];
+#endif
+
+#if (defined(OS_LINUX) && defined(TOOLKIT_VIEWS)) || defined(USE_AURA)
+extern const char kChromeUICollectedCookiesHost[];
+extern const char kChromeUIHttpAuthHost[];
+extern const char kChromeUITabModalConfirmDialogHost[];
+#endif
+
+// Options sub-pages.
+extern const char kAutofillSubPage[];
+extern const char kClearBrowserDataSubPage[];
+extern const char kContentSettingsExceptionsSubPage[];
+extern const char kContentSettingsSubPage[];
+extern const char kExtensionsSubPage[];
+extern const char kHandlerSettingsSubPage[];
+extern const char kImportDataSubPage[];
+extern const char kLanguageOptionsSubPage[];
+extern const char kManageProfileSubPage[];
+extern const char kPasswordManagerSubPage[];
+extern const char kSearchEnginesSubPage[];
+extern const char kSearchSubPage[];
+extern const char kSyncSetupForceLoginSubPage[];
+extern const char kSyncSetupSubPage[];
+#if defined(OS_CHROMEOS)
+extern const char kInternetOptionsSubPage[];
+extern const char kBluetoothAddDeviceSubPage[];
+#endif
+
+// Extensions sub pages.
+extern const char kExtensionConfigureCommandsSubPage[];
+
+// URLs used to indicate that an extension resource load request
+// was invalid.
+extern const char kExtensionInvalidRequestURL[];
+extern const char kExtensionResourceInvalidRequestURL[];
+
+extern const char kSyncGoogleDashboardURL[];
+
+// "Learn more" URL for the auto password generation.
+extern const char kAutoPasswordGenerationLearnMoreURL[];
+
+extern const char kPasswordManagerLearnMoreURL[];
+
+// General help links for Chrome, opened using various actions.
+extern const char kChromeHelpViaKeyboardURL[];
+extern const char kChromeHelpViaMenuURL[];
+extern const char kChromeHelpViaWebUIURL[];
+
+// "Learn more" URL for the one click signin infobar.
+extern const char kChromeSyncLearnMoreURL[];
+
+// Help URL for the settings page's search feature.
+extern const char kSettingsSearchHelpURL[];
+
+// "About" URL for the translate bar's options menu.
+extern const char kAboutGoogleTranslateURL[];
+
+// Help URL for the Autofill dialog.
+extern const char kAutofillHelpURL[];
+
+// Help URL for the Omnibox setting.
+extern const char kOmniboxLearnMoreURL[];
+
+// "Learn more" URL for the Instant feature.
+extern const char kInstantLearnMoreURL[];
+
+// "What do these mean?" URL for the Page Info bubble.
+extern const char kPageInfoHelpCenterURL[];
+
+// "Learn more" URL for "Aw snap" page.
+extern const char kCrashReasonURL[];
+
+// "Learn more" URL for killed tab page.
+extern const char kKillReasonURL[];
+
+// "Learn more" URL for the Privacy section under Options.
+extern const char kPrivacyLearnMoreURL[];
+
+// "Learn more" URL for the "Do not track" setting in the privacy section.
+extern const char kDoNotTrackLearnMoreURL[];
+
+// The URL for the Chromium project used in the About dialog.
+extern const char kChromiumProjectURL[];
+
+// The URL for the "Learn more" page for the usage/crash reporting option in the
+// first run dialog.
+extern const char kLearnMoreReportingURL[];
+
+// The URL for the "Learn more" page for the outdated plugin infobar.
+extern const char kOutdatedPluginLearnMoreURL[];
+
+// The URL for the "Learn more" page for the blocked plugin infobar.
+extern const char kBlockedPluginLearnMoreURL[];
+
+// The URL for the "About Voice Recognition" menu item.
+extern const char kSpeechInputAboutURL[];
+
+// The URL for the "Learn more" page for register protocol handler infobars.
+extern const char kLearnMoreRegisterProtocolHandlerURL[];
+
+// The URL for the "Learn more" page for sync setup on the personal stuff page.
+extern const char kSyncLearnMoreURL[];
+
+// The URL for the "Learn more" page for download scanning.
+extern const char kDownloadScanningLearnMoreURL[];
+
+// The URL for the "Learn more" page on the sync setup dialog, when syncing
+// everything.
+extern const char kSyncEverythingLearnMoreURL[];
+
+// The URL for information on how to recover your password.
+extern const char kInvalidPasswordHelpURL[];
+
+// The URL for information on what to do if you can't sign in to your Google
+// account.
+extern const char kCanNotAccessAccountURL[];
+
+// The URL for the "Learn more" page on sync encryption.
+extern const char kSyncEncryptionHelpURL[];
+
+// The URL for the "Learn more" link when there is a sync error.
+extern const char kSyncErrorsHelpURL[];
+
+// The URL to create a new Google account via sync.
+extern const char kSyncCreateNewAccountURL[];
+
+// The URL for the "Learn more" link in the Chrome To Mobile bubble.
+extern const char kChromeToMobileLearnMoreURL[];
+
+// The URL for the help article explaining sideload wipeout in more details.
+extern const char kSideloadWipeoutHelpURL[];
+
+#if defined(OS_CHROMEOS)
+// The URL for the "Learn more" link for natural scrolling on ChromeOS.
+extern const char kNaturalScrollHelpURL[];
+#endif
+
+// "Debug" pages which are dangerous and not for general consumption.
+extern const char* const kChromeDebugURLs[];
+extern const int kNumberOfChromeDebugURLs;
+
+// Canonical schemes you can use as input to GURL.SchemeIs().
+extern const char kExtensionScheme[];
+
+// Canonical schemes you can use as input to GURL.SchemeIs().
+extern const char kExtensionResourceScheme[];
+
+#if defined(OS_CHROMEOS)
+extern const char kDriveScheme[];
+#endif
+
+#if defined(OS_CHROMEOS)
+// "Learn more" URL for the Cloud Print section under Options.
+extern const char kCloudPrintLearnMoreURL[];
+#endif
+
+// Parameters that get appended to force SafeSearch.
+extern const char kSafeSearchSafeParameter[];
+extern const char kSafeSearchSsuiParameter[];
+
+}  // namespace chrome
+
+#endif  // CHROME_COMMON_URL_CONSTANTS_H_
diff --git a/chrome/common/view_type.cc b/chrome/common/view_type.cc
new file mode 100644
index 0000000..5824b4c
--- /dev/null
+++ b/chrome/common/view_type.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/view_type.h"
+
+namespace chrome {
+
+const char kViewTypeTabContents[] = "TAB";
+const char kViewTypeBackgroundPage[] = "BACKGROUND";
+const char kViewTypePopup[] = "POPUP";
+const char kViewTypePanel[] = "PANEL";
+const char kViewTypeInfobar[] = "INFOBAR";
+const char kViewTypeNotification[] = "NOTIFICATION";
+const char kViewTypeExtensionDialog[] = "EXTENSION_DIALOG";
+const char kViewTypeAppShell[] = "SHELL";
+const char kViewTypeAll[] = "ALL";
+
+}  // namespace chrome
diff --git a/chrome/common/view_type.h b/chrome/common/view_type.h
new file mode 100644
index 0000000..c7f303a
--- /dev/null
+++ b/chrome/common/view_type.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_VIEW_TYPE_H_
+#define CHROME_COMMON_VIEW_TYPE_H_
+
+namespace chrome {
+
+// Icky RTTI used by a few systems to distinguish the host type of a given
+// RenderViewHost or WebContents.
+//
+// TODO(aa): Remove this and teach those systems to keep track of their own
+// data.
+enum ViewType {
+  VIEW_TYPE_INVALID,
+  VIEW_TYPE_APP_SHELL,
+  VIEW_TYPE_BACKGROUND_CONTENTS,
+  VIEW_TYPE_EXTENSION_BACKGROUND_PAGE,
+  VIEW_TYPE_EXTENSION_DIALOG,
+  VIEW_TYPE_EXTENSION_INFOBAR,
+  VIEW_TYPE_EXTENSION_POPUP,
+  VIEW_TYPE_NOTIFICATION,
+  VIEW_TYPE_PANEL,
+  VIEW_TYPE_TAB_CONTENTS,
+};
+
+// Constant strings corresponding to the Type enumeration values.  Used
+// when converting JS arguments.
+extern const char kViewTypeAll[];
+extern const char kViewTypeAppShell[];
+extern const char kViewTypeBackgroundPage[];
+extern const char kViewTypeExtensionDialog[];
+extern const char kViewTypeInfobar[];
+extern const char kViewTypeNotification[];
+extern const char kViewTypePanel[];
+extern const char kViewTypePopup[];
+extern const char kViewTypeTabContents[];
+
+}  // namespace chrome
+
+#endif  // CHROME_COMMON_VIEW_TYPE_H_
diff --git a/chrome/common/visitedlink_common.cc b/chrome/common/visitedlink_common.cc
new file mode 100644
index 0000000..7c21054
--- /dev/null
+++ b/chrome/common/visitedlink_common.cc
@@ -0,0 +1,98 @@
+// 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.
+
+#include "chrome/common/visitedlink_common.h"
+
+#include <string.h>  // for memset()
+
+#include "base/logging.h"
+#include "base/md5.h"
+#include "googleurl/src/gurl.h"
+
+const VisitedLinkCommon::Fingerprint VisitedLinkCommon::null_fingerprint_ = 0;
+const VisitedLinkCommon::Hash VisitedLinkCommon::null_hash_ = -1;
+
+VisitedLinkCommon::VisitedLinkCommon()
+    : hash_table_(NULL),
+      table_length_(0) {
+  memset(salt_, 0, sizeof(salt_));
+}
+
+VisitedLinkCommon::~VisitedLinkCommon() {
+}
+
+// FIXME: this uses linear probing, it should be replaced with quadratic
+// probing or something better. See VisitedLinkMaster::AddFingerprint
+bool VisitedLinkCommon::IsVisited(const char* canonical_url,
+                                  size_t url_len) const {
+  if (url_len == 0)
+    return false;
+  if (!hash_table_ || table_length_ == 0)
+    return false;
+  return IsVisited(ComputeURLFingerprint(canonical_url, url_len));
+}
+
+bool VisitedLinkCommon::IsVisited(const GURL& url) const {
+  return IsVisited(url.spec().data(), url.spec().size());
+}
+
+bool VisitedLinkCommon::IsVisited(Fingerprint fingerprint) const {
+  // Go through the table until we find the item or an empty spot (meaning it
+  // wasn't found). This loop will terminate as long as the table isn't full,
+  // which should be enforced by AddFingerprint.
+  Hash first_hash = HashFingerprint(fingerprint);
+  Hash cur_hash = first_hash;
+  while (true) {
+    Fingerprint cur_fingerprint = FingerprintAt(cur_hash);
+    if (cur_fingerprint == null_fingerprint_)
+      return false;  // End of probe sequence found.
+    if (cur_fingerprint == fingerprint)
+      return true;  // Found a match.
+
+    // This spot was taken, but not by the item we're looking for, search in
+    // the next position.
+    cur_hash++;
+    if (cur_hash == table_length_)
+      cur_hash = 0;
+    if (cur_hash == first_hash) {
+      // Wrapped around and didn't find an empty space, this means we're in an
+      // infinite loop because AddFingerprint didn't do its job resizing.
+      NOTREACHED();
+      return false;
+    }
+  }
+}
+
+// Uses the top 64 bits of the MD5 sum of the canonical URL as the fingerprint,
+// this is as random as any other subset of the MD5SUM.
+//
+// FIXME: this uses the MD5SUM of the 16-bit character version. For systems
+// where wchar_t is not 16 bits (Linux uses 32 bits, I think), this will not be
+// compatable. We should define explicitly what should happen here across
+// platforms, and convert if necessary (probably to UTF-16).
+
+// static
+VisitedLinkCommon::Fingerprint VisitedLinkCommon::ComputeURLFingerprint(
+    const char* canonical_url,
+    size_t url_len,
+    const uint8 salt[LINK_SALT_LENGTH]) {
+  DCHECK(url_len > 0) << "Canonical URLs should not be empty";
+
+  base::MD5Context ctx;
+  base::MD5Init(&ctx);
+  base::MD5Update(&ctx, base::StringPiece(reinterpret_cast<const char*>(salt),
+                                          LINK_SALT_LENGTH));
+  base::MD5Update(&ctx, base::StringPiece(canonical_url, url_len));
+
+  base::MD5Digest digest;
+  base::MD5Final(&digest, &ctx);
+
+  // This is the same as "return *(Fingerprint*)&digest.a;" but if we do that
+  // direct cast the alignment could be wrong, and we can't access a 64-bit int
+  // on arbitrary alignment on some processors. This reinterpret_casts it
+  // down to a char array of the same size as fingerprint, and then does the
+  // bit cast, which amounts to a memcpy. This does not handle endian issues.
+  return bit_cast<Fingerprint, uint8[8]>(
+      *reinterpret_cast<uint8(*)[8]>(&digest.a));
+}
diff --git a/chrome/common/visitedlink_common.h b/chrome/common/visitedlink_common.h
new file mode 100644
index 0000000..546bd4f
--- /dev/null
+++ b/chrome/common/visitedlink_common.h
@@ -0,0 +1,134 @@
+// Copyright (c) 2006-2008 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.
+
+#ifndef CHROME_COMMON_VISITEDLINK_COMMON_H__
+#define CHROME_COMMON_VISITEDLINK_COMMON_H__
+
+#include <vector>
+
+#include "base/basictypes.h"
+
+class GURL;
+
+// number of bytes in the salt
+#define LINK_SALT_LENGTH 8
+
+// A multiprocess-safe database of the visited links for the browser. There
+// should be exactly one process that has write access (implemented by
+// VisitedLinkMaster), while all other processes should be read-only
+// (implemented by VisitedLinkSlave). These other processes add links by calling
+// the writer process to add them for it. The writer may also notify the readers
+// to replace their table when the table is resized.
+//
+// IPC is not implemented in these classes. This is done through callback
+// functions supplied by the creator of these objects to allow more flexibility,
+// especially for testing.
+//
+// This class defines the common base for these others. We implement accessors
+// for looking things up in the hash table, and for computing hash values and
+// fingerprints. Both the master and the slave inherit from this, and add their
+// own code to set up and change these values as their design requires. The
+// slave pretty much just sets up the shared memory and saves the pointer. The
+// master does a lot of work to manage the table, reading and writing it to and
+// from disk, and resizing it when it gets too full.
+//
+// To ask whether a page is in history, we compute a 64-bit fingerprint of the
+// URL. This URL is hashed and we see if it is in the URL hashtable. If it is,
+// we consider it visited. Otherwise, it is unvisited. Note that it is possible
+// to get collisions, which is the penalty for not storing all URL strings in
+// memory (which could get to be more than we want to have in memory). We use
+// a salt value for the links on one computer so that an attacker can not
+// manually create a link that causes a collision.
+class VisitedLinkCommon {
+ public:
+  // A number that identifies the URL.
+  typedef uint64 Fingerprint;
+  typedef std::vector<Fingerprint> Fingerprints;
+
+  // A hash value of a fingerprint
+  typedef int32 Hash;
+
+  // A fingerprint or hash value that does not exist
+  static const Fingerprint null_fingerprint_;
+  static const Hash null_hash_;
+
+  VisitedLinkCommon();
+  virtual ~VisitedLinkCommon();
+
+  // Returns the fingerprint for the given URL.
+  Fingerprint ComputeURLFingerprint(const char* canonical_url,
+                                    size_t url_len) const {
+    return ComputeURLFingerprint(canonical_url, url_len, salt_);
+  }
+
+  // Looks up the given key in the table. The fingerprint for the URL is
+  // computed if you call one with the string argument. Returns true if found.
+  // Does not modify the hastable.
+  bool IsVisited(const char* canonical_url, size_t url_len) const;
+  bool IsVisited(const GURL& url) const;
+  bool IsVisited(Fingerprint fingerprint) const;
+
+#ifdef UNIT_TEST
+  // Returns statistics about DB usage
+  void GetUsageStatistics(int32* table_size,
+                          VisitedLinkCommon::Fingerprint** fingerprints) {
+    *table_size = table_length_;
+    *fingerprints = hash_table_;
+  }
+#endif
+
+ protected:
+  // This structure is at the beginning of the shared memory so that the slaves
+  // can get stats on the table
+  struct SharedHeader {
+    // see goes into table_length_
+    uint32 length;
+
+    // goes into salt_
+    uint8 salt[LINK_SALT_LENGTH];
+  };
+
+  // Returns the fingerprint at the given index into the URL table. This
+  // function should be called instead of accessing the table directly to
+  // contain endian issues.
+  Fingerprint FingerprintAt(int32 table_offset) const {
+    if (!hash_table_)
+      return null_fingerprint_;
+    return hash_table_[table_offset];
+  }
+
+  // Computes the fingerprint of the given canonical URL. It is static so the
+  // same algorithm can be re-used by the table rebuilder, so you will have to
+  // pass the salt as a parameter. See the non-static version above if you
+  // want to use the current class' salt.
+  static Fingerprint ComputeURLFingerprint(const char* canonical_url,
+                                           size_t url_len,
+                                           const uint8 salt[LINK_SALT_LENGTH]);
+
+  // Computes the hash value of the given fingerprint, this is used as a lookup
+  // into the hashtable.
+  static Hash HashFingerprint(Fingerprint fingerprint, int32 table_length) {
+    if (table_length == 0)
+      return null_hash_;
+    return static_cast<Hash>(fingerprint % table_length);
+  }
+  // Uses the current hashtable.
+  Hash HashFingerprint(Fingerprint fingerprint) const {
+    return HashFingerprint(fingerprint, table_length_);
+  }
+
+  // pointer to the first item
+  VisitedLinkCommon::Fingerprint* hash_table_;
+
+  // the number of items in the hash table
+  int32 table_length_;
+
+  // salt used for each URL when computing the fingerprint
+  uint8 salt_[LINK_SALT_LENGTH];
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(VisitedLinkCommon);
+};
+
+#endif  // CHROME_COMMON_VISITEDLINK_COMMON_H_
diff --git a/chrome/common/web_app_schema.json b/chrome/common/web_app_schema.json
new file mode 100644
index 0000000..fe23966
--- /dev/null
+++ b/chrome/common/web_app_schema.json
@@ -0,0 +1,58 @@
+// Copyright (c) 2010 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.
+
+// This file contains the schema for web app defintion files.
+
+{
+  "type": "object",
+  "properties": {
+    // TODO(aa): Need to figure out what max length the store is using for name
+    // and description.
+    "name": {
+      "type": "string",
+      "minLength": 1,
+      "maxLength": 45
+    },
+    "description": {
+      "type": "string",
+      "maxLength": 132,
+      "optional": true
+    },
+    "launch_url": {
+      "type": "string",
+      "minLength": 1
+    },
+    "launch_container": {
+      "enum": ["tab", "panel"],
+      "optional": true
+    },
+    // TODO(aa): We had problems with a simple array of strings in extensions.
+    // Consider something else.
+    "permissions": {
+      "type": "array",
+      "optional": true,
+      "items": {
+        "type": "string",
+        "minLength": 1
+      }
+    },
+    "urls": {
+      "type": "array",
+      "optional": true,
+      "items": {
+        "type": "string",
+        "minLength": 1
+      }
+    },
+    "icons": {
+      "type": "object",
+      "optional": true,
+      "properties": {
+        "16": { "optional": true, "type": "string", "minLength": 1 },
+        "48": { "optional": true, "type": "string", "minLength": 1 },
+        "128": { "optional": true, "type": "string", "minLength": 1 }
+      }
+    }
+  }
+}
diff --git a/chrome/common/web_apps.cc b/chrome/common/web_apps.cc
new file mode 100644
index 0000000..a0eb1aa
--- /dev/null
+++ b/chrome/common/web_apps.cc
@@ -0,0 +1,333 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/web_apps.h"
+
+#include <string>
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/string16.h"
+#include "base/string_number_conversions.h"
+#include "base/string_split.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/common/json_schema_validator.h"
+#include "googleurl/src/gurl.h"
+#include "grit/common_resources.h"
+#include "grit/generated_resources.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebNodeList.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURL.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/size.h"
+#include "webkit/glue/dom_operations.h"
+
+using WebKit::WebDocument;
+using WebKit::WebElement;
+using WebKit::WebFrame;
+using WebKit::WebNode;
+using WebKit::WebNodeList;
+using WebKit::WebString;
+
+namespace {
+
+// Sizes a single size (the width or height) from a 'sizes' attribute. A size
+// matches must match the following regex: [1-9][0-9]*.
+static int ParseSingleIconSize(const string16& text) {
+  // Size must not start with 0, and be between 0 and 9.
+  if (text.empty() || !(text[0] >= L'1' && text[0] <= L'9'))
+    return 0;
+
+  // Make sure all chars are from 0-9.
+  for (size_t i = 1; i < text.length(); ++i) {
+    if (!(text[i] >= L'0' && text[i] <= L'9'))
+      return 0;
+  }
+  int output;
+  if (!base::StringToInt(text, &output))
+    return 0;
+  return output;
+}
+
+void AddInstallIcon(const WebElement& link,
+                    std::vector<WebApplicationInfo::IconInfo>* icons) {
+  WebString href = link.getAttribute("href");
+  if (href.isNull() || href.isEmpty())
+    return;
+
+  // Get complete url.
+  GURL url = link.document().completeURL(href);
+  if (!url.is_valid())
+    return;
+
+  if (!link.hasAttribute("sizes"))
+    return;
+
+  bool is_any = false;
+  std::vector<gfx::Size> icon_sizes;
+  if (!web_apps::ParseIconSizes(link.getAttribute("sizes"), &icon_sizes,
+                                &is_any) ||
+      is_any ||
+      icon_sizes.size() != 1) {
+    return;
+  }
+  WebApplicationInfo::IconInfo icon_info;
+  icon_info.width = icon_sizes[0].width();
+  icon_info.height = icon_sizes[0].height();
+  icon_info.url = url;
+  icons->push_back(icon_info);
+}
+
+}
+
+const char WebApplicationInfo::kInvalidDefinitionURL[] =
+    "Invalid application definition URL. Must be a valid relative URL or "
+    "an absolute URL with the same origin as the document.";
+const char WebApplicationInfo::kInvalidLaunchURL[] =
+    "Invalid value for property 'launch_url'. Must be a valid relative URL or "
+    "an absolute URL with the same origin as the application definition.";
+const char WebApplicationInfo::kInvalidURL[] =
+    "Invalid value for property 'urls[*]'. Must be a valid relative URL or "
+    "an absolute URL with the same origin as the application definition.";
+const char WebApplicationInfo::kInvalidIconURL[] =
+    "Invalid value for property 'icons.*'. Must be a valid relative URL or "
+    "an absolute URL with the same origin as the application definition.";
+
+WebApplicationInfo::WebApplicationInfo() {
+  is_bookmark_app = false;
+  is_offline_enabled = false;
+}
+
+WebApplicationInfo::~WebApplicationInfo() {
+}
+
+
+namespace web_apps {
+
+gfx::Size ParseIconSize(const string16& text) {
+  std::vector<string16> sizes;
+  base::SplitStringDontTrim(text, L'x', &sizes);
+  if (sizes.size() != 2)
+    return gfx::Size();
+
+  return gfx::Size(ParseSingleIconSize(sizes[0]),
+                   ParseSingleIconSize(sizes[1]));
+}
+
+bool ParseIconSizes(const string16& text,
+                    std::vector<gfx::Size>* sizes,
+                    bool* is_any) {
+  *is_any = false;
+  std::vector<string16> size_strings;
+  base::SplitStringAlongWhitespace(text, &size_strings);
+  for (size_t i = 0; i < size_strings.size(); ++i) {
+    if (EqualsASCII(size_strings[i], "any")) {
+      *is_any = true;
+    } else {
+      gfx::Size size = ParseIconSize(size_strings[i]);
+      if (size.width() <= 0 || size.height() <= 0)
+        return false;  // Bogus size.
+      sizes->push_back(size);
+    }
+  }
+  if (*is_any && !sizes->empty()) {
+    // If is_any is true, it must occur by itself.
+    return false;
+  }
+  return (*is_any || !sizes->empty());
+}
+
+bool ParseWebAppFromWebDocument(WebFrame* frame,
+                                WebApplicationInfo* app_info,
+                                string16* error) {
+  WebDocument document = frame->document();
+  if (document.isNull())
+    return true;
+
+  WebElement head = document.head();
+  if (head.isNull())
+    return true;
+
+  GURL document_url = document.url();
+  WebNodeList children = head.childNodes();
+  for (unsigned i = 0; i < children.length(); ++i) {
+    WebNode child = children.item(i);
+    if (!child.isElementNode())
+      continue;
+    WebElement elem = child.to<WebElement>();
+
+    if (elem.hasTagName("link")) {
+      std::string rel = elem.getAttribute("rel").utf8();
+      // "rel" attribute may use either "icon" or "shortcut icon".
+      // see also
+      //   <http://en.wikipedia.org/wiki/Favicon>
+      //   <http://dev.w3.org/html5/spec/Overview.html#rel-icon>
+      if (LowerCaseEqualsASCII(rel, "icon") ||
+          LowerCaseEqualsASCII(rel, "shortcut icon")) {
+        AddInstallIcon(elem, &app_info->icons);
+      } else if (LowerCaseEqualsASCII(rel, "chrome-application-definition")) {
+        std::string definition_url_string(elem.getAttribute("href").utf8());
+        GURL definition_url;
+        if (!(definition_url =
+              document_url.Resolve(definition_url_string)).is_valid() ||
+            definition_url.GetOrigin() != document_url.GetOrigin()) {
+          *error = UTF8ToUTF16(WebApplicationInfo::kInvalidDefinitionURL);
+          return false;
+        }
+
+        // If there is a definition file, all attributes come from it.
+        *app_info = WebApplicationInfo();
+        app_info->manifest_url = definition_url;
+        return true;
+      }
+    } else if (elem.hasTagName("meta") && elem.hasAttribute("name")) {
+      std::string name = elem.getAttribute("name").utf8();
+      WebString content = elem.getAttribute("content");
+      if (name == "application-name") {
+        app_info->title = content;
+      } else if (name == "description") {
+        app_info->description = content;
+      } else if (name == "application-url") {
+        std::string url = content.utf8();
+        app_info->app_url = document_url.is_valid() ?
+            document_url.Resolve(url) : GURL(url);
+        if (!app_info->app_url.is_valid())
+          app_info->app_url = GURL();
+      }
+    }
+  }
+
+  return true;
+}
+
+bool ParseWebAppFromDefinitionFile(Value* definition_value,
+                                   WebApplicationInfo* web_app,
+                                   string16* error) {
+  DCHECK(web_app->manifest_url.is_valid());
+
+  int error_code = 0;
+  std::string error_message;
+  scoped_ptr<Value> schema(
+      base::JSONReader::ReadAndReturnError(
+          ResourceBundle::GetSharedInstance().GetRawDataResource(
+              IDR_WEB_APP_SCHEMA),
+          base::JSON_PARSE_RFC,  // options
+          &error_code,
+          &error_message));
+  DCHECK(schema.get())
+      << "Error parsing JSON schema: " << error_code << ": " << error_message;
+  DCHECK(schema->IsType(Value::TYPE_DICTIONARY))
+      << "schema root must be dictionary.";
+
+  JSONSchemaValidator validator(static_cast<DictionaryValue*>(schema.get()));
+
+  // We allow extra properties in the schema for easy compat with other systems,
+  // and for forward compat with ourselves.
+  validator.set_default_allow_additional_properties(true);
+
+  if (!validator.Validate(definition_value)) {
+    *error = UTF8ToUTF16(
+        validator.errors()[0].path + ": " + validator.errors()[0].message);
+    return false;
+  }
+
+  // This must be true because the schema requires the root value to be a
+  // dictionary.
+  DCHECK(definition_value->IsType(Value::TYPE_DICTIONARY));
+  DictionaryValue* definition = static_cast<DictionaryValue*>(definition_value);
+
+  // Parse launch URL. It must be a valid URL in the same origin as the
+  // manifest.
+  std::string app_url_string;
+  GURL app_url;
+  CHECK(definition->GetString("launch_url", &app_url_string));
+  if (!(app_url = web_app->manifest_url.Resolve(app_url_string)).is_valid() ||
+      app_url.GetOrigin() != web_app->manifest_url.GetOrigin()) {
+    *error = UTF8ToUTF16(WebApplicationInfo::kInvalidLaunchURL);
+    return false;
+  }
+
+  // Parse out the permissions if present.
+  std::vector<std::string> permissions;
+  ListValue* permissions_value = NULL;
+  if (definition->GetList("permissions", &permissions_value)) {
+    for (size_t i = 0; i < permissions_value->GetSize(); ++i) {
+      std::string permission;
+      CHECK(permissions_value->GetString(i, &permission));
+      permissions.push_back(permission);
+    }
+  }
+
+  // Parse out the URLs if present.
+  std::vector<GURL> urls;
+  ListValue* urls_value = NULL;
+  if (definition->GetList("urls", &urls_value)) {
+    for (size_t i = 0; i < urls_value->GetSize(); ++i) {
+      std::string url_string;
+      GURL url;
+      CHECK(urls_value->GetString(i, &url_string));
+      if (!(url = web_app->manifest_url.Resolve(url_string)).is_valid() ||
+          url.GetOrigin() != web_app->manifest_url.GetOrigin()) {
+        *error = UTF8ToUTF16(
+            JSONSchemaValidator::FormatErrorMessage(
+                WebApplicationInfo::kInvalidURL, base::Uint64ToString(i)));
+        return false;
+      }
+      urls.push_back(url);
+    }
+  }
+
+  // Parse out the icons if present.
+  std::vector<WebApplicationInfo::IconInfo> icons;
+  DictionaryValue* icons_value = NULL;
+  if (definition->GetDictionary("icons", &icons_value)) {
+    for (DictionaryValue::key_iterator iter = icons_value->begin_keys();
+         iter != icons_value->end_keys(); ++iter) {
+      // Ignore unknown properties. Better for forward compat.
+      int size = 0;
+      if (!base::StringToInt(*iter, &size) || size < 0 || size > 128)
+        continue;
+
+      std::string icon_url_string;
+      GURL icon_url;
+      if (!icons_value->GetString(*iter, &icon_url_string) ||
+          !(icon_url = web_app->manifest_url.Resolve(
+              icon_url_string)).is_valid()) {
+        *error = UTF8ToUTF16(
+            JSONSchemaValidator::FormatErrorMessage(
+                WebApplicationInfo::kInvalidIconURL, base::IntToString(size)));
+        return false;
+      }
+
+      WebApplicationInfo::IconInfo icon;
+      icon.url = icon_url;
+      icon.width = size;
+      icon.height = size;
+
+      icons.push_back(icon);
+    }
+  }
+
+  // Parse if offline mode is enabled.
+  definition->GetBoolean("offline_enabled", &web_app->is_offline_enabled);
+
+  CHECK(definition->GetString("name", &web_app->title));
+  definition->GetString("description", &web_app->description);
+  definition->GetString("launch_container", &web_app->launch_container);
+  web_app->app_url = app_url;
+  web_app->urls = urls;
+  web_app->permissions = permissions;
+  web_app->icons = icons;
+
+  return true;
+
+}
+
+}  // namespace web_apps
diff --git a/chrome/common/web_apps.h b/chrome/common/web_apps.h
new file mode 100644
index 0000000..b3ea039
--- /dev/null
+++ b/chrome/common/web_apps.h
@@ -0,0 +1,120 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_WEB_APPS_H_
+#define CHROME_COMMON_WEB_APPS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/string16.h"
+#include "googleurl/src/gurl.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/size.h"
+
+namespace WebKit {
+class WebDocument;
+class WebFrame;
+}
+
+namespace base {
+class Value;
+}
+
+// Structure used when installing a web page as an app.
+struct WebApplicationInfo {
+  struct IconInfo {
+    GURL url;
+    int width;
+    int height;
+    SkBitmap data;
+  };
+
+  static const char kInvalidDefinitionURL[];
+  static const char kInvalidLaunchURL[];
+  static const char kInvalidURL[];
+  static const char kInvalidIconSize[];
+  static const char kInvalidIconURL[];
+
+  WebApplicationInfo();
+  ~WebApplicationInfo();
+
+  // URL to a manifest that defines the application. If specified, all other
+  // attributes are derived from this manifest, and the manifest is the unique
+  // ID of the application.
+  GURL manifest_url;
+
+  // Setting indicating this application is artificially constructed. If set,
+  // the application was created from bookmark-style data (title, url, possibly
+  // icon), and not from an official manifest file. In that case, the app_url
+  // can be checked for the later creation of an official manifest instead of
+  // reloading the manifest_url.
+  bool is_bookmark_app;
+
+  // Title of the application.
+  string16 title;
+
+  // Description of the application.
+  string16 description;
+
+  // The launch URL for the app.
+  GURL app_url;
+
+  // Set of available icons.
+  std::vector<IconInfo> icons;
+
+  // The permissions the app requests. Only supported with manifest-based apps.
+  std::vector<std::string> permissions;
+
+  // Set of URLs that comprise the app. Only supported with manifest-based apps.
+  // All these must be of the same origin as manifest_url.
+  std::vector<GURL> urls;
+
+  // The type of launch container to use with the app. Currently supported
+  // values are 'tab' and 'panel'. Only supported with manifest-based apps.
+  std::string launch_container;
+
+  // This indicates if the app is functional in offline mode or not.
+  bool is_offline_enabled;
+};
+
+
+namespace web_apps {
+
+// Parses an icon size. An icon size must match the following regex:
+// [1-9][0-9]*x[1-9][0-9]*.
+// If the input couldn't be parsed, a size with a width/height == 0 is returned.
+gfx::Size ParseIconSize(const string16& text);
+
+// Parses the icon's size attribute as defined in the HTML 5 spec. Returns true
+// on success, false on errors. On success either all the sizes specified in
+// the attribute are added to sizes, or is_any is set to true.
+//
+// You shouldn't have a need to invoke this directly, it's public for testing.
+bool ParseIconSizes(const string16& text, std::vector<gfx::Size>* sizes,
+                    bool* is_any);
+
+// Parses |web_app| information out of the document in frame. Returns true on
+// success, or false and |error| on failure. Note that the document may contain
+// no web application information, in which case |web_app| is unchanged and the
+// function returns true.
+//
+// Documents can also contain a link to a application 'definition'. In this case
+// web_app will have manifest_url set and nothing else. The caller must fetch
+// this URL and pass the result to ParseWebAppFromDefinitionFile() for further
+// processing.
+bool ParseWebAppFromWebDocument(WebKit::WebFrame* frame,
+                                WebApplicationInfo* web_app,
+                                string16* error);
+
+// Parses |web_app| information out of |definition|. Returns true on success, or
+// false and |error| on failure. This function assumes that |web_app| has a
+// valid manifest_url.
+bool ParseWebAppFromDefinitionFile(base::Value* definition,
+                                   WebApplicationInfo* web_app,
+                                   string16* error);
+
+}  // namespace web_apps
+
+#endif  // CHROME_COMMON_WEB_APPS_H_
diff --git a/chrome/common/web_apps_unittest.cc b/chrome/common/web_apps_unittest.cc
new file mode 100644
index 0000000..5f71fcd
--- /dev/null
+++ b/chrome/common/web_apps_unittest.cc
@@ -0,0 +1,192 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/web_apps.h"
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/json_schema_validator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+DictionaryValue* LoadDefinitionFile(const std::string& name) {
+  FilePath path;
+  if (!PathService::Get(chrome::DIR_TEST_DATA, &path)) {
+    ADD_FAILURE() << "Could not get test data dir.";
+    return NULL;
+  }
+
+  path = path.AppendASCII("web_app_info").AppendASCII(name.c_str());
+  if (!file_util::PathExists(path)) {
+    ADD_FAILURE() << "Path does not exist: " << path.value();
+    return NULL;
+  }
+
+  std::string error;
+  JSONFileValueSerializer serializer(path);
+  DictionaryValue* result = static_cast<DictionaryValue*>(
+      serializer.Deserialize(NULL, &error));
+  if (!result) {
+    ADD_FAILURE() << "Error parsing " << name << ": " << error;
+    return NULL;
+  }
+
+  return result;
+}
+
+WebApplicationInfo* ParseFromDefinitionAndExpectSuccess(
+    const std::string& name) {
+  scoped_ptr<DictionaryValue> defintion(LoadDefinitionFile(name));
+  if (!defintion.get())
+    return NULL;
+
+  scoped_ptr<WebApplicationInfo> web_app(new WebApplicationInfo());
+  web_app->manifest_url = GURL("http://example.com/");
+
+  string16 error;
+  if (!web_apps::ParseWebAppFromDefinitionFile(defintion.get(), web_app.get(),
+                                               &error)) {
+    ADD_FAILURE() << "Error parsing " << name << ": " << UTF16ToUTF8(error);
+    return NULL;
+  }
+
+  return web_app.release();
+}
+
+void ParseFromDefinitionAndExpectFailure(const std::string& name,
+                                         const string16& expected_error) {
+  scoped_ptr<DictionaryValue> definition(LoadDefinitionFile(name));
+  if (!definition.get())
+    return;
+
+  WebApplicationInfo web_app;
+  web_app.manifest_url = GURL("http://example.com/");
+
+  string16 error;
+  if (web_apps::ParseWebAppFromDefinitionFile(definition.get(), &web_app,
+                                              &error)) {
+    ADD_FAILURE() << "Expected error parsing " << name
+                  << " but parse succeeded.";
+    return;
+  }
+
+  EXPECT_EQ(UTF16ToUTF8(expected_error), UTF16ToUTF8(error)) << name;
+}
+
+}
+
+TEST(WebAppInfo, ParseFromDefinitionFileErrors) {
+  // Test one definition file with a JSON schema error, just to make sure we're
+  // correctly propagating those. We don't extensively test all the properties
+  // covered by the schema, since we assume JSON schema is working correctly.
+  ParseFromDefinitionAndExpectFailure(
+      "missing_name.json",
+      UTF8ToUTF16(std::string("name: ") +
+                      JSONSchemaValidator::kObjectPropertyIsRequired));
+
+  ParseFromDefinitionAndExpectFailure(
+      "invalid_launch_url.json",
+      UTF8ToUTF16(WebApplicationInfo::kInvalidLaunchURL));
+
+  ParseFromDefinitionAndExpectFailure(
+      "invalid_urls.json",
+      UTF8ToUTF16(
+          JSONSchemaValidator::FormatErrorMessage(
+              WebApplicationInfo::kInvalidURL, "2")));
+}
+
+TEST(WebAppInfo, Minimal) {
+  scoped_ptr<WebApplicationInfo> web_app(
+      ParseFromDefinitionAndExpectSuccess("minimal.json"));
+
+  EXPECT_EQ(UTF8ToUTF16("hello"), web_app->title);
+  EXPECT_EQ(UTF8ToUTF16(""), web_app->description);
+  EXPECT_EQ(GURL("http://example.com/launch_url"), web_app->app_url);
+  EXPECT_EQ(0u, web_app->icons.size());
+  EXPECT_EQ(0u, web_app->urls.size());
+  EXPECT_EQ(0u, web_app->permissions.size());
+  EXPECT_EQ("", web_app->launch_container);
+}
+
+TEST(WebAppInfo, Full) {
+  scoped_ptr<WebApplicationInfo> web_app(
+      ParseFromDefinitionAndExpectSuccess("full.json"));
+
+  EXPECT_EQ(UTF8ToUTF16("hello"), web_app->title);
+  EXPECT_EQ(UTF8ToUTF16("This app is super awesome"), web_app->description);
+  EXPECT_EQ(GURL("http://example.com/launch_url"), web_app->app_url);
+  ASSERT_EQ(1u, web_app->icons.size());
+  EXPECT_EQ("http://example.com/16.png", web_app->icons[0].url.spec());
+  EXPECT_EQ(16, web_app->icons[0].width);
+  EXPECT_EQ(16, web_app->icons[0].height);
+  ASSERT_EQ(2u, web_app->urls.size());
+  EXPECT_EQ("http://example.com/foobar", web_app->urls[0].spec());
+  EXPECT_EQ("http://example.com/baz", web_app->urls[1].spec());
+  ASSERT_EQ(2u, web_app->permissions.size());
+  EXPECT_EQ("geolocation", web_app->permissions[0]);
+  EXPECT_EQ("notifications", web_app->permissions[1]);
+  EXPECT_EQ("panel", web_app->launch_container);
+  EXPECT_EQ(true, web_app->is_offline_enabled);
+}
+
+// Tests ParseIconSizes with various input.
+TEST(WebAppInfo, ParseIconSizes) {
+  struct TestData {
+    const char* input;
+    const bool expected_result;
+    const bool is_any;
+    const size_t expected_size_count;
+    const int width1;
+    const int height1;
+    const int width2;
+    const int height2;
+  } data[] = {
+    // Bogus input cases.
+    { "10",         false, false, 0, 0, 0, 0, 0 },
+    { "10 10",      false, false, 0, 0, 0, 0, 0 },
+    { "010",        false, false, 0, 0, 0, 0, 0 },
+    { " 010 ",      false, false, 0, 0, 0, 0, 0 },
+    { " 10x ",      false, false, 0, 0, 0, 0, 0 },
+    { " x10 ",      false, false, 0, 0, 0, 0, 0 },
+    { "any 10x10",  false, false, 0, 0, 0, 0, 0 },
+    { "",           false, false, 0, 0, 0, 0, 0 },
+    { "10ax11",     false, false, 0, 0, 0, 0, 0 },
+
+    // Any.
+    { "any",        true, true, 0, 0, 0, 0, 0 },
+    { " any",       true, true, 0, 0, 0, 0, 0 },
+    { " any ",      true, true, 0, 0, 0, 0, 0 },
+
+    // Sizes.
+    { "10x11",      true, false, 1, 10, 11, 0, 0 },
+    { " 10x11 ",    true, false, 1, 10, 11, 0, 0 },
+    { " 10x11 1x2", true, false, 2, 10, 11, 1, 2 },
+  };
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) {
+    bool is_any;
+    std::vector<gfx::Size> sizes;
+    bool result = web_apps::ParseIconSizes(ASCIIToUTF16(data[i].input), &sizes,
+                                           &is_any);
+    ASSERT_EQ(result, data[i].expected_result);
+    if (result) {
+      ASSERT_EQ(data[i].is_any, is_any);
+      ASSERT_EQ(data[i].expected_size_count, sizes.size());
+      if (!sizes.empty()) {
+        ASSERT_EQ(data[i].width1, sizes[0].width());
+        ASSERT_EQ(data[i].height1, sizes[0].height());
+      }
+      if (sizes.size() > 1) {
+        ASSERT_EQ(data[i].width2, sizes[1].width());
+        ASSERT_EQ(data[i].height2, sizes[1].height());
+      }
+    }
+  }
+}
diff --git a/chrome/common/web_resource/web_resource_unpacker.cc b/chrome/common/web_resource/web_resource_unpacker.cc
new file mode 100644
index 0000000..34ed510
--- /dev/null
+++ b/chrome/common/web_resource/web_resource_unpacker.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/web_resource/web_resource_unpacker.h"
+
+#include "base/json/json_reader.h"
+#include "base/values.h"
+
+const char* WebResourceUnpacker::kInvalidDataTypeError =
+    "Data from web resource server is missing or not valid JSON.";
+
+const char* WebResourceUnpacker::kUnexpectedJSONFormatError =
+    "Data from web resource server does not have expected format.";
+
+WebResourceUnpacker::WebResourceUnpacker(const std::string &resource_data)
+    : resource_data_(resource_data) {
+}
+
+WebResourceUnpacker::~WebResourceUnpacker() {
+}
+
+// TODO(mrc): Right now, this reads JSON data from the experimental popgadget
+// server.  Change so the format is based on a template, once we have
+// decided on final server format.
+bool WebResourceUnpacker::Run() {
+  scoped_ptr<Value> value;
+  if (!resource_data_.empty()) {
+    value.reset(base::JSONReader::Read(resource_data_));
+    if (!value.get()) {
+      // Page information not properly read, or corrupted.
+      error_message_ = kInvalidDataTypeError;
+      return false;
+    }
+    if (!value->IsType(Value::TYPE_DICTIONARY)) {
+      error_message_ = kUnexpectedJSONFormatError;
+      return false;
+    }
+    parsed_json_.reset(static_cast<DictionaryValue*>(value.release()));
+    return true;
+  }
+  error_message_ = kInvalidDataTypeError;
+  return false;
+}
+
diff --git a/chrome/common/web_resource/web_resource_unpacker.h b/chrome/common/web_resource/web_resource_unpacker.h
new file mode 100644
index 0000000..3711e33
--- /dev/null
+++ b/chrome/common/web_resource/web_resource_unpacker.h
@@ -0,0 +1,57 @@
+// 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.
+
+// This class is called by the WebResourceService in a sandboxed process
+// to unpack data retrieved from a web resource feed.  Right now, it
+// takes a string of data in JSON format, parses it, and hands it back
+// to the WebResourceService as a list of items.  In the future
+// it will be set up to unpack and verify image data in addition to
+// just parsing a JSON feed.
+
+#ifndef CHROME_COMMON_WEB_RESOURCE_WEB_RESOURCE_UNPACKER_H_
+#define CHROME_COMMON_WEB_RESOURCE_WEB_RESOURCE_UNPACKER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+class WebResourceUnpacker {
+ public:
+  static const char* kInvalidDataTypeError;
+  static const char* kUnexpectedJSONFormatError;
+
+  explicit WebResourceUnpacker(const std::string &resource_data);
+  ~WebResourceUnpacker();
+
+  // This does the actual parsing.  In case of an error, error_message_
+  // is set to an appropriate value.
+  bool Run();
+
+  // Returns the last error message set by Run().
+  const std::string& error_message() { return error_message_; }
+
+  // Gets data which has been parsed by Run().
+  base::DictionaryValue* parsed_json() {
+    return parsed_json_.get();
+  }
+
+ private:
+  // Holds the string which is to be parsed.
+  std::string resource_data_;
+
+  // Holds the result of JSON parsing of resource_data_.
+  scoped_ptr<base::DictionaryValue> parsed_json_;
+
+  // Holds the last error message produced by Run().
+  std::string error_message_;
+
+  DISALLOW_COPY_AND_ASSIGN(WebResourceUnpacker);
+};
+
+#endif  // CHROME_COMMON_WEB_RESOURCE_WEB_RESOURCE_UNPACKER_H_
diff --git a/chrome/common/worker_thread_ticker.cc b/chrome/common/worker_thread_ticker.cc
new file mode 100644
index 0000000..b1dc279
--- /dev/null
+++ b/chrome/common/worker_thread_ticker.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/worker_thread_ticker.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/threading/thread.h"
+
+WorkerThreadTicker::WorkerThreadTicker(int tick_interval)
+    : timer_thread_("worker_thread_ticker"),
+      is_running_(false),
+      tick_interval_(base::TimeDelta::FromMilliseconds(tick_interval)) {
+}
+
+WorkerThreadTicker::~WorkerThreadTicker() {
+  Stop();
+}
+
+bool WorkerThreadTicker::RegisterTickHandler(Callback *tick_handler) {
+  DCHECK(tick_handler);
+  base::AutoLock lock(lock_);
+  // You cannot change the list of handlers when the timer is running.
+  // You need to call Stop first.
+  if (IsRunning())
+    return false;
+  tick_handler_list_.push_back(tick_handler);
+  return true;
+}
+
+bool WorkerThreadTicker::UnregisterTickHandler(Callback *tick_handler) {
+  DCHECK(tick_handler);
+  base::AutoLock lock(lock_);
+  // You cannot change the list of handlers when the timer is running.
+  // You need to call Stop first.
+  if (IsRunning()) {
+    return false;
+  }
+  TickHandlerListType::iterator index = std::remove(tick_handler_list_.begin(),
+                                                    tick_handler_list_.end(),
+                                                    tick_handler);
+  if (index == tick_handler_list_.end()) {
+    return false;
+  }
+  tick_handler_list_.erase(index, tick_handler_list_.end());
+  return true;
+}
+
+bool WorkerThreadTicker::Start() {
+  // Do this in a lock because we don't want 2 threads to
+  // call Start at the same time
+  base::AutoLock lock(lock_);
+  if (IsRunning())
+    return false;
+  if (!timer_thread_.Start())
+    return false;
+  is_running_ = true;
+  ScheduleTimerTask();
+  return true;
+}
+
+bool WorkerThreadTicker::Stop() {
+  // Do this in a lock because we don't want 2 threads to
+  // call Stop at the same time
+  base::AutoLock lock(lock_);
+  if (!IsRunning())
+    return false;
+  is_running_ = false;
+  timer_thread_.Stop();
+  return true;
+}
+
+void WorkerThreadTicker::ScheduleTimerTask() {
+  timer_thread_.message_loop()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&WorkerThreadTicker::TimerTask, base::Unretained(this)),
+      tick_interval_);
+}
+
+void WorkerThreadTicker::TimerTask() {
+  // When the ticker is running, the handler list CANNOT be modified.
+  // So we can do the enumeration safely without a lock
+  const TickHandlerListType& handlers = tick_handler_list_;
+  for (TickHandlerListType::const_iterator i = handlers.begin();
+       i != handlers.end(); ++i) {
+    (*i)->OnTick();
+  }
+
+  ScheduleTimerTask();
+}
+
diff --git a/chrome/common/worker_thread_ticker.h b/chrome/common/worker_thread_ticker.h
new file mode 100644
index 0000000..2cdf3f3
--- /dev/null
+++ b/chrome/common/worker_thread_ticker.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_WORKER_THREAD_TICKER_H_
+#define CHROME_COMMON_WORKER_THREAD_TICKER_H_
+
+#include <vector>
+
+#include "base/synchronization/lock.h"
+#include "base/threading/thread.h"
+
+// This class provides the following functionality:
+// It invokes a set of registered handlers at periodic intervals in
+// the context of an arbitrary worker thread.
+// The timer runs on a separate thread, so it will run even if the current
+// thread is hung. Similarly, the callbacks will be called on a separate
+// thread so they won't block the main thread.
+class WorkerThreadTicker {
+ public:
+  // This callback interface to be implemented by clients of this
+  // class
+  class Callback {
+   public:
+    // Gets invoked when the timer period is up
+    virtual void OnTick() = 0;
+
+   protected:
+    virtual ~Callback() {}
+  };
+
+  // tick_interval is the periodic interval in which to invoke the
+  // registered handlers (in milliseconds)
+  explicit WorkerThreadTicker(int tick_interval);
+
+  ~WorkerThreadTicker();
+
+  // Registers a callback handler interface
+  // tick_handler is the handler interface to register. The ownership of this
+  // object is not transferred to this class.
+  bool RegisterTickHandler(Callback *tick_handler);
+
+  // Unregisters a callback handler interface
+  // tick_handler is the handler interface to unregister
+  bool UnregisterTickHandler(Callback *tick_handler);
+
+  // Starts the ticker. Returns false if the ticker is already running
+  // or if the Start fails.
+  bool Start();
+  // Stops the ticker and waits for all callbacks. to be done. This method
+  // does not provide a way to stop without waiting for the callbacks to be
+  // done because this is inherently risky.
+  // Returns false is the ticker is not running
+  bool Stop();
+  bool IsRunning() const {
+    return is_running_;
+  }
+
+  void set_tick_interval(int tick_interval) {
+    tick_interval_ = base::TimeDelta::FromMilliseconds(tick_interval);
+  }
+
+  int tick_interval() const {
+    return tick_interval_.InMilliseconds();
+  }
+
+ private:
+  void ScheduleTimerTask();
+
+  void TimerTask();
+
+  // A list type that holds all registered callback interfaces
+  typedef std::vector<Callback*> TickHandlerListType;
+
+  // Lock to protect is_running_ and tick_handler_list_
+  base::Lock lock_;
+
+  base::Thread timer_thread_;
+  bool is_running_;
+
+  // The interval at which the callbacks are to be invoked
+  base::TimeDelta tick_interval_;
+
+  // A list that holds all registered callback interfaces
+  TickHandlerListType tick_handler_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(WorkerThreadTicker);
+};
+
+#endif  // CHROME_COMMON_WORKER_THREAD_TICKER_H_
diff --git a/chrome/common/worker_thread_ticker_unittest.cc b/chrome/common/worker_thread_ticker_unittest.cc
new file mode 100644
index 0000000..ffb0d00
--- /dev/null
+++ b/chrome/common/worker_thread_ticker_unittest.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/worker_thread_ticker.h"
+
+#include "base/message_loop.h"
+#include "base/threading/platform_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class TestCallback : public WorkerThreadTicker::Callback {
+ public:
+  TestCallback() : counter_(0), message_loop_(MessageLoop::current()) {
+  }
+
+  virtual void OnTick() {
+    counter_++;
+
+    // Finish the test faster.
+    message_loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+  }
+
+  int counter() const { return counter_; }
+
+ private:
+  int counter_;
+  MessageLoop* message_loop_;
+};
+
+class LongCallback : public WorkerThreadTicker::Callback {
+ public:
+  virtual void OnTick() {
+    base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1500));
+  }
+};
+
+void RunMessageLoopForAWhile() {
+  MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      MessageLoop::QuitClosure(),
+      base::TimeDelta::FromMilliseconds(500));
+  MessageLoop::current()->Run();
+}
+
+}  // namespace
+
+TEST(WorkerThreadTickerTest, Basic) {
+  MessageLoop loop;
+
+  TestCallback callback;
+  WorkerThreadTicker ticker(50);
+  EXPECT_FALSE(ticker.IsRunning());
+  EXPECT_TRUE(ticker.RegisterTickHandler(&callback));
+  EXPECT_TRUE(ticker.UnregisterTickHandler(&callback));
+  EXPECT_TRUE(ticker.Start());
+  EXPECT_FALSE(ticker.RegisterTickHandler(&callback));
+  EXPECT_FALSE(ticker.UnregisterTickHandler(&callback));
+  EXPECT_TRUE(ticker.IsRunning());
+  EXPECT_FALSE(ticker.Start());  // Can't start when it is running.
+  EXPECT_TRUE(ticker.Stop());
+  EXPECT_FALSE(ticker.IsRunning());
+  EXPECT_FALSE(ticker.Stop());  // Can't stop when it isn't running.
+}
+
+TEST(WorkerThreadTickerTest, Callback) {
+  MessageLoop loop;
+
+  TestCallback callback;
+  WorkerThreadTicker ticker(50);
+  ASSERT_TRUE(ticker.RegisterTickHandler(&callback));
+
+  ASSERT_TRUE(ticker.Start());
+  RunMessageLoopForAWhile();
+  EXPECT_TRUE(callback.counter() > 0);
+
+  ASSERT_TRUE(ticker.Stop());
+  const int counter_value = callback.counter();
+  RunMessageLoopForAWhile();
+  EXPECT_EQ(counter_value, callback.counter());
+}
+
+TEST(WorkerThreadTickerTest, OutOfScope) {
+  MessageLoop loop;
+
+  TestCallback callback;
+  {
+    WorkerThreadTicker ticker(50);
+    ASSERT_TRUE(ticker.RegisterTickHandler(&callback));
+
+    ASSERT_TRUE(ticker.Start());
+    RunMessageLoopForAWhile();
+    EXPECT_TRUE(callback.counter() > 0);
+  }
+  const int counter_value = callback.counter();
+  RunMessageLoopForAWhile();
+  EXPECT_EQ(counter_value, callback.counter());
+}
+
+TEST(WorkerThreadTickerTest, LongCallback) {
+  MessageLoop loop;
+
+  LongCallback callback;
+  WorkerThreadTicker ticker(50);
+  ASSERT_TRUE(ticker.RegisterTickHandler(&callback));
+
+  ASSERT_TRUE(ticker.Start());
+  RunMessageLoopForAWhile();
+}
diff --git a/chrome/common/zip.cc b/chrome/common/zip.cc
new file mode 100644
index 0000000..3e2aa81
--- /dev/null
+++ b/chrome/common/zip.cc
@@ -0,0 +1,206 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/zip.h"
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/string16.h"
+#include "base/string_util.h"
+#include "chrome/common/zip_internal.h"
+#include "chrome/common/zip_reader.h"
+#include "net/base/file_stream.h"
+
+#if defined(USE_SYSTEM_MINIZIP)
+#include <minizip/unzip.h>
+#include <minizip/zip.h>
+#else
+#include "third_party/zlib/contrib/minizip/unzip.h"
+#include "third_party/zlib/contrib/minizip/zip.h"
+#endif
+
+namespace {
+
+bool AddFileToZip(zipFile zip_file, const FilePath& src_dir) {
+  net::FileStream stream(NULL);
+  int flags = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ;
+  if (stream.OpenSync(src_dir, flags) != 0) {
+    DLOG(ERROR) << "Could not open stream for path "
+                << src_dir.value();
+    return false;
+  }
+
+  int num_bytes;
+  char buf[zip::internal::kZipBufSize];
+  do {
+    num_bytes = stream.ReadSync(buf, zip::internal::kZipBufSize);
+    if (num_bytes > 0) {
+      if (ZIP_OK != zipWriteInFileInZip(zip_file, buf, num_bytes)) {
+        DLOG(ERROR) << "Could not write data to zip for path "
+                    << src_dir.value();
+        return false;
+      }
+    }
+  } while (num_bytes > 0);
+
+  return true;
+}
+
+bool AddEntryToZip(zipFile zip_file, const FilePath& path,
+                   const FilePath& root_path) {
+  std::string str_path =
+      path.AsUTF8Unsafe().substr(root_path.value().length() + 1);
+#if defined(OS_WIN)
+  ReplaceSubstringsAfterOffset(&str_path, 0u, "\\", "/");
+#endif
+
+  bool is_directory = file_util::DirectoryExists(path);
+  if (is_directory)
+    str_path += "/";
+
+  if (ZIP_OK != zipOpenNewFileInZip(
+      zip_file, str_path.c_str(),
+      NULL, NULL, 0u, NULL, 0u, NULL,  // file info, extrafield local, length,
+                                       // extrafield global, length, comment
+      Z_DEFLATED, Z_DEFAULT_COMPRESSION)) {
+    DLOG(ERROR) << "Could not open zip file entry " << str_path;
+    return false;
+  }
+
+  bool success = true;
+  if (!is_directory) {
+    success = AddFileToZip(zip_file, path);
+  }
+
+  if (ZIP_OK != zipCloseFileInZip(zip_file)) {
+    DLOG(ERROR) << "Could not close zip file entry " << str_path;
+    return false;
+  }
+
+  return success;
+}
+
+bool ExcludeNoFilesFilter(const FilePath& file_path) {
+  return true;
+}
+
+bool ExcludeHiddenFilesFilter(const FilePath& file_path) {
+  return file_path.BaseName().value()[0] != '.';
+}
+
+}  // namespace
+
+namespace zip {
+
+bool Unzip(const FilePath& src_file, const FilePath& dest_dir) {
+  ZipReader reader;
+  if (!reader.Open(src_file)) {
+    DLOG(WARNING) << "Failed to open " << src_file.value();
+    return false;
+  }
+  while (reader.HasMore()) {
+    if (!reader.OpenCurrentEntryInZip()) {
+      DLOG(WARNING) << "Failed to open the current file in zip";
+      return false;
+    }
+    if (reader.current_entry_info()->is_unsafe()) {
+      DLOG(WARNING) << "Found an unsafe file in zip "
+                    << reader.current_entry_info()->file_path().value();
+      return false;
+    }
+    if (!reader.ExtractCurrentEntryIntoDirectory(dest_dir)) {
+      DLOG(WARNING) << "Failed to extract "
+                    << reader.current_entry_info()->file_path().value();
+      return false;
+    }
+    if (!reader.AdvanceToNextEntry()) {
+      DLOG(WARNING) << "Failed to advance to the next file";
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ZipWithFilterCallback(const FilePath& src_dir, const FilePath& dest_file,
+                           const FilterCallback& filter_cb) {
+  DCHECK(file_util::DirectoryExists(src_dir));
+
+  zipFile zip_file = internal::OpenForZipping(dest_file.AsUTF8Unsafe(),
+                                              APPEND_STATUS_CREATE);
+
+  if (!zip_file) {
+    DLOG(WARNING) << "couldn't create file " << dest_file.value();
+    return false;
+  }
+
+  bool success = true;
+  file_util::FileEnumerator file_enumerator(src_dir, true /* recursive */,
+      file_util::FileEnumerator::FILES |
+      file_util::FileEnumerator::DIRECTORIES);
+  for (FilePath path = file_enumerator.Next(); !path.value().empty();
+       path = file_enumerator.Next()) {
+    if (!filter_cb.Run(path)) {
+      continue;
+    }
+
+    if (!AddEntryToZip(zip_file, path, src_dir)) {
+      success = false;
+      return false;
+    }
+  }
+
+  if (ZIP_OK != zipClose(zip_file, NULL)) {
+    DLOG(ERROR) << "Error closing zip file " << dest_file.value();
+    return false;
+  }
+
+  return success;
+}
+
+bool Zip(const FilePath& src_dir, const FilePath& dest_file,
+         bool include_hidden_files) {
+  if (include_hidden_files) {
+    return ZipWithFilterCallback(
+        src_dir, dest_file, base::Bind(&ExcludeNoFilesFilter));
+  } else {
+    return ZipWithFilterCallback(
+        src_dir, dest_file, base::Bind(&ExcludeHiddenFilesFilter));
+  }
+}
+
+bool ZipFiles(const FilePath& src_dir,
+              const std::vector<FilePath>& src_relative_paths,
+              const FilePath& dest_file) {
+  DCHECK(file_util::DirectoryExists(src_dir));
+
+  // TODO(hshi): check that |src_relative_paths| does not contain |dest_file|.
+  zipFile zip_file = internal::OpenForZipping(dest_file.AsUTF8Unsafe(),
+                                              APPEND_STATUS_CREATE);
+
+  if (!zip_file) {
+    DLOG(WARNING) << "couldn't create file " << dest_file.value();
+    return false;
+  }
+
+  bool success = true;
+  for (std::vector<FilePath>::const_iterator iter = src_relative_paths.begin();
+      iter != src_relative_paths.end(); ++iter) {
+    const FilePath& path = src_dir.Append(*iter);
+    if (!AddEntryToZip(zip_file, path, src_dir)) {
+      // TODO(hshi): clean up the partial zip file when error occurs.
+      success = false;
+      break;
+    }
+  }
+
+  if (ZIP_OK != zipClose(zip_file, NULL)) {
+    DLOG(ERROR) << "Error closing zip file " << dest_file.value();
+    success = false;
+  }
+
+  return success;
+}
+
+}  // namespace zip
diff --git a/chrome/common/zip.h b/chrome/common/zip.h
new file mode 100644
index 0000000..feda101
--- /dev/null
+++ b/chrome/common/zip.h
@@ -0,0 +1,41 @@
+// 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.
+
+#ifndef CHROME_COMMON_ZIP_H_
+#define CHROME_COMMON_ZIP_H_
+
+#include "base/callback.h"
+#include "base/file_path.h"
+
+namespace zip {
+
+// Zip the contents of src_dir into dest_file. src_path must be a directory.
+// An entry will *not* be created in the zip for the root folder -- children
+// of src_dir will be at the root level of the created zip. For each file in
+// src_dir, include it only if the callback |filter_cb| returns true. Otherwise
+// omit it.
+typedef base::Callback<bool(const FilePath&)> FilterCallback;
+bool ZipWithFilterCallback(const FilePath& src_dir, const FilePath& dest_file,
+                           const FilterCallback& filter_cb);
+
+// Convenience method for callers who don't need to set up the filter callback.
+// If |include_hidden_files| is true, files starting with "." are included.
+// Otherwise they are omitted.
+bool Zip(const FilePath& src_dir, const FilePath& dest_file,
+         bool include_hidden_files);
+
+// Zips files listed in |src_relative_paths| to |dest_file|.
+// The paths listed in |src_relative_paths| are relative to |src_dir| and will
+// be used as the file names in the created zip file. All source paths must be
+// under |src_dir| in the file system hierarchy.
+bool ZipFiles(const FilePath& src_dir,
+              const std::vector<FilePath>& src_relative_paths,
+              const FilePath& dest_file);
+
+// Unzip the contents of zip_file into dest_dir.
+bool Unzip(const FilePath& zip_file, const FilePath& dest_dir);
+
+}  // namespace zip
+
+#endif  // CHROME_COMMON_ZIP_H_
diff --git a/chrome/common/zip_internal.cc b/chrome/common/zip_internal.cc
new file mode 100644
index 0000000..fe84696
--- /dev/null
+++ b/chrome/common/zip_internal.cc
@@ -0,0 +1,280 @@
+// 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.
+
+#include "chrome/common/zip.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+
+#if defined(USE_SYSTEM_MINIZIP)
+#include <minizip/unzip.h>
+#include <minizip/zip.h>
+#include <minizip/ioapi.h>
+#else
+#include "third_party/zlib/contrib/minizip/unzip.h"
+#include "third_party/zlib/contrib/minizip/zip.h"
+#if defined(OS_WIN)
+#include "third_party/zlib/contrib/minizip/iowin32.h"
+#elif defined(OS_POSIX)
+#include "third_party/zlib/contrib/minizip/ioapi.h"
+#endif  // defined(OS_POSIX)
+#endif  // defined(USE_SYSTEM_MINIZIP)
+
+namespace {
+
+#if defined(OS_WIN)
+typedef struct {
+  HANDLE hf;
+  int error;
+} WIN32FILE_IOWIN;
+
+// This function is derived from third_party/minizip/iowin32.c.
+// Its only difference is that it treats the char* as UTF8 and
+// uses the Unicode version of CreateFile.
+void* ZipOpenFunc(void *opaque, const char* filename, int mode) {
+  DWORD desired_access, creation_disposition;
+  DWORD share_mode, flags_and_attributes;
+  HANDLE file = 0;
+  void* ret = NULL;
+
+  desired_access = share_mode = flags_and_attributes = 0;
+
+  if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ) {
+    desired_access = GENERIC_READ;
+    creation_disposition = OPEN_EXISTING;
+    share_mode = FILE_SHARE_READ;
+  } else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) {
+    desired_access = GENERIC_WRITE | GENERIC_READ;
+    creation_disposition = OPEN_EXISTING;
+  } else if (mode & ZLIB_FILEFUNC_MODE_CREATE) {
+    desired_access = GENERIC_WRITE | GENERIC_READ;
+    creation_disposition = CREATE_ALWAYS;
+  }
+
+  string16 filename16 = UTF8ToUTF16(filename);
+  if ((filename != NULL) && (desired_access != 0)) {
+    file = CreateFile(filename16.c_str(), desired_access, share_mode,
+        NULL, creation_disposition, flags_and_attributes, NULL);
+  }
+
+  if (file == INVALID_HANDLE_VALUE)
+    file = NULL;
+
+  if (file != NULL) {
+    WIN32FILE_IOWIN file_ret;
+    file_ret.hf = file;
+    file_ret.error = 0;
+    ret = malloc(sizeof(WIN32FILE_IOWIN));
+    if (ret == NULL)
+      CloseHandle(file);
+    else
+      *(static_cast<WIN32FILE_IOWIN*>(ret)) = file_ret;
+  }
+  return ret;
+}
+#endif
+
+#if defined(OS_POSIX)
+// Callback function for zlib that opens a file stream from a file descriptor.
+void* FdOpenFileFunc(void* opaque, const char* filename, int mode) {
+  FILE* file = NULL;
+  const char* mode_fopen = NULL;
+
+  if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)
+    mode_fopen = "rb";
+  else if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
+    mode_fopen = "r+b";
+  else if (mode & ZLIB_FILEFUNC_MODE_CREATE)
+    mode_fopen = "wb";
+
+  if ((filename != NULL) && (mode_fopen != NULL))
+    file = fdopen(*static_cast<int*>(opaque), mode_fopen);
+
+  return file;
+}
+
+// We don't actually close the file stream since that would close
+// the underlying file descriptor, and we don't own it. We do free
+// |opaque| since we malloc'ed it in FillFdOpenFileFunc.
+int CloseFileFunc(void* opaque, void* stream) {
+  free(opaque);
+  return 0;
+}
+
+// Fills |pzlib_filecunc_def| appropriately to handle the zip file
+// referred to by |fd|.
+void FillFdOpenFileFunc(zlib_filefunc_def* pzlib_filefunc_def, int fd) {
+  fill_fopen_filefunc(pzlib_filefunc_def);
+  pzlib_filefunc_def->zopen_file = FdOpenFileFunc;
+  pzlib_filefunc_def->zclose_file = CloseFileFunc;
+  int* ptr_fd = static_cast<int*>(malloc(sizeof(fd)));
+  *ptr_fd = fd;
+  pzlib_filefunc_def->opaque = ptr_fd;
+}
+#endif  // defined(OS_POSIX)
+
+// A struct that contains data required for zlib functions to extract files from
+// a zip archive stored in memory directly. The following I/O API functions
+// expect their opaque parameters refer to this struct.
+struct ZipBuffer {
+  const char* data;  // weak
+  size_t length;
+  size_t offset;
+};
+
+// Opens the specified file. When this function returns a non-NULL pointer, zlib
+// uses this pointer as a stream parameter while compressing or uncompressing
+// data. (Returning NULL represents an error.) This function initializes the
+// given opaque parameter and returns it because this parameter stores all
+// information needed for uncompressing data. (This function does not support
+// writing compressed data and it returns NULL for this case.)
+void* OpenZipBuffer(void* opaque, const char* /*filename*/, int mode) {
+  if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) != ZLIB_FILEFUNC_MODE_READ) {
+    NOTREACHED();
+    return NULL;
+  }
+  ZipBuffer* buffer = static_cast<ZipBuffer*>(opaque);
+  if (!buffer || !buffer->data || !buffer->length)
+    return NULL;
+  buffer->offset = 0;
+  return opaque;
+}
+
+// Reads compressed data from the specified stream. This function copies data
+// refered by the opaque parameter and returns the size actually copied.
+uLong ReadZipBuffer(void* opaque, void* /*stream*/, void* buf, uLong size) {
+  ZipBuffer* buffer = static_cast<ZipBuffer*>(opaque);
+  DCHECK_LE(buffer->offset, buffer->length);
+  size_t remaining_bytes = buffer->length - buffer->offset;
+  if (!buffer || !buffer->data || !remaining_bytes)
+    return 0;
+  size = std::min(size, static_cast<uLong>(remaining_bytes));
+  memcpy(buf, &buffer->data[buffer->offset], size);
+  buffer->offset += size;
+  return size;
+}
+
+// Writes compressed data to the stream. This function always returns zero
+// because this implementation is only for reading compressed data.
+uLong WriteZipBuffer(void* /*opaque*/,
+                     void* /*stream*/,
+                     const void* /*buf*/,
+                     uLong /*size*/) {
+  NOTREACHED();
+  return 0;
+}
+
+// Returns the offset from the beginning of the data.
+long GetOffsetOfZipBuffer(void* opaque, void* /*stream*/) {
+  ZipBuffer* buffer = static_cast<ZipBuffer*>(opaque);
+  if (!buffer)
+    return -1;
+  return static_cast<long>(buffer->offset);
+}
+
+// Moves the current offset to the specified position.
+long SeekZipBuffer(void* opaque, void* /*stream*/, uLong offset, int origin) {
+  ZipBuffer* buffer = static_cast<ZipBuffer*>(opaque);
+  if (!buffer)
+    return -1;
+  if (origin == ZLIB_FILEFUNC_SEEK_CUR) {
+    buffer->offset = std::min(buffer->offset + static_cast<size_t>(offset),
+                              buffer->length);
+    return 0;
+  }
+  if (origin == ZLIB_FILEFUNC_SEEK_END) {
+    buffer->offset = (buffer->length > offset) ? buffer->length - offset : 0;
+    return 0;
+  }
+  if (origin == ZLIB_FILEFUNC_SEEK_SET) {
+    buffer->offset = std::min(buffer->length, static_cast<size_t>(offset));
+    return 0;
+  }
+  NOTREACHED();
+  return -1;
+}
+
+// Closes the input offset and deletes all resources used for compressing or
+// uncompressing data. This function deletes the ZipBuffer object referred by
+// the opaque parameter since zlib deletes the unzFile object and it does not
+// use this object any longer.
+int CloseZipBuffer(void* opaque, void* /*stream*/) {
+  if (opaque)
+    free(opaque);
+  return 0;
+}
+
+// Returns the last error happened when reading or writing data. This function
+// always returns zero, which means there are not any errors.
+int GetErrorOfZipBuffer(void* /*opaque*/, void* /*stream*/) {
+  return 0;
+}
+
+}  // namespace
+
+namespace zip {
+namespace internal {
+
+unzFile OpenForUnzipping(const std::string& file_name_utf8) {
+  zlib_filefunc_def* zip_func_ptrs = NULL;
+#if defined(OS_WIN)
+  zlib_filefunc_def zip_funcs;
+  fill_win32_filefunc(&zip_funcs);
+  zip_funcs.zopen_file = ZipOpenFunc;
+  zip_func_ptrs = &zip_funcs;
+#endif
+  return unzOpen2(file_name_utf8.c_str(), zip_func_ptrs);
+}
+
+#if defined(OS_POSIX)
+unzFile OpenFdForUnzipping(int zip_fd) {
+  zlib_filefunc_def zip_funcs;
+  FillFdOpenFileFunc(&zip_funcs, zip_fd);
+  // Passing dummy "fd" filename to zlib.
+  return unzOpen2("fd", &zip_funcs);
+}
+#endif
+
+// static
+unzFile PreprareMemoryForUnzipping(const std::string& data) {
+  if (data.empty())
+    return NULL;
+
+  ZipBuffer* buffer = static_cast<ZipBuffer*>(malloc(sizeof(ZipBuffer)));
+  if (!buffer)
+    return NULL;
+  buffer->data = data.data();
+  buffer->length = data.length();
+  buffer->offset = 0;
+
+  zlib_filefunc_def zip_functions;
+  zip_functions.zopen_file = OpenZipBuffer;
+  zip_functions.zread_file = ReadZipBuffer;
+  zip_functions.zwrite_file = WriteZipBuffer;
+  zip_functions.ztell_file = GetOffsetOfZipBuffer;
+  zip_functions.zseek_file = SeekZipBuffer;
+  zip_functions.zclose_file = CloseZipBuffer;
+  zip_functions.zerror_file = GetErrorOfZipBuffer;
+  zip_functions.opaque = static_cast<void*>(buffer);
+  return unzOpen2(NULL, &zip_functions);
+}
+
+zipFile OpenForZipping(const std::string& file_name_utf8, int append_flag) {
+  zlib_filefunc_def* zip_func_ptrs = NULL;
+#if defined(OS_WIN)
+  zlib_filefunc_def zip_funcs;
+  fill_win32_filefunc(&zip_funcs);
+  zip_funcs.zopen_file = ZipOpenFunc;
+  zip_func_ptrs = &zip_funcs;
+#endif
+  return zipOpen2(file_name_utf8.c_str(),
+                  append_flag,
+                  NULL,  // global comment
+                  zip_func_ptrs);
+}
+
+}  // namespace internal
+}  // namespace zip
diff --git a/chrome/common/zip_internal.h b/chrome/common/zip_internal.h
new file mode 100644
index 0000000..3bf8683
--- /dev/null
+++ b/chrome/common/zip_internal.h
@@ -0,0 +1,47 @@
+// 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.
+
+#ifndef CHROME_COMMON_ZIP_INTERNAL_H_
+#define CHROME_COMMON_ZIP_INTERNAL_H_
+
+#include <string>
+
+#if defined(USE_SYSTEM_MINIZIP)
+#include <minizip/unzip.h>
+#include <minizip/zip.h>
+#else
+#include "third_party/zlib/contrib/minizip/unzip.h"
+#include "third_party/zlib/contrib/minizip/zip.h"
+#endif
+
+// Utility functions and constants used internally for the zip file
+// library in the directory. Don't use them outside of the library.
+namespace zip {
+namespace internal {
+
+// Opens the given file name in UTF-8 for unzipping, with some setup for
+// Windows.
+unzFile OpenForUnzipping(const std::string& file_name_utf8);
+
+#if defined(OS_POSIX)
+// Opens the file referred to by |zip_fd| for unzipping.
+unzFile OpenFdForUnzipping(int zip_fd);
+#endif
+
+// Creates a custom unzFile object which reads data from the specified string.
+// This custom unzFile object overrides the I/O API functions of zlib so it can
+// read data from the specified string.
+unzFile PreprareMemoryForUnzipping(const std::string& data);
+
+// Opens the given file name in UTF-8 for zipping, with some setup for
+// Windows. |append_flag| will be passed to zipOpen2().
+zipFile OpenForZipping(const std::string& file_name_utf8, int append_flag);
+
+const int kZipMaxPath = 256;
+const int kZipBufSize = 8192;
+
+}  // namespace internal
+}  // namespace zip
+
+#endif  // CHROME_COMMON_ZIP_INTERNAL_H_
diff --git a/chrome/common/zip_reader.cc b/chrome/common/zip_reader.cc
new file mode 100644
index 0000000..14960aa
--- /dev/null
+++ b/chrome/common/zip_reader.cc
@@ -0,0 +1,308 @@
+// Copyright (c) 2012 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.
+
+#include "chrome/common/zip_reader.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/zip_internal.h"
+#include "net/base/file_stream.h"
+
+#if defined(USE_SYSTEM_MINIZIP)
+#include <minizip/unzip.h>
+#else
+#include "third_party/zlib/contrib/minizip/unzip.h"
+#if defined(OS_WIN)
+#include "third_party/zlib/contrib/minizip/iowin32.h"
+#endif  // defined(OS_WIN)
+#endif  // defined(USE_SYSTEM_MINIZIP)
+
+namespace zip {
+
+// TODO(satorux): The implementation assumes that file names in zip files
+// are encoded in UTF-8. This is true for zip files created by Zip()
+// function in zip.h, but not true for user-supplied random zip files.
+ZipReader::EntryInfo::EntryInfo(const std::string& file_name_in_zip,
+                                const unz_file_info& raw_file_info)
+    : file_path_(FilePath::FromUTF8Unsafe(file_name_in_zip)),
+      is_directory_(false) {
+  original_size_ = raw_file_info.uncompressed_size;
+
+  // Directory entries in zip files end with "/".
+  is_directory_ = EndsWith(file_name_in_zip, "/", false);
+
+  // Check the file name here for directory traversal issues. In the name of
+  // simplicity and security, we might reject a valid file name such as "a..b".
+  is_unsafe_ = file_name_in_zip.find("..") != std::string::npos;
+
+  // We also consider that the file name is unsafe, if it's invalid UTF-8.
+  string16 file_name_utf16;
+  if (!UTF8ToUTF16(file_name_in_zip.data(), file_name_in_zip.size(),
+                   &file_name_utf16)) {
+    is_unsafe_ = true;
+  }
+
+  // We also consider that the file name is unsafe, if it's absolute.
+  // On Windows, IsAbsolute() returns false for paths starting with "/".
+  if (file_path_.IsAbsolute() || StartsWithASCII(file_name_in_zip, "/", false))
+    is_unsafe_ = true;
+
+  // Construct the last modified time. The timezone info is not present in
+  // zip files, so we construct the time as local time.
+  base::Time::Exploded exploded_time = {};  // Zero-clear.
+  exploded_time.year = raw_file_info.tmu_date.tm_year;
+  // The month in zip file is 0-based, whereas ours is 1-based.
+  exploded_time.month = raw_file_info.tmu_date.tm_mon + 1;
+  exploded_time.day_of_month = raw_file_info.tmu_date.tm_mday;
+  exploded_time.hour = raw_file_info.tmu_date.tm_hour;
+  exploded_time.minute = raw_file_info.tmu_date.tm_min;
+  exploded_time.second = raw_file_info.tmu_date.tm_sec;
+  exploded_time.millisecond = 0;
+  if (exploded_time.HasValidValues()) {
+    last_modified_ = base::Time::FromLocalExploded(exploded_time);
+  } else {
+    // Use Unix time epoch if the time stamp data is invalid.
+    last_modified_ = base::Time::UnixEpoch();
+  }
+}
+
+ZipReader::ZipReader() {
+  Reset();
+}
+
+ZipReader::~ZipReader() {
+  Close();
+}
+
+bool ZipReader::Open(const FilePath& zip_file_path) {
+  DCHECK(!zip_file_);
+
+  // Use of "Unsafe" function does not look good, but there is no way to do
+  // this safely on Linux. See file_util.h for details.
+  zip_file_ = internal::OpenForUnzipping(zip_file_path.AsUTF8Unsafe());
+  if (!zip_file_) {
+    return false;
+  }
+
+  return OpenInternal();
+}
+
+#if defined(OS_POSIX)
+bool ZipReader::OpenFromFd(const int zip_fd) {
+  DCHECK(!zip_file_);
+
+  zip_file_ = internal::OpenFdForUnzipping(zip_fd);
+  if (!zip_file_) {
+    return false;
+  }
+
+  return OpenInternal();
+}
+#endif
+
+bool ZipReader::OpenFromString(const std::string& data) {
+  zip_file_ = internal::PreprareMemoryForUnzipping(data);
+  if (!zip_file_)
+    return false;
+  return OpenInternal();
+}
+
+void ZipReader::Close() {
+  if (zip_file_) {
+    unzClose(zip_file_);
+  }
+  Reset();
+}
+
+bool ZipReader::HasMore() {
+  return !reached_end_;
+}
+
+bool ZipReader::AdvanceToNextEntry() {
+  DCHECK(zip_file_);
+
+  // Should not go further if we already reached the end.
+  if (reached_end_)
+    return false;
+
+  unz_file_pos position = {};
+  if (unzGetFilePos(zip_file_, &position) != UNZ_OK)
+    return false;
+  const int current_entry_index = position.num_of_file;
+  // If we are currently at the last entry, then the next position is the
+  // end of the zip file, so mark that we reached the end.
+  if (current_entry_index + 1 == num_entries_) {
+    reached_end_ = true;
+  } else {
+    DCHECK_LT(current_entry_index + 1, num_entries_);
+    if (unzGoToNextFile(zip_file_) != UNZ_OK) {
+      return false;
+    }
+  }
+  current_entry_info_.reset();
+  return true;
+}
+
+bool ZipReader::OpenCurrentEntryInZip() {
+  DCHECK(zip_file_);
+
+  unz_file_info raw_file_info = {};
+  char raw_file_name_in_zip[internal::kZipMaxPath] = {};
+  const int result = unzGetCurrentFileInfo(zip_file_,
+                                           &raw_file_info,
+                                           raw_file_name_in_zip,
+                                           sizeof(raw_file_name_in_zip) - 1,
+                                           NULL,  // extraField.
+                                           0,  // extraFieldBufferSize.
+                                           NULL,  // szComment.
+                                           0);  // commentBufferSize.
+  if (result != UNZ_OK)
+    return false;
+  if (raw_file_name_in_zip[0] == '\0')
+    return false;
+  current_entry_info_.reset(
+      new EntryInfo(raw_file_name_in_zip, raw_file_info));
+  return true;
+}
+
+bool ZipReader::LocateAndOpenEntry(const FilePath& path_in_zip) {
+  DCHECK(zip_file_);
+
+  current_entry_info_.reset();
+  reached_end_ = false;
+  const int kDefaultCaseSensivityOfOS = 0;
+  const int result = unzLocateFile(zip_file_,
+                                   path_in_zip.AsUTF8Unsafe().c_str(),
+                                   kDefaultCaseSensivityOfOS);
+  if (result != UNZ_OK)
+    return false;
+
+  // Then Open the entry.
+  return OpenCurrentEntryInZip();
+}
+
+bool ZipReader::ExtractCurrentEntryToFilePath(
+    const FilePath& output_file_path) {
+  DCHECK(zip_file_);
+
+  // If this is a directory, just create it and return.
+  if (current_entry_info()->is_directory())
+    return file_util::CreateDirectory(output_file_path);
+
+  const int open_result = unzOpenCurrentFile(zip_file_);
+  if (open_result != UNZ_OK)
+    return false;
+
+  // We can't rely on parent directory entries being specified in the
+  // zip, so we make sure they are created.
+  FilePath output_dir_path = output_file_path.DirName();
+  if (!file_util::CreateDirectory(output_dir_path))
+    return false;
+
+  net::FileStream stream(NULL);
+  const int flags = (base::PLATFORM_FILE_CREATE_ALWAYS |
+                     base::PLATFORM_FILE_WRITE);
+  if (stream.OpenSync(output_file_path, flags) != 0)
+    return false;
+
+  bool success = true;  // This becomes false when something bad happens.
+  while (true) {
+    char buf[internal::kZipBufSize];
+    const int num_bytes_read = unzReadCurrentFile(zip_file_, buf,
+                                                  internal::kZipBufSize);
+    if (num_bytes_read == 0) {
+      // Reached the end of the file.
+      break;
+    } else if (num_bytes_read < 0) {
+      // If num_bytes_read < 0, then it's a specific UNZ_* error code.
+      success = false;
+      break;
+    } else if (num_bytes_read > 0) {
+      // Some data is read. Write it to the output file.
+      if (num_bytes_read != stream.WriteSync(buf, num_bytes_read)) {
+        success = false;
+        break;
+      }
+    }
+  }
+
+  unzCloseCurrentFile(zip_file_);
+  return success;
+}
+
+bool ZipReader::ExtractCurrentEntryIntoDirectory(
+    const FilePath& output_directory_path) {
+  DCHECK(current_entry_info_.get());
+
+  FilePath output_file_path = output_directory_path.Append(
+      current_entry_info()->file_path());
+  return ExtractCurrentEntryToFilePath(output_file_path);
+}
+
+#if defined(OS_POSIX)
+bool ZipReader::ExtractCurrentEntryToFd(const int fd) {
+  DCHECK(zip_file_);
+
+  // If this is a directory, there's nothing to extract to the file descriptor,
+  // so return false.
+  if (current_entry_info()->is_directory())
+    return false;
+
+  const int open_result = unzOpenCurrentFile(zip_file_);
+  if (open_result != UNZ_OK)
+    return false;
+
+  bool success = true;  // This becomes false when something bad happens.
+  while (true) {
+    char buf[internal::kZipBufSize];
+    const int num_bytes_read = unzReadCurrentFile(zip_file_, buf,
+                                                  internal::kZipBufSize);
+    if (num_bytes_read == 0) {
+      // Reached the end of the file.
+      break;
+    } else if (num_bytes_read < 0) {
+      // If num_bytes_read < 0, then it's a specific UNZ_* error code.
+      success = false;
+      break;
+    } else if (num_bytes_read > 0) {
+      // Some data is read. Write it to the output file descriptor.
+      if (num_bytes_read !=
+          file_util::WriteFileDescriptor(fd, buf, num_bytes_read)) {
+        success = false;
+        break;
+      }
+    }
+  }
+
+  unzCloseCurrentFile(zip_file_);
+  return success;
+}
+#endif  // defined(OS_POSIX)
+
+bool ZipReader::OpenInternal() {
+  DCHECK(zip_file_);
+
+  unz_global_info zip_info = {};  // Zero-clear.
+  if (unzGetGlobalInfo(zip_file_, &zip_info) != UNZ_OK) {
+    return false;
+  }
+  num_entries_ = zip_info.number_entry;
+  if (num_entries_ < 0)
+    return false;
+
+  // We are already at the end if the zip file is empty.
+  reached_end_ = (num_entries_ == 0);
+  return true;
+}
+
+void ZipReader::Reset() {
+  zip_file_ = NULL;
+  num_entries_ = 0;
+  reached_end_ = false;
+  current_entry_info_.reset();
+}
+
+}  // namespace zip
diff --git a/chrome/common/zip_reader.h b/chrome/common/zip_reader.h
new file mode 100644
index 0000000..cd3209d
--- /dev/null
+++ b/chrome/common/zip_reader.h
@@ -0,0 +1,177 @@
+// 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.
+
+#ifndef CHROME_COMMON_ZIP_READER_H_
+#define CHROME_COMMON_ZIP_READER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time.h"
+
+#if defined(USE_SYSTEM_MINIZIP)
+#include <minizip/unzip.h>
+#else
+#include "third_party/zlib/contrib/minizip/unzip.h"
+#endif
+
+namespace zip {
+
+// This class is used for reading zip files. A typical use case of this
+// class is to scan entries in a zip file and extract them. The code will
+// look like:
+//
+//   ZipReader reader;
+//   reader.Open(zip_file_path);
+//   while (reader.HasMore()) {
+//     reader.OpenCurrentEntryInZip();
+//     reader.ExtractCurrentEntryToDirectory(output_directory_path);
+//     reader.AdvanceToNextEntry();
+//   }
+//
+// For simplicty, error checking is omitted in the example code above. The
+// production code should check return values from all of these functions.
+//
+// This calls can also be used for random access of contents in a zip file
+// using LocateAndOpenEntry().
+//
+class ZipReader {
+ public:
+  // This class represents information of an entry (file or directory) in
+  // a zip file.
+  class EntryInfo {
+   public:
+    EntryInfo(const std::string& filename_in_zip,
+              const unz_file_info& raw_file_info);
+
+    // Returns the file path. The path is usually relative like
+    // "foo/bar.txt", but if it's absolute, is_unsafe() returns true.
+    const FilePath& file_path() const { return file_path_; }
+
+    // Returns the size of the original file (i.e. after uncompressed).
+    // Returns 0 if the entry is a directory.
+    int64 original_size() const { return original_size_; }
+
+    // Returns the last modified time.
+    base::Time last_modified() const { return last_modified_; }
+
+    // Returns true if the entry is a directory.
+    bool is_directory() const { return is_directory_; }
+
+    // Returns true if the entry is unsafe, like having ".." or invalid
+    // UTF-8 characters in its file name, or the file path is absolute.
+    bool is_unsafe() const { return is_unsafe_; }
+
+   private:
+    const FilePath file_path_;
+    int64 original_size_;
+    base::Time last_modified_;
+    bool is_directory_;
+    bool is_unsafe_;
+    DISALLOW_COPY_AND_ASSIGN(EntryInfo);
+  };
+
+  ZipReader();
+  ~ZipReader();
+
+  // Opens the zip file specified by |zip_file_path|. Returns true on
+  // success.
+  bool Open(const FilePath& zip_file_path);
+
+#if defined(OS_POSIX)
+  // Opens the zip file referred to by the file descriptor |zip_fd|.
+  // Returns true on success.
+  bool OpenFromFd(int zip_fd);
+#endif
+
+  // Opens the zip data stored in |data|. This class uses a weak reference to
+  // the given sring while extracting files, i.e. the caller should keep the
+  // string until it finishes extracting files.
+  bool OpenFromString(const std::string& data);
+
+  // Closes the currently opened zip file. This function is called in the
+  // destructor of the class, so you usually don't need to call this.
+  void Close();
+
+  // Returns true if there is at least one entry to read. This function is
+  // used to scan entries with AdvanceToNextEntry(), like:
+  //
+  // while (reader.HasMore()) {
+  //   // Do something with the current file here.
+  //   reader.AdvanceToNextEntry();
+  // }
+  bool HasMore();
+
+  // Advances the next entry. Returns true on success.
+  bool AdvanceToNextEntry();
+
+  // Opens the current entry in the zip file. On success, returns true and
+  // updates the the current entry state (i.e. current_entry_info() is
+  // updated). This function should be called before operations over the
+  // current entry like ExtractCurrentEntryToFile().
+  //
+  // Note that there is no CloseCurrentEntryInZip(). The the current entry
+  // state is reset automatically as needed.
+  bool OpenCurrentEntryInZip();
+
+  // Locates an entry in the zip file and opens it. Returns true on
+  // success. This function internally calls OpenCurrentEntryInZip() on
+  // success. On failure, current_entry_info() becomes NULL.
+  bool LocateAndOpenEntry(const FilePath& path_in_zip);
+
+  // Extracts the current entry to the given output file path. If the
+  // current file is a directory, just creates a directory
+  // instead. Returns true on success. OpenCurrentEntryInZip() must be
+  // called beforehand.
+  //
+  // This function does not preserve the timestamp of the original entry.
+  bool ExtractCurrentEntryToFilePath(const FilePath& output_file_path);
+
+  // Extracts the current entry to the given output directory path using
+  // ExtractCurrentEntryToFilePath(). Sub directories are created as needed
+  // based on the file path of the current entry. For example, if the file
+  // path in zip is "foo/bar.txt", and the output directory is "output",
+  // "output/foo/bar.txt" will be created.
+  //
+  // Returns true on success. OpenCurrentEntryInZip() must be called
+  // beforehand.
+  bool ExtractCurrentEntryIntoDirectory(const FilePath& output_directory_path);
+
+#if defined(OS_POSIX)
+  // Extracts the current entry by writing directly to a file descriptor.
+  // Does not close the file descriptor. Returns true on success.
+  bool ExtractCurrentEntryToFd(int fd);
+#endif
+
+  // Returns the current entry info. Returns NULL if the current entry is
+  // not yet opened. OpenCurrentEntryInZip() must be called beforehand.
+  EntryInfo* current_entry_info() const {
+    return current_entry_info_.get();
+  }
+
+  // Returns the number of entries in the zip file.
+  // Open() must be called beforehand.
+  int num_entries() const { return num_entries_; }
+
+ private:
+  // Common code used both in Open and OpenFromFd.
+  bool OpenInternal();
+
+  // Resets the internal state.
+  void Reset();
+
+  unzFile zip_file_;
+  int num_entries_;
+  bool reached_end_;
+  scoped_ptr<EntryInfo> current_entry_info_;
+
+  DISALLOW_COPY_AND_ASSIGN(ZipReader);
+};
+
+}  // namespace zip
+
+#endif  // CHROME_COMMON_ZIP_READER_H_
diff --git a/chrome/common/zip_reader_unittest.cc b/chrome/common/zip_reader_unittest.cc
new file mode 100644
index 0000000..ea65ae3
--- /dev/null
+++ b/chrome/common/zip_reader_unittest.cc
@@ -0,0 +1,416 @@
+// 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.
+
+#include "chrome/common/zip_reader.h"
+
+#if defined(OS_POSIX)
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#endif
+
+#include <set>
+#include <string>
+
+#include "base/file_util.h"
+#include "base/md5.h"
+#include "base/path_service.h"
+#include "base/scoped_temp_dir.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/zip_internal.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace {
+
+#if defined(OS_POSIX)
+// Wrap file descriptors in a class so that we don't leak them in tests.
+class FdWrapper {
+ public:
+  typedef enum {
+    READ_ONLY,
+    READ_WRITE
+  } AccessMode;
+
+  FdWrapper(const FilePath& file, AccessMode mode) : fd_(-1) {
+    switch (mode) {
+    case READ_ONLY:
+      fd_ = open(file.value().c_str(), O_RDONLY);
+      break;
+    case READ_WRITE:
+      fd_ = open(file.value().c_str(),
+                 O_RDWR | O_CREAT,
+                 S_IRUSR | S_IWUSR);
+      break;
+    default:
+      NOTREACHED();
+    }
+    return;
+  }
+
+  ~FdWrapper() {
+    close(fd_);
+  }
+
+  int fd() { return fd_; }
+
+ private:
+  int fd_;
+};
+#endif
+
+}   // namespace
+
+namespace zip {
+
+// Make the test a PlatformTest to setup autorelease pools properly on Mac.
+class ZipReaderTest : public PlatformTest {
+ protected:
+  virtual void SetUp() {
+    PlatformTest::SetUp();
+
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    test_dir_ = temp_dir_.path();
+
+    ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_));
+    test_data_dir_ = test_data_dir_.AppendASCII("zip");
+
+    test_zip_file_ = test_data_dir_.AppendASCII("test.zip");
+    evil_zip_file_ = test_data_dir_.AppendASCII("evil.zip");
+    evil_via_invalid_utf8_zip_file_ = test_data_dir_.AppendASCII(
+        "evil_via_invalid_utf8.zip");
+    evil_via_absolute_file_name_zip_file_ = test_data_dir_.AppendASCII(
+        "evil_via_absolute_file_name.zip");
+
+    test_zip_contents_.insert(FilePath(FILE_PATH_LITERAL("foo/")));
+    test_zip_contents_.insert(FilePath(FILE_PATH_LITERAL("foo/bar/")));
+    test_zip_contents_.insert(FilePath(FILE_PATH_LITERAL("foo/bar/baz.txt")));
+    test_zip_contents_.insert(FilePath(FILE_PATH_LITERAL("foo/bar/quux.txt")));
+    test_zip_contents_.insert(FilePath(FILE_PATH_LITERAL("foo/bar.txt")));
+    test_zip_contents_.insert(FilePath(FILE_PATH_LITERAL("foo.txt")));
+    test_zip_contents_.insert(FilePath(FILE_PATH_LITERAL("foo/bar/.hidden")));
+  }
+
+  virtual void TearDown() {
+    PlatformTest::TearDown();
+  }
+
+  // The path to temporary directory used to contain the test operations.
+  FilePath test_dir_;
+  // The path to the test data directory where test.zip etc. are located.
+  FilePath test_data_dir_;
+  // The path to test.zip in the test data directory.
+  FilePath test_zip_file_;
+  // The path to evil.zip in the test data directory.
+  FilePath evil_zip_file_;
+  // The path to evil_via_invalid_utf8.zip in the test data directory.
+  FilePath evil_via_invalid_utf8_zip_file_;
+  // The path to evil_via_absolute_file_name.zip in the test data directory.
+  FilePath evil_via_absolute_file_name_zip_file_;
+  std::set<FilePath> test_zip_contents_;
+
+  ScopedTempDir temp_dir_;
+};
+
+TEST_F(ZipReaderTest, Open_ValidZipFile) {
+  ZipReader reader;
+  ASSERT_TRUE(reader.Open(test_zip_file_));
+}
+
+#if defined(OS_POSIX)
+TEST_F(ZipReaderTest, Open_ValidZipFd) {
+  ZipReader reader;
+  FdWrapper zip_fd_wrapper(test_zip_file_, FdWrapper::READ_ONLY);
+  ASSERT_TRUE(reader.OpenFromFd(zip_fd_wrapper.fd()));
+}
+#endif
+
+TEST_F(ZipReaderTest, Open_NonExistentFile) {
+  ZipReader reader;
+  ASSERT_FALSE(reader.Open(test_data_dir_.AppendASCII("nonexistent.zip")));
+}
+
+TEST_F(ZipReaderTest, Open_ExistentButNonZipFile) {
+  ZipReader reader;
+  ASSERT_FALSE(reader.Open(test_data_dir_.AppendASCII("create_test_zip.sh")));
+}
+
+// Iterate through the contents in the test zip file, and compare that the
+// contents collected from the zip reader matches the expected contents.
+TEST_F(ZipReaderTest, Iteration) {
+  std::set<FilePath> actual_contents;
+  ZipReader reader;
+  ASSERT_TRUE(reader.Open(test_zip_file_));
+  while (reader.HasMore()) {
+    ASSERT_TRUE(reader.OpenCurrentEntryInZip());
+    actual_contents.insert(reader.current_entry_info()->file_path());
+    ASSERT_TRUE(reader.AdvanceToNextEntry());
+  }
+  EXPECT_FALSE(reader.AdvanceToNextEntry());  // Shouldn't go further.
+  EXPECT_EQ(test_zip_contents_.size(),
+            static_cast<size_t>(reader.num_entries()));
+  EXPECT_EQ(test_zip_contents_.size(), actual_contents.size());
+  EXPECT_EQ(test_zip_contents_, actual_contents);
+}
+
+#if defined(OS_POSIX)
+// Open the test zip file from a file descriptor, iterate through its contents,
+// and compare that they match the expected contents.
+TEST_F(ZipReaderTest, FdIteration) {
+  std::set<FilePath> actual_contents;
+  ZipReader reader;
+  FdWrapper zip_fd_wrapper(test_zip_file_, FdWrapper::READ_ONLY);
+  ASSERT_TRUE(reader.OpenFromFd(zip_fd_wrapper.fd()));
+  while (reader.HasMore()) {
+    ASSERT_TRUE(reader.OpenCurrentEntryInZip());
+    actual_contents.insert(reader.current_entry_info()->file_path());
+    ASSERT_TRUE(reader.AdvanceToNextEntry());
+  }
+  EXPECT_FALSE(reader.AdvanceToNextEntry());  // Shouldn't go further.
+  EXPECT_EQ(test_zip_contents_.size(),
+            static_cast<size_t>(reader.num_entries()));
+  EXPECT_EQ(test_zip_contents_.size(), actual_contents.size());
+  EXPECT_EQ(test_zip_contents_, actual_contents);
+}
+#endif
+
+TEST_F(ZipReaderTest, LocateAndOpenEntry_ValidFile) {
+  std::set<FilePath> actual_contents;
+  ZipReader reader;
+  ASSERT_TRUE(reader.Open(test_zip_file_));
+  FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
+  ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
+  EXPECT_EQ(target_path, reader.current_entry_info()->file_path());
+}
+
+TEST_F(ZipReaderTest, LocateAndOpenEntry_NonExistentFile) {
+  std::set<FilePath> actual_contents;
+  ZipReader reader;
+  ASSERT_TRUE(reader.Open(test_zip_file_));
+  FilePath target_path(FILE_PATH_LITERAL("nonexistent.txt"));
+  ASSERT_FALSE(reader.LocateAndOpenEntry(target_path));
+  EXPECT_EQ(NULL, reader.current_entry_info());
+}
+
+TEST_F(ZipReaderTest, ExtractCurrentEntryToFilePath_RegularFile) {
+  ZipReader reader;
+  ASSERT_TRUE(reader.Open(test_zip_file_));
+  FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
+  ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
+  ASSERT_TRUE(reader.ExtractCurrentEntryToFilePath(
+      test_dir_.AppendASCII("quux.txt")));
+  // Read the output file ans compute the MD5.
+  std::string output;
+  ASSERT_TRUE(file_util::ReadFileToString(test_dir_.AppendASCII("quux.txt"),
+                                          &output));
+  const std::string md5 = base::MD5String(output);
+  const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6";
+  EXPECT_EQ(kExpectedMD5, md5);
+  // quux.txt should be larger than kZipBufSize so that we can exercise
+  // the loop in ExtractCurrentEntry().
+  EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size());
+}
+
+#if defined(OS_POSIX)
+TEST_F(ZipReaderTest, FdExtractCurrentEntryToFilePath_RegularFile) {
+  ZipReader reader;
+  FdWrapper zip_fd_wrapper(test_zip_file_, FdWrapper::READ_ONLY);
+  ASSERT_TRUE(reader.OpenFromFd(zip_fd_wrapper.fd()));
+  FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
+  ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
+  ASSERT_TRUE(reader.ExtractCurrentEntryToFilePath(
+      test_dir_.AppendASCII("quux.txt")));
+  // Read the output file and compute the MD5.
+  std::string output;
+  ASSERT_TRUE(file_util::ReadFileToString(test_dir_.AppendASCII("quux.txt"),
+                                          &output));
+  const std::string md5 = base::MD5String(output);
+  const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6";
+  EXPECT_EQ(kExpectedMD5, md5);
+  // quux.txt should be larger than kZipBufSize so that we can exercise
+  // the loop in ExtractCurrentEntry().
+  EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size());
+}
+
+TEST_F(ZipReaderTest, FdExtractCurrentEntryToFd_RegularFile) {
+  ZipReader reader;
+  FdWrapper zip_fd_wrapper(test_zip_file_, FdWrapper::READ_ONLY);
+  ASSERT_TRUE(reader.OpenFromFd(zip_fd_wrapper.fd()));
+  FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
+  FilePath out_path = test_dir_.AppendASCII("quux.txt");
+  FdWrapper out_fd_w(out_path, FdWrapper::READ_WRITE);
+  ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
+  ASSERT_TRUE(reader.ExtractCurrentEntryToFd(out_fd_w.fd()));
+  // Read the output file and compute the MD5.
+  std::string output;
+  ASSERT_TRUE(file_util::ReadFileToString(test_dir_.AppendASCII("quux.txt"),
+                                          &output));
+  const std::string md5 = base::MD5String(output);
+  const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6";
+  EXPECT_EQ(kExpectedMD5, md5);
+  // quux.txt should be larger than kZipBufSize so that we can exercise
+  // the loop in ExtractCurrentEntry().
+  EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size());
+}
+#endif
+
+TEST_F(ZipReaderTest, ExtractCurrentEntryToFilePath_Directory) {
+  ZipReader reader;
+  ASSERT_TRUE(reader.Open(test_zip_file_));
+  FilePath target_path(FILE_PATH_LITERAL("foo/"));
+  ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
+  ASSERT_TRUE(reader.ExtractCurrentEntryToFilePath(
+      test_dir_.AppendASCII("foo")));
+  // The directory should be created.
+  ASSERT_TRUE(file_util::DirectoryExists(test_dir_.AppendASCII("foo")));
+}
+
+TEST_F(ZipReaderTest, ExtractCurrentEntryIntoDirectory_RegularFile) {
+  ZipReader reader;
+  ASSERT_TRUE(reader.Open(test_zip_file_));
+  FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
+  ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
+  ASSERT_TRUE(reader.ExtractCurrentEntryIntoDirectory(test_dir_));
+  // Sub directories should be created.
+  ASSERT_TRUE(file_util::DirectoryExists(test_dir_.AppendASCII("foo/bar")));
+  // And the file should be created.
+  std::string output;
+  ASSERT_TRUE(file_util::ReadFileToString(
+      test_dir_.AppendASCII("foo/bar/quux.txt"), &output));
+  const std::string md5 = base::MD5String(output);
+  const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6";
+  EXPECT_EQ(kExpectedMD5, md5);
+}
+
+TEST_F(ZipReaderTest, current_entry_info_RegularFile) {
+  ZipReader reader;
+  ASSERT_TRUE(reader.Open(test_zip_file_));
+  FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
+  ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
+  ZipReader::EntryInfo* current_entry_info = reader.current_entry_info();
+
+  EXPECT_EQ(target_path, current_entry_info->file_path());
+  EXPECT_EQ(13527, current_entry_info->original_size());
+
+  // The expected time stamp: 2009-05-29 06:22:20
+  base::Time::Exploded exploded = {};  // Zero-clear.
+  current_entry_info->last_modified().LocalExplode(&exploded);
+  EXPECT_EQ(2009, exploded.year);
+  EXPECT_EQ(5, exploded.month);
+  EXPECT_EQ(29, exploded.day_of_month);
+  EXPECT_EQ(6, exploded.hour);
+  EXPECT_EQ(22, exploded.minute);
+  EXPECT_EQ(20, exploded.second);
+  EXPECT_EQ(0, exploded.millisecond);
+
+  EXPECT_FALSE(current_entry_info->is_unsafe());
+  EXPECT_FALSE(current_entry_info->is_directory());
+}
+
+TEST_F(ZipReaderTest, current_entry_info_DotDotFile) {
+  ZipReader reader;
+  ASSERT_TRUE(reader.Open(evil_zip_file_));
+  FilePath target_path(FILE_PATH_LITERAL(
+      "../levilevilevilevilevilevilevilevilevilevilevilevil"));
+  ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
+  ZipReader::EntryInfo* current_entry_info = reader.current_entry_info();
+  EXPECT_EQ(target_path, current_entry_info->file_path());
+
+  // This file is unsafe because of ".." in the file name.
+  EXPECT_TRUE(current_entry_info->is_unsafe());
+  EXPECT_FALSE(current_entry_info->is_directory());
+}
+
+TEST_F(ZipReaderTest, current_entry_info_InvalidUTF8File) {
+  ZipReader reader;
+  ASSERT_TRUE(reader.Open(evil_via_invalid_utf8_zip_file_));
+  // The evil file is the 2nd file in the zip file.
+  // We cannot locate by the file name ".\x80.\\evil.txt",
+  // as FilePath may internally convert the string.
+  ASSERT_TRUE(reader.AdvanceToNextEntry());
+  ASSERT_TRUE(reader.OpenCurrentEntryInZip());
+  ZipReader::EntryInfo* current_entry_info = reader.current_entry_info();
+
+  // This file is unsafe because of invalid UTF-8 in the file name.
+  EXPECT_TRUE(current_entry_info->is_unsafe());
+  EXPECT_FALSE(current_entry_info->is_directory());
+}
+
+TEST_F(ZipReaderTest, current_entry_info_AbsoluteFile) {
+  ZipReader reader;
+  ASSERT_TRUE(reader.Open(evil_via_absolute_file_name_zip_file_));
+  FilePath target_path(FILE_PATH_LITERAL("/evil.txt"));
+  ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
+  ZipReader::EntryInfo* current_entry_info = reader.current_entry_info();
+  EXPECT_EQ(target_path, current_entry_info->file_path());
+
+  // This file is unsafe because of the absolute file name.
+  EXPECT_TRUE(current_entry_info->is_unsafe());
+  EXPECT_FALSE(current_entry_info->is_directory());
+}
+
+TEST_F(ZipReaderTest, current_entry_info_Directory) {
+  ZipReader reader;
+  ASSERT_TRUE(reader.Open(test_zip_file_));
+  FilePath target_path(FILE_PATH_LITERAL("foo/bar/"));
+  ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
+  ZipReader::EntryInfo* current_entry_info = reader.current_entry_info();
+
+  EXPECT_EQ(FilePath(FILE_PATH_LITERAL("foo/bar/")),
+            current_entry_info->file_path());
+  // The directory size should be zero.
+  EXPECT_EQ(0, current_entry_info->original_size());
+
+  // The expected time stamp: 2009-05-31 15:49:52
+  base::Time::Exploded exploded = {};  // Zero-clear.
+  current_entry_info->last_modified().LocalExplode(&exploded);
+  EXPECT_EQ(2009, exploded.year);
+  EXPECT_EQ(5, exploded.month);
+  EXPECT_EQ(31, exploded.day_of_month);
+  EXPECT_EQ(15, exploded.hour);
+  EXPECT_EQ(49, exploded.minute);
+  EXPECT_EQ(52, exploded.second);
+  EXPECT_EQ(0, exploded.millisecond);
+
+  EXPECT_FALSE(current_entry_info->is_unsafe());
+  EXPECT_TRUE(current_entry_info->is_directory());
+}
+
+// Verifies that the ZipReader class can extract a file from a zip archive
+// stored in memory. This test opens a zip archive in a std::string object,
+// extracts its content, and verifies the content is the same as the expected
+// text.
+TEST_F(ZipReaderTest, OpenFromString) {
+  // A zip archive consisting of one file "test.txt", which is a 16-byte text
+  // file that contains "This is a test.\n".
+  const char kTestData[] =
+      "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\xa4\x66\x24\x41\x13\xe8"
+      "\xcb\x27\x10\x00\x00\x00\x10\x00\x00\x00\x08\x00\x1c\x00\x74\x65"
+      "\x73\x74\x2e\x74\x78\x74\x55\x54\x09\x00\x03\x34\x89\x45\x50\x34"
+      "\x89\x45\x50\x75\x78\x0b\x00\x01\x04\x8e\xf0\x00\x00\x04\x88\x13"
+      "\x00\x00\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x74\x65\x73\x74"
+      "\x2e\x0a\x50\x4b\x01\x02\x1e\x03\x0a\x00\x00\x00\x00\x00\xa4\x66"
+      "\x24\x41\x13\xe8\xcb\x27\x10\x00\x00\x00\x10\x00\x00\x00\x08\x00"
+      "\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa4\x81\x00\x00\x00\x00"
+      "\x74\x65\x73\x74\x2e\x74\x78\x74\x55\x54\x05\x00\x03\x34\x89\x45"
+      "\x50\x75\x78\x0b\x00\x01\x04\x8e\xf0\x00\x00\x04\x88\x13\x00\x00"
+      "\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00\x4e\x00\x00\x00"
+      "\x52\x00\x00\x00\x00\x00";
+  std::string data(kTestData, arraysize(kTestData));
+  ZipReader reader;
+  ASSERT_TRUE(reader.OpenFromString(data));
+  FilePath target_path(FILE_PATH_LITERAL("test.txt"));
+  ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
+  ASSERT_TRUE(reader.ExtractCurrentEntryToFilePath(
+      test_dir_.AppendASCII("test.txt")));
+
+  std::string actual;
+  ASSERT_TRUE(file_util::ReadFileToString(
+      test_dir_.AppendASCII("test.txt"), &actual));
+  EXPECT_EQ(std::string("This is a test.\n"), actual);
+}
+
+}  // namespace zip
diff --git a/chrome/common/zip_unittest.cc b/chrome/common/zip_unittest.cc
new file mode 100644
index 0000000..d2cff71
--- /dev/null
+++ b/chrome/common/zip_unittest.cc
@@ -0,0 +1,186 @@
+// 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.
+
+#include <set>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/scoped_temp_dir.h"
+#include "base/string_util.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/zip.h"
+#include "chrome/common/zip_reader.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace {
+
+// Make the test a PlatformTest to setup autorelease pools properly on Mac.
+class ZipTest : public PlatformTest {
+ protected:
+  virtual void SetUp() {
+    PlatformTest::SetUp();
+
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    test_dir_ = temp_dir_.path();
+
+    FilePath zip_path(test_dir_);
+    zip_contents_.insert(zip_path.AppendASCII("foo.txt"));
+    zip_path = zip_path.AppendASCII("foo");
+    zip_contents_.insert(zip_path);
+    zip_contents_.insert(zip_path.AppendASCII("bar.txt"));
+    zip_path = zip_path.AppendASCII("bar");
+    zip_contents_.insert(zip_path);
+    zip_contents_.insert(zip_path.AppendASCII("baz.txt"));
+    zip_contents_.insert(zip_path.AppendASCII("quux.txt"));
+    zip_contents_.insert(zip_path.AppendASCII(".hidden"));
+
+    // Include a subset of files in |zip_file_list_| to test ZipFiles().
+    zip_file_list_.push_back(FilePath(FILE_PATH_LITERAL("foo.txt")));
+    zip_file_list_.push_back(FilePath(FILE_PATH_LITERAL("foo/bar/quux.txt")));
+    zip_file_list_.push_back(FilePath(FILE_PATH_LITERAL("foo/bar/.hidden")));
+  }
+
+  virtual void TearDown() {
+    PlatformTest::TearDown();
+  }
+
+  void TestUnzipFile(const FilePath::StringType& filename,
+                     bool expect_hidden_files) {
+    FilePath test_dir;
+    ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
+    test_dir = test_dir.AppendASCII("zip");
+    TestUnzipFile(test_dir.Append(filename), expect_hidden_files);
+  }
+
+  void TestUnzipFile(const FilePath& path, bool expect_hidden_files) {
+    ASSERT_TRUE(file_util::PathExists(path)) << "no file " << path.value();
+    ASSERT_TRUE(zip::Unzip(path, test_dir_));
+
+    file_util::FileEnumerator files(test_dir_, true,
+        file_util::FileEnumerator::FILES |
+        file_util::FileEnumerator::DIRECTORIES);
+    FilePath next_path = files.Next();
+    size_t count = 0;
+    while (!next_path.value().empty()) {
+      if (next_path.value().find(FILE_PATH_LITERAL(".svn")) ==
+          FilePath::StringType::npos) {
+        EXPECT_EQ(zip_contents_.count(next_path), 1U) <<
+            "Couldn't find " << next_path.value();
+        count++;
+      }
+      next_path = files.Next();
+    }
+
+    size_t expected_count = 0;
+    for (std::set<FilePath>::iterator iter = zip_contents_.begin();
+         iter != zip_contents_.end(); ++iter) {
+      if (expect_hidden_files || iter->BaseName().value()[0] != '.')
+        ++expected_count;
+    }
+
+    EXPECT_EQ(expected_count, count);
+  }
+
+  // The path to temporary directory used to contain the test operations.
+  FilePath test_dir_;
+
+  ScopedTempDir temp_dir_;
+
+  // Hard-coded contents of a known zip file.
+  std::set<FilePath> zip_contents_;
+
+  // Hard-coded list of relative paths for a zip file created with ZipFiles.
+  std::vector<FilePath> zip_file_list_;
+};
+
+TEST_F(ZipTest, Unzip) {
+  TestUnzipFile(FILE_PATH_LITERAL("test.zip"), true);
+}
+
+TEST_F(ZipTest, UnzipUncompressed) {
+  TestUnzipFile(FILE_PATH_LITERAL("test_nocompress.zip"), true);
+}
+
+TEST_F(ZipTest, UnzipEvil) {
+  FilePath path;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
+  path = path.AppendASCII("zip").AppendASCII("evil.zip");
+  // Unzip the zip file into a sub directory of test_dir_ so evil.zip
+  // won't create a persistent file outside test_dir_ in case of a
+  // failure.
+  FilePath output_dir = test_dir_.AppendASCII("out");
+  ASSERT_FALSE(zip::Unzip(path, output_dir));
+  FilePath evil_file = output_dir;
+  evil_file = evil_file.AppendASCII(
+      "../levilevilevilevilevilevilevilevilevilevilevilevil");
+  ASSERT_FALSE(file_util::PathExists(evil_file));
+}
+
+TEST_F(ZipTest, UnzipEvil2) {
+  FilePath path;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
+  // The zip file contains an evil file with invalid UTF-8 in its file
+  // name.
+  path = path.AppendASCII("zip").AppendASCII("evil_via_invalid_utf8.zip");
+  // See the comment at UnzipEvil() for why we do this.
+  FilePath output_dir = test_dir_.AppendASCII("out");
+  // This should fail as it contains an evil file.
+  ASSERT_FALSE(zip::Unzip(path, output_dir));
+  FilePath evil_file = output_dir;
+  evil_file = evil_file.AppendASCII("../evil.txt");
+  ASSERT_FALSE(file_util::PathExists(evil_file));
+}
+
+TEST_F(ZipTest, Zip) {
+  FilePath src_dir;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &src_dir));
+  src_dir = src_dir.AppendASCII("zip").AppendASCII("test");
+
+  ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  FilePath zip_file = temp_dir.path().AppendASCII("out.zip");
+
+  EXPECT_TRUE(zip::Zip(src_dir, zip_file, true));
+  TestUnzipFile(zip_file, true);
+}
+
+TEST_F(ZipTest, ZipIgnoreHidden) {
+  FilePath src_dir;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &src_dir));
+  src_dir = src_dir.AppendASCII("zip").AppendASCII("test");
+
+  ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  FilePath zip_file = temp_dir.path().AppendASCII("out.zip");
+
+  EXPECT_TRUE(zip::Zip(src_dir, zip_file, false));
+  TestUnzipFile(zip_file, false);
+}
+
+TEST_F(ZipTest, ZipFiles) {
+  FilePath src_dir;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &src_dir));
+  src_dir = src_dir.AppendASCII("zip").AppendASCII("test");
+
+  ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  FilePath zip_file = temp_dir.path().AppendASCII("out.zip");
+
+  EXPECT_TRUE(zip::ZipFiles(src_dir, zip_file_list_, zip_file));
+
+  zip::ZipReader reader;
+  EXPECT_TRUE(reader.Open(zip_file));
+  EXPECT_EQ(zip_file_list_.size(), static_cast<size_t>(reader.num_entries()));
+  for (size_t i = 0; i < zip_file_list_.size(); ++i) {
+    EXPECT_TRUE(reader.LocateAndOpenEntry(zip_file_list_[i]));
+    // Check the path in the entry just in case.
+    const zip::ZipReader::EntryInfo* entry_info = reader.current_entry_info();
+    EXPECT_EQ(entry_info->file_path(), zip_file_list_[i]);
+  }
+}
+
+}  // namespace
+